Skip to content
Closed
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
49 changes: 49 additions & 0 deletions docs/ipv6-management-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,52 @@ To switch back to IPv4 management, simply run `deploy-mg` without the `--ipv6-on
```

This will regenerate and deploy a minigraph with IPv4-only management configuration.

## Running Tests in IPv6-Only Management Mode

After deploying the DUT with IPv6-only management, you need to run tests with the `-6` flag to ensure the test framework uses IPv6 for management connectivity.

### Using run_tests.sh

Add the `-6` flag to your `run_tests.sh` command:

```bash
cd tests/

# Basic test execution with IPv6-only management
./run_tests.sh -6 -n vms-kvm-t0 -d vlab-01 -c bgp/test_bgp_fact.py -f vtestbed.yaml -i ../ansible/veos_vtb

# With additional options
./run_tests.sh -6 -n vms-kvm-t0 -d vlab-01 -c platform_tests/test_reboot.py -f vtestbed.yaml -i ../ansible/veos_vtb -m individual -t t0,any
```

### Using pytest directly

Alternatively, use the `--ipv6_only_mgmt` pytest option:

```bash
pytest --ipv6_only_mgmt \
--testbed vms-kvm-t0 \
--testbed_file ../ansible/vtestbed.yaml \
--inventory ../ansible/veos_vtb \
--host-pattern vlab-01 \
bgp/test_bgp_fact.py
```

### What the `-6` flag does

When IPv6-only management mode is enabled:

1. **Management IP**: `dut.mgmt_ip` returns the IPv6 address (`ansible_hostv6`) instead of IPv4 (`ansible_host`)
2. **Sanity Checks**: The IPv4 management ping check is skipped when `mgmt_ip` is an IPv6 address
3. **Reboot Operations**: Ping commands use `ping6` for IPv6 management addresses
4. **Connection Handling**: All SSH connections use the IPv6 management address

### Verifying IPv6-Only Mode

You can verify tests are running in IPv6-only mode by checking the test logs:

```
INFO Using IPv6-only management mode: using fec0::ffff:afa:1 as mgmt_ip for vlab-01
INFO vlab-01 is using IPv6 management address (fec0::ffff:afa:1). Skip the ipv4 mgmt reachability check.
```
21 changes: 21 additions & 0 deletions docs/testbed/README.testbed.VsSetup.md
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,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
42 changes: 38 additions & 4 deletions tests/common/devices/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,31 @@

logger = logging.getLogger(__name__)


# Module-level flag for IPv6-only management mode.
# This is set by the ipv6_only_mgmt_enabled fixture in conftest.py
_ipv6_only_mgmt_enabled = False
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to avoid using global variable?



def set_ipv6_only_mgmt(enabled: bool):
"""Set the IPv6-only management mode flag.

Called by the ipv6_only_mgmt_enabled fixture in conftest.py.
"""
global _ipv6_only_mgmt_enabled
_ipv6_only_mgmt_enabled = enabled
if enabled:
logger.info("IPv6-only management mode enabled")


def is_ipv6_only_mgmt() -> bool:
"""Check if running in IPv6-only management mode.

Returns True if --ipv6_only_mgmt pytest option was passed.
"""
return _ipv6_only_mgmt_enabled


# 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 Down Expand Up @@ -46,11 +71,20 @@ 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"]
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 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)
else:
self.mgmt_ipv6 = None
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
4 changes: 3 additions & 1 deletion tests/common/reboot.py
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,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 ping6 for IPv6 addresses, ping for IPv4
ping_cmd = "ping6" if ":" in str(duthost.mgmt_ip) else "ping"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to use is_ipv6_address defined in tests.common.utilities to check if it is IPv6 address here.

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 @@ -131,6 +131,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 @@ -344,6 +346,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 set_ipv6_only_mgmt

enabled = request.config.getoption("ipv6_only_mgmt", default=False)
set_ipv6_only_mgmt(enabled)
return enabled


@pytest.fixture(scope="session", autouse=True)
def enhance_inventory(request, tbinfo):
"""
Expand Down Expand Up @@ -517,7 +541,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
Expand All @@ -526,6 +550,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),
Expand Down
19 changes: 14 additions & 5 deletions 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 @@ -124,6 +125,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 @@ -205,6 +207,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 @@ -314,15 +320,15 @@ function pre_post_extra_params()
function prepare_dut()
{
echo "=== Preparing DUT for subsequent tests ==="
echo Running: ${PYTEST_EXEC} ${PYTEST_UTIL_OPTS} ${PRET_LOGGING_OPTIONS} ${UTIL_TOPOLOGY_OPTIONS} $(pre_post_extra_params) -m pretest
${PYTEST_EXEC} ${PYTEST_UTIL_OPTS} ${PRET_LOGGING_OPTIONS} ${UTIL_TOPOLOGY_OPTIONS} $(pre_post_extra_params) -m pretest
echo Running: ${PYTEST_EXEC} . ${PYTEST_UTIL_OPTS} ${PRET_LOGGING_OPTIONS} ${UTIL_TOPOLOGY_OPTIONS} $(pre_post_extra_params) -m pretest
${PYTEST_EXEC} . ${PYTEST_UTIL_OPTS} ${PRET_LOGGING_OPTIONS} ${UTIL_TOPOLOGY_OPTIONS} $(pre_post_extra_params) -m pretest
}

function cleanup_dut()
{
echo "=== Cleaning up DUT after tests ==="
echo Running: ${PYTEST_EXEC} ${PYTEST_UTIL_OPTS} ${POST_LOGGING_OPTIONS} ${UTIL_TOPOLOGY_OPTIONS} $(pre_post_extra_params) -m posttest
${PYTEST_EXEC} ${PYTEST_UTIL_OPTS} ${POST_LOGGING_OPTIONS} ${UTIL_TOPOLOGY_OPTIONS} $(pre_post_extra_params) -m posttest
echo Running: ${PYTEST_EXEC} . ${PYTEST_UTIL_OPTS} ${POST_LOGGING_OPTIONS} ${UTIL_TOPOLOGY_OPTIONS} $(pre_post_extra_params) -m posttest
${PYTEST_EXEC} . ${PYTEST_UTIL_OPTS} ${POST_LOGGING_OPTIONS} ${UTIL_TOPOLOGY_OPTIONS} $(pre_post_extra_params) -m posttest
}

function run_group_tests()
Expand Down Expand Up @@ -423,7 +429,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 @@ -513,6 +519,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