Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dualtor] Add demo test template for active-active cable type #5424

Merged
merged 2 commits into from
Apr 7, 2022
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
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)
46 changes: 34 additions & 12 deletions tests/common/dualtor/data_plane_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import collections
import pytest
import json
import time
from tests.common.dualtor.dual_tor_io import DualTorIO

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 DualTorIOActiveStandby
from tests.common.helpers.assertions import pytest_assert
from tests.common.utilities import InterruptableThread
from tests.common.utilities import wait_until
Expand Down Expand Up @@ -127,12 +131,29 @@ 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, activehosts, 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)

if cable_type == CableType.active_standby:
if isinstance(activehosts, collections.Sequence):
if (len(activehosts) != 1):
raise ValueError("One active DUT only for `active-standby` cable type!")
activehost = activehosts[0]
else:
activehost = activehosts
standbyhost = get_standbyhost(duthosts, activehost)
tor_IO = DualTorIOActiveStandby(
activehost, standbyhost, ptfhost, ptfadapter, tbinfo,
io_ready, tor_vlan_port=tor_vlan_port, send_interval=send_interval
)
elif cable_type == CableType.active_active:
# TODO: initialize dual tor IO object for active-active
pass

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 @@ -197,7 +218,7 @@ def send_t1_to_server_with_action(duthosts, ptfhost, ptfadapter, tbinfo):
"""
arp_setup(ptfhost)

def t1_to_server_io_test(activehost, tor_vlan_port=None,
def t1_to_server_io_test(activehosts, tor_vlan_port=None,
delay=0, allowed_disruption=0, action=None, verify=False, send_interval=0.01,
stop_after=None):
"""
Expand All @@ -224,7 +245,7 @@ def t1_to_server_io_test(activehost, tor_vlan_port=None,
data_plane_test_report (dict): traffic test statistics (sent/rcvd/dropped)
"""

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

Expand All @@ -241,7 +262,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 All @@ -262,7 +283,7 @@ def send_server_to_t1_with_action(duthosts, ptfhost, ptfadapter, tbinfo):
"""
arp_setup(ptfhost)

def server_to_t1_io_test(activehost, tor_vlan_port=None,
def server_to_t1_io_test(activehosts, tor_vlan_port=None,
delay=0, allowed_disruption=0, action=None, verify=False, send_interval=0.01,
stop_after=None):
"""
Expand All @@ -288,9 +309,10 @@ def server_to_t1_io_test(activehost, tor_vlan_port=None,
data_plane_test_report (dict): traffic test statistics (sent/rcvd/dropped)
"""

tor_IO = run_test(duthosts, activehost, ptfhost, ptfadapter,
tor_IO = run_test(duthosts, activehosts, 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
61 changes: 57 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 @@ -27,7 +28,9 @@
logger = logging.getLogger(__name__)


class DualTorIO:
class DualTorIOActiveStandby:
"""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):
self.tor_pc_intf = None
Expand Down Expand Up @@ -74,11 +77,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 +105,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 +120,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) == CableType.active_standby:
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 +272,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 +660,42 @@ def check_tcp_payload(self, packet):
return True
except Exception as err:
return False


class DualTorIOActiveActive(object):
"""Class to conduct IO over ports in `active-active` mode."""

def generate_from_server_to_t1(self):
"""
@summary: Generate (not send) the packets to be sent from server to T1
"""
pass

def generate_from_t1_to_server(self):
"""
@summary: Generate (not send) the packets to be sent from T1 to server
"""
pass

def start_io_test(self, traffic_generator=None):
"""
@summary: The entry point to start the TOR dataplane I/O test.
Args:
traffic_generator (function): A callback function to decide the
traffic direction (T1 to server / server to T1)
Allowed values: self.generate_from_t1_to_server or
self.generate_from_server_to_t1
"""
pass

def examine_flow(self):
"""
@summary: This method examines packets collected by sniffer thread
The method compares TCP payloads of the packets one by one (assuming all
payloads are consecutive integers), and the losses if found - are treated
as disruptions in Dataplane forwarding. All disruptions are saved to
self.lost_packets dictionary, in format:
disrupt_start_id = (missing_packets_count, disrupt_time,
disrupt_start_timestamp, disrupt_stop_timestamp)
"""
pass
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