Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ansible/config_sonic_basedon_testbed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions docs/ipv6-management-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
21 changes: 21 additions & 0 deletions docs/testbed/README.testbed.VsSetup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
48 changes: 44 additions & 4 deletions tests/common/devices/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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):
Expand Down
7 changes: 7 additions & 0 deletions tests/common/plugins/sanity_check/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions tests/common/reboot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
27 changes: 26 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -385,6 +387,28 @@
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):
"""
Expand Down Expand Up @@ -561,7 +585,7 @@


@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):

Check notice

Code scanning / CodeQL

Explicit returns mixed with implicit (fall through) returns Note test

Mixing implicit and explicit returns may indicate an error, as implicit returns always return None.
"""
@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
Expand All @@ -570,6 +594,7 @@
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),
Expand Down
11 changes: 10 additions & 1 deletion tests/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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/
Expand Down Expand Up @@ -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}"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
Loading