Skip to content

Commit

Permalink
[dualtor][non-functional] add test template for active-active cable…
Browse files Browse the repository at this point in the history
… type (#5545)

### Description of PR
Summary:
Fixes # (issue)

Submitting this PR to re-add test template for `active-active` cable type. The change (#5424) was reverted earlier due to regression.  

sign-off: Jing Zhang zhangjing@microsoft.com 

### Type of change
- [x] Test case(new/improvement)

#### What is the motivation for this PR?
Re-add the roadmap for supporting `active-active` dualtor_io testcases.

#### How did you do it?
1. Pick the commit from the original PR. 
2. Import `cable_type` fixture where the modified fixtures are requested. 
3. Update `data_plane_utils.py` to re-use `DualtorIO` object. 

#### How did you verify/test it?
Tested `test_normal_op` and `test_link_failure`, all passed.
  • Loading branch information
zjswhhh committed May 18, 2022
1 parent a5af14d commit e80445f
Show file tree
Hide file tree
Showing 16 changed files with 196 additions and 40 deletions.
1 change: 1 addition & 0 deletions tests/common/dualtor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from tests.common.dualtor.dual_tor_utils import *
from tests.common.dualtor.mux_simulator_control import *
from tests.common.dualtor.nic_simulator_control import *
62 changes: 46 additions & 16 deletions tests/common/dualtor/control_plane_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Contains functions used to verify control plane(APP_DB, STATE_DB) values."""
import collections
import json
import logging

from tests.common.dualtor.dual_tor_common import CableType
from tests.common.helpers.assertions import pytest_assert
from tests.common.utilities import wait_until

Expand Down Expand Up @@ -39,7 +41,7 @@

class DBChecker:

def __init__(self, duthost, state, health, intf_names='all'):
def __init__(self, duthost, state, health, intf_names='all', cable_type=CableType.active_standby):
"""
Create a DBChecker object
Args:
Expand All @@ -51,11 +53,14 @@ def __init__(self, duthost, state, health, intf_names='all'):
MUX_LINKMGR_TABLE table (only needed for STATE_DB)
intf_names: A list of the PORTNAME to check in each table, or 'all'
(by default) to check all MUX_CABLE interfaces
cable_type: Select ports with specified cable_type to check.
"""
self.duthost = duthost
self.state = state
self.health = health
self.intf_names = intf_names
self.cable_type = cable_type
self._parse_intf_names()
self.mismatch_ports = {}

def _dump_db(self, db, key_pattern):
Expand All @@ -77,6 +82,19 @@ def verify_db(self, db):
indent=4,
sort_keys=True)))

def _parse_intf_names(self):
mux_cable_table = self.duthost.get_running_config_facts()['MUX_CABLE']
selected_intfs = set(
_ for _ in mux_cable_table if mux_cable_table[_].get("cable_type", CableType.active_standby) == self.cable_type
)
if self.intf_names == 'all':
self.intf_names = selected_intfs
else:
for intf in self.intf_names:
if intf not in selected_intfs:
raise ValueError("Interface %s not in %s cable type" % (intf, self.cable_type))
self.intf_names = set(self.intf_names)

def get_mismatched_ports(self, db):
"""
Query db on `tor_host` and check if the mux-related fields match the
Expand All @@ -91,10 +109,6 @@ def get_mismatched_ports(self, db):
logger.info("Verifying {} values on {}: "
"expected state = {}, expected health = {}".format(
DB_NAME_MAP[db], self.duthost, self.state, self.health))
if self.intf_names == 'all':
mux_intfs = self.duthost.get_running_config_facts()['MUX_CABLE'].keys()
else:
mux_intfs = self.intf_names

mismatch_ports = {}
separator = DB_SEPARATOR_MAP[db]
Expand All @@ -112,7 +126,7 @@ def get_mismatched_ports(self, db):
else:
target_value = self.state

for intf_name in mux_intfs:
for intf_name in self.intf_names:
table_key = '{}{}{}'.format(table, separator, intf_name)

if db_dump[table_key]['value'][field] != target_value:
Expand All @@ -123,17 +137,33 @@ def get_mismatched_ports(self, db):
return not bool(mismatch_ports)


def verify_tor_states(expected_active_host, expected_standby_host,
expected_standby_health='healthy', intf_names='all'):
def verify_tor_states(
expected_active_host, expected_standby_host,
expected_standby_health='healthy', intf_names='all',
cable_type=CableType.active_standby
):
"""
Verifies that the expected states for active and standby ToRs are
reflected in APP_DB and STATE_DB on each device
"""

active_db_checker = DBChecker(expected_active_host, 'active', 'healthy', intf_names=intf_names)
standby_db_checker = DBChecker(expected_standby_host, 'standby', expected_standby_health, intf_names=intf_names)

active_db_checker.verify_db(APP_DB)
active_db_checker.verify_db(STATE_DB)
standby_db_checker.verify_db(APP_DB)
standby_db_checker.verify_db(STATE_DB)
if isinstance(expected_active_host, collections.Iterable):
for duthost in expected_active_host:
db_checker = DBChecker(duthost, 'active', 'healthy', intf_names=intf_names, cable_type=cable_type)
db_checker.verify_db(APP_DB)
db_checker.verify_db(STATE_DB)
elif expected_active_host is not None:
duthost = expected_active_host
db_checker = DBChecker(duthost, 'active', 'healthy', intf_names=intf_names, cable_type=cable_type)
db_checker.verify_db(APP_DB)
db_checker.verify_db(STATE_DB)

if isinstance(expected_standby_host, collections.Iterable):
for duthost in expected_standby_host:
db_checker = DBChecker(duthost, 'standby', expected_standby_health, intf_names=intf_names, cable_type=cable_type)
db_checker.verify_db(APP_DB)
db_checker.verify_db(STATE_DB)
elif expected_standby_host is not None:
duthost = expected_standby_host
db_checker = DBChecker(duthost, 'standby', expected_standby_health, intf_names=intf_names)
db_checker.verify_db(APP_DB)
db_checker.verify_db(STATE_DB)
33 changes: 23 additions & 10 deletions tests/common/dualtor/data_plane_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import collections
import pytest
import json
import time

from tests.common.dualtor.dual_tor_common import cable_type # lgtm[py/unused-import]
from tests.common.dualtor.dual_tor_common import CableType
from tests.common.dualtor.dual_tor_io import DualTorIO
from tests.common.helpers.assertions import pytest_assert
from tests.common.utilities import InterruptableThread
Expand All @@ -13,7 +17,7 @@
logger = logging.getLogger(__name__)


def get_standbyhost(duthosts, activehost):
def get_peerhost(duthosts, activehost):
if duthosts[0] == activehost:
return duthosts[1]
else:
Expand Down Expand Up @@ -127,12 +131,19 @@ def verify_and_report(tor_IO, verify, delay, allowed_disruption):
return tor_IO.get_test_results()


def run_test(duthosts, activehost, ptfhost, ptfadapter, action,
tbinfo, tor_vlan_port, send_interval, traffic_direction, stop_after):
def run_test(
duthosts, activehost, ptfhost, ptfadapter, action,
tbinfo, tor_vlan_port, send_interval, traffic_direction,
stop_after, cable_type=CableType.active_standby
):
io_ready = threading.Event()
standbyhost = get_standbyhost(duthosts, activehost)
tor_IO = DualTorIO(activehost, standbyhost, ptfhost, ptfadapter, tbinfo,
io_ready, tor_vlan_port=tor_vlan_port, send_interval=send_interval)

peerhost = get_peerhost(duthosts, activehost)
tor_IO = DualTorIO(
activehost, peerhost, ptfhost, ptfadapter, tbinfo,
io_ready, tor_vlan_port=tor_vlan_port, send_interval=send_interval, cable_type=cable_type
)

if traffic_direction == "server_to_t1":
traffic_generator = tor_IO.generate_from_server_to_t1
elif traffic_direction == "t1_to_server":
Expand Down Expand Up @@ -177,7 +188,7 @@ def cleanup(ptfadapter, duthosts_list):


@pytest.fixture
def send_t1_to_server_with_action(duthosts, ptfhost, ptfadapter, tbinfo):
def send_t1_to_server_with_action(duthosts, ptfhost, ptfadapter, tbinfo, cable_type):
"""
Starts IO test from T1 router to server.
As part of IO test the background thread sends and sniffs packets.
Expand Down Expand Up @@ -226,7 +237,8 @@ def t1_to_server_io_test(activehost, tor_vlan_port=None,

tor_IO = run_test(duthosts, activehost, ptfhost, ptfadapter,
action, tbinfo, tor_vlan_port, send_interval,
traffic_direction="t1_to_server", stop_after=stop_after)
traffic_direction="t1_to_server", stop_after=stop_after,
cable_type=cable_type)

# If a delay is allowed but no numebr of allowed disruptions
# is specified, default to 1 allowed disruption
Expand All @@ -241,7 +253,7 @@ def t1_to_server_io_test(activehost, tor_vlan_port=None,


@pytest.fixture
def send_server_to_t1_with_action(duthosts, ptfhost, ptfadapter, tbinfo):
def send_server_to_t1_with_action(duthosts, ptfhost, ptfadapter, tbinfo, cable_type):
"""
Starts IO test from server to T1 router.
As part of IO test the background thread sends and sniffs packets.
Expand Down Expand Up @@ -290,7 +302,8 @@ def server_to_t1_io_test(activehost, tor_vlan_port=None,

tor_IO = run_test(duthosts, activehost, ptfhost, ptfadapter,
action, tbinfo, tor_vlan_port, send_interval,
traffic_direction="server_to_t1", stop_after=stop_after)
traffic_direction="server_to_t1", stop_after=stop_after,
cable_type=cable_type)

# If a delay is allowed but no numebr of allowed disruptions
# is specified, default to 1 allowed disruption
Expand Down
14 changes: 14 additions & 0 deletions tests/common/dualtor/dual_tor_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""DualToR related common utilities for other modules."""
import pytest


class CableType(object):
"""Dualtor cable type."""
active_active = "active-active"
active_standby = "active-standby"


@pytest.fixture(params=[CableType.active_standby])
def cable_type(request):
"""Dualtor cable type."""
return request.param
25 changes: 21 additions & 4 deletions tests/common/dualtor/dual_tor_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from operator import itemgetter
from itertools import groupby

from tests.common.dualtor.dual_tor_common import CableType
from tests.common.utilities import InterruptableThread
from natsort import natsorted
from collections import defaultdict
Expand All @@ -28,8 +29,10 @@


class DualTorIO:
"""Class to conduct IO over ports in `active-standby` mode."""

def __init__(self, activehost, standbyhost, ptfhost, ptfadapter, tbinfo,
io_ready, tor_vlan_port=None, send_interval=0.01):
io_ready, tor_vlan_port=None, send_interval=0.01, cable_type=CableType.active_standby):
self.tor_pc_intf = None
self.tor_vlan_intf = tor_vlan_port
self.duthost = activehost
Expand All @@ -41,6 +44,8 @@ def __init__(self, activehost, standbyhost, ptfhost, ptfadapter, tbinfo,
self.active_mac = self.dut_mac
self.standby_mac = standbyhost.facts["router_mac"]

self.cable_type = cable_type

self.dataplane = self.ptfadapter.dataplane
self.dataplane.flush()
self.test_results = dict()
Expand Down Expand Up @@ -74,11 +79,14 @@ def __init__(self, activehost, standbyhost, ptfhost, ptfadapter, tbinfo,
self.vlan_mac = vlan_table[vlan_name]['mac']
self.mux_cable_table = config_facts['MUX_CABLE']

self.test_interfaces = self._select_test_interfaces()

self.ptf_intf_to_server_ip_map = self._generate_vlan_servers()
self.__configure_arp_responder()

logger.info("VLAN interfaces: {}".format(str(self.vlan_interfaces)))
logger.info("PORTCHANNEL interfaces: {}".format(str(self.tor_pc_intfs)))
logger.info("Selected testing interfaces: %s", self.test_interfaces)

self.time_to_listen = 300.0
self.sniff_time_incr = 0
Expand All @@ -99,7 +107,7 @@ def __init__(self, activehost, standbyhost, ptfhost, ptfadapter, tbinfo,
if self.tor_vlan_intf:
self.packets_per_server = self.packets_to_send
else:
self.packets_per_server = self.packets_to_send // len(self.vlan_interfaces)
self.packets_per_server = self.packets_to_send // len(self.test_interfaces)

self.all_packets = []

Expand All @@ -114,14 +122,22 @@ def _generate_vlan_servers(self):
logger.info("ALL server address:\n {}".format(server_ip_list))

ptf_to_server_map = dict()
for i, vlan_intf in enumerate(natsorted(self.vlan_interfaces)):
for i, vlan_intf in enumerate(natsorted(self.test_interfaces)):
ptf_intf = self.tor_to_ptf_intf_map[vlan_intf]
addr = server_ip_list[i]
ptf_to_server_map[ptf_intf] = [str(addr)]

logger.debug('VLAN intf to server IP map: {}'.format(json.dumps(ptf_to_server_map, indent=4, sort_keys=True)))
return ptf_to_server_map

def _select_test_interfaces(self):
"""Select DUT interfaces that is in `active-standby` cable type."""
test_interfaces = []
for port, port_config in natsorted(self.mux_cable_table.items()):
if port_config.get("cable_type", CableType.active_standby) == self.cable_type:
test_interfaces.append(port)
return test_interfaces

def __configure_arp_responder(self):
"""
@summary: Generate ARP responder configuration using vlan_host_map.
Expand Down Expand Up @@ -258,7 +274,7 @@ def generate_from_server_to_t1(self):
# use only the connected server
else:
# Otherwise send packets to all servers
vlan_src_intfs = self.vlan_interfaces
vlan_src_intfs = self.test_interfaces

ptf_intf_to_mac_map = {}

Expand Down Expand Up @@ -646,3 +662,4 @@ def check_tcp_payload(self, packet):
return True
except Exception as err:
return False

18 changes: 12 additions & 6 deletions tests/common/dualtor/mux_simulator_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import requests

from tests.common import utilities
from tests.common.dualtor.dual_tor_common import cable_type # lgtm[py/unused-import]
from tests.common.dualtor.dual_tor_common import CableType
from tests.common.helpers.assertions import pytest_assert
from tests.common.dualtor.constants import UPPER_TOR, LOWER_TOR, TOGGLE, RANDOM, NIC, DROP, OUTPUT, FLAP_COUNTER, CLEAR_FLAP_COUNTER, RESET

Expand Down Expand Up @@ -342,25 +344,29 @@ def _toggle(side):
_toggle_all_simulator_ports(mux_server_url, side, tbinfo)
return _toggle


@pytest.fixture
def toggle_all_simulator_ports_to_upper_tor(mux_server_url, tbinfo):
def toggle_all_simulator_ports_to_upper_tor(mux_server_url, tbinfo, cable_type):
"""
A function level fixture to toggle all ports to upper_tor
A function level fixture to toggle all active-standby ports to upper_tor
For this fixture to work properly, ICMP responder must be running. Please ensure that fixture run_icmp_responder
is imported in test script. The run_icmp_responder fixture is defined in tests.common.fixtures.ptfhost_utils
"""
_toggle_all_simulator_ports(mux_server_url, UPPER_TOR, tbinfo)
if cable_type == CableType.active_standby:
_toggle_all_simulator_ports(mux_server_url, UPPER_TOR, tbinfo)


@pytest.fixture
def toggle_all_simulator_ports_to_lower_tor(mux_server_url, tbinfo):
def toggle_all_simulator_ports_to_lower_tor(mux_server_url, tbinfo, cable_type):
"""
A function level fixture to toggle all ports to lower_tor
A function level fixture to toggle all active-standby ports to lower_tor
For this fixture to work properly, ICMP responder must be running. Please ensure that fixture run_icmp_responder
is imported in test script. The run_icmp_responder fixture is defined in tests.common.fixtures.ptfhost_utils
"""
_toggle_all_simulator_ports(mux_server_url, LOWER_TOR, tbinfo)
if cable_type == CableType.active_standby:
_toggle_all_simulator_ports(mux_server_url, LOWER_TOR, tbinfo)


def _are_muxcables_active(duthost):
Expand Down
Loading

0 comments on commit e80445f

Please sign in to comment.