Skip to content

Commit a031542

Browse files
[pytest]: Re-use DVS container when possible (sonic-net#1816)
- If the fake_platform does not change between test modules, re-use the same DVS container (but still restart it between modules and do some cleanup/re-init to ensure a consistent start state for each test module) - Add a CLI option --force-recreate-dvs to revert to the previous behavior of creating a new DVS per test module Signed-off-by: Lawrence Lee <[email protected]>
1 parent a875d60 commit a031542

2 files changed

Lines changed: 101 additions & 26 deletions

File tree

tests/conftest.py

Lines changed: 100 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,18 @@ def pytest_addoption(parser):
4747
parser.addoption("--dvsname",
4848
action="store",
4949
default=None,
50-
help="Name of a persistent DVS container to run the tests with")
50+
help="Name of a persistent DVS container to run the tests with. Mutually exclusive with --force-recreate-dvs")
5151

5252
parser.addoption("--forcedvs",
5353
action="store_true",
5454
default=False,
5555
help="Force tests to run in persistent DVS containers with <32 ports")
5656

57+
parser.addoption("--force-recreate-dvs",
58+
action="store_true",
59+
default=False,
60+
help="Force the DVS container to be recreated between each test module. Mutually exclusive with --dvsname")
61+
5762
parser.addoption("--keeptb",
5863
action="store_true",
5964
default=False,
@@ -192,6 +197,9 @@ def __init__(self, ctn_name: str, pid: int, i: int):
192197
ensure_system(f"nsenter -t {pid} -n ip link set arp off dev {self.pifname}")
193198
ensure_system(f"nsenter -t {pid} -n sysctl -w net.ipv6.conf.{self.pifname}.disable_ipv6=1")
194199

200+
def __repr__(self):
201+
return f'<VirtualServer> {self.nsname}'
202+
195203
def kill_all_processes(self) -> None:
196204
pids = subprocess.check_output(f"ip netns pids {self.nsname}", shell=True).decode("utf-8")
197205
if pids:
@@ -339,9 +347,7 @@ def __init__(
339347

340348
# create virtual server
341349
self.servers = []
342-
for i in range(NUM_PORTS):
343-
server = VirtualServer(self.ctn_sw.name, self.ctn_sw_pid, i)
344-
self.servers.append(server)
350+
self.create_servers()
345351

346352
if self.vct:
347353
self.vct_connect(newctnname)
@@ -377,13 +383,7 @@ def __init__(
377383
self.redis_sock = os.path.join(self.mount, "redis.sock")
378384
self.redis_chassis_sock = os.path.join(self.mount, "redis_chassis.sock")
379385

380-
# DB wrappers are declared here, lazy-loaded in the tests
381-
self.app_db = None
382-
self.asic_db = None
383-
self.counters_db = None
384-
self.config_db = None
385-
self.flex_db = None
386-
self.state_db = None
386+
self.reset_dbs()
387387

388388
# Make sure everything is up and running before turning over control to the caller
389389
self.check_ready_status_and_init_db()
@@ -392,23 +392,40 @@ def __init__(
392392
if buffer_model == 'dynamic':
393393
enable_dynamic_buffer(self.get_config_db(), self.runcmd)
394394

395+
def create_servers(self):
396+
for i in range(NUM_PORTS):
397+
server = VirtualServer(self.ctn_sw.name, self.ctn_sw_pid, i)
398+
self.servers.append(server)
399+
400+
def reset_dbs(self):
401+
# DB wrappers are declared here, lazy-loaded in the tests
402+
self.app_db = None
403+
self.asic_db = None
404+
self.counters_db = None
405+
self.config_db = None
406+
self.flex_db = None
407+
self.state_db = None
408+
395409
def destroy(self) -> None:
396-
if self.appldb:
410+
if getattr(self, 'appldb', False):
397411
del self.appldb
398412

399413
# In case persistent dvs was used removed all the extra server link
400414
# that were created
401415
if self.persistent:
402-
for s in self.servers:
403-
s.destroy()
416+
self.destroy_servers()
404417

405418
# persistent and clean-up flag are mutually exclusive
406419
elif self.cleanup:
407420
self.ctn.remove(force=True)
408421
self.ctn_sw.remove(force=True)
409422
os.system(f"rm -rf {self.mount}")
410-
for s in self.servers:
411-
s.destroy()
423+
self.destroy_servers()
424+
425+
def destroy_servers(self):
426+
for s in self.servers:
427+
s.destroy()
428+
self.servers = []
412429

413430
def check_ready_status_and_init_db(self) -> None:
414431
try:
@@ -423,6 +440,7 @@ def check_ready_status_and_init_db(self) -> None:
423440
# Initialize the databases.
424441
self.init_asic_db_validator()
425442
self.init_appl_db_validator()
443+
self.reset_dbs()
426444

427445
# Verify that SWSS has finished initializing.
428446
self.check_swss_ready()
@@ -451,9 +469,9 @@ def _polling_function():
451469

452470
for pname in self.alld:
453471
if process_status.get(pname, None) != "RUNNING":
454-
return (False, None)
472+
return (False, process_status)
455473

456-
return (process_status.get("start.sh", None) == "EXITED", None)
474+
return (process_status.get("start.sh", None) == "EXITED", process_status)
457475

458476
wait_for_result(_polling_function, service_polling_config)
459477

@@ -1556,35 +1574,92 @@ def verify_vct(self):
15561574
print("vct verifications passed ? %s" % (ret1 and ret2))
15571575
return ret1 and ret2
15581576

1559-
@pytest.yield_fixture(scope="module")
1560-
def dvs(request) -> DockerVirtualSwitch:
1577+
@pytest.fixture(scope="session")
1578+
def manage_dvs(request) -> str:
1579+
"""
1580+
Main fixture to manage the lifecycle of the DVS (Docker Virtual Switch) for testing
1581+
1582+
Returns:
1583+
(func) update_dvs function which can be called on a per-module basis
1584+
to handle re-creating the DVS if necessary
1585+
"""
15611586
if sys.version_info[0] < 3:
15621587
raise NameError("Python 2 is not supported, please install python 3")
15631588

15641589
if subprocess.check_call(["/sbin/modprobe", "team"]):
15651590
raise NameError("Cannot install kernel team module, please install a generic kernel")
15661591

15671592
name = request.config.getoption("--dvsname")
1593+
using_persistent_dvs = name is not None
15681594
forcedvs = request.config.getoption("--forcedvs")
15691595
keeptb = request.config.getoption("--keeptb")
15701596
imgname = request.config.getoption("--imgname")
15711597
max_cpu = request.config.getoption("--max_cpu")
15721598
buffer_model = request.config.getoption("--buffer_model")
1573-
fakeplatform = getattr(request.module, "DVS_FAKE_PLATFORM", None)
1574-
log_path = name if name else request.module.__name__
1599+
force_recreate = request.config.getoption("--force-recreate-dvs")
1600+
dvs = None
1601+
curr_fake_platform = None # lgtm[py/unused-local-variable]
1602+
1603+
if using_persistent_dvs and force_recreate:
1604+
pytest.fail("Options --dvsname and --force-recreate-dvs are mutually exclusive")
1605+
1606+
def update_dvs(log_path, new_fake_platform=None):
1607+
"""
1608+
Decides whether or not to create a new DVS
1609+
1610+
Create a new the DVS in the following cases:
1611+
1. CLI option `--force-recreate-dvs` was specified (recreate for every module)
1612+
2. The fake_platform has changed (this can only be set at container creation,
1613+
so it is necessary to spin up a new DVS)
1614+
3. No DVS currently exists (i.e. first time startup)
1615+
1616+
Otherwise, restart the existing DVS (to get to a clean state)
1617+
1618+
Returns:
1619+
(DockerVirtualSwitch) a DVS object
1620+
"""
1621+
nonlocal curr_fake_platform, dvs
1622+
if force_recreate or \
1623+
new_fake_platform != curr_fake_platform or \
1624+
dvs is None:
15751625

1576-
dvs = DockerVirtualSwitch(name, imgname, keeptb, fakeplatform, log_path, max_cpu, forcedvs, buffer_model = buffer_model)
1626+
if dvs is not None:
1627+
dvs.get_logs()
1628+
dvs.destroy()
15771629

1578-
yield dvs
1630+
dvs = DockerVirtualSwitch(name, imgname, keeptb, new_fake_platform, log_path, max_cpu, forcedvs, buffer_model = buffer_model)
1631+
1632+
curr_fake_platform = new_fake_platform
1633+
1634+
else:
1635+
# First generate GCDA files for GCov
1636+
dvs.runcmd('killall5 -15')
1637+
# If not re-creating the DVS, restart container
1638+
# between modules to ensure a consistent start state
1639+
dvs.net_cleanup()
1640+
dvs.destroy_servers()
1641+
dvs.create_servers()
1642+
dvs.restart()
1643+
1644+
return dvs
1645+
1646+
yield update_dvs
15791647

15801648
dvs.get_logs()
15811649
dvs.destroy()
15821650

1583-
# restore original config db
15841651
if dvs.persistent:
15851652
dvs.runcmd("mv /etc/sonic/config_db.json.orig /etc/sonic/config_db.json")
15861653
dvs.ctn_restart()
15871654

1655+
@pytest.yield_fixture(scope="module")
1656+
def dvs(request, manage_dvs) -> DockerVirtualSwitch:
1657+
fakeplatform = getattr(request.module, "DVS_FAKE_PLATFORM", None)
1658+
name = request.config.getoption("--dvsname")
1659+
log_path = name if name else request.module.__name__
1660+
1661+
return manage_dvs(log_path, fakeplatform)
1662+
15881663
@pytest.yield_fixture(scope="module")
15891664
def vct(request):
15901665
vctns = request.config.getoption("--vctns")

tests/dvslib/dvs_common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def wait_for_result(
5656
time.sleep(polling_config.polling_interval)
5757

5858
if polling_config.strict:
59-
message = failure_message or f"Operation timed out after {polling_config.timeout} seconds"
59+
message = failure_message or f"Operation timed out after {polling_config.timeout} seconds with result {result}"
6060
assert False, message
6161

6262
return (False, result)

0 commit comments

Comments
 (0)