Skip to content

Commit

Permalink
Add loopback action test cases (sonic-net#5871)
Browse files Browse the repository at this point in the history
What is the motivation for this PR?
Add new test cases for loopback action

How did you do it?
Add 3 testcases for the loopback action feature:
test_loopback_action_basic
test_loopback_action_port_flap
test_loopback_action_reload

How did you verify/test it?
Run all the new test cases, and tests pass
  • Loading branch information
nhe-NV authored and kellyyeh committed Mar 31, 2023
1 parent bf71677 commit 994a51d
Show file tree
Hide file tree
Showing 8 changed files with 1,027 additions and 25 deletions.
50 changes: 42 additions & 8 deletions tests/common/devices/sonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class SonicHost(AnsibleHostBase):
This type of host contains information about the SONiC device (device info, services, etc.),
and also provides the ability to run Ansible modules on the SONiC device.
"""
DEFAULT_ASIC_SERVICES = ["bgp", "database", "lldp", "swss", "syncd", "teamd"]

def __init__(self, ansible_adhoc, hostname,
shell_user=None, shell_passwd=None,
Expand Down Expand Up @@ -1063,8 +1064,13 @@ def get_ip_route_info(self, dstip, ns=""):
@param dstip: destination. either ip_address or ip_network
Please beware: if dstip is an ip network, you will receive all ECMP nexthops
<<<<<<< HEAD
But if dstip is an ip address, only one nexthop will be returned,
the one which is going to be used to send a packet to the destination.
=======
But if dstip is an ip address, only one nexthop will be returned, the one which is going to be used to
send a packet to the destination.
>>>>>>> 090bc7a72 (Add loopback action test cases)
Exanples:
----------------
Expand All @@ -1077,9 +1083,14 @@ def get_ip_route_info(self, dstip, ns=""):
----------------
get_ip_route_info(ipaddress.ip_network(unicode("192.168.8.0/25")))
returns {'set_src': IPv4Address(u'10.1.0.32'), 'nexthops': [(IPv4Address(u'10.0.0.1'), u'PortChannel0001'),
<<<<<<< HEAD
(IPv4Address(u'10.0.0.5'), u'PortChannel0002'),
(IPv4Address(u'10.0.0.9'), u'PortChannel0003'),
(IPv4Address(u'10.0.0.13'), u'PortChannel0004')]}
=======
(IPv4Address(u'10.0.0.5'), u'PortChannel0002'), (IPv4Address(u'10.0.0.9'), u'PortChannel0003'),
(IPv4Address(u'10.0.0.13'), u'PortChannel0004')]}
>>>>>>> 090bc7a72 (Add loopback action test cases)
raw data
192.168.8.0/25 proto 186 src 10.1.0.32 metric 20
Expand All @@ -1103,9 +1114,14 @@ def get_ip_route_info(self, dstip, ns=""):
----------------
get_ip_route_info(ipaddress.ip_network(unicode("20c0:a818::/64")))
returns {'set_src': IPv6Address(u'fc00:1::32'), 'nexthops': [(IPv6Address(u'fc00::2'), u'PortChannel0001'),
<<<<<<< HEAD
(IPv6Address(u'fc00::a'), u'PortChannel0002'),
(IPv6Address(u'fc00::12'), u'PortChannel0003'),
(IPv6Address(u'fc00::1a'), u'PortChannel0004')]}
=======
(IPv6Address(u'fc00::a'), u'PortChannel0002'), (IPv6Address(u'fc00::12'), u'PortChannel0003'),
(IPv6Address(u'fc00::1a'), u'PortChannel0004')]}
>>>>>>> 090bc7a72 (Add loopback action test cases)
raw data
20c0:a818::/64 via fc00::2 dev PortChannel0001 proto 186 src fc00:1::32 metric 20 pref medium
Expand All @@ -1122,9 +1138,14 @@ def get_ip_route_info(self, dstip, ns=""):
----------------
get_ip_route_info(ipaddress.ip_network(unicode("0.0.0.0/0")))
returns {'set_src': IPv4Address(u'10.1.0.32'), 'nexthops': [(IPv4Address(u'10.0.0.1'), u'PortChannel0001'),
<<<<<<< HEAD
(IPv4Address(u'10.0.0.5'), u'PortChannel0002'),
(IPv4Address(u'10.0.0.9'), u'PortChannel0003'),
(IPv4Address(u'10.0.0.13'), u'PortChannel0004')]}
=======
(IPv4Address(u'10.0.0.5'), u'PortChannel0002'), (IPv4Address(u'10.0.0.9'), u'PortChannel0003'),
(IPv4Address(u'10.0.0.13'), u'PortChannel0004')]}
>>>>>>> 090bc7a72 (Add loopback action test cases)
raw data
default proto 186 src 10.1.0.32 metric 20
Expand All @@ -1141,10 +1162,18 @@ def get_ip_route_info(self, dstip, ns=""):
nexthop via 10.0.0.63 dev PortChannel0004 weight 1
----------------
get_ip_route_info(ipaddress.ip_network(unicode("::/0")))
<<<<<<< HEAD
returns {'set_src': IPv6Address(u'fc00:1::32'), 'nexthops': [(IPv6Address(u'fc00::2'), u'PortChannel0001'),
(IPv6Address(u'fc00::a'), u'PortChannel0002'),
(IPv6Address(u'fc00::12'), u'PortChannel0003'),
(IPv6Address(u'fc00::1a'), u'PortChannel0004')]}
=======
returns {'set_src': IPv6Address(u'fc00:1::32'),
'nexthops': [(IPv6Address(u'fc00::2'), u'PortChannel0001'),
(IPv6Address(u'fc00::a'), u'PortChannel0002'),
(IPv6Address(u'fc00::12'), u'PortChannel0003'),
(IPv6Address(u'fc00::1a'), u'PortChannel0004')]}
>>>>>>> 090bc7a72 (Add loopback action test cases)
raw data
default via fc00::2 dev PortChannel0001 proto 186 src fc00:1::32 metric 20 pref medium
Expand Down Expand Up @@ -1386,8 +1415,8 @@ def _parse_column_positions(self, sep_line, sep_char='-'):
sep_char: The character used in separation line. Defaults to '-'.
Returns:
Returns a list. Each item is a tuple with two elements. The first element is start position of a column. The
second element is the end position of the column.
Returns a list. Each item is a tuple with two elements. The first element is start position of a column.
The second element is the end position of the column.
"""
prev = ' ',
positions = []
Expand All @@ -1402,7 +1431,7 @@ def _parse_column_positions(self, sep_line, sep_char='-'):
prev = char
return positions

def _parse_show(self, output_lines):
def _parse_show(self, output_lines, header_len=1):

result = []

Expand All @@ -1411,7 +1440,7 @@ def _parse_show(self, output_lines):
for idx, line in enumerate(output_lines):
if sep_line_pattern.match(line):
sep_line_found = True
header_line = output_lines[idx-1]
header_lines = output_lines[idx-header_len:idx]
sep_line = output_lines[idx]
content_lines = output_lines[idx+1:]
break
Expand All @@ -1428,7 +1457,8 @@ def _parse_show(self, output_lines):

headers = []
for (left, right) in positions:
headers.append(header_line[left:right].strip().lower())
header = " ".join([header_line[left:right].strip().lower() for header_line in header_lines]).strip()
headers.append(header)

for content_line in content_lines:
# When an empty line is encountered while parsing the tabulate content, it is highly possible that the
Expand All @@ -1444,7 +1474,7 @@ def _parse_show(self, output_lines):

return result

def show_and_parse(self, show_cmd, **kwargs):
def show_and_parse(self, show_cmd, header_len=1, **kwargs):
"""Run a show command and parse the output using a generic pattern.
This method can adapt to the column changes as long as the output format follows the pattern of
Expand Down Expand Up @@ -1482,7 +1512,11 @@ def show_and_parse(self, show_cmd, **kwargs):
"lanes": "4,5,6,7",
"fec": "N/A",
"asym pfc": "off",
"admin": "up", "type": "QSFP+ or later", "vlan": "PortChannel0002", "mtu": "9100", "alias": "etp2",
"admin": "up",
"type": "QSFP+ or later",
"vlan": "PortChannel0002",
"mtu": "9100",
"alias": "etp2",
"interface": "Ethernet4",
"speed": "40G"
},
Expand Down Expand Up @@ -1517,7 +1551,7 @@ def show_and_parse(self, show_cmd, **kwargs):
output = output[start_line_index:]
else:
output = output[start_line_index:end_line_index]
return self._parse_show(output)
return self._parse_show(output, header_len)

@cached(name='mg_facts')
def get_extended_minigraph_facts(self, tbinfo, namespace=DEFAULT_NAMESPACE):
Expand Down
51 changes: 34 additions & 17 deletions tests/common/fixtures/duthost_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ def backup_and_restore_config_db_module(duthosts, rand_one_dut_hostname):
for func in _backup_and_restore_config_db(duthost, "module"):
yield func


@pytest.fixture(scope="package")
def backup_and_restore_config_db_package(duthosts):

for func in _backup_and_restore_config_db(duthosts, "package"):
yield func


@pytest.fixture(scope="session")
def backup_and_restore_config_db_session(duthosts):

Expand Down Expand Up @@ -151,21 +159,20 @@ def disable_fdb_aging(duthost):
duthost.shell_cmds(cmds=cmds)
duthost.file(path=TMP_SWITCH_CONFIG_FILE, state="absent")


@pytest.fixture(scope="module")
def ports_list(duthosts, rand_one_dut_hostname, rand_selected_dut, tbinfo):
duthost = duthosts[rand_one_dut_hostname]
cfg_facts = duthost.config_facts(host=duthost.hostname, source="persistent")['ansible_facts']
mg_facts = rand_selected_dut.get_extended_minigraph_facts(tbinfo)
config_ports = {k: v for k,v in cfg_facts['PORT'].items() if v.get('admin_status', 'down') == 'up'}
config_ports = {k: v for k, v in cfg_facts['PORT'].items() if v.get('admin_status', 'down') == 'up'}
config_port_indices = {k: v for k, v in mg_facts['minigraph_ptf_indices'].items() if k in config_ports}
ptf_ports_available_in_topo = {port_index: 'eth{}'.format(port_index) for port_index in config_port_indices.values()}
config_portchannels = cfg_facts.get('PORTCHANNEL', {})
config_port_channel_members = [port_channel['members'] for port_channel in config_portchannels.values()]
config_port_channel_member_ports = list(itertools.chain.from_iterable(config_port_channel_members))
ports = [port for port in config_ports
if config_port_indices[port] in ptf_ports_available_in_topo
and config_ports[port].get('admin_status', 'down') == 'up'
and port not in config_port_channel_member_ports]
ports = [port for port in config_ports if config_port_indices[port] in ptf_ports_available_in_topo and
config_ports[port].get('admin_status', 'down') == 'up' and port not in config_port_channel_member_ports]
return ports


Expand All @@ -182,7 +189,7 @@ def check_orch_cpu_utilization(dut, orch_cpu_threshold):
orch_cpu = dut.shell("COLUMNS=512 show processes cpu | grep orchagent | awk '{print $9}'")["stdout_lines"]
for line in orch_cpu:
if int(float(line)) > orch_cpu_threshold:
return False
return False
time.sleep(1)
return True

Expand All @@ -191,12 +198,15 @@ def check_ebgp_routes(num_v4_routes, num_v6_routes, duthost):
MAX_DIFF = 5
sumv4, sumv6 = duthost.get_ip_route_summary()
rtn_val = True
if 'ebgp' in sumv4 and 'routes' in sumv4['ebgp'] and abs(int(float(sumv4['ebgp']['routes'])) - int(float(num_v4_routes))) >= MAX_DIFF:
if 'ebgp' in sumv4 and 'routes' in sumv4['ebgp'] and \
abs(int(float(sumv4['ebgp']['routes'])) - int(float(num_v4_routes))) >= MAX_DIFF:
rtn_val = False
if 'ebgp' in sumv6 and 'routes' in sumv6['ebgp'] and abs(int(float(sumv6['ebgp']['routes'])) - int(float(num_v6_routes))) >= MAX_DIFF:
if 'ebgp' in sumv6 and 'routes' in sumv6['ebgp'] and \
abs(int(float(sumv6['ebgp']['routes'])) - int(float(num_v6_routes))) >= MAX_DIFF:
rtn_val = False
return rtn_val


@pytest.fixture(scope="module")
def shutdown_ebgp(duthosts):
# To store the original number of eBGP v4 and v6 routes.
Expand Down Expand Up @@ -229,11 +239,14 @@ def shutdown_ebgp(duthosts):
orig_v4_ebgp = v4ebgps[duthost.hostname]
orig_v6_ebgp = v6ebgps[duthost.hostname]
pytest_assert(wait_until(120, 10, 10, check_ebgp_routes, orig_v4_ebgp, orig_v6_ebgp, duthost),
"eBGP v4 routes are {}, and v6 route are {}, and not what they were originally after enabling all neighbors on {}".format(orig_v4_ebgp, orig_v6_ebgp, duthost))
"eBGP v4 routes are {}, and v6 route are {}, and not what they were originally after enabling "
"all neighbors on {}".format(orig_v4_ebgp, orig_v6_ebgp, duthost))
pytest_assert(wait_until(60, 2, 0, check_orch_cpu_utilization, duthost, orch_cpu_threshold),
"Orch CPU utilization {} > orch cpu threshold {} after startup all eBGP"
.format(duthost.shell("show processes cpu | grep orchagent | awk '{print $9}'")["stdout"],
orch_cpu_threshold))


@pytest.fixture(scope="module")
def utils_vlan_ports_list(duthosts, rand_one_dut_hostname, rand_selected_dut, tbinfo, ports_list):
"""
Expand All @@ -243,7 +256,7 @@ def utils_vlan_ports_list(duthosts, rand_one_dut_hostname, rand_selected_dut, tb
cfg_facts = duthost.config_facts(host=duthost.hostname, source="persistent")['ansible_facts']
mg_facts = rand_selected_dut.get_extended_minigraph_facts(tbinfo)
vlan_ports_list = []
config_ports = {k: v for k,v in cfg_facts['PORT'].items() if v.get('admin_status', 'down') == 'up'}
config_ports = {k: v for k, v in cfg_facts['PORT'].items() if v.get('admin_status', 'down') == 'up'}
config_portchannels = cfg_facts.get('PORTCHANNEL', {})
config_port_indices = {k: v for k, v in mg_facts['minigraph_ptf_indices'].items() if k in config_ports}
config_ports_vlan = collections.defaultdict(list)
Expand All @@ -264,14 +277,14 @@ def utils_vlan_ports_list(duthosts, rand_one_dut_hostname, rand_selected_dut, tb
if 'tagging_mode' not in vlan_members[k][port]:
continue
mode = vlan_members[k][port]['tagging_mode']
config_ports_vlan[port].append({'vlanid':int(vlanid), 'ip':ip, 'tagging_mode':mode})
config_ports_vlan[port].append({'vlanid':int(vlanid), 'ip': ip, 'tagging_mode': mode})

if config_portchannels:
for po in config_portchannels:
vlan_port = {
'dev' : po,
'port_index' : [config_port_indices[member] for member in config_portchannels[po]['members']],
'permit_vlanid' : []
'dev': po,
'port_index': [config_port_indices[member] for member in config_portchannels[po]['members']],
'permit_vlanid': []
}
if po in config_ports_vlan:
vlan_port['pvid'] = 0
Expand All @@ -286,9 +299,9 @@ def utils_vlan_ports_list(duthosts, rand_one_dut_hostname, rand_selected_dut, tb

for i, port in enumerate(ports_list):
vlan_port = {
'dev' : port,
'port_index' : [config_port_indices[port]],
'permit_vlanid' : []
'dev': port,
'port_index': [config_port_indices[port]],
'permit_vlanid': []
}
if port in config_ports_vlan:
vlan_port['pvid'] = 0
Expand All @@ -303,11 +316,13 @@ def utils_vlan_ports_list(duthosts, rand_one_dut_hostname, rand_selected_dut, tb

return vlan_ports_list


def compare_network(src_ipprefix, dst_ipprefix):
src_network = ipaddress.IPv4Interface(src_ipprefix).network
dst_network = ipaddress.IPv4Interface(dst_ipprefix).network
return src_network.overlaps(dst_network)


@pytest.fixture(scope="module")
def utils_vlan_intfs_dict_orig(duthosts, rand_one_dut_hostname, tbinfo):
'''A module level fixture to record duthost's original vlan info
Expand Down Expand Up @@ -343,6 +358,7 @@ def utils_vlan_intfs_dict_orig(duthosts, rand_one_dut_hostname, tbinfo):
vlan_intfs_dict[int(vlanid)] = {'ip': ip, 'orig': True}
return vlan_intfs_dict


def utils_vlan_intfs_dict_add(vlan_intfs_dict, add_cnt):
'''Utilities function to add add_cnt of new VLAN
Expand Down Expand Up @@ -377,6 +393,7 @@ def utils_vlan_intfs_dict_add(vlan_intfs_dict, add_cnt):
assert vlan_cnt == add_cnt
return vlan_intfs_dict


def utils_create_test_vlans(duthost, cfg_facts, vlan_ports_list, vlan_intfs_dict, delete_untagged_vlan):
'''Utilities function to create vlans for test
Expand Down
Empty file.
8 changes: 8 additions & 0 deletions tests/common/plugins/allure_wrapper/allure_step_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import logging
from allure_commons._allure import step as raw_allure_step
logger = logging.getLogger(__name__)


def step(title):
logger.info("Allure step: {}".format(title))
return raw_allure_step(title)
Empty file.
Loading

0 comments on commit 994a51d

Please sign in to comment.