diff --git a/ansible/config_sonic_basedon_testbed.yml b/ansible/config_sonic_basedon_testbed.yml index c752561a8c9..4e47967815f 100644 --- a/ansible/config_sonic_basedon_testbed.yml +++ b/ansible/config_sonic_basedon_testbed.yml @@ -981,6 +981,10 @@ - name: Wait for chrony.service restart after reload minigraph become: true service: name=chrony state=started + register: chrony_start_result + retries: 12 + delay: 10 + until: chrony_start_result is succeeded - name: Stop chrony.service on DUT become: true diff --git a/docs/ipv6-management-setup.md b/docs/ipv6-management-setup.md index 25473d9c30c..472d89560af 100644 --- a/docs/ipv6-management-setup.md +++ b/docs/ipv6-management-setup.md @@ -259,6 +259,39 @@ The local NTP server is configured as the primary NTP source in: docker exec sonic-mgmt-ntp chronyc tracking ``` +## Running Tests in IPv6-Only Management Mode + +When running tests against a DUT configured with IPv6-only management, you must tell the test framework to use IPv6 addresses. + +### Using run_tests.sh + +Use the `-6` flag to enable IPv6-only management mode: + +```bash +./run_tests.sh -6 -n vms-kvm-t0 -d vlab-01 -c bgp/test_bgp_fact.py -f vtestbed.yaml -i ../ansible/veos_vtb +``` + +### Using pytest directly + +Use the `--ipv6_only_mgmt` option: + +```bash +pytest --ipv6_only_mgmt --testbed vms-kvm-t0 --testbed_file vtestbed.yaml --inventory ../ansible/veos_vtb --host-pattern vlab-01 bgp/test_bgp_fact.py +``` + +### What the IPv6-only flag does + +When enabled, the test framework will: +- Use `ansible_hostv6` as the DUT management IP instead of `ansible_host` +- Skip IPv4 management connectivity sanity checks +- Use `ping6` for management reachability tests + +### Important notes + +- The `--ipv6_only_mgmt` / `-6` flag only affects test execution, not deployment +- The DUT must already be configured with IPv6 management (via `deploy-mg --ipv6-only-mgmt`) +- Ensure `ansible_hostv6` is defined in the inventory for all DUTs + ## Reverting to IPv4 Management To switch back to IPv4 management, simply run `deploy-mg` without the `--ipv6-only-mgmt` flag: diff --git a/docs/testbed/README.testbed.VsSetup.md b/docs/testbed/README.testbed.VsSetup.md index a08edaa87ca..db66e625e40 100644 --- a/docs/testbed/README.testbed.VsSetup.md +++ b/docs/testbed/README.testbed.VsSetup.md @@ -552,6 +552,27 @@ If neighbor devices are SONiC You should see three sets of tests run and pass. You're now set up and ready to use the KVM testbed! +### Running Tests with IPv6-Only Management + +If you deployed the DUT with `--ipv6-only-mgmt` (see [IPv6-Only Management Network](#ipv6-only-management-network-optional)), you must run tests with the `-6` flag: + +``` +./run_tests.sh -6 -n vms-kvm-t0 -d vlab-01 -c bgp/test_bgp_fact.py -f vtestbed.yaml -i ../ansible/veos_vtb +``` + +The `-6` flag tells the test framework to: +- Use the IPv6 address (`ansible_hostv6`) as the DUT management IP +- Skip IPv4 management connectivity checks +- Use `ping6` for reachability tests + +Alternatively, you can use pytest directly with the `--ipv6_only_mgmt` option: + +``` +pytest --ipv6_only_mgmt --testbed vms-kvm-t0 --testbed_file vtestbed.yaml --inventory ../ansible/veos_vtb --host-pattern vlab-01 bgp/test_bgp_fact.py +``` + +For more details, see [IPv6 Management Setup Guide](../ipv6-management-setup.md#running-tests-in-ipv6-only-management-mode). + ## Restore/Remove the testing environment If you want to clear your testing environment, you can log into your mgmt docker that you created at step three in section [README.testbed.VsSetup.md#prepare-testbed-host](README.testbed.VsSetup.md#prepare-testbed-host). diff --git a/tests/common/devices/base.py b/tests/common/devices/base.py index 5644d0e0724..1cfbbf5f6bb 100644 --- a/tests/common/devices/base.py +++ b/tests/common/devices/base.py @@ -9,6 +9,7 @@ logger = logging.getLogger(__name__) + # HACK: This is a hack for issue https://github.com/sonic-net/sonic-mgmt/issues/1941 and issue # https://github.com/ansible/pytest-ansible/issues/47 # Detailed root cause analysis of the issue: https://github.com/sonic-net/sonic-mgmt/issues/1941#issuecomment-670434790 @@ -33,6 +34,28 @@ class AnsibleHostBase(object): on the host. """ + # Class-level flag for IPv6-only management mode. + # Set by the ipv6_only_mgmt_enabled fixture in conftest.py + _ipv6_only_mgmt_mode = False + + @classmethod + def set_ipv6_only_mgmt(cls, enabled: bool): + """Set the IPv6-only management mode flag. + + Called by the ipv6_only_mgmt_enabled fixture in conftest.py. + """ + cls._ipv6_only_mgmt_mode = enabled + if enabled: + logger.info("IPv6-only management mode enabled") + + @classmethod + def is_ipv6_only_mgmt(cls) -> bool: + """Check if running in IPv6-only management mode. + + Returns True if --ipv6_only_mgmt pytest option was passed. + """ + return cls._ipv6_only_mgmt_mode + class CustomEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, bytes): @@ -46,11 +69,28 @@ def __init__(self, ansible_adhoc, hostname, *args, **kwargs): self.host = ansible_adhoc(connection='local', host_pattern=hostname)[hostname] else: self.host = ansible_adhoc(become=True, *args, **kwargs)[hostname] - self.mgmt_ip = self.host.options["inventory_manager"].get_host(hostname).vars["ansible_host"] - if "ansible_hostv6" in self.host.options["inventory_manager"].get_host(hostname).vars: - self.mgmt_ipv6 = self.host.options["inventory_manager"].get_host(hostname).vars["ansible_hostv6"] - else: + host_vars = self.host.options["inventory_manager"].get_host(hostname).vars + ansible_host = host_vars.get("ansible_host") + ansible_hostv6 = host_vars.get("ansible_hostv6") + + # In IPv6-only management mode, use IPv6 address as the primary mgmt_ip + if self.is_ipv6_only_mgmt() and ansible_hostv6: + self.mgmt_ip = ansible_hostv6 + self.mgmt_ipv6 = ansible_hostv6 + # Keep IPv4 available for reference but it won't be used for connectivity + self._mgmt_ipv4 = ansible_host + logger.debug("IPv6-only management mode: using %s as mgmt_ip for %s", ansible_hostv6, hostname) + elif self.is_ipv6_only_mgmt(): + logger.warning( + "IPv6-only mode requested but ansible_hostv6 not defined for %s, " + "falling back to IPv4.", + hostname, + ) + self.mgmt_ip = ansible_host self.mgmt_ipv6 = None + else: + self.mgmt_ip = ansible_host + self.mgmt_ipv6 = ansible_hostv6 self.hostname = hostname def __getattr__(self, module_name): diff --git a/tests/common/plugins/sanity_check/checks.py b/tests/common/plugins/sanity_check/checks.py index a41ef6b69a0..4a8db4c9b35 100644 --- a/tests/common/plugins/sanity_check/checks.py +++ b/tests/common/plugins/sanity_check/checks.py @@ -1137,6 +1137,13 @@ def _check_ipv4_mgmt_to_dut(*args, **kwargs): results[dut.hostname] = check_result return + # Skip IPv4 check if mgmt_ip is an IPv6 address (IPv6-only management mode) + if is_ipv6_address(dut.mgmt_ip): + logger.info("%s is using IPv6 management address (%s). Skip the ipv4 mgmt reachability check." + % (dut.hostname, dut.mgmt_ip)) + results[dut.hostname] = check_result + return + # most of the testbed should reply within 10 ms, Set the timeout to 2 seconds to reduce the impact of delay. try: shell_result = localhost.shell("ping -c 2 -W 2 " + dut.mgmt_ip) diff --git a/tests/common/reboot.py b/tests/common/reboot.py index 659da944ee6..b4573bed69b 100644 --- a/tests/common/reboot.py +++ b/tests/common/reboot.py @@ -13,7 +13,7 @@ from .platform.interface_utils import check_interface_status_of_up_ports from .platform.processes_utils import wait_critical_processes from .plugins.loganalyzer.utils import support_ignore_loganalyzer -from .utilities import wait_until, get_plt_reboot_ctrl +from .utilities import wait_until, get_plt_reboot_ctrl, is_ipv6_address from tests.common.helpers.dut_utils import ignore_t2_syslog_msgs, create_duthost_console, creds_on_dut from tests.common.fixtures.conn_graph_facts import get_graph_facts @@ -806,7 +806,9 @@ def ssh_connection_with_retry(localhost, host_ip, port, delay, timeout): def collect_mgmt_config_by_console(duthost, localhost): logger.info("check if dut is pingable") - localhost.shell(f"ping -c 5 {duthost.mgmt_ip}", module_ignore_errors=True) + # Use ping -6 for IPv6 so the check works on newer distros where ping6 may not exist. + ping_cmd = "ping -6" if is_ipv6_address(str(duthost.mgmt_ip)) else "ping" + localhost.shell(f"{ping_cmd} -c 5 {duthost.mgmt_ip}", module_ignore_errors=True) logger.info("Start: collect mgmt config by console") creds = creds_on_dut(duthost) diff --git a/tests/conftest.py b/tests/conftest.py index 5b29990f84e..7ad4daed3af 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -135,6 +135,8 @@ def pytest_addoption(parser): parser.addoption("--testbed", action="store", default=None, help="testbed name") parser.addoption("--testbed_file", action="store", default=None, help="testbed file name") + parser.addoption("--ipv6_only_mgmt", action="store_true", default=False, + help="Use IPv6-only management network. DUT mgmt_ip will be set to IPv6 address.") parser.addoption("--uhd_config", action="store", help="Enable UHD config mode") parser.addoption("--save_uhd_config", action="store_true", help="Save UHD config mode") parser.addoption("--npu_dpu_startup", action="store_true", help="Startup NPU and DPUs and install configurations") @@ -385,6 +387,28 @@ def pytest_configure(config): config.pluginmanager.register(MacsecPluginT0()) +@pytest.fixture(scope="session") +def ipv6_only_mgmt_enabled(request): + """ + Fixture to check and configure IPv6-only management mode. + + When --ipv6_only_mgmt is passed to pytest (or -6 to run_tests.sh), + this enables IPv6-only management mode where DUT mgmt_ip will use + the IPv6 address (ansible_hostv6) instead of IPv4 (ansible_host). + + This fixture must be used before duthosts fixture to ensure the + mode is configured before SonicHost objects are created. + + Returns: + bool: True if IPv6-only management mode is enabled, False otherwise. + """ + from tests.common.devices.base import AnsibleHostBase + + enabled = request.config.getoption("ipv6_only_mgmt", default=False) + AnsibleHostBase.set_ipv6_only_mgmt(enabled) + return enabled + + @pytest.fixture(scope="session", autouse=True) def enhance_inventory(request, tbinfo): """ @@ -561,7 +585,7 @@ def pytest_sessionfinish(session, exitstatus): @pytest.fixture(name="duthosts", scope="session") -def fixture_duthosts(enhance_inventory, ansible_adhoc, tbinfo, request): +def fixture_duthosts(enhance_inventory, ansible_adhoc, tbinfo, request, ipv6_only_mgmt_enabled): """ @summary: fixture to get DUT hosts defined in testbed. @param enhance_inventory: fixture to enhance the capability of parsing the value of pytest cli argument @@ -570,6 +594,7 @@ def fixture_duthosts(enhance_inventory, ansible_adhoc, tbinfo, request): mandatory argument for the class constructors. @param tbinfo: fixture provides information about testbed. @param request: pytest request object + @param ipv6_only_mgmt_enabled: fixture to configure IPv6-only management mode before DUT initialization """ try: host = DutHosts(ansible_adhoc, tbinfo, request, get_specified_duts(request), diff --git a/tests/run_tests.sh b/tests/run_tests.sh index e4725e392ae..690c3d5c41b 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -31,6 +31,7 @@ function show_help_and_exit() echo " -u : bypass util group" echo " -w : warm run, don't clear cache before running tests" echo " -x : print commands and their arguments as they are executed" + echo " -6 : IPv6-only management mode (use IPv6 for DUT mgmt connectivity)" exit $1 } @@ -150,6 +151,7 @@ function setup_environment() TEST_MAX_FAIL=0 DPU_NAME="None" NO_CLEAR_CACHE="False" + IPV6_ONLY_MGMT="False" export ANSIBLE_CONFIG=${BASE_PATH}/ansible export ANSIBLE_LIBRARY=${BASE_PATH}/ansible/library/ @@ -231,6 +233,10 @@ function setup_test_options() PYTEST_COMMON_OPTS="${PYTEST_COMMON_OPTS} --allow_recover" fi + if [[ x"${IPV6_ONLY_MGMT}" == x"True" ]]; then + PYTEST_COMMON_OPTS="${PYTEST_COMMON_OPTS} --ipv6_only_mgmt" + fi + for skip in ${SKIP_SCRIPTS} ${SKIP_FOLDERS}; do if [[ $skip == *"::"* ]]; then PYTEST_COMMON_OPTS="${PYTEST_COMMON_OPTS} --deselect=${skip}" @@ -449,7 +455,7 @@ for arg in "$@"; do fi done -while getopts "h?a:b:Bc:C:d:e:Ef:F:H:i:I:k:l:m:n:oOp:q:rs:S:t:uxw" opt; do +while getopts "h?a:b:Bc:C:d:e:Ef:F:H:i:I:k:l:m:n:oOp:q:rs:S:t:uxw6" opt; do case ${opt} in h|\? ) show_help_and_exit 0 @@ -539,6 +545,9 @@ while getopts "h?a:b:Bc:C:d:e:Ef:F:H:i:I:k:l:m:n:oOp:q:rs:S:t:uxw" opt; do x ) set -x ;; + 6 ) + IPV6_ONLY_MGMT="True" + ;; esac done