From 0bd790c499e6c7179d4c64ce3d950d9eaa35e8ae Mon Sep 17 00:00:00 2001 From: MirceaDan Date: Thu, 4 May 2023 21:13:25 -0700 Subject: [PATCH 1/9] MD034/no-bare-urls Bare URL used [Context: "snoble@sonn.com"] --- MAINTAINERS.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 2757be7da..d9e73ba84 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -6,6 +6,6 @@ such as architecture, process, existing issues. This is the list of maintainers, including their email address for direct communications: -| Name | GitHub Id | email | Area of expertise | -|------------------------|--------------------------|--------------------------------|---------------------------------| -| Steven Noble | @sonoble | snoble@sonn.com | Development | +| Name | GitHub Id | email | Area of expertise | +|------------------------|--------------------------|------------------------------------|---------------------------------| +| Steven Noble | @sonoble | [snoble@sonn.com](snoble@sonn.com) | Development | From 7a2a88eb40f6671b900ad2c9fa1b75a3fc91e10b Mon Sep 17 00:00:00 2001 From: Serhiy Boiko <120576441+SerhiyBoikoPLV@users.noreply.github.com> Date: Fri, 5 May 2023 07:24:13 +0300 Subject: [PATCH 2/9] Add IPv6 route and neighbor tests (#315) * Update IPv6 verify_dut_routes function Add should_exist flag to help verifying routes that should or should not exist. Signed-off-by: Serhiy Boiko * Update tb_ping_device Add ability to specify the number (count) of icmp messages that are sent from dut. Signed-off-by: Serhiy Boiko * Add IPv6 route and neighbor tests - test_ipv6_nei_ageing - test_ipv6_route_metrics - test_ipv6_nh_state Signed-off-by: Serhiy Boiko --------- Signed-off-by: Serhiy Boiko --- .../test_suite/functional/ipv6/ipv6_utils.py | 22 +- .../test_suite/functional/ipv6/test_ipv6.py | 1 + .../functional/ipv6/test_ipv6_bridge.py | 2 + .../functional/ipv6/test_ipv6_ipv4.py | 4 + .../functional/ipv6/test_ipv6_nei.py | 329 +++++++++++++++++ .../functional/ipv6/test_ipv6_route.py | 340 ++++++++++++++++++ .../utils/test_utils/tb_utils.py | 4 +- 7 files changed, 690 insertions(+), 12 deletions(-) create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_nei.py diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/ipv6_utils.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/ipv6_utils.py index d90410101..fb3ecbd2c 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/ipv6_utils.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/ipv6_utils.py @@ -18,17 +18,19 @@ async def verify_dut_routes(dent, expected_routes): for expected_route in expected_routes: for route in routes: - for key in expected_route: # find matching route - if key == 'flags': - continue - if expected_route[key] != route[key]: - break - else: # route found - assert all(flag in route['flags'] for flag in expected_route['flags']), \ - f'Route {route} should have {expected_route["flags"]} flags' - break + if not all(expected_route[key] == route.get(key) + for key in expected_route + if key not in ['flags', 'should_exist']): + # if some keys do not match go to the next route + continue + # route found + assert expected_route['should_exist'], f'Route {route} found, but not expected' + assert all(flag in route['flags'] for flag in expected_route['flags']), \ + f'Route {route} should have {expected_route["flags"]} flags' + break else: # route not found - raise LookupError(f'Route {expected_route} expected, but not found') + if expected_route['should_exist']: + raise LookupError(f'Route {expected_route} expected, but not found') async def verify_dut_neighbors(dent, expected_neis): diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6.py index f3490da34..697b5cd5b 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6.py @@ -90,6 +90,7 @@ async def test_ipv6_basic_config(testbed): expected_routes = [{'dev': info.swp, 'dst': info.swp_ip[:-1] + f'/{info.plen}', + 'should_exist': True, 'flags': ['rt_trap']} for info in address_map] await verify_dut_routes(dent, expected_routes) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_bridge.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_bridge.py index 5e9481650..7ff8a63c0 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_bridge.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_bridge.py @@ -115,6 +115,7 @@ async def test_ipv6_on_bridge(testbed): # connected routes added and offloaded expected_routes = [{'dev': info.swp, 'dst': info.swp_ip[:-1] + f'/{info.plen}', + 'should_exist': True, 'flags': ['rt_trap']} for info in address_map] await verify_dut_routes(dent, expected_routes) @@ -334,6 +335,7 @@ async def test_ipv6_move_host_on_bridge(testbed): # 2. Verify IP configuration: no errors on IP address adding, connected routes added and offloaded expected_routes = [{'dev': info.swp, 'dst': info.swp_ip[:-1] + f'/{info.plen}', + 'should_exist': True, 'flags': ['rt_trap']} for info in address_map] await verify_dut_routes(dent, expected_routes) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_ipv4.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_ipv4.py index f0786fd73..baa517e08 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_ipv4.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_ipv4.py @@ -205,10 +205,12 @@ async def test_ipv64_nh_reconfig(testbed): expected_routes = [{'dev': info.swp, 'dst': info.swp_ip[:-1] + ('0/' if version == IPV4 else '/') + str(info.plen), + 'should_exist': True, 'flags': ['rt_trap']} for info in address_map[version]] expected_routes += [{'dev': nh_route[version].swp, 'dst': f'{nh_route[version].dst}/{nh_route[version].plen}', + 'should_exist': True, 'flags': ['offload', 'rt_offload']}] await verify_dut_routes(dent, expected_routes) @@ -377,10 +379,12 @@ async def test_ipv64_nh_routes(testbed): # Verify connected routes added and offloaded expected_routes = [{'dev': info.swp, 'dst': info.swp_ip[:-1] + ('/' if ':' in info.swp_ip else '0/') + str(info.plen), + 'should_exist': True, 'flags': ['rt_trap']} for info in address_map] expected_routes += [{'dev': route.swp, 'dst': f'{route.dst}/{route.plen}', + 'should_exist': True, 'flags': ['offload', 'rt_offload']} for route in nh_route] await verify_dut_routes(dent, expected_routes) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_nei.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_nei.py new file mode 100644 index 000000000..f5b1346b7 --- /dev/null +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_nei.py @@ -0,0 +1,329 @@ +from collections import namedtuple +import asyncio +import pytest +import time + +from dent_os_testbed.lib.ip.ip_link import IpLink +from dent_os_testbed.lib.ip.ip_address import IpAddress +from dent_os_testbed.lib.ip.ip_neighbor import IpNeighbor +from dent_os_testbed.lib.os.recoverable_sysctl import RecoverableSysctl + +from dent_os_testbed.utils.test_utils.tgen_utils import ( + tgen_utils_get_dent_devices_with_tgen, + tgen_utils_traffic_generator_connect, + tgen_utils_dev_groups_from_config, + tgen_utils_setup_streams, + tgen_utils_send_ns, +) + +from dent_os_testbed.utils.test_utils.tb_utils import ( + tb_ping_device, +) + +from dent_os_testbed.test.test_suite.functional.ipv6.ipv6_utils import ( + verify_dut_routes, +) + +pytestmark = [ + pytest.mark.suite_functional_ipv6, + pytest.mark.usefixtures('cleanup_ip_addrs', 'enable_ipv6_forwarding', 'cleanup_sysctl'), + pytest.mark.asyncio, +] + + +def neigbors_match_expectation(neighbors, expected_neis): + status = [] + for expected in expected_neis: + for nei in neighbors: + if expected['dev'] == nei['dev'] and \ + expected['dst'] == nei['dst']: + status.append(expected['should_exist'] and + expected['state'] == nei['state'][0]) + break + else: # none of the neighbors matched expected + status.append(not expected['should_exist']) + return all(status) + + +async def wait_for_nei_state(dent_dev, expected_neis, timeout=60, poll_interval=10): + dent_dev.applog.info('Begin neighbor polling') + start = time.time() + while time.time() < start + timeout: + out = await IpNeighbor.show(input_data=[{dent_dev.host_name: [ + {'cmd_options': '-j -6'} + ]}], parse_output=True) + assert out[0][dent_dev.host_name]['rc'] == 0, 'Failed to get DUT neighbors' + + neighbors = out[0][dent_dev.host_name]['parsed_output'] + if neigbors_match_expectation(neighbors, expected_neis): + dent_dev.applog.info(f'Neighbors matched expectation after {(time.time() - start) // 1}s') + return + dent_dev.applog.debug(f'Neighbors did not match expectation. Trying again in {poll_interval}s') + await asyncio.sleep(poll_interval) + + raise TimeoutError(f'Expected neighbors {expected_neis}, but not found.\n{neighbors}') + + +async def test_ipv6_nei_ageing(testbed): + """ + Test Name: test_ipv6_nei_ageing + Test Suite: suite_functional_ipv6 + Test Overview: + Verify neighbor ageing and flush + Test Procedure: + 0. Set IP addresses on DUT ports, add ip interfaces to TG + 1.1 Set gc_interval, but leave default threshold values + 1.2 Resolve neighbors + 1.3 Check that neighbor entries are STALE + 1.4 Check that neighbor entries are still STALE and not aged + 2.1 Set a large gc_stale_time_s value, set small threshold values + 2.2 Resolve neighbors + 2.3 Check that neighbor entries are REACHABLE + 2.4 Check that neighbor entries are STALE + 2.5 Check that neighbor entries are aged + 3.1 Set a large gc_stale_time_s value, set small threshold values + 3.2 Resolve neighbors on first port + 3.3 Resolve neighbors on second port in the next time window + 3.4 Check that neighbor entries are STALE + 3.5 Check that neighbor entries are aged only on the first port + 3.6 Check that neighbor entries on the second port are aged in the next time window + """ + num_of_ports = 2 + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], num_of_ports) + if not tgen_dev or not dent_devices: + pytest.skip('The testbed does not have enough dent with tgen connections') + dent_dev = dent_devices[0] + dent = dent_dev.host_name + tg_ports = tgen_dev.links_dict[dent][0][:num_of_ports] + ports = tgen_dev.links_dict[dent][1][:num_of_ports] + addr_info = namedtuple('addr_info', ['swp', 'tg', 'swp_ip', 'tg_ip', 'plen']) + nei_update_time_s = 5 + base_reach_time_s = 15 + gc_interval_s = 5 + gc_stale_time_s = 30 + + address_map = ( + addr_info(ports[0], tg_ports[0], '2001:1111::1', '2001:1111::2', 64), + addr_info(ports[0], tg_ports[0], '2001:2222::1', '2001:2222::2', 64), + addr_info(ports[1], tg_ports[1], '2001:3333::1', '2001:3333::2', 64), + addr_info(ports[1], tg_ports[1], '2001:4444::1', '2001:4444::2', 64), + ) + config = { + 'net.ipv6.neigh.default.gc_interval': gc_interval_s, + 'net.ipv6.neigh.default.gc_stale_time': gc_stale_time_s, + f'net.ipv6.neigh.{ports[0]}.gc_stale_time': gc_stale_time_s, + f'net.ipv6.neigh.{ports[1]}.gc_stale_time': gc_stale_time_s, + 'net.ipv6.neigh.default.gc_thresh1': 1, + 'net.ipv6.neigh.default.gc_thresh2': 20, + 'net.ipv6.neigh.default.gc_thresh3': 30, + 'net.ipv6.neigh.default.base_reachable_time_ms': base_reach_time_s * 1000, + 'net.ipv6.neigh.default.delay_first_probe_time': nei_update_time_s, + } + + # 0. Set IP addresses on DUT ports, add ip interfaces to TG + out = await IpLink.set(input_data=[{dent: [ + {'device': port, 'operstate': 'up'} for port in ports + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to set port state UP' + + out = await IpAddress.add(input_data=[{dent: [ + {'dev': info.swp, 'prefix': f'{info.swp_ip}/{info.plen}'} + for info in address_map + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to add IP addr to port' + + dev_groups = tgen_utils_dev_groups_from_config( + {'ixp': info.tg, 'ip': info.tg_ip, 'gw': info.swp_ip, + 'plen': info.plen, 'version': 'ipv6'} + for info in address_map + ) + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + await tgen_utils_setup_streams(tgen_dev, None, { + 'dummy': { + 'type': 'raw', + 'ep_source': ports[0], + 'ep_destination': ports[1] + }, + }) + + expected_routes = [ + {'dev': info.swp, + 'dst': info.swp_ip[:-1] + f'/{info.plen}', + 'should_exist': True, + 'flags': ['rt_trap']} + for info in address_map + ] + await verify_dut_routes(dent, expected_routes) + + # Scenario #1 + # 1.1 Set gc_interval, but leave default threshold values + out = await RecoverableSysctl.set(input_data=[{dent: [ + {'variable': variable, 'value': value} + for variable, value in config.items() if 'gc_thresh' not in variable + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to update sysctl values' + + # 1.2 Resolve neighbors + out = await tgen_utils_send_ns(tgen_dev, ({'ixp': tg} for tg in tg_ports)) + assert all(rc['success'] for rc in out), 'Failed to send NS from TG' + + out = await asyncio.gather(*[ + tb_ping_device(dent_dev, info.tg_ip, pkt_loss_treshold=0, dump=True, count=1) + for info in address_map + ]) + assert all(rc == 0 for rc in out), 'Some pings did not have a reply' + + start = time.time() + + # 1.3 Check that neighbor entries are STALE + expected = [ + {'dev': info.swp, 'dst': info.tg_ip, 'should_exist': True, 'state': 'STALE'} + for info in address_map + ] + await wait_for_nei_state(dent_dev, expected) + elapsed = time.time() - start + assert elapsed < gc_stale_time_s + gc_interval_s, \ + f'Expected neighbors to be STALE after no more than {gc_stale_time_s + gc_interval_s = }s, ' + \ + f'but waited for {elapsed // 1}s' + + # 1.4 Check that neighbor entries are still STALE and not aged + dent_dev.applog.info(f'Wait for a total of {gc_stale_time_s*3 = }s to make sure that neighbors did not age') + await asyncio.sleep(start - time.time() + gc_stale_time_s*3) + await wait_for_nei_state(dent_dev, expected, timeout=10) + + # Scenario #2 + # 2.1 Set a large gc_stale_time_s value, set small threshold values + new_gc_stale_time_s = gc_stale_time_s * 3 + + out = await RecoverableSysctl.set(input_data=[{dent: [ + {'variable': variable, 'value': value} + for variable, value in config.items() if 'stale_time' not in variable + ] + [ + {'variable': variable, 'value': new_gc_stale_time_s} + for variable in config if 'stale_time' in variable + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to update sysctl values' + + # 2.2 Resolve neighbors + out = await tgen_utils_send_ns(tgen_dev, ({'ixp': tg} for tg in tg_ports)) + assert all(rc['success'] for rc in out), 'Failed to send NS from TG' + + out = await asyncio.gather(*[ + tb_ping_device(dent_dev, info.tg_ip, pkt_loss_treshold=0, dump=True, count=1) + for info in address_map + ]) + assert all(rc == 0 for rc in out), 'Some pings did not have a reply' + + start = time.time() + + # 2.3 Check that neighbor entries are REACHABLE + expected = [ + {'dev': info.swp, 'dst': info.tg_ip, 'should_exist': True, 'state': 'REACHABLE'} + for info in address_map + ] + await wait_for_nei_state(dent_dev, expected, poll_interval=nei_update_time_s) + elapsed = time.time() - start + assert elapsed < base_reach_time_s, \ + f'Expected neighbors to be REACHABLE after no more than {base_reach_time_s = }s, ' + \ + f'but waited for {elapsed // 1}s' + + # 2.4 Check that neighbor entries are STALE + expected = [ + {'dev': info.swp, 'dst': info.tg_ip, 'should_exist': True, 'state': 'STALE'} + for info in address_map + ] + await wait_for_nei_state(dent_dev, expected) + elapsed = time.time() - start + assert elapsed < new_gc_stale_time_s + gc_interval_s, \ + f'Expected neighbors to be STALE after no more than {new_gc_stale_time_s + gc_interval_s = }s, ' + \ + f'but waited for {elapsed // 1}s' + + # 2.5 Check that neighbor entries are aged + expected = [ + {'dev': info.swp, 'dst': info.tg_ip, 'should_exist': False} + for info in address_map + ] + await wait_for_nei_state(dent_dev, expected, timeout=new_gc_stale_time_s*2) + elapsed = time.time() - start + assert gc_stale_time_s*2 < elapsed < new_gc_stale_time_s*2, \ + f'Expected neighbors to be STALE after no more than {new_gc_stale_time_s*2 = }s, ' + \ + f'but waited for {elapsed // 1}s' + + # Scenario #3 + # 3.1 Set a large gc_stale_time_s value, set small threshold values + gc_stale_time_s = 60 + gc_interval_s = 15 + config.update({ + 'net.ipv6.neigh.default.gc_interval': gc_interval_s, + 'net.ipv6.neigh.default.gc_stale_time': gc_stale_time_s, + f'net.ipv6.neigh.{ports[0]}.gc_stale_time': gc_stale_time_s, + f'net.ipv6.neigh.{ports[1]}.gc_stale_time': gc_stale_time_s, + }) + + out = await RecoverableSysctl.set(input_data=[{dent: [ + {'variable': variable, 'value': value} + for variable, value in config.items() + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to update sysctl values' + + # 3.2 Resolve neighbors on first port + out = await tgen_utils_send_ns(tgen_dev, ({'ixp': tg_ports[0]},)) + assert all(rc['success'] for rc in out), 'Failed to send NS from TG' + + out = await asyncio.gather(*[ + tb_ping_device(dent_dev, info.tg_ip, pkt_loss_treshold=0, dump=True, count=1) + for info in address_map if info.tg == tg_ports[0] + ]) + assert all(rc == 0 for rc in out), 'Some pings did not have a reply' + + start = time.time() + + # 3.3 Resolve neighbors on second port in the next time window + dent_dev.applog.info(f'Wait {gc_interval_s = }s for the next GC interval') + await asyncio.sleep(start - time.time() + gc_interval_s) + + out = await tgen_utils_send_ns(tgen_dev, ({'ixp': tg_ports[1]},)) + assert all(rc['success'] for rc in out), 'Failed to send NS from TG' + + out = await asyncio.gather(*[ + tb_ping_device(dent_dev, info.tg_ip, pkt_loss_treshold=0, dump=True, count=1) + for info in address_map if info.tg == tg_ports[1] + ]) + assert all(rc == 0 for rc in out), 'Some pings did not have a reply' + + # 3.4 Check that neighbor entries are STALE + expected = [ + {'dev': info.swp, 'dst': info.tg_ip, 'should_exist': True, 'state': 'STALE'} + for info in address_map + ] + await wait_for_nei_state(dent_dev, expected, poll_interval=nei_update_time_s) + elapsed = time.time() - start + expected_time = base_reach_time_s*1.5 + gc_interval_s*2 + nei_update_time_s + assert elapsed < expected_time, \ + f'Expected neighbors to be STALE after no more than {expected_time}s, ' + \ + f'but waited for {elapsed // 1}s' + + # 3.5 Check that neighbor entries are aged only on the first port + expected = [ + {'dev': info.swp, 'dst': info.tg_ip, 'should_exist': info.tg == tg_ports[1], 'state': 'STALE'} + for info in address_map + ] + await wait_for_nei_state(dent_dev, expected, timeout=gc_stale_time_s + gc_interval_s*2) + elapsed = time.time() - start + expected_time = gc_stale_time_s + gc_interval_s*2 + nei_update_time_s + assert elapsed < expected_time, \ + f'Expected neighbors to be STALE/aged after no more than {expected_time}s, ' + \ + f'but waited for {elapsed // 1}s' + + # 3.6 Check that neighbor entries on the second port are aged in the next time window + expected = [ + {'dev': info.swp, 'dst': info.tg_ip, 'should_exist': False} + for info in address_map + ] + await wait_for_nei_state(dent_dev, expected) + elapsed = time.time() - start + expected_time = gc_stale_time_s + gc_interval_s*3 + assert elapsed < expected_time, \ + f'Expected neighbors to be aged after no more than {expected_time}s, ' + \ + f'but waited for {elapsed // 1}s' diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_route.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_route.py index 1349ddfdc..7d9f5830e 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_route.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv6/test_ipv6_route.py @@ -125,6 +125,7 @@ async def test_ipv6_route_default_offload(testbed): 'dst': 'default', 'gateway': route['gw'], 'metric': route['metric'], + 'should_exist': True, 'flags': ['rt_offload'] if route['should_offload'] else []} for route in route_map] await verify_dut_routes(dent, expected_routes) @@ -156,6 +157,7 @@ async def test_ipv6_route_default_offload(testbed): 'dst': 'default', 'gateway': route['gw'], 'metric': route['metric'], + 'should_exist': True, 'flags': ['rt_offload'] if route['should_offload'] else []} for route in route_map] await verify_dut_routes(dent, expected_routes) @@ -275,10 +277,12 @@ async def test_ipv6_route_hosts_offload(testbed): # 3. Verify routes offloaded expected_routes = [{'dev': info.swp, 'dst': info.swp_ip[:-1] + f'/{info.plen}', + 'should_exist': True, 'flags': ['rt_trap']} for info in address_map] expected_routes += [{'dev': link.swp, 'dst': dst + (f'/{plen}' if plen != 128 else ''), + 'should_exist': True, 'flags': ['offload', 'rt_offload']} for dst, plen, link in routes] await verify_dut_routes(dent, expected_routes) @@ -336,6 +340,342 @@ async def test_ipv6_route_hosts_offload(testbed): routes.pop() expected_routes += [{'dev': link.swp, 'dst': f'{dst}/{plen}', + 'should_exist': True, 'flags': ['offload', 'rt_offload']} for dst, plen, link in routes] await verify_dut_routes(dent, expected_routes) + + +async def test_ipv6_nh_state(testbed): + """ + Test Name: test_ipv6_nh_state + Test Suite: suite_functional_ipv6 + Test Overview: + Verify traffic over next hop routes with port in link up state + Test Procedure: + 1. Add IP address for 3 interfaces + 2. Add 2 net routes and 2 host routes for one of subnets + 3. Add less specific routes to same networks (shorter mask) + 4. Set the 2d interface to down state + 5. Verify routes through down port are deleted and other routes to same network are not + 6. Send traffic for deleted networks + 7. Verify traffic reaches the 3d interface despite better paths were defined to the 2d interface + """ + num_of_ports = 3 + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], num_of_ports) + if not tgen_dev or not dent_devices: + pytest.skip('The testbed does not have enough dent with tgen connections') + dent_dev = dent_devices[0] + dent = dent_dev.host_name + tg_ports = tgen_dev.links_dict[dent][0][:num_of_ports] + ports = tgen_dev.links_dict[dent][1][:num_of_ports] + addr_info = namedtuple('addr_info', ['swp', 'tg', 'swp_ip', 'tg_ip', 'plen']) + traffic_duration = 10 + wait_for_stats = 5 + + address_map = ( + addr_info(ports[0], tg_ports[0], '2001:1111::1', '2001:1111::2', 64), + addr_info(ports[1], tg_ports[1], '2001:2222::1', '2001:2222::2', 64), + addr_info(ports[2], tg_ports[2], '2001:3333::1', '2001:3333::2', 64), + ) + + egress_port1 = address_map[1] + egress_port2 = address_map[2] + route_info = namedtuple('route_info', ['swp', 'net', 'host', 'plen', 'gw', 'metric']) + routes = [ + # 2 net routes and 2 host routes for one of subnets + route_info(egress_port1.swp, '2001:1::', '5', 128, egress_port1.tg_ip, None), + route_info(egress_port1.swp, '2001:1::', '8', 126, egress_port1.tg_ip, None), + route_info(egress_port1.swp, '2001:2::', '5', 128, egress_port1.tg_ip, None), + route_info(egress_port1.swp, '2001:2::', '8', 126, egress_port1.tg_ip, None), + # less specific routes to same networks (shorter mask) + route_info(egress_port1.swp, '2001:3::', '', 64, egress_port1.tg_ip, 10), + route_info(egress_port2.swp, '2001:3::', '', 64, egress_port2.tg_ip, 100), + route_info(egress_port2.swp, '2001:2::', '', 64, egress_port2.tg_ip, None), + ] + + out = await IpLink.show(input_data=[{dent: [ + {'cmd_options': '-j'} + ]}], parse_output=True) + assert out[0][dent]['rc'] == 0, 'Failed to get port info' + + dut_mac = {link['ifname']: link['address'] + for link in out[0][dent]['parsed_output'] + if link['ifname'] in ports} + tg_to_swp = {tg: swp for tg, swp in zip(tg_ports, ports)} + + # Configure ports up + out = await IpLink.set(input_data=[{dent: [ + {'device': port, 'operstate': 'up'} for port in ports + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to set port state UP' + + # 1. Add IP address for 3 interfaces + out = await IpAddress.add(input_data=[{dent: [ + {'dev': info.swp, 'prefix': f'{info.swp_ip}/{info.plen}'} + for info in address_map + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to add IP addr to port' + + # Configure TG interfaces + dev_groups = tgen_utils_dev_groups_from_config( + {'ixp': info.tg, 'ip': info.tg_ip, 'gw': info.swp_ip, + 'plen': info.plen, 'version': 'ipv6'} + for info in address_map + ) + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + # Configure traffic items + nets = list(set(r.net for r in routes)) + hosts = list(set(r.host for r in routes if r.host)) + streams = { + f'traffic to {net}{host}': { + 'type': 'raw', + 'protocol': 'ipv6', + 'ip_source': dev_groups[tg_ports[0]][0]['name'], + 'rate': 10, # % + 'frame_rate_type': 'line_rate', + 'srcMac': '02:00:00:00:00:01', + 'dstMac': dut_mac[tg_to_swp[tg_ports[0]]], + 'srcIp': dev_groups[tg_ports[0]][0]['ip'], + 'dstIp': f'{net}{host}', + } for net in nets for host in hosts + } + await tgen_utils_setup_streams(tgen_dev, None, streams) + + # 2. Add 2 net routes and 2 host routes for one of subnets + # 3. Add less specific routes to same networks (shorter mask) + config = [ + {'dst': f'{net}/{plen}', 'via': via} + for _, net, _, plen, via, metric in routes if metric is None + ] + config += [ + {'dst': f'{net}/{plen}', 'metric': metric, 'via': via} + for _, net, _, plen, via, metric in routes if metric is not None + ] + out = await IpRoute.add(input_data=[{dent: config}]) + assert out[0][dent]['rc'] == 0, 'Failed to add routes' + + # 4. Set the 2d interface to down state + out = await IpLink.set(input_data=[{dent: [ + {'device': egress_port1.swp, 'operstate': 'down'} + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to set port state UP' + + # 5. Verify routes through down port are deleted and other routes to same network are not + expected_routes = [{'dev': info.swp, + 'dst': f'{info.swp_ip[:-1]}/{info.plen}', + 'should_exist': info.swp != egress_port1.swp, + 'flags': ['rt_trap']} + for info in address_map] + expected_routes += [{'dev': route.swp, + 'dst': f'{route.net}{route.host}/{route.plen}', + 'gateway': route.gw, + 'should_exist': route.swp != egress_port1.swp, + 'flags': ['offload', 'rt_offload']} + for route in routes] + await verify_dut_routes(dent, expected_routes) + + # 6. Send traffic for deleted networks + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(traffic_duration) + await tgen_utils_stop_traffic(tgen_dev) + + # 7. Verify traffic reaches the 3d interface despite better paths were defined to the 2d interface + await asyncio.sleep(wait_for_stats) + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Flow Statistics') + for row in stats.Rows: + loss = tgen_utils_get_loss(row) + if row['Rx Port'] != egress_port1.tg and routes[0].net not in row['Traffic Item']: + expected_loss = 0 + else: + expected_loss = 100 + assert loss == expected_loss, f'Expected loss: {expected_loss}%, actual: {loss}%' + + +async def test_ipv6_route_metrics(testbed): + """ + Test Name: test_ipv6_route_metrics + Test Suite: suite_functional_ipv6 + Test Overview: + Verify that the route with the best metric is used and offloaded + Test Procedure: + 1. Add IP address for 3 interfaces + 2. Add multiple routes to a subnet with different metrics via 2nd and 3rd interfaces + 3. Send traffic and verify it's passed through the route with best metric + 4. Verify the route to the network with best metric is the only offloaded route + 5. Delete route with the best metrics (via 2nd interface) + 6. Send traffic and verify it's passed through the route with best metric + 7. Verify the route to the network with best metric is the only offloaded route + 8. Add route with best metrics (via 3rd interface) + 9. Send traffic and verify it's passed through the route with best metric + 10. Verify the route to the network with best metric is the only offloaded route + """ + num_of_ports = 3 + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], num_of_ports) + if not tgen_dev or not dent_devices: + pytest.skip('The testbed does not have enough dent with tgen connections') + dent_dev = dent_devices[0] + dent = dent_dev.host_name + tg_ports = tgen_dev.links_dict[dent][0][:num_of_ports] + ports = tgen_dev.links_dict[dent][1][:num_of_ports] + addr_info = namedtuple('addr_info', ['swp', 'tg', 'swp_ip', 'tg_ip', 'plen']) + traffic_duration = 10 + wait_for_stats = 5 + + address_map = ( + addr_info(ports[0], tg_ports[0], '2001:1111::1', '2001:1111::2', 64), + addr_info(ports[1], tg_ports[1], '2001:2222::1', '2001:2222::2', 64), + addr_info(ports[2], tg_ports[2], '2001:3333::1', '2001:3333::2', 64), + addr_info(ports[2], tg_ports[2], '2001:4444::1', '2001:4444::2', 64), + ) + + route_info = namedtuple('route_info', ['swp', 'dst', 'plen', 'gw', 'metric']) + dst_ip = address_map[-1].tg_ip[:-1] + '5' + routes = [ + route_info(address_map[1].swp, dst_ip[:-1], 64, address_map[1].tg_ip, 100), + route_info(address_map[1].swp, dst_ip[:-1], 64, address_map[1].tg_ip, 101), + route_info(address_map[2].swp, dst_ip[:-1], 64, address_map[2].tg_ip, 102), + route_info(address_map[2].swp, dst_ip[:-1], 64, address_map[2].tg_ip, 99), + ] + + out = await IpLink.show(input_data=[{dent: [ + {'cmd_options': '-j'} + ]}], parse_output=True) + assert out[0][dent]['rc'] == 0, 'Failed to get port info' + + dut_mac = {link['ifname']: link['address'] + for link in out[0][dent]['parsed_output'] + if link['ifname'] in ports} + tg_to_swp = {tg: swp for tg, swp in zip(tg_ports, ports)} + + # Configure ports up + out = await IpLink.set(input_data=[{dent: [ + {'device': port, 'operstate': 'up'} for port in ports + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to set port state UP' + + # 1. Add IP address for 3 interfaces + out = await IpAddress.add(input_data=[{dent: [ + {'dev': info.swp, 'prefix': f'{info.swp_ip}/{info.plen}'} + for info in address_map + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to add IP addr to port' + + # Configure TG interfaces + dev_groups = tgen_utils_dev_groups_from_config( + {'ixp': info.tg, 'ip': info.tg_ip, 'gw': info.swp_ip, + 'plen': info.plen, 'version': 'ipv6'} + for info in address_map + ) + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + # Configure traffic items + streams = { + 'traffic': { + 'type': 'raw', + 'protocol': 'ipv6', + 'ip_source': dev_groups[tg_ports[0]][0]['name'], + 'rate': 10, # % + 'frame_rate_type': 'line_rate', + 'srcMac': '02:00:00:00:00:01', + 'dstMac': dut_mac[tg_to_swp[tg_ports[0]]], + 'srcIp': dev_groups[tg_ports[0]][0]['ip'], + 'dstIp': dst_ip, + } + } + await tgen_utils_setup_streams(tgen_dev, None, streams) + + # 2. Add multiple routes to a subnet with different metrics via 2nd and 3rd interfaces + out = await IpRoute.add(input_data=[{dent: [ + {'dst': f'{route.dst}/{route.plen}', 'metric': route.metric, 'via': route.gw} + for route in routes[:-1] # don't add the last route yet + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to add routes' + + # 3. Send traffic and verify it's passed through the route with best metric + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(traffic_duration) + await tgen_utils_stop_traffic(tgen_dev) + + await asyncio.sleep(wait_for_stats) + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Flow Statistics') + for row in stats.Rows: + loss = tgen_utils_get_loss(row) + expected_loss = 0 if tg_to_swp[row['Rx Port']] == routes[0].swp else 100 + assert loss == expected_loss, f'Expected loss: {expected_loss}%, actual: {loss}%' + + # 4. Verify the route to the network with best metric is the only offloaded route + connected_routes = [{'dev': info.swp, + 'dst': f'{info.swp_ip[:-1]}/{info.plen}', + 'should_exist': True, + 'flags': ['rt_trap'] if info != address_map[-1] else []} + for info in address_map] + expected_routes = [{'dev': route.swp, + 'dst': f'{route.dst}/{route.plen}', + 'gateway': route.gw, + 'metric': route.metric, + 'should_exist': route.metric != 99, + 'flags': ['offload', 'rt_offload'] if route.metric == 100 else []} + for route in routes[:-1]] + await verify_dut_routes(dent, connected_routes + expected_routes) + + # 5. Delete route with the best metrics (via 2nd interface) + out = await IpRoute.delete(input_data=[{dent: [ + {'dst': f'{routes[0].dst}/{routes[0].plen}', + 'metric': routes[0].metric, + 'gateway': routes[0].gw, + 'dev': routes[0].swp} + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to delete routes' + + # 6. Send traffic and verify it's passed through the route with best metric + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(traffic_duration) + await tgen_utils_stop_traffic(tgen_dev) + + await asyncio.sleep(wait_for_stats) + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Flow Statistics') + for row in stats.Rows: + loss = tgen_utils_get_loss(row) + expected_loss = 0 if tg_to_swp[row['Rx Port']] == routes[0].swp else 100 + assert loss == expected_loss, f'Expected loss: {expected_loss}%, actual: {loss}%' + + # 7. Verify the route to the network with best metric is the only offloaded route + expected_routes[0]['should_exist'] = False # metric 100 + expected_routes[1]['flags'] = ['offload', 'rt_offload'] # metric 101 + await verify_dut_routes(dent, connected_routes + expected_routes) + + # 8. Add route with best metrics (via 3rd interface) + out = await IpRoute.add(input_data=[{dent: [ + {'dst': f'{routes[-1].dst}/{routes[-1].plen}', + 'metric': routes[-1].metric, + 'via': routes[-1].gw} + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to add routes' + + # 9. Send traffic and verify it's passed through the route with best metric + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(traffic_duration) + await tgen_utils_stop_traffic(tgen_dev) + + await asyncio.sleep(wait_for_stats) + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Flow Statistics') + for row in stats.Rows: + loss = tgen_utils_get_loss(row) + expected_loss = 0 if tg_to_swp[row['Rx Port']] == routes[-1].swp else 100 + assert loss == expected_loss, f'Expected loss: {expected_loss}%, actual: {loss}%' + + # 10. Verify the route to the network with best metric is the only offloaded route + expected_routes[1]['flags'] = ['offload'] # metric 101 + expected_routes[2]['should_exist'] = True # metric 102 + expected_routes[2]['flags'] = [] + expected_routes.append({ + 'dev': routes[-1].swp, + 'dst': f'{routes[-1].dst}/{routes[-1].plen}', + 'gateway': routes[-1].gw, + 'metric': routes[-1].metric, # metric 99 + 'should_exist': True, + 'flags': ['offload', 'rt_offload']}) + + await verify_dut_routes(dent, connected_routes + expected_routes) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/tb_utils.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/tb_utils.py index b96e494fd..1e6d57417 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/tb_utils.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/tb_utils.py @@ -364,9 +364,9 @@ async def tb_device_reload_firewall(device): check_asyncio_results(results, 'tb_reload_firewall') -async def tb_ping_device(device, target, pkt_loss_treshold=50, dump=False): +async def tb_ping_device(device, target, pkt_loss_treshold=50, dump=False, count=10): pkt_stats = '' - cmd = f'ping -c 10 {target}' + cmd = f'ping -c {count} {target}' rc, out = await device.run_cmd(cmd, sudo=True) if dump: device.applog.info(f'Ran {cmd} on {device.host_name} with rc {rc} and out {out}') From dd3ab46b593441d1c7d0c9e791d0ff7039a074ed Mon Sep 17 00:00:00 2001 From: Kostiantyn Stavruk <119867993+stavrukPLV@users.noreply.github.com> Date: Fri, 5 May 2023 07:24:45 +0300 Subject: [PATCH 3/9] Add cleanup_mtu and refactor (#324) Signed-off-by: Kostiantyn Stavruk --- .../functional/bridging/test_bridging_jumbo_frame.py | 2 +- .../test/test_suite/functional/ipv4/conftest.py | 9 +-------- .../test/test_suite/functional/ipv4/test_ipv4_mtu.py | 10 ++++++++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/bridging/test_bridging_jumbo_frame.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/bridging/test_bridging_jumbo_frame.py index 4f738388a..95fed8942 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/bridging/test_bridging_jumbo_frame.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/bridging/test_bridging_jumbo_frame.py @@ -19,7 +19,7 @@ pytestmark = [ pytest.mark.suite_functional_bridging, pytest.mark.asyncio, - pytest.mark.usefixtures('cleanup_bridges', 'cleanup_tgen') + pytest.mark.usefixtures('cleanup_bridges', 'cleanup_tgen', 'cleanup_mtu') ] diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv4/conftest.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv4/conftest.py index 6a9b2ae52..22095f794 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv4/conftest.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv4/conftest.py @@ -94,14 +94,13 @@ async def remove_default_gateway(testbed): @pytest_asyncio.fixture() -async def change_port_mtu(testbed): +async def cleanup_mtu(testbed): tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 4) if not tgen_dev or not dent_devices: print('The testbed does not have enough dent with tgen connections') return dent = dent_devices[0].host_name ports = tgen_dev.links_dict[dent][1] - mtu = 1000 # Get current mtu to restore it later out = await IpLink.show(input_data=[{dent: [ @@ -111,12 +110,6 @@ async def change_port_mtu(testbed): def_mtu_map = [link for link in out[0][dent]['parsed_output'] if link['ifname'] in ports] - # Configure new mtu - out = await IpLink.set(input_data=[{dent: [ - {'device': port, 'mtu': mtu} for port in ports - ]}]) - assert out[0][dent]['rc'] == 0, 'Failed to set port mtu' - yield # Run the test # Restore old mtu diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv4/test_ipv4_mtu.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv4/test_ipv4_mtu.py index de5911a53..b48c45c0c 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv4/test_ipv4_mtu.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/ipv4/test_ipv4_mtu.py @@ -34,7 +34,7 @@ async def get_port_stats(dent, ports): return stats -@pytest.mark.usefixtures('change_port_mtu') +@pytest.mark.usefixtures('cleanup_mtu') async def test_ipv4_oversized_mtu(testbed): """ Test Name: test_ipv4_oversized_mtu @@ -57,6 +57,8 @@ async def test_ipv4_oversized_mtu(testbed): ports = tgen_dev.links_dict[dent][1] traffic_duration = 10 delayed_stats_update_time = 10 + mtu = 1000 + address_map = ( # swp port, tg port, swp ip, tg ip, plen (ports[0], tg_ports[0], '1.1.1.1', '1.1.1.2', 24), @@ -101,7 +103,11 @@ async def test_ipv4_oversized_mtu(testbed): await tgen_utils_setup_streams(tgen_dev, None, streams) - # 4. Port mtu should be configured in the change_port_mtu fixture + # 4. Configure interfaces MTU to 1000 + out = await IpLink.set(input_data=[{dent: [ + {'device': port, 'mtu': mtu} for port in ports + ]}]) + assert out[0][dent]['rc'] == 0, 'Failed to set port mtu' # 5. Generate traffic with packet size 1200 old_stats = await get_port_stats(dent, (port for port, *_ in address_map)) From 73b46a62e854cfad9edaef2997aabe14528de75f Mon Sep 17 00:00:00 2001 From: Kostiantyn Stavruk <119867993+stavrukPLV@users.noreply.github.com> Date: Fri, 5 May 2023 07:24:51 +0300 Subject: [PATCH 4/9] Add storm_control tests (#323) Signed-off-by: Kostiantyn Stavruk --- .../storm_control/storm_control_utils.py | 33 +++ .../test_storm_control_different_rates.py | 277 ++++++++++++++++++ ...storm_control_interaction_policer_rules.py | 224 ++++++++++++++ ...est_storm_control_interaction_span_rule.py | 198 +++++++++++++ 4 files changed, 732 insertions(+) create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/test_storm_control_different_rates.py create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/test_storm_control_interaction_policer_rules.py create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/test_storm_control_interaction_span_rule.py diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/storm_control_utils.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/storm_control_utils.py index eaae0a5f1..811ee7b40 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/storm_control_utils.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/storm_control_utils.py @@ -1,4 +1,5 @@ from dent_os_testbed.lib.devlink.devlink_port import DevlinkPort +from dent_os_testbed.lib.tc.tc_filter import TcFilter async def devlink_rate_value(dev, name, value, cmode=False, device_host_name=True, set=False, verify=False): @@ -13,3 +14,35 @@ async def devlink_rate_value(dev, name, value, cmode=False, device_host_name=Tru devlink_info = out[0][device_host_name]['parsed_output'] kbyte_value = devlink_info['param'][dev][0]['values'][0]['value'] assert kbyte_value == value, f"Verify that storm control rate configured is '{value}' kbps.\n" + + +async def tc_filter_add(dev, vlan_id, src_mac, dst_mac, rate, burst, device_host_name=True): + out = await TcFilter.add( + input_data=[ + { + device_host_name: [ + { + 'dev': dev, + 'direction': 'ingress', + 'protocol': '0x8100', + 'filtertype': { + 'skip_sw': '', + 'vlan_id': vlan_id, + 'src_mac': src_mac, + 'dst_mac': dst_mac, + }, + 'action': { + 'trap': '', + 'police': { + 'rate': rate, + 'burst': burst, + 'conform-exceed': '', + 'drop': '', + } + }, + } + ] + } + ] + ) + assert out[0][device_host_name]['rc'] == 0, f'Failed to create tc rules.\n{out}' diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/test_storm_control_different_rates.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/test_storm_control_different_rates.py new file mode 100644 index 000000000..bf084e06c --- /dev/null +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/test_storm_control_different_rates.py @@ -0,0 +1,277 @@ +import math +import pytest +import asyncio + +from dent_os_testbed.test.test_suite.functional.storm_control.storm_control_utils import devlink_rate_value +from dent_os_testbed.utils.test_utils.cleanup_utils import cleanup_kbyte_per_sec_rate_value +from dent_os_testbed.lib.bridge.bridge_vlan import BridgeVlan +from dent_os_testbed.lib.ip.ip_link import IpLink +from random import randrange + +from dent_os_testbed.utils.test_utils.tgen_utils import ( + tgen_utils_get_dent_devices_with_tgen, + tgen_utils_traffic_generator_connect, + tgen_utils_dev_groups_from_config, + tgen_utils_get_traffic_stats, + tgen_utils_setup_streams, + tgen_utils_start_traffic, + tgen_utils_stop_traffic +) + +pytestmark = [ + pytest.mark.suite_functional_storm_control, + pytest.mark.asyncio, + pytest.mark.usefixtures('cleanup_bridges', 'cleanup_tgen') +] + + +async def set_rates(kbyte_value_stream, ports, device_host_name): + params = [ + {'port': ports[0], 'name': 'bc_kbyte_per_sec_rate', 'value': kbyte_value_stream[0]}, + {'port': ports[0], 'name': 'unreg_mc_kbyte_per_sec_rate', 'value': kbyte_value_stream[1]}, + {'port': ports[0], 'name': 'unk_uc_kbyte_per_sec_rate', 'value': kbyte_value_stream[2]}, + {'port': ports[1], 'name': 'bc_kbyte_per_sec_rate', 'value': kbyte_value_stream[3]}, + {'port': ports[2], 'name': 'unreg_mc_kbyte_per_sec_rate', 'value': kbyte_value_stream[4]}, + {'port': ports[3], 'name': 'unk_uc_kbyte_per_sec_rate', 'value': kbyte_value_stream[5]} + ] + for value in params: + await devlink_rate_value(dev=f'pci/0000:01:00.0/{value["port"].replace("swp","")}', + name=value['name'], value=value['value'], + cmode='runtime', device_host_name=device_host_name, set=True, verify=True) + + +async def verify_rates(kbyte_value_stream, tgen_dev, correlation, deviation): + # check the traffic stats + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Traffic Item Statistics') + collected = {row['Traffic Item']: + {'tx_rate': row['Tx Rate (Bps)'], 'rx_rate': row['Rx Rate (Bps)']} for row in stats.Rows} + rate_value = { + 'stream_1_swp1->swp2': kbyte_value_stream[0], + 'stream_2_swp1->swp2': kbyte_value_stream[1], + 'stream_3_swp1->swp2': kbyte_value_stream[2], + 'stream_4_swp2->swp1': kbyte_value_stream[3], + 'stream_5_swp3->swp4': kbyte_value_stream[4], + 'stream_6_swp4->swp3': kbyte_value_stream[5] + } + for stream, value in rate_value.items(): + assert math.isclose(value*correlation*1000, float(collected[stream]['rx_rate']), rel_tol=deviation), \ + f'Failed: the rate is not limited by storm control for {rate_value}.' + + +async def test_storm_control_different_rates(testbed): + """ + Test Name: test_storm_control_different_rates + Test Suite: suite_functional_storm_control + Test Overview: Verify rate is limited according to the changes in Storm Control Rules. + Test Author: Kostiantyn Stavruk + Test Procedure: + 1. Set entities swp1, swp2, swp3, swp4 UP state. + 2. Init vlan aware bridge entity br0. + 3. Set bridge br0 admin state UP. + 4. Set ports swp1, swp2, swp3, swp4 master br0. + 5. Add swp1 and swp2 to the same vlan. Add swp3 and swp4 to another vlan. + 6. Set up the following streams: + Ixia port 1: broadcast, multicast and unknown unicast streams, with random generated size of packet; + Ixia port 2: broadcast stream, with random generated size of packet; + Ixia port 3: multicast stream, with random generated size of packet; + Ixia port 4: unknown unicast stream, with random generated size of packet. + 7. Set a storm control rate limit for all types of traffic on all ports. + 8. Transmit continues traffic by TG. + 9. Verify the RX rate on the RX port is as expected - the rate is limited by storm control. + 10. Change storm control rates for all ports. + 11. Verify the RX rate on the RX port is as expected - the rate is limited by storm control. + 12. Change storm control rates for all ports again. + 13. Verify the RX rate on the RX port is as expected - the rate is limited by storm control. + 14. Disable storm control for all ports. + 15. Verify the RX rate on the RX port is as expected - the rate is not limited by storm control. + """ + + bridge = 'br0' + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 4) + if not tgen_dev or not dent_devices: + pytest.skip('The testbed does not have enough dent with tgen connections') + dent_dev = dent_devices[0] + device_host_name = dent_dev.host_name + tg_ports = tgen_dev.links_dict[device_host_name][0] + ports = tgen_dev.links_dict[device_host_name][1] + traffic_duration = 15 + correlation = 0.33 + deviation = 0.10 + kbyte_value_stream = [randrange(start, end+1) for start, end in [(1500, 1700), (2800, 3000), (3800, 4000), + (4500, 4700), (1000, 1200), (5100, 5300)]] + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': port, 'operstate': 'up'} for port in ports]}]) + assert out[0][device_host_name]['rc'] == 0, f"Verify that entities set to 'UP' state.\n{out}" + + out = await IpLink.add( + input_data=[{device_host_name: [ + {'device': bridge, 'vlan_filtering': 1, 'vlan_default_pvid': 0, 'type': 'bridge'}]}]) + err_msg = f"Verify that bridge created, vlan filtering set to 'ON' and vlan_default_pvid set to '0'.\n{out}" + assert out[0][device_host_name]['rc'] == 0, err_msg + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': bridge, 'operstate': 'up'}]}]) + assert out[0][device_host_name]['rc'] == 0, f"Verify that bridge set to 'UP' state.\n{out}" + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': port, 'master': bridge} for port in ports]}]) + err_msg = f'Verify that bridge entities enslaved to bridge.\n{out}' + assert out[0][device_host_name]['rc'] == 0, err_msg + + out = await BridgeVlan.add( + input_data=[{device_host_name: [ + {'device': ports[x], 'vid': f'{1 if x<2 else 2}', 'untagged': True, 'pvid': True} + for x in range(4)]}]) + assert out[0][device_host_name]['rc'] == 0, f"Verify that entities added to vid '1' and '2'.\n{out}" + + # set a storm control rate limits + await set_rates(kbyte_value_stream, ports, device_host_name) + + try: + address_map = ( + # swp port, tg port, tg ip, gw, plen + (ports[0], tg_ports[0], '1.1.1.2', '1.1.1.1', 24), + (ports[1], tg_ports[1], '1.1.1.3', '1.1.1.1', 24), + (ports[2], tg_ports[2], '1.1.1.4', '1.1.1.1', 24), + (ports[3], tg_ports[3], '1.1.1.5', '1.1.1.1', 24) + ) + + dev_groups = tgen_utils_dev_groups_from_config( + {'ixp': port, 'ip': ip, 'gw': gw, 'plen': plen} + for _, port, ip, gw, plen in address_map + ) + + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + """ + Set up the following streams: + — stream_1 — | — stream_2 — | — stream_3 — | — stream_4 — | — stream_5 — | — stream_6 — + swp1 -> swp4 | swp1 -> swp4 | swp1 -> swp4 | swp2 -> swp4 | swp3 -> swp4 | swp4 -> swp3 + + — stream_1 — | — stream_2 — | — stream_3 — | — stream_4 — | — stream_5 — | — stream_6 — + swp1 -> swp3 | swp1 -> swp3 | swp1 -> swp3 | swp2 -> swp3 | swp3 -> swp2 | swp4 -> swp2 + + — stream_1 — | — stream_2 — | — stream_3 — | — stream_4 — | — stream_5 — | — stream_6 — + swp1 -> swp2 | swp1 -> swp2 | swp1 -> swp2 | swp2 -> swp1 | swp3 -> swp1 | swp4 -> swp1 + """ + + streams = { + f'stream_1_swp1->swp{4-x}': { + 'ip_source': dev_groups[tg_ports[0]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3-x]][0]['name'], + 'srcMac': f'16:ea:c3:{x+5}d:1e:ec', + 'dstMac': 'ff:ff:ff:ff:ff:ff', + 'frameSize': randrange(100, 1500), + 'frame_rate_type': 'line_rate', + 'rate': 5, + 'protocol': '0x0800', + 'type': 'raw' + } for x in range(3) + } + streams.update({ + f'stream_2_swp1->swp{4-x}': { + 'ip_source': dev_groups[tg_ports[0]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3-x]][0]['name'], + 'srcIp': f'3.6.92.20{x+1}', + 'dstIp': f'228.68.176.2{x+11}', + 'srcMac': f'36:11:3d:38:9a:{x+5}e', + 'dstMac': f'01:00:5E:4{x+4}:b0:d3', + 'frameSize': randrange(100, 1500), + 'frame_rate_type': 'line_rate', + 'rate': 5, + 'protocol': '0x0800', + 'type': 'raw' + } for x in range(3) + }) + streams.update({ + f'stream_3_swp1->swp{4-x}': { + 'ip_source': dev_groups[tg_ports[0]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3-x]][0]['name'], + 'srcMac': f'72:88:c5:ec:f5:0{x+5}', + 'dstMac': f'92:ff:e{x+6}:07:88:a2', + 'frameSize': randrange(100, 1500), + 'frame_rate_type': 'line_rate', + 'rate': 5, + 'protocol': '0x0800', + 'type': 'raw' + } for x in range(3) + }) + streams.update({ + f'stream_4_swp2->swp{4-x if x < 2 else 1}': { + 'ip_source': dev_groups[tg_ports[1]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3-x if x < 2 else 0]][0]['name'], + 'srcMac': f'84:fc:70:36:2a:7{x+3}', + 'dstMac': 'ff:ff:ff:ff:ff:ff', + 'frameSize': randrange(100, 1500), + 'frame_rate_type': 'line_rate', + 'rate': 5, + 'protocol': '0x0800', + 'type': 'raw' + } for x in range(3) + }) + streams.update({ + f'stream_5_swp3->swp{4-x if x <= 0 else 3-x}': { + 'ip_source': dev_groups[tg_ports[2]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3-x if x <= 0 else 2-x]][0]['name'], + 'srcMac': f'e4:c7:7f:{x+6}e:60:2b', + 'dstMac': f'01:00:5E:19:bd:a{x+5}', + 'frameSize': randrange(100, 1500), + 'frame_rate_type': 'line_rate', + 'rate': 5, + 'protocol': '0x9100', + 'type': 'raw' + } for x in range(3) + }) + streams.update({ + f'stream_6_swp4->swp{3-x}': { + 'ip_source': dev_groups[tg_ports[3]][0]['name'], + 'ip_destination': dev_groups[tg_ports[2-x]][0]['name'], + 'srcMac': f'70:c5:30:7c:ef:f{x+5}', + 'dstMac': f'00:8{x+5}:4f:1b:80:91', + 'frameSize': randrange(100, 1500), + 'frame_rate_type': 'line_rate', + 'rate': 5, + 'protocol': '0x88a8', + 'type': 'raw' + } for x in range(3) + }) + + await tgen_utils_setup_streams(tgen_dev, config_file_name=None, streams=streams) + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(traffic_duration) + + # verify the rate is limited + await verify_rates(kbyte_value_stream, tgen_dev, correlation, deviation) + + for _ in range(2): + # change storm control rates for all ports twice + await set_rates(kbyte_value_stream, ports, device_host_name) + await asyncio.sleep(traffic_duration) + + # verify the rate is limited + await verify_rates(kbyte_value_stream, tgen_dev, correlation, deviation) + + # disable storm control for all ports + await cleanup_kbyte_per_sec_rate_value(dent_dev, tgen_dev, all_values=True) + await asyncio.sleep(traffic_duration) + + # verify the rate is not limited + stream_names = ['stream_1_swp1->swp2', 'stream_2_swp1->swp2', 'stream_3_swp1->swp2', + 'stream_4_swp2->swp1', 'stream_5_swp3->swp4', 'stream_6_swp4->swp3'] + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Traffic Item Statistics') + collected = {row['Traffic Item']: + {'tx_rate': row['Tx Rate (Bps)'], 'rx_rate': row['Rx Rate (Bps)']} for row in stats.Rows} + for stream in stream_names: + if stream in collected: + rates = collected[stream] + tx_rate = float(rates['tx_rate']) + rx_rate = float(rates['rx_rate']) + err_msg = 'Failed: the rate is limited by storm control.' + assert math.isclose(tx_rate, rx_rate, rel_tol=deviation), err_msg + finally: + await tgen_utils_stop_traffic(tgen_dev) + await cleanup_kbyte_per_sec_rate_value(dent_dev, tgen_dev, all_values=True) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/test_storm_control_interaction_policer_rules.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/test_storm_control_interaction_policer_rules.py new file mode 100644 index 000000000..fcf2ab6ab --- /dev/null +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/test_storm_control_interaction_policer_rules.py @@ -0,0 +1,224 @@ +import math +import pytest +import asyncio +import random + +from dent_os_testbed.test.test_suite.functional.storm_control.storm_control_utils import devlink_rate_value +from dent_os_testbed.test.test_suite.functional.storm_control.storm_control_utils import tc_filter_add +from dent_os_testbed.utils.test_utils.cleanup_utils import cleanup_kbyte_per_sec_rate_value +from dent_os_testbed.lib.tc.tc_filter import TcFilter +from dent_os_testbed.lib.tc.tc_qdisc import TcQdisc +from dent_os_testbed.lib.ip.ip_link import IpLink + +from dent_os_testbed.utils.test_utils.tgen_utils import ( + tgen_utils_get_dent_devices_with_tgen, + tgen_utils_traffic_generator_connect, + tgen_utils_dev_groups_from_config, + tgen_utils_get_traffic_stats, + tgen_utils_setup_streams, + tgen_utils_start_traffic, + tgen_utils_stop_traffic +) + + +pytestmark = [ + pytest.mark.suite_functional_storm_control, + pytest.mark.asyncio, + pytest.mark.usefixtures('cleanup_bridges', 'cleanup_tgen', 'cleanup_qdiscs') +] + + +async def test_storm_control_interaction_policer_rules(testbed, define_bash_utils): + """ + Test Name: test_storm_control_interaction_policer_rules + Test Suite: suite_functional_storm_control + Test Overview: Verify that Policer rules (dynamic and data paths) take precedence over Storm Control rules. + Test Author: Kostiantyn Stavruk + Test Procedure: + 1. Set entities swp1, swp2, swp3, swp4 UP state. + 2. Set a storm control rate limit for all types of traffic on all ports. + 3. Create an ingress qdisc on each TX port and define a dynamic policer trap rule for each qdisc. + 4. Set up streams. + 5. Transmit continues traffic from all ports simultaneously. + 6. Verify that the CPU trapped packet rate is according to police trap rules (Storm Control has no effect). + 7. Delete the rule from all ports' qdisc. + 8. Init bridge entity br0. + 9. Set bridge br0 admin state UP. + 10. Set ports swp1, swp2, swp3, swp4 master br0. + 11. Set entities swp1, swp2, swp3, swp4 UP state. + 12. Define a police pass rule for each qdisc that matches the selectors. + 13. Transmit continues traffic. + 14. Verify the RX rate on ports is according to policer dp limit rules (Storm Control has no effect). + """ + + bridge = 'br0' + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 4) + if not tgen_dev or not dent_devices: + pytest.skip('The testbed does not have enough dent with tgen connections') + dent_dev = dent_devices[0] + device_host_name = dent_dev.host_name + tg_ports = tgen_dev.links_dict[device_host_name][0] + ports = tgen_dev.links_dict[device_host_name][1] + size_packets = random.randint(500, 1000) + traffic_duration = 15 + expected_rate = 4000 + cpu_stat_code = 195 + counter_type = 'sw' + deviation = 0.10 + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': port, 'operstate': 'up'} for port in ports]}]) + assert out[0][device_host_name]['rc'] == 0, f"Verify that entities set to 'UP' state.\n{out}" + + params = [ + {'port': ports[0], 'name': 'unk_uc_kbyte_per_sec_rate', 'value': 37686}, + {'port': ports[1], 'name': 'unreg_mc_kbyte_per_sec_rate', 'value': 109413}, + {'port': ports[2], 'name': 'bc_kbyte_per_sec_rate', 'value': 75373} + ] + for value in params: + await devlink_rate_value(dev=f'pci/0000:01:00.0/{value["port"].replace("swp","")}', + name=value['name'], value=value['value'], + cmode='runtime', device_host_name=device_host_name, set=True, verify=True) + + try: + out = await TcQdisc.add( + input_data=[{device_host_name: [ + {'dev': port, 'kind': 'ingress'} for port in ports[:3]]}]) + assert out[0][device_host_name]['rc'] == 0, f'Failed to configure ingress qdisc.\n{out}' + + await tc_filter_add(dev=ports[0], vlan_id=853, src_mac='10:62:5a:cf:ab:39', dst_mac='34:1e:60:35:58:ac', + rate='14836kbit', burst=15836, device_host_name=device_host_name) + await tc_filter_add(dev=ports[1], vlan_id=2830, src_mac='98:92:be:4c:c8:53', dst_mac='01:00:5E:51:14:af', + rate='14836kbit', burst=15836, device_host_name=device_host_name) + await tc_filter_add(dev=ports[2], vlan_id=2454, src_mac='54:84:c3:74:89:37', dst_mac='ff:ff:ff:ff:ff:ff', + rate='14836kbit', burst=15836, device_host_name=device_host_name) + + address_map = ( + # swp port, tg port, tg ip, gw, plen + (ports[0], tg_ports[0], '1.1.1.2', '1.1.1.1', 24), + (ports[1], tg_ports[1], '1.1.1.3', '1.1.1.1', 24), + (ports[2], tg_ports[2], '1.1.1.4', '1.1.1.1', 24), + (ports[3], tg_ports[3], '1.1.1.5', '1.1.1.1', 24) + ) + + dev_groups = tgen_utils_dev_groups_from_config( + {'ixp': port, 'ip': ip, 'gw': gw, 'plen': plen} + for _, port, ip, gw, plen in address_map + ) + + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + """ + Set up the following streams: + — stream_1 — | — stream_2 — | — stream_3 — | - stream_4 - + swp1 -> swp4 | swp2 -> swp4 | swp3 -> swp4 | swp3 -> swp4 + swp3 -> swp2 + swp3 -> swp1 + """ + + streams = { + 'stream_1': { + 'ip_source': dev_groups[tg_ports[0]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3]][0]['name'], + 'srcMac': '10:62:5a:cf:ab:39', + 'dstMac': '34:1e:60:35:58:ac', + 'frameSize': size_packets, + 'frame_rate_type': 'line_rate', + 'rate': 100, + 'protocol': '0x8100', + 'type': 'raw', + 'vlanID': 853 + }, + 'stream_2': { + 'ip_source': dev_groups[tg_ports[1]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3]][0]['name'], + 'srcMac': '98:92:be:4c:c8:53', + 'dstMac': '01:00:5E:51:14:af', + 'frameSize': size_packets, + 'frame_rate_type': 'line_rate', + 'rate': 100, + 'protocol': '0x8100', + 'type': 'raw', + 'vlanID': 2830 + }, + 'stream_3': { + 'ip_source': dev_groups[tg_ports[2]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3]][0]['name'], + 'srcMac': '54:84:c3:74:89:37', + 'dstMac': 'ff:ff:ff:ff:ff:ff', + 'frameSize': size_packets, + 'frame_rate_type': 'line_rate', + 'rate': 100, + 'protocol': '0x8100', + 'type': 'raw', + 'vlanID': 2454 + } + } + streams.update({ + f'stream_4_swp3->swp{4-x if x <= 0 else 3-x}': { + 'ip_source': dev_groups[tg_ports[2]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3-x if x <= 0 else 2-x]][0]['name'], + 'srcMac': f'e4:c7:7f:{x+6}e:60:2b', + 'dstMac': f'01:00:5E:19:bd:a{x+5}', + 'frameSize': size_packets, + 'frame_rate_type': 'line_rate', + 'rate': 100, + 'protocol': '0x0800', + 'type': 'raw', + 'vlanID': 2830 + } for x in range(3) + }) + + await tgen_utils_setup_streams(tgen_dev, config_file_name=None, streams=streams) + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(traffic_duration) + + rc, out = await dent_dev.run_cmd(f'get_cpu_traps_rate_code_avg {cpu_stat_code} {counter_type}') + assert not rc, f'get_cpu_traps_rate_code_avg failed with rc {rc}' + err_msg = 'Failed: CPU trapped packet rate does not meet police trap rules.' + assert math.isclose(int(out.strip()), expected_rate, rel_tol=deviation/2), err_msg + + await tgen_utils_stop_traffic(tgen_dev) + + out = await TcFilter.delete( + input_data=[{device_host_name: [ + {'dev': port, 'direction': 'ingress', 'pref': '49152'} for port in ports[:3]]}]) + assert out[0][device_host_name]['rc'] == 0, f'Failed to configure ingress qdisc.\n{out}' + + out = await IpLink.add( + input_data=[{device_host_name: [ + {'device': bridge, 'type': 'bridge', 'vlan_default_pvid': 0}]}]) + assert out[0][device_host_name]['rc'] == 0, f"Verify that vlan_default_pvid set to '0'.\n{out}" + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': bridge, 'operstate': 'up'}]}]) + assert out[0][device_host_name]['rc'] == 0, f"Verify that bridge set to 'UP' state.\n{out}" + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': port, 'master': bridge, 'operstate': 'up'} for port in ports]}]) + err_msg = f"Verify that bridge entities set to 'UP' state and links enslaved to bridge.\n{out}" + assert out[0][device_host_name]['rc'] == 0, err_msg + + await tc_filter_add(dev=ports[0], vlan_id=853, src_mac='10:62:5a:cf:ab:39', dst_mac='34:1e:60:35:58:ac', + rate='18972709bps', burst=18973709, device_host_name=device_host_name) + await tc_filter_add(dev=ports[1], vlan_id=2830, src_mac='98:92:be:4c:c8:53', dst_mac='01:00:5E:51:14:af', + rate='18972709bps', burst=18973709, device_host_name=device_host_name) + await tc_filter_add(dev=ports[2], vlan_id=2454, src_mac='54:84:c3:74:89:37', dst_mac='ff:ff:ff:ff:ff:ff', + rate='18972709bps', burst=18973709, device_host_name=device_host_name) + + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(traffic_duration) + + # check the traffic stats + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Traffic Item Statistics') + collected = {row['Traffic Item']: + {'tx_rate': row['Tx Rate (Bps)'], 'rx_rate': row['Rx Rate (Bps)']} for row in stats.Rows} + assert all(math.isclose(float(collected[f'stream_4_swp3->swp{4-x if x <= 0 else 3-x}']['tx_rate']), + float(collected[f'stream_4_swp3->swp{4-x if x <= 0 else 3-x}']['rx_rate']), + rel_tol=deviation) for x in range(3)), 'Failed: the rate is limited by storm control.' + finally: + await tgen_utils_stop_traffic(tgen_dev) + await cleanup_kbyte_per_sec_rate_value(dent_dev, tgen_dev, all_values=True) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/test_storm_control_interaction_span_rule.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/test_storm_control_interaction_span_rule.py new file mode 100644 index 000000000..d89a5d6df --- /dev/null +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/storm_control/test_storm_control_interaction_span_rule.py @@ -0,0 +1,198 @@ +import math +import pytest +import asyncio + +from dent_os_testbed.test.test_suite.functional.storm_control.storm_control_utils import devlink_rate_value +from dent_os_testbed.utils.test_utils.cleanup_utils import cleanup_kbyte_per_sec_rate_value +from dent_os_testbed.lib.tc.tc_qdisc import TcQdisc +from dent_os_testbed.lib.ip.ip_link import IpLink +from random import randrange + +from dent_os_testbed.utils.test_utils.tgen_utils import ( + tgen_utils_get_dent_devices_with_tgen, + tgen_utils_traffic_generator_connect, + tgen_utils_dev_groups_from_config, + tgen_utils_get_traffic_stats, + tgen_utils_setup_streams, + tgen_utils_start_traffic, + tgen_utils_stop_traffic +) + +pytestmark = [ + pytest.mark.suite_functional_storm_control, + pytest.mark.asyncio, + pytest.mark.usefixtures('cleanup_bridges', 'cleanup_tgen', 'cleanup_qdiscs') +] + + +async def set_rates(kbyte_value_stream, ports, device_host_name): + params = [ + {'port': ports[0], 'name': 'unk_uc_kbyte_per_sec_rate', 'value': kbyte_value_stream[0]}, + {'port': ports[0], 'name': 'unreg_mc_kbyte_per_sec_rate', 'value': kbyte_value_stream[1]}, + {'port': ports[0], 'name': 'bc_kbyte_per_sec_rate', 'value': kbyte_value_stream[2]} + ] + for value in params: + await devlink_rate_value(dev=f'pci/0000:01:00.0/{value["port"].replace("swp","")}', + name=value['name'], value=value['value'], + cmode='runtime', device_host_name=device_host_name, set=True, verify=True) + + +async def test_storm_control_interaction_span_rule(testbed): + """ + Test Name: test_storm_control_interaction_span_rule + Test Suite: suite_functional_storm_control + Test Overview: Verify rate is not limited by Storm Control due to the mirred rule. + Test Author: Kostiantyn Stavruk + Test Procedure: + 1. Init bridge entity br0. + 2. Set bridge br0 admin state UP. + 3. Set ports swp1, swp2, swp3, swp4 master br0. + 4. Set entities swp1, swp2, swp3, swp4 UP state. + 5. Set storm control rate limit rule for all streams. + 6. Define a SPAN rule with a source port. + 7. Set up the following streams: + - broadcast with random generated size of packet; + - multicast with random generated size of packet; + - unknown unicast with random generated size of packet. + 8. Transmit continues traffic by TG. + 9. Verify the RX rate on the RX port is as expected - the rate is limited according to storm control limits. + Echamine the impact of the SPAN rule. + 10. Disable storm control rate limit rule for all streams. + 12. Verify the RX rate on the RX port is as expected - the rate is not limited by storm control. + """ + + bridge = 'br0' + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 4) + if not tgen_dev or not dent_devices: + pytest.skip('The testbed does not have enough dent with tgen connections') + dent_dev = dent_devices[0] + device_host_name = dent_dev.host_name + tg_ports = tgen_dev.links_dict[device_host_name][0] + ports = tgen_dev.links_dict[device_host_name][1] + size_packets = randrange(500, 1000) + traffic_duration = 15 + deviation = 0.10 + kbyte_value_stream = [randrange(start, end+1) for start, end in [(1500, 1700), (3800, 4000), (5100, 5300)]] + + out = await IpLink.add( + input_data=[{device_host_name: [ + {'device': bridge, 'vlan_filtering': 1, 'type': 'bridge'}]}]) + err_msg = f"Verify that bridge created and vlan filtering set to 'ON'.\n{out}" + assert out[0][device_host_name]['rc'] == 0, err_msg + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': bridge, 'operstate': 'up'}]}]) + assert out[0][device_host_name]['rc'] == 0, f"Verify that bridge set to 'UP' state.\n{out}" + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': port, 'master': bridge, 'operstate': 'up'} for port in ports]}]) + err_msg = f"Verify that bridge entities set to 'UP' state and links enslaved to bridge.\n{out}" + assert out[0][device_host_name]['rc'] == 0, err_msg + + # set a storm control rate limits + await set_rates(kbyte_value_stream, ports, device_host_name) + + try: + out = await TcQdisc.add( + input_data=[{device_host_name: [ + {'dev': ports[0], 'kind': 'ingress'}]}]) + assert out[0][device_host_name]['rc'] == 0, f'Failed to configure ingress qdisc.\n{out}' + + rc, out = await dent_dev.run_cmd(f'tc filter add dev {ports[0]} ingress matchall skip_sw action mirred \ + egress mirror dev {ports[1]}') + assert rc == 0, 'Failed to configure ingress matchall.' + + address_map = ( + # swp port, tg port, tg ip, gw, plen + (ports[0], tg_ports[0], '1.1.1.2', '1.1.1.1', 24), + (ports[1], tg_ports[1], '1.1.1.3', '1.1.1.1', 24), + (ports[2], tg_ports[2], '1.1.1.4', '1.1.1.1', 24), + (ports[3], tg_ports[3], '1.1.1.5', '1.1.1.1', 24) + ) + + dev_groups = tgen_utils_dev_groups_from_config( + {'ixp': port, 'ip': ip, 'gw': gw, 'plen': plen} + for _, port, ip, gw, plen in address_map + ) + + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + """ + Set up the following streams: + — stream_1 — | — stream_2 — | — stream_3 — | + swp1 -> swp2 | swp1 -> swp3 | swp1 -> swp4 | + """ + + streams = { + 'stream_1': { + 'ip_source': dev_groups[tg_ports[0]][0]['name'], + 'ip_destination': dev_groups[tg_ports[1]][0]['name'], + 'srcMac': '10:62:5a:cf:ab:39', + 'dstMac': '34:1e:60:35:58:ac', + 'frameSize': size_packets, + 'frame_rate_type': 'line_rate', + 'rate': 33, + 'protocol': '0x0800', + 'type': 'raw' + }, + 'stream_2': { + 'ip_source': dev_groups[tg_ports[0]][0]['name'], + 'ip_destination': dev_groups[tg_ports[2]][0]['name'], + 'srcMac': '98:92:be:4c:c8:53', + 'dstMac': '01:00:5E:51:14:af', + 'frameSize': size_packets, + 'frame_rate_type': 'line_rate', + 'rate': 33, + 'protocol': '0x0800', + 'type': 'raw' + }, + 'stream_3': { + 'ip_source': dev_groups[tg_ports[0]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3]][0]['name'], + 'srcMac': '54:84:c3:74:89:37', + 'dstMac': 'ff:ff:ff:ff:ff:ff', + 'frameSize': size_packets, + 'frame_rate_type': 'line_rate', + 'rate': 33, + 'protocol': '0x0800', + 'type': 'raw' + } + } + + await tgen_utils_setup_streams(tgen_dev, config_file_name=None, streams=streams) + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(traffic_duration) + + # check the traffic stats + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Traffic Item Statistics') + collected = {row['Traffic Item']: + {'tx_rate': row['Tx Rate (Bps)'], 'rx_rate': row['Rx Rate (Bps)']} for row in stats.Rows} + assert math.isclose(float(collected['stream_1']['tx_rate']), + float(collected['stream_1']['rx_rate']), rel_tol=deviation), \ + 'Failed: the rate is limited by storm control due to mirred rule.' + for x in range(2): + assert math.isclose(kbyte_value_stream[x+1]*1000, + float(collected[f'stream_{x+2}']['rx_rate']), rel_tol=deviation), \ + 'Failed: the rate is not limited by storm control.' + + # disable storm control for all streams + await cleanup_kbyte_per_sec_rate_value(dent_dev, tgen_dev, all_values=True) + await asyncio.sleep(traffic_duration) + + # verify the rate is not limited + stream_names = [key for key in streams] + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Traffic Item Statistics') + collected = {row['Traffic Item']: + {'tx_rate': row['Tx Rate (Bps)'], 'rx_rate': row['Rx Rate (Bps)']} for row in stats.Rows} + for stream in stream_names: + if stream in collected: + rates = collected[stream] + tx_rate = float(rates['tx_rate']) + rx_rate = float(rates['rx_rate']) + err_msg = 'Failed: the rate is limited by storm control.' + assert math.isclose(tx_rate, rx_rate, rel_tol=deviation), err_msg + finally: + await tgen_utils_stop_traffic(tgen_dev) + await cleanup_kbyte_per_sec_rate_value(dent_dev, tgen_dev, all_values=True) From 651d5579cd92df293029e7386a080e15b469c6fa Mon Sep 17 00:00:00 2001 From: StepanVovkPLV <117652059+StepanVovkPLV@users.noreply.github.com> Date: Fri, 5 May 2023 07:25:01 +0300 Subject: [PATCH 5/9] Add policer tests (#322) Signed-off-by: Stepan Vovk --- .../src/dent_os_testbed/constants.py | 2 + .../test_suite/functional/policer/__init__.py | 0 .../test_policer_basic_functionality.py | 160 +++++++++++++ .../test_policer_negative_add_rule_to_bond.py | 90 ++++++++ .../policer/test_policer_rate_per_rule.py | 150 ++++++++++++ .../policer/test_policer_rate_units.py | 164 ++++++++++++++ .../policer/test_policer_rules_priority.py | 213 ++++++++++++++++++ .../test_policer_same_pref_rules_priority.py | 204 +++++++++++++++++ .../utils/test_utils/tc_flower_utils.py | 19 +- .../lib/ip/linux/linux_ip_link_impl.py | 2 + 10 files changed, 996 insertions(+), 8 deletions(-) create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/__init__.py create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_basic_functionality.py create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_negative_add_rule_to_bond.py create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_rate_per_rule.py create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_rate_units.py create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_rules_priority.py create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_same_pref_rules_priority.py diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/constants.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/constants.py index f6eb1ff7e..f8814d16e 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/constants.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/constants.py @@ -79,6 +79,7 @@ 'suite_functional_port_isolation': 'Functional Port Isolation tests', 'suite_functional_igmp': 'IGMP snooping functional tests', 'suite_functional_storm_control': 'Functional Storm Control tests', + 'suite_functional_policer': 'Policer functional tests', } PYTEST_SUITE_GROUPS = { @@ -123,5 +124,6 @@ 'suite_functional_port_isolation', 'suite_functional_igmp', 'suite_functional_storm_control', + 'suite_functional_policer', ] } diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/__init__.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_basic_functionality.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_basic_functionality.py new file mode 100644 index 000000000..59e2d5e36 --- /dev/null +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_basic_functionality.py @@ -0,0 +1,160 @@ +import asyncio +import pytest +import random +from math import isclose + +from dent_os_testbed.lib.tc.tc_filter import TcFilter +from dent_os_testbed.lib.tc.tc_qdisc import TcQdisc +from dent_os_testbed.lib.ip.ip_link import IpLink + +from dent_os_testbed.utils.test_utils.tgen_utils import ( + tgen_utils_get_dent_devices_with_tgen, + tgen_utils_traffic_generator_connect, + tgen_utils_dev_groups_from_config, + tgen_utils_get_traffic_stats, + tgen_utils_setup_streams, + tgen_utils_start_traffic, + tgen_utils_stop_traffic, +) + +from dent_os_testbed.utils.test_utils.tc_flower_utils import ( + tcutil_generate_rule_with_random_selectors, + tcutil_tc_rules_to_tgen_streams, +) + +pytestmark = [ + pytest.mark.suite_functional_policer, + pytest.mark.usefixtures('cleanup_qdiscs', 'cleanup_bridges', 'cleanup_tgen'), + pytest.mark.asyncio, +] + + +@pytest.mark.parametrize('traffic_type', ['ipv4', 'l2', 'udp', 'tcp']) +@pytest.mark.parametrize('qdisc_type', ['port', 'shared_block']) +async def test_policer_basic_functionality(testbed, traffic_type, qdisc_type): + """ + Test Name: Basic policer functionality + Test Suite: suite_functional_policer + Test Overview: Verify basic policer functionality + Test Procedure: + 1. Create a bridge entity and set link up on it. + 2. Set link up on interfaces on all participant ports. Enslave all participant ports to the bridge. + 3. Create an ingress qdisc on first port connected to Tgen port/shared block and add a rule with random selectors + 4. Prepare a matching stream for the randomly selected selectors. + 5. Send traffic + 6. Verify RX rate on RX ports is as expected (rate is limited by the police pass rule). + """ + + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 2) + if not tgen_dev or not dent_devices: + pytest.skip( + 'The testbed does not have enough dent with tgen connections') + device = dent_devices[0] + dent = device.host_name + tg_ports = tgen_dev.links_dict[dent][0] + ports = tgen_dev.links_dict[dent][1][:2] + ports_with_rule = ports[:1] if qdisc_type == 'port' else ports[1:] + bridge = 'bridge0' + block = random.randint(5, 3000) + frame_rate = 300000 # bps + tolerance = 0.12 + + # 1. Create a bridge entity and set link up on it. + out = await IpLink.add(input_data=[{dent: [{ + 'dev': bridge, + 'type': 'bridge'}] + }]) + assert out[0][dent]['rc'] == 0, 'Failed creating bridge.' + + await IpLink.set(input_data=[{dent: [{'device': bridge, 'operstate': 'up'}]}]) + assert out[0][dent]['rc'] == 0, 'Failed setting bridge to state UP.' + + # 2. Set link up on interfaces on all participant ports. Enslave all participant ports to the bridge. + out = await IpLink.set(input_data=[{dent: [{ + 'device': port, + 'operstate': 'up', + 'master': bridge + } for port in ports]}]) + assert out[0][dent]['rc'] == 0, 'Failed setting link to state UP' + + # 3. Create an ingress qdisc on first port connected to Tgen port/shared block and add a rule with random selectors + config = [{'dev': port, 'ingress_block': block, 'direction': 'ingress'} for port in ports_with_rule] + if qdisc_type == 'port': + for item in config: + del item['ingress_block'] + + out = await TcQdisc.add(input_data=[{dent: config}]) + assert out[0][dent]['rc'] == 0, 'Failed to add a qdisc' + + rule_config = { + 'action': {'police': {'rate': frame_rate, 'burst': frame_rate + 1000, 'conform-exceed': 'drop'}}, + 'want_ip': traffic_type != 'l2', + 'want_port': traffic_type in ['tcp', 'udp'], + 'skip_sw': True, + 'want_vlan': traffic_type == 'l2', + 'want_tcp': traffic_type != 'udp', + 'want_mac': traffic_type == 'l2', + 'want_vlan_ethtype': False, + } + + tc_rule = tcutil_generate_rule_with_random_selectors(ports_with_rule[0], **rule_config) + + # Update tc rule protocol based on traffic type + if traffic_type == 'ipv4': # forcibly select ipv4 + tc_rule['protocol'] = 'ipv4' + + if qdisc_type == 'port': + out = await TcFilter.add(input_data=[{dent: [tc_rule]}]) + assert out[0][dent]['rc'] == 0, 'Failed to create tc rule' + else: + del tc_rule['dev'] + tc_rule['block'] = block + out = await TcFilter.add(input_data=[{dent: [tc_rule]}]) + assert out[0][dent]['rc'] == 0, 'Failed to create tc rule' + + created_rules = { + 'dev': ports_with_rule[0], + 'direction': 'ingress', + 'options': '-j'} + if qdisc_type == 'shared_block': + del created_rules['dev'] + created_rules['block'] = block + + out = await TcFilter.show(input_data=[{dent: [created_rules]}], parse_output=True) + assert out[0][dent]['rc'] == 0, 'Failed to get tc rule' + + # 4 Prepare a matching stream for the randomly selected selectors and with random generated size of packet. + streams = tcutil_tc_rules_to_tgen_streams({port: out[0][dent]['parsed_output'] for port in ports_with_rule}, + frame_rate_type='line_rate', + frame_rate_pps=100) + + dev_groups = tgen_utils_dev_groups_from_config(( + {'ixp': tg_ports[0], 'ip': '1.1.1.1', 'gw': '1.1.1.10', 'plen': 24}, + {'ixp': tg_ports[1], 'ip': '1.1.1.2', 'gw': '1.1.1.10', 'plen': 24}, + {'ixp': tg_ports[2], 'ip': '1.1.1.3', 'gw': '1.1.1.10', 'plen': 24}, + {'ixp': tg_ports[3], 'ip': '1.1.1.4', 'gw': '1.1.1.10', 'plen': 24} + )) + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + await tgen_utils_setup_streams(tgen_dev, None, streams) + + # 5. Send Traffic + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(15) + + # 6 Verify RX rate on RX ports is as expected (rate is limited by the police pass rule). + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Port Statistics') + for row in stats.Rows: + + is_correct_rate = isclose(float(row['Rx. Rate (bps)']), frame_rate, rel_tol=tolerance) + err_msg = f'Expected rate between {(frame_rate - (frame_rate * tolerance))} and ' \ + f"{float(frame_rate + (frame_rate * tolerance))} got: { float(row['Rx. Rate (bps)'])}" + if qdisc_type == 'port': + if row['Port Name'] == tg_ports[0]: + continue + assert is_correct_rate, err_msg + else: + if row['Port Name'] == tg_ports[0]: + assert is_correct_rate, err_msg + + await tgen_utils_stop_traffic(tgen_dev) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_negative_add_rule_to_bond.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_negative_add_rule_to_bond.py new file mode 100644 index 000000000..578037803 --- /dev/null +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_negative_add_rule_to_bond.py @@ -0,0 +1,90 @@ +import pytest + +from dent_os_testbed.lib.tc.tc_filter import TcFilter +from dent_os_testbed.lib.tc.tc_qdisc import TcQdisc +from dent_os_testbed.lib.ip.ip_link import IpLink + +from dent_os_testbed.utils.test_utils.tgen_utils import tgen_utils_get_dent_devices_with_tgen + +from dent_os_testbed.utils.test_utils.tc_flower_utils import tcutil_generate_rule_with_random_selectors + +pytestmark = [ + pytest.mark.suite_functional_policer, + pytest.mark.usefixtures('cleanup_qdiscs', 'cleanup_tgen', 'cleanup_bonds'), + pytest.mark.asyncio, +] + + +async def test_policer_bond_rule_not_offloaded(testbed): + """ + Test Name: Policer with bond entity + Test Suite: suite_functional_policer + Test Overview: Verify rule is not offloaded when created on soft entity + Test Procedure: + 1. Create a bond entity + 2. Set link up on bond; enslave port(s) to bond entity + 3. Create an ingress qdisc on the LAG + 4. Add a police pass rule to on bond entity + 5. Verify the rule is not offloaded + """ + + # 1. Create a bond entity + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 2) + if not tgen_dev or not dent_devices: + pytest.skip( + 'The testbed does not have enough dent with tgen connections') + device = dent_devices[0] + dent = device.host_name + ports = tgen_dev.links_dict[dent][1][:2] + + dent = device.host_name + bond = 'bond33' + + out = await IpLink.add(input_data=[{dent: [{ + 'name': bond, + 'type': 'bond', + 'mode': '802.3ad' + }] + }]) + assert out[0][dent]['rc'] == 0, 'Failed creating bond entity.' + + await IpLink.set(input_data=[{dent: [{'device': bond, 'operstate': 'up'}]}]) + assert out[0][dent]['rc'] == 0, 'Failed setting bond to state UP.' + + # 2. Set link up on bond; enslave port(s) to bond entity + + # Device can not be enslaved while up. + out = await IpLink.set(input_data=[{dent: [{ + 'device': port, + 'operstate': 'down', + } for port in ports]}]) + assert out[0][dent]['rc'] == 0, 'Failed setting port(s) to state down' + out = await IpLink.set(input_data=[{dent: [{ + 'device': port, + 'master': bond + } for port in ports]}]) + assert out[0][dent]['rc'] == 0, 'Failed enslaving port(s) to bond' + + out = await IpLink.set(input_data=[{dent: [{ + 'device': port, + 'operstate': 'up', + } for port in ports]}]) + assert out[0][dent]['rc'] == 0, 'Failed setting link(s) to state up' + + # 3. Create an ingress qdisc on the LAG + + out = await TcQdisc.add(input_data=[{dent: [{ + 'dev': bond, + 'direction': 'ingress'}]}]) + assert out[0][dent]['rc'] == 0, 'Failed adding qdisc' + + # 4. Add a police pass rule to on bond entity + tc_rule = tcutil_generate_rule_with_random_selectors(bond) + out = await TcFilter.add(input_data=[{dent: [tc_rule]}]) + assert out[0][dent]['rc'] == 0, 'Fail in adding tc rule to bond' + + # 5. Verify the rule is not offloaded + out = await TcFilter.show(input_data=[{dent: [ + {'dev': bond, 'direction': 'ingress', 'options': '-j'}]}], parse_output=True) + not_offloaded = out[0][dent]['parsed_output'][1]['options'].get('not_in_hw') + assert not_offloaded, 'Verify the rule is not offloaded' diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_rate_per_rule.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_rate_per_rule.py new file mode 100644 index 000000000..3bf04f045 --- /dev/null +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_rate_per_rule.py @@ -0,0 +1,150 @@ +import asyncio +import pytest +from math import isclose + +from dent_os_testbed.lib.tc.tc_filter import TcFilter +from dent_os_testbed.lib.tc.tc_qdisc import TcQdisc +from dent_os_testbed.lib.ip.ip_link import IpLink + +from dent_os_testbed.utils.test_utils.tgen_utils import ( + tgen_utils_get_dent_devices_with_tgen, + tgen_utils_traffic_generator_connect, + tgen_utils_dev_groups_from_config, + tgen_utils_setup_streams, + tgen_utils_start_traffic, + tgen_utils_stop_traffic, + tgen_utils_get_traffic_stats +) + +from dent_os_testbed.utils.test_utils.tc_flower_utils import ( + tcutil_generate_rule_with_random_selectors, + tcutil_tc_rules_to_tgen_streams, +) + +pytestmark = [ + pytest.mark.suite_functional_policer, + pytest.mark.usefixtures('cleanup_bridges', 'cleanup_qdiscs', 'cleanup_tgen'), + pytest.mark.asyncio, +] + + +async def test_policer_rate_per_rule(testbed): + """ + Test Name: Policer rate per rule + Test Suite: suite_functional_policer + Test Overview: Verify rate per rule when stream are sent from the same port in parallel + Test Procedure: + 1. Set link up on interfaces on all participant ports + 2. Create a bridge entity and set link up on it. Enslave all participant ports to the bridge. + 3. Create an ingress qdisc on first port + 4. Add multiple rules with randomly generated selectors + 5. Prepare a matching streams for the randomly selected selectors + 6. Transmit traffic + 7. Verify RX rate per stream is as expected (limited by the rule) + """ + + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 4) + if not tgen_dev or not dent_devices: + pytest.skip( + 'The testbed does not have enough dent with tgen connections') + device = dent_devices[0] + dent = device.host_name + tg_ports = tgen_dev.links_dict[dent][0] + ports = tgen_dev.links_dict[dent][1] + port_with_rule = ports[0] + bridge = 'bridge0' + rates = [250000, 400000, 600000] # bps + tolerance = 0.12 + + # 1. Create a bridge entity and set link up on it. + out = await IpLink.add(input_data=[{dent: [{ + 'dev': 'bridge0', + 'type': 'bridge'}] + }]) + assert out[0][dent]['rc'] == 0, 'Failed creating bridge.' + + await IpLink.set(input_data=[{dent: [{'device': bridge, 'operstate': 'up'}]}]) + assert out[0][dent]['rc'] == 0, 'Failed setting bridge to state UP.' + + # 2. Set link up on interfaces on all participant ports. Enslave all participant ports to the bridge. + out = await IpLink.set(input_data=[{dent: [{ + 'device': port, + 'operstate': 'up', + 'master': bridge + } for port in ports]}]) + assert out[0][dent]['rc'] == 0, 'Failed setting link to state UP.' + + config = [{'dev': port_with_rule, 'direction': 'ingress'}] + + # 3. Create an ingress qdisc on first port + out = await TcQdisc.add(input_data=[{dent: config}]) + assert out[0][dent]['rc'] == 0, 'Failed to create a qdisc' + + # 4.Add multiple rules with randomly generated selectors + # 'pref' in ascending order for correct stream generation in tcutil_tc_rules_to_tgen_streams() + configs = [ + {'action': {'police': {'rate': rates[0], + 'burst': rates[0] + 1000, + 'conform-exceed': 'drop'}}, + 'want_ip': True, + 'pref': 100, + 'want_port': True, + 'want_proto': True, + 'want_mac': True, + }, + {'action': {'police': {'rate': rates[1], + 'burst': rates[1] + 1000, + 'conform-exceed': 'drop'}}, + 'want_ip': True, + 'pref': 200, + 'want_port': True, + 'want_proto': True, + 'want_mac': True, + }, + {'action': {'police': {'rate': rates[2], + 'burst': rates[2] + 1000, + 'conform-exceed': 'drop'}}, + 'want_ip': True, + 'pref': 300, + 'want_port': True, + 'want_proto': True, + 'want_mac': True, + }] + + for config in configs: + rule = tcutil_generate_rule_with_random_selectors(port_with_rule, **config) + out = await TcFilter.add(input_data=[{dent: [rule]}]) + assert out[0][dent]['rc'] == 0, 'Failed to create tc rule' + + out = await TcFilter.show(input_data=[{dent: [{ + 'dev': port_with_rule, + 'direction': 'ingress', + 'options': '-j'}]}], parse_output=True) + assert out[0][dent]['rc'] == 0, 'Failed to get tc rule' + + # 5. Prepare a matching streams for the randomly selected selectors + streams = tcutil_tc_rules_to_tgen_streams({port_with_rule: out[0][dent]['parsed_output']}, + frame_rate_type='line_rate', frame_rate_pps=100) + + dev_groups = tgen_utils_dev_groups_from_config(( + {'ixp': tg_ports[0], 'ip': '1.1.1.1', 'gw': '1.1.1.10', 'plen': 24}, + {'ixp': tg_ports[1], 'ip': '1.1.1.2', 'gw': '1.1.1.10', 'plen': 24}, + {'ixp': tg_ports[2], 'ip': '1.1.1.3', 'gw': '1.1.1.10', 'plen': 24}, + {'ixp': tg_ports[3], 'ip': '1.1.1.4', 'gw': '1.1.1.10', 'plen': 24} + )) + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + await tgen_utils_setup_streams(tgen_dev, None, streams) + + # 6. Transmit traffic + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(15) + + # 7. Verify RX rate per stream is as expected (limited by the rule) + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Traffic Item Statistics') + rate_stream_matching = zip(rates, streams.keys()) + for row, stream in zip(stats.Rows, rate_stream_matching): + device.applog.info(f'Veryfing rate for the stream {stream[1]}') + err_msg = f'Expected rate for stream {stream[1]} to be {stream[0]} got {row["Rx Rate (bps)"]} ' + assert isclose(float(row['Rx Rate (bps)']), stream[0], rel_tol=tolerance), err_msg + await tgen_utils_stop_traffic(tgen_dev) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_rate_units.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_rate_units.py new file mode 100644 index 000000000..a8ea5541b --- /dev/null +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_rate_units.py @@ -0,0 +1,164 @@ +import asyncio +import pytest +from math import isclose + +from dent_os_testbed.lib.tc.tc_filter import TcFilter +from dent_os_testbed.lib.tc.tc_qdisc import TcQdisc +from dent_os_testbed.lib.ip.ip_link import IpLink + +from dent_os_testbed.utils.test_utils.tgen_utils import ( + tgen_utils_get_dent_devices_with_tgen, + tgen_utils_traffic_generator_connect, + tgen_utils_dev_groups_from_config, + tgen_utils_get_traffic_stats, + tgen_utils_setup_streams, + tgen_utils_start_traffic, + tgen_utils_stop_traffic, +) + +from dent_os_testbed.utils.test_utils.tc_flower_utils import ( + tcutil_generate_rule_with_random_selectors, + tcutil_tc_rules_to_tgen_streams, +) + +pytestmark = [ + pytest.mark.suite_functional_policer, + pytest.mark.usefixtures('cleanup_bridges', 'cleanup_qdiscs', 'cleanup_tgen'), + pytest.mark.asyncio, +] + + +@pytest.mark.parametrize('unit_type', ['IEC', 'SI']) +async def test_policer_rate_config(testbed, unit_type): + """ + Test Name: Policer rate config functionality + Test Suite: suite_functional_policer + Test Overview: Verify different units for the same rate and burst function the same (same RX rate) + Test Procedure: + 1. Create a bridge entity and set link up on it. + 2. Set link up on interfaces on all participant ports. Enslave all participant ports to the bridge. + 3. Create an ingress qdisc on first port connected to Ixia port and add a rule with random selectors + 4. Prepare a matching stream for the randomly selected selectors and with random generated size of packet. + 5. Send traffic + 6. Verify RX rate on RX ports is as expected (rate is limited by the police pass rule). + """ + + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 2) + if not tgen_dev or not dent_devices: + pytest.skip( + 'The testbed does not have enough dent with tgen connections') + device = dent_devices[0] + dent = device.host_name + tg_ports = tgen_dev.links_dict[dent][0][:2] + ports = tgen_dev.links_dict[dent][1][:2] + port_with_rule = ports[0] + bridge = 'bridge0' + frame_rate = 250000 + tolerance = 0.1 + + # 1. Create a bridge entity and set link up on it. + out = await IpLink.add(input_data=[{dent: [{ + 'dev': 'bridge0', + 'type': 'bridge'}] + }]) + assert out[0][dent]['rc'] == 0, 'Failed creating bridge.' + + await IpLink.set(input_data=[{dent: [{'device': bridge, 'operstate': 'up'}]}]) + assert out[0][dent]['rc'] == 0, 'Failed setting bridge to state UP.' + + # 2. Set link up on interfaces on all participant ports. Enslave all participant ports to the bridge. + out = await IpLink.set(input_data=[{dent: [{ + 'device': port, + 'operstate': 'up', + 'master': bridge + } for port in ports]}]) + assert out[0][dent]['rc'] == 0, 'Failed setting link to state UP.' + + # 3. Create an ingress qdisc on first port connected to Ixia port and add a rule with random selectors + out = await TcQdisc.add(input_data=[{dent: [{ + 'dev': ports[0], + 'direction': 'ingress'}]}]) + assert out[0][dent]['rc'] == 0, f'Failed creating qdisc on port: {ports[0]}.' + + rule_config = { + 'action': {'police': {'rate': frame_rate, 'burst': frame_rate + 1000, 'conform-exceed': 'drop'}}, + 'want_ip': True, + 'want_port': True, + 'skip_sw': True, + 'want_proto': True, + 'want_mac': True, + 'want_tcp': True, + } + + tc_rule = tcutil_generate_rule_with_random_selectors( + port_with_rule, **rule_config) + + out = await TcFilter.add(input_data=[{dent: [tc_rule]}]) + assert out[0][dent]['rc'] == 0, 'Failed to create tc rule' + + out = await TcFilter.show(input_data=[{dent: [{ + 'dev': port_with_rule, + 'direction': 'ingress', + 'options': '-j'}]}], parse_output=True) + assert out[0][dent]['rc'] == 0, 'Failed to get tc rule' + + # 4 Prepare a matching stream for the randomly selected selectors and with random generated size of packet. + streams = tcutil_tc_rules_to_tgen_streams({port_with_rule: out[0][dent]['parsed_output']}, + frame_rate_pps=frame_rate, + frame_rate_type='line_rate') + + dev_groups = tgen_utils_dev_groups_from_config(( + {'ixp': tg_ports[0], 'ip': '1.1.1.1', 'gw': '1.1.1.10', 'plen': 24}, + {'ixp': tg_ports[1], 'ip': '1.1.1.2', 'gw': '1.1.1.10', 'plen': 24} + )) + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + await tgen_utils_setup_streams(tgen_dev, None, streams) + + # 5. Send Traffic + await tgen_utils_start_traffic(tgen_dev) + + unit_multiplier_map = { + 'SI': { + 'gbit': 1e-9, + 'bps': 0.125, + 'kbps': 1.25e-4, + 'mbps': 1.25E-7, + 'gbps': 1.25e-10, + 'tbps': 1.25e-13 + }, + 'IEC': { + 'gibit': 9.3132257461548E-10, + 'kibps': 1.220703125E-4, + 'mibps': 1.1920928955078E-7, + 'gibps': 1.1641532182694E-10, + 'tibps': 1.1368683772162E-13 + }, + } + + for unit, mul in unit_multiplier_map[unit_type].items(): + out = await TcFilter.delete(input_data=[{dent: [{ + 'dev': port_with_rule, + 'direction': 'ingress'}]}]) + assert out[0][dent]['rc'] == 0, 'Failed to delete tc rule' + + tc_rule['action']['police']['rate'] = f'{frame_rate * mul}{unit}' + tc_rule['action']['police']['burst'] = frame_rate + 1000 + + # Add rule with new unit same rate + out = await TcFilter.add(input_data=[{dent: [tc_rule]}]) + assert out[0][dent]['rc'] == 0, 'Failed to create tc rule' + await asyncio.sleep(20) + + # 6 Verify RX rate on RX ports is as expected (rate is limited by the police pass rule). + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Port Statistics') + for row in stats.Rows: + if row['Port Name'] == tg_ports[0]: + continue + deviation = frame_rate * tolerance + actual_rate = float(row['Rx. Rate (bps)']) + err_msg = f'Expected {actual_rate} got : {frame_rate + deviation} and {frame_rate - deviation}' \ + f' with unit {unit} and and mul {mul}' + assert isclose(float(row['Rx. Rate (bps)']), frame_rate, rel_tol=tolerance), err_msg + + await tgen_utils_stop_traffic(tgen_dev) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_rules_priority.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_rules_priority.py new file mode 100644 index 000000000..fbacde468 --- /dev/null +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_rules_priority.py @@ -0,0 +1,213 @@ +import asyncio +import pytest +import random +import copy +from math import isclose + +from dent_os_testbed.lib.tc.tc_filter import TcFilter +from dent_os_testbed.lib.tc.tc_qdisc import TcQdisc +from dent_os_testbed.lib.ip.ip_link import IpLink + +from dent_os_testbed.utils.test_utils.tgen_utils import ( + tgen_utils_get_dent_devices_with_tgen, + tgen_utils_traffic_generator_connect, + tgen_utils_dev_groups_from_config, + tgen_utils_setup_streams, + tgen_utils_start_traffic, + tgen_utils_stop_traffic, + tgen_utils_get_traffic_stats +) + +from dent_os_testbed.utils.test_utils.tc_flower_utils import tcutil_tc_rules_to_tgen_streams + +pytestmark = [ + pytest.mark.suite_functional_policer, + pytest.mark.usefixtures('cleanup_bridges', 'cleanup_qdiscs', 'cleanup_tgen'), + pytest.mark.asyncio, +] + + +@pytest.mark.parametrize('qdisc_type', ['port', 'shared_block']) +async def test_policer_rules_priority(testbed, qdisc_type): + """ + Test Name: Policer rules priority + Test Suite: suite_functional_policer + Test Overview: Verify policer rules priority with port/shared block + Test Procedure: + 1. Create a bridge entity and set link up on it. + 2. Set link up on interfaces on all participant ports. Enslave all participant ports to the bridge. + 3. Create an ingress queue for all participant TX ports + 4. Create within the ingress qdisc two rules with the same selectors (generated random), + and with: different police rate value second rule pref < first rule pref + 5. Prepare matching traffic and transmit + 6. Verify it is handled according to the first rule add action + 7. Delete the first rule and add it again with the same priority as before + 8. Send traffic matching the rules selectors + 9. Verify it is still handled according to the rule with the lowest priority + 10. Delete the rule again and add it with higher priority than the other rule + 11. Send traffic matching the rules selectors + 12. Verify it is still handled according to the rule with the lowest priority + """ + + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 4) + if not tgen_dev or not dent_devices: + pytest.skip( + 'The testbed does not have enough dent with tgen connections') + device = dent_devices[0] + dent = device.host_name + tg_ports = tgen_dev.links_dict[dent][0] + ports = tgen_dev.links_dict[dent][1] + ports_with_rule = ports[:1] if qdisc_type == 'port' else ports[1:] + bridge = 'bridge0' + block = random.randint(5, 3000) + tc_rule_1_frame_rate = 250000 # bps + tc_rule_2_frame_rate = tc_rule_1_frame_rate + tc_rule_1_frame_rate * 0.10 # bps + dev_or_block = {'dev': ports_with_rule[0]} if qdisc_type == 'port' else {'block': block} + tolerance = 0.12 + + # 1. Create a bridge entity and set link up on it. + out = await IpLink.add(input_data=[{dent: [{ + 'dev': 'bridge0', + 'type': 'bridge'}] + }]) + assert out[0][dent]['rc'] == 0, 'Failed creating bridge.' + + await IpLink.set(input_data=[{dent: [{'device': bridge, 'operstate': 'up'}]}]) + assert out[0][dent]['rc'] == 0, 'Failed setting bridge to state UP.' + + # 2. Set link up on interfaces on all participant ports. Enslave all participant ports to the bridge. + out = await IpLink.set(input_data=[{dent: [{ + 'device': port, + 'operstate': 'up', + 'master': bridge + } for port in ports]}]) + assert out[0][dent]['rc'] == 0, 'Failed setting link to state UP.' + + config = [{'dev': port, 'ingress_block': block, 'direction': 'ingress'} for port in ports_with_rule] + if qdisc_type == 'port': + for item in config: + del item['ingress_block'] + + # 3. Create an ingress queue for all participant TX ports + out = await TcQdisc.add(input_data=[{dent: config}]) + assert out[0][dent]['rc'] == 0, 'Failed to create a qdisc' + + # 4.Create within the ingress qdisc two rules with the same selectors (generated random), + # and with: different police rate value first rule pref < second rule pref + + tc_rule_1 = { + 'action': { + 'police': { + 'rate': tc_rule_1_frame_rate, + 'burst': tc_rule_1_frame_rate + 1000, + 'conform-exceed': 'drop'} + }, + 'direction': 'ingress', + 'protocol': '0x8100 ', + 'filtertype': { + 'skip_sw': '', + 'src_mac': '02:15:53:62:36:d1', + 'dst_mac': '02:06:a2:54:22:9f', + 'vlan_id': 1942}, + 'pref': 100, + } + + # First rule + tc_rule_1.update(dev_or_block) + out = await TcFilter.add(input_data=[{dent: [tc_rule_1]}]) + assert out[0][dent]['rc'] == 0, 'Failed to create tc rule' + + # Second rule + tc_rule_2 = copy.deepcopy(tc_rule_1) + tc_rule_2['pref'] = 200 + tc_rule_2['action']['police']['rate'] = tc_rule_2_frame_rate + tc_rule_2['action']['police']['burst'] = tc_rule_2_frame_rate + 1000 + + out = await TcFilter.add(input_data=[{dent: [tc_rule_2]}]) + assert out[0][dent]['rc'] == 0, 'Failed to create tc rule' + + # 5.Prepare matching traffic and transmit + created_rules = { + 'direction': 'ingress', + 'options': '-j'} + created_rules.update(dev_or_block) + + out = await TcFilter.show(input_data=[{dent: [created_rules]}], parse_output=True) + assert out[0][dent]['rc'] == 0, 'Failed to get tc rule' + + out = await TcFilter.show(input_data=[{dent: [created_rules]}], parse_output=True) + streams = tcutil_tc_rules_to_tgen_streams({port: out[0][dent]['parsed_output'] for port in ports_with_rule}, + frame_rate_type='line_rate', + frame_rate_pps=100) + + dev_groups = tgen_utils_dev_groups_from_config(( + {'ixp': tg_ports[0], 'ip': '1.1.1.1', 'gw': '1.1.1.10', 'plen': 24}, + {'ixp': tg_ports[1], 'ip': '1.1.1.2', 'gw': '1.1.1.10', 'plen': 24}, + {'ixp': tg_ports[2], 'ip': '1.1.1.3', 'gw': '1.1.1.10', 'plen': 24}, + {'ixp': tg_ports[3], 'ip': '1.1.1.4', 'gw': '1.1.1.10', 'plen': 24} + )) + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + await tgen_utils_setup_streams(tgen_dev, None, streams) + + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(30) + + # 6. Verify it is handled according to the first rule add action + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Port Statistics') + for row in stats.Rows: + err_msg = f'Expected {tc_rule_1_frame_rate} got : {float(row["Rx. Rate (bps)"])}' + if qdisc_type == 'port': + if row['Port Name'] == tg_ports[0]: + continue + assert isclose(float(row['Rx. Rate (bps)']), tc_rule_1_frame_rate, rel_tol=tolerance), err_msg + else: + if row['Port Name'] == tg_ports[0]: + assert isclose(float(row['Rx. Rate (bps)']), tc_rule_1_frame_rate, rel_tol=tolerance), err_msg + + # 7. Delete the first rule and add it again with the same priority as before + out = await TcFilter.delete(input_data=[{dent: [tc_rule_1]}]) + assert out[0][dent]['rc'] == 0, 'Failed to delete tc rule ' + + out = await TcFilter.add(input_data=[{dent: [tc_rule_1]}]) + assert out[0][dent]['rc'] == 0, 'Failed to create tc rule' + + # 8. Send traffic matching the rules selectors + await asyncio.sleep(5) + + # 9. Verify it is still handled according to the rule with the lowest priority + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Port Statistics') + for row in stats.Rows: + err_msg = f'Expected {tc_rule_1_frame_rate} got : {float(row["Rx. Rate (bps)"])}' + if qdisc_type == 'port': + if row['Port Name'] == tg_ports[0]: + continue + assert isclose(float(row['Rx. Rate (bps)']), tc_rule_1_frame_rate, rel_tol=tolerance), err_msg + else: + if row['Port Name'] == tg_ports[0]: + assert isclose(float(row['Rx. Rate (bps)']), tc_rule_1_frame_rate, rel_tol=tolerance), err_msg + + # 10. Delete the rule again and add it with higher priority than the other rule + out = await TcFilter.delete(input_data=[{dent: [tc_rule_1]}]) + assert out[0][dent]['rc'] == 0, 'Failed to delete tc rule ' + + tc_rule_1['pref'] = tc_rule_2['pref'] + 100 + out = await TcFilter.add(input_data=[{dent: [tc_rule_1]}]) + assert out[0][dent]['rc'] == 0, 'Failed to create tc rule' + + # 11. Send traffic matching the rules selectors + await asyncio.sleep(10) + + # 12. Verify it is still handled according to the rule with the lowest priority + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Port Statistics') + for row in stats.Rows: + err_msg = f'Expected {tc_rule_2_frame_rate} got : {float(row["Rx. Rate (bps)"])}' + if qdisc_type == 'port': + if row['Port Name'] == tg_ports[0]: + continue + assert isclose(float(row['Rx. Rate (bps)']), tc_rule_2_frame_rate, rel_tol=tolerance), err_msg + else: + if row['Port Name'] == tg_ports[0]: + assert isclose(float(row['Rx. Rate (bps)']), tc_rule_2_frame_rate, rel_tol=tolerance), err_msg + + await tgen_utils_stop_traffic(tgen_dev) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_same_pref_rules_priority.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_same_pref_rules_priority.py new file mode 100644 index 000000000..1c5d9ffac --- /dev/null +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/policer/test_policer_same_pref_rules_priority.py @@ -0,0 +1,204 @@ +import asyncio +import pytest +import random +from math import isclose + +from dent_os_testbed.lib.tc.tc_filter import TcFilter +from dent_os_testbed.lib.tc.tc_qdisc import TcQdisc +from dent_os_testbed.lib.ip.ip_link import IpLink + +from dent_os_testbed.utils.test_utils.tgen_utils import ( + tgen_utils_get_dent_devices_with_tgen, + tgen_utils_traffic_generator_connect, + tgen_utils_dev_groups_from_config, + tgen_utils_setup_streams, + tgen_utils_start_traffic, + tgen_utils_get_traffic_stats +) + +from dent_os_testbed.utils.test_utils.tc_flower_utils import tcutil_tc_rules_to_tgen_streams + +pytestmark = [ + pytest.mark.suite_functional_policer, + pytest.mark.usefixtures('cleanup_bridges', 'cleanup_qdiscs', 'cleanup_tgen'), + pytest.mark.asyncio, +] + + +@pytest.mark.parametrize('qdisc_type', ['port', 'shared_block']) +async def test_policer_same_pref_rules(testbed, qdisc_type): + """ + Test Name: Policer rules priority + Test Suite: suite_functional_policer + Test Overview: Verify prioritization between 2 rules with the same priority (last one should be preferred ) + Test Procedure: + 1. Create a bridge entity and set link up on it. + 2. Set link up on interfaces on all participant ports. Enslave all participant ports to the bridge. + 3. Create an ingress queue for all participant TX ports + 4. Create within the ingress qdisc two rules with: + # - same selectors, + # - different police rate value, + # - second rule pref = first rule pref + # - second rule handle != first rule handle + 5. Prepare matching traffic and transmit + 6. Verify it is handled according to the first rule add action + 7. Delete the first rule and add it again with the same priority as before + 8. Send traffic matching the rules selectors + 9. Verify traffic is handled by the first rule added to the qdisc + """ + + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 4) + if not tgen_dev or not dent_devices: + pytest.skip( + 'The testbed does not have enough dent with tgen connections') + device = dent_devices[0] + dent = device.host_name + tg_ports = tgen_dev.links_dict[dent][0] + ports = tgen_dev.links_dict[dent][1] + ports_with_rule = ports[:1] if qdisc_type == 'port' else ports[1:] + bridge = 'bridge0' + block = random.randint(5, 3000) + tc_rule_1_frame_rate = 250_000 # bps + tc_rule_2_frame_rate = 500_000 # bps + tolerance = 0.12 + dev_or_block = {'dev': ports_with_rule[0]} if qdisc_type == 'port' else {'block': block} + + # 1. Create a bridge entity and set link up on it. + out = await IpLink.add(input_data=[{dent: [{ + 'dev': 'bridge0', + 'type': 'bridge'}] + }]) + assert out[0][dent]['rc'] == 0, 'Failed creating bridge.' + + await IpLink.set(input_data=[{dent: [{'device': bridge, 'operstate': 'up'}]}]) + assert out[0][dent]['rc'] == 0, 'Failed setting bridge to state UP.' + + # 2. Set link up on interfaces on all participant ports. Enslave all participant ports to the bridge. + out = await IpLink.set(input_data=[{dent: [{ + 'device': port, + 'operstate': 'up', + 'master': bridge + } for port in ports]}]) + assert out[0][dent]['rc'] == 0, 'Failed setting link to state UP.' + + config = [{'dev': port, 'ingress_block': block, 'direction': 'ingress'} for port in ports_with_rule] + if qdisc_type == 'port': + for item in config: + del item['ingress_block'] + + # 3. Create an ingress queue for all participant TX ports + out = await TcQdisc.add(input_data=[{dent: config}]) + assert out[0][dent]['rc'] == 0, 'Failed to create a qdisc' + + # 4.Create within the ingress qdisc two rules with: + # - same selectors, + # - different police rate value, + # - second rule pref = first rule pref + # - second rule handle != first rule handle + + rules = [{ + 'action': { + 'police': { + 'rate': tc_rule_1_frame_rate, + 'burst': tc_rule_1_frame_rate + 1000, + 'conform-exceed': 'drop'} + }, + 'direction': 'ingress', + 'protocol': '0x8100 ', + 'filtertype': { + 'skip_sw': '', + 'dst_mac': '02:06:a2:54:22:9f', + 'vlan_id': 1942}, + 'pref': 100, + }, + { + 'action': { + 'police': { + 'rate': tc_rule_2_frame_rate, + 'burst': tc_rule_2_frame_rate + 1000, + 'conform-exceed': 'drop'} + }, + 'direction': 'ingress', + 'protocol': '0x8100 ', + 'filtertype': { + 'skip_sw': '', + 'src_mac': '02:15:53:62:36:d1', + 'dst_mac': '02:06:a2:54:22:9f', + 'vlan_id': 1942 + }, + 'pref': 100, + }] + for rule in rules: + rule.update(dev_or_block) + + out = await TcFilter.add(input_data=[{dent: rules}]) + assert out[0][dent]['rc'] == 0, 'Failed to create tc rule' + + # 5.Prepare matching traffic and transmit + created_rules = { + 'direction': 'ingress', + 'options': '-j'} + created_rules.update(dev_or_block) + + out = await TcFilter.show(input_data=[{dent: [created_rules]}], parse_output=True) + assert out[0][dent]['rc'] == 0, 'Failed to get tc rule' + + out = await TcFilter.show(input_data=[{dent: [created_rules]}], parse_output=True) + streams = tcutil_tc_rules_to_tgen_streams({port: out[0][dent]['parsed_output'] for port in ports_with_rule}, + frame_rate_type='line_rate', + frame_rate_pps=100, start=2) + + dev_groups = tgen_utils_dev_groups_from_config(( + {'ixp': tg_ports[0], 'ip': '1.1.1.1', 'gw': '1.1.1.10', 'plen': 24}, + {'ixp': tg_ports[1], 'ip': '1.1.1.2', 'gw': '1.1.1.10', 'plen': 24}, + {'ixp': tg_ports[2], 'ip': '1.1.1.3', 'gw': '1.1.1.10', 'plen': 24}, + {'ixp': tg_ports[3], 'ip': '1.1.1.4', 'gw': '1.1.1.10', 'plen': 24} + )) + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + await tgen_utils_setup_streams(tgen_dev, None, streams) + + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(30) + + # 6. Verify it is handled according to the first rule add action + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Port Statistics') + for row in stats.Rows: + err_msg = f'Expected {tc_rule_1_frame_rate} got : {float(row["Rx. Rate (bps)"])}' + if qdisc_type == 'port': + if row['Port Name'] == tg_ports[0]: + continue + assert isclose(float(row['Rx. Rate (bps)']), tc_rule_1_frame_rate, rel_tol=tolerance), err_msg + else: + if row['Port Name'] == tg_ports[0]: + assert isclose(float(row['Rx. Rate (bps)']), tc_rule_1_frame_rate, rel_tol=tolerance), err_msg + + # 7. Delete the first rule and add it again with the same priority as before + tc_filter_to_delete = { + 'direction': 'ingress', + 'pref': 100, + 'handle': '0x1', + 'filtertype': {} + } + + tc_filter_to_delete.update(dev_or_block) + out = await TcFilter.delete(input_data=[{dent: [tc_filter_to_delete]}]) + assert out[0][dent]['rc'] == 0, 'Failed to delete tc rule ' + + out = await TcFilter.add(input_data=[{dent: [rules[0]]}]) + assert out[0][dent]['rc'] == 0, 'Failed to create tc rule' + + # 8. Send traffic matching the rules selectors + await asyncio.sleep(5) + + # 9. Verify traffic is handled by the first rule added to the qdisc + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Port Statistics') + for row in stats.Rows: + err_msg = f'Expected {tc_rule_2_frame_rate} got : {float(row["Rx. Rate (bps)"])}' + if qdisc_type == 'port': + if row['Port Name'] == tg_ports[0]: + continue + assert isclose(float(row['Rx. Rate (bps)']), tc_rule_2_frame_rate, rel_tol=tolerance), err_msg + else: + if row['Port Name'] == tg_ports[0]: + assert isclose(float(row['Rx. Rate (bps)']), tc_rule_2_frame_rate, rel_tol=tolerance), err_msg diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/tc_flower_utils.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/tc_flower_utils.py index 1fca70867..a6b0d7cb8 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/tc_flower_utils.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/tc_flower_utils.py @@ -126,7 +126,7 @@ async def tcutil_cleanup_tc_rules(dent_dev, swp_tgen_ports, swp_tc_rules): def tcutil_tc_rules_to_tgen_streams(swp_tc_rules, streams=None, start=0, cnt=None, - frame_rate_pps=10, frame_size=256): + frame_rate_pps=10, frame_size=256, frame_rate_type=None): """ - swp_tc_rules: dict - streams: streams dict that will be modified @@ -134,6 +134,7 @@ def tcutil_tc_rules_to_tgen_streams(swp_tc_rules, streams=None, start=0, cnt=Non - cnt: used to specify the number of streams to be created - frame_rate_pps: frame rate for each stream - frame_size: packet size for each stream + - frame_rate_type which rate type to use when sending traffic(defaults to pps) Expects swp_tc_rules to be a dict: { @@ -163,6 +164,7 @@ def tcutil_tc_rules_to_tgen_streams(swp_tc_rules, streams=None, start=0, cnt=Non 'dstIp': f'20.0.{swp[3:]}.3', 'rate': frame_rate_pps, 'frameSize': frame_size, + 'frame_rate_type': frame_rate_type } st['type'] = 'ethernetVlan' if rule['protocol'] == '802.1Q' else 'ethernet' name = swp @@ -279,7 +281,7 @@ async def tcutil_iptables_rules_to_tgen_streams( def tcutil_generate_rule_with_random_selectors( port, pref=None, want_mac=False, want_vlan=False, want_ip=False, want_tcp=False, want_port=False, want_icmp=False, action=None, direction='ingress', - skip_sw=False, skip_hw=False, want_proto=True, + skip_sw=False, skip_hw=False, want_proto=True, want_vlan_ethtype=True ): """ Creates a single tc rule with specified selectors: @@ -307,7 +309,7 @@ def random_ip(): def random_icmp_type(): return random.choice((0, 3, 4, 5, 8, 11, 12, 13, 14, 15, 16, 17, 18)) ip_protocols = ('ip', 'ipv4', '0x0800') - vlan_protocols = ('0x8100', '0x88a8', '802.1q') + vlan_protocols = ('0x8100', '802.1q') if want_vlan: protocols = vlan_protocols @@ -326,7 +328,7 @@ def random_icmp_type(): filter_t = {} rule = { 'dev': port, - 'action': random.choice(action_list), + 'action': action_list if type(action_list) is dict else random.choice(action_list), 'direction': direction, 'protocol': random.choice(protocols), 'filtertype': filter_t, @@ -345,10 +347,11 @@ def random_icmp_type(): return rule if want_vlan: filter_t['vlan_id'] = random.randint(1, 4095) - if want_ip: - filter_t['vlan_ethtype'] = random.choice(ip_protocols) - else: - filter_t['vlan_ethtype'] = f'0x9{random.randint(1, 3)}00' + if want_vlan_ethtype: + if want_ip: + filter_t['vlan_ethtype'] = random.choice(ip_protocols) + else: + filter_t['vlan_ethtype'] = f'0x9{random.randint(1, 3)}00' if want_ip: filter_t['src_ip'] = random_ip() filter_t['dst_ip'] = random_ip() diff --git a/DentOS_Framework/DentOsTestbedLib/src/dent_os_testbed/lib/ip/linux/linux_ip_link_impl.py b/DentOS_Framework/DentOsTestbedLib/src/dent_os_testbed/lib/ip/linux/linux_ip_link_impl.py index 05845c04d..5179fba4e 100644 --- a/DentOS_Framework/DentOsTestbedLib/src/dent_os_testbed/lib/ip/linux/linux_ip_link_impl.py +++ b/DentOS_Framework/DentOsTestbedLib/src/dent_os_testbed/lib/ip/linux/linux_ip_link_impl.py @@ -44,6 +44,8 @@ def format_add(self, command, *argv, **kwarg): cmd += 'vlan_filtering {} '.format((params['vlan_filtering'])) if 'vlan_default_pvid' in params: cmd += 'vlan_default_pvid {} '.format((params['vlan_default_pvid'])) + if 'mode' in params: + cmd += 'mode {} '.format((params['mode'])) return cmd def format_delete(self, command, *argv, **kwarg): From e7fb3d9e5888ecebdc847a5b4ae040a20ba539f5 Mon Sep 17 00:00:00 2001 From: Kostiantyn Stavruk <119867993+stavrukPLV@users.noreply.github.com> Date: Fri, 5 May 2023 07:25:12 +0300 Subject: [PATCH 6/9] Refactor cleanup function (#320) Signed-off-by: Kostiantyn Stavruk --- .../utils/test_utils/cleanup_utils.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/cleanup_utils.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/cleanup_utils.py index a19d3b99d..4a0d57c1c 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/cleanup_utils.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/cleanup_utils.py @@ -139,13 +139,13 @@ async def cleanup_sysctl(): await RecoverableSysctl.recover() -async def cleanup_kbyte_per_sec_rate_value(dev, all_values=False, bc=False, unk_uc=False, unreg_mc=False): +async def cleanup_kbyte_per_sec_rate_value(dev, tgen_dev, all_values=False, bc=False, unk_uc=False, unreg_mc=False): """ Restore values changed during test viz: - all kbyte_per_sec_rate values - bc_kbyte_per_sec_rate values - - unk_uc_kbyte_per_sec_rate - - unreg_mc_kbyte_per_sec_rate + - unk_uc_kbyte_per_sec_rate values + - unreg_mc_kbyte_per_sec_rate values """ logger = AppLogger(DEFAULT_LOGGER) logger.info('Restoring kbyte_per_sec_rate values') @@ -153,13 +153,15 @@ async def cleanup_kbyte_per_sec_rate_value(dev, all_values=False, bc=False, unk_ input_data=[{dev.host_name: [{'options': '-j'}]}], parse_output=True) devlink_entries = out[0][dev.host_name]['parsed_output'] + rate_names = ['bc_kbyte_per_sec_rate', 'unk_uc_kbyte_per_sec_rate', 'unreg_mc_kbyte_per_sec_rate'] + device = '/'.join(list(devlink_entries['param'])[0].split('/')[:2]) + '/' + ports_num = [num[3:] for num in tgen_dev.links_dict[dev.host_name][1]] # restoring kbyte_per_sec_rate all values if all_values: input_data = ({dev.host_name: [ - {'dev': f'{device}', 'name': f"{item['name']}", 'value': '0', 'cmode': 'runtime'}]} - for device in devlink_entries['param'] - for item in devlink_entries['param'][device] + {'dev': f'{device}{num}', 'name': name, 'value': '0', 'cmode': 'runtime'} for num in ports_num]} + for name in rate_names ) await DevlinkPort.set(input_data=input_data) return @@ -167,24 +169,24 @@ async def cleanup_kbyte_per_sec_rate_value(dev, all_values=False, bc=False, unk_ # restoring bc_kbyte_per_sec_rate values if bc: input_data = ({dev.host_name: [ - {'dev': f'{device}', 'name': 'bc_kbyte_per_sec_rate', 'value': '0', 'cmode': 'runtime'}]} - for device in devlink_entries['param'] + {'dev': f'{device}{num}', 'name': 'bc_kbyte_per_sec_rate', 'value': '0', 'cmode': 'runtime'}]} + for num in ports_num ) await DevlinkPort.set(input_data=input_data) # restoring unk_uc_kbyte_per_sec_rate values if unk_uc: input_data = ({dev.host_name: [ - {'dev': f'{device}', 'name': 'unk_uc_kbyte_per_sec_rate', 'value': '0', 'cmode': 'runtime'}]} - for device in devlink_entries['param'] + {'dev': f'{device}{num}', 'name': 'unk_uc_kbyte_per_sec_rate', 'value': '0', 'cmode': 'runtime'}]} + for num in ports_num ) await DevlinkPort.set(input_data=input_data) # restoring unreg_mc_kbyte_per_sec_rate values if unreg_mc: input_data = ({dev.host_name: [ - {'dev': f'{device}', 'name': 'unreg_mc_kbyte_per_sec_rate', 'value': '0', 'cmode': 'runtime'}]} - for device in devlink_entries['param'] + {'dev': f'{device}{num}', 'name': 'unreg_mc_kbyte_per_sec_rate', 'value': '0', 'cmode': 'runtime'}]} + for num in ports_num ) await DevlinkPort.set(input_data=input_data) From ab9dc79f987b20efa244a7faf25f87499abc964e Mon Sep 17 00:00:00 2001 From: Kostiantyn Stavruk <119867993+stavrukPLV@users.noreply.github.com> Date: Fri, 5 May 2023 07:25:19 +0300 Subject: [PATCH 7/9] Add function to tb_utils.py (#319) Signed-off-by: Kostiantyn Stavruk --- .../functional/bridging/test_bridging_packets.py | 13 +------------ .../dent_os_testbed/utils/test_utils/tb_utils.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/bridging/test_bridging_packets.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/bridging/test_bridging_packets.py index fc95fcff1..9045580ea 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/bridging/test_bridging_packets.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/bridging/test_bridging_packets.py @@ -1,9 +1,9 @@ import pytest import asyncio +from dent_os_testbed.utils.test_utils.tb_utils import get_port_stats from dent_os_testbed.lib.bridge.bridge_link import BridgeLink from dent_os_testbed.lib.bridge.bridge_fdb import BridgeFdb -from dent_os_testbed.lib.ethtool.ethtool import Ethtool from dent_os_testbed.lib.ip.ip_link import IpLink from dent_os_testbed.utils.test_utils.tgen_utils import ( @@ -25,17 +25,6 @@ ] -async def get_port_stats(device_host_name, ports): - stats = {} - for port in ports: - out = await Ethtool.show(input_data=[{device_host_name: [ - {'devname': port, 'options': '-S'} - ]}], parse_output=True) - assert out[0][device_host_name]['rc'] == 0 - stats[port] = out[0][device_host_name]['parsed_output'] - return stats - - async def test_bridging_packets_oversize(testbed): """ Test Name: test_bridging_packets_oversize diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/tb_utils.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/tb_utils.py index 1e6d57417..da55ad261 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/tb_utils.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/utils/test_utils/tb_utils.py @@ -587,3 +587,15 @@ async def tb_get_qualified_ports(device, ports, speed, duplex, required_ports=2) err_msg = f'Need {required_ports} ports with the same speed of {speed} and duplex {duplex}' assert len(speed_ports) >= required_ports, err_msg return speed_ports + + +async def get_port_stats(device_host_name, ports): + await asyncio.sleep(6) + stats = {} + for port in ports: + out = await Ethtool.show(input_data=[{device_host_name: [ + {'devname': port, 'options': '-S'} + ]}], parse_output=True) + assert out[0][device_host_name]['rc'] == 0 + stats[port] = out[0][device_host_name]['parsed_output'] + return stats From 500ffb148142bcf2eeb8a9c79a946fcf450a6ea8 Mon Sep 17 00:00:00 2001 From: Kostiantyn Stavruk <119867993+stavrukPLV@users.noreply.github.com> Date: Fri, 5 May 2023 07:25:28 +0300 Subject: [PATCH 8/9] Add and refactor port_isolation tests (#318) Signed-off-by: Kostiantyn Stavruk --- ...test_port_isolation_basic_functionality.py | 12 +- ...enable_disable_feature_under_ws_traffic.py | 3 + ...rt_isolation_incremental_mac_addresses.py} | 14 +- ..._isolation_interaction_br_storm_control.py | 165 +++++++++++++ ...ion_interaction_mc_and_br_storm_control.py | 163 +++++++++++++ ..._isolation_interaction_ports_inside_lag.py | 2 +- ..._interaction_route_between_vlan_devices.py | 4 +- ...st_port_isolation_interaction_span_rule.py | 17 +- ...ort_isolation_interaction_storm_control.py | 221 ------------------ ...lation_interaction_unk_uc_storm_control.py | 165 +++++++++++++ ...lation_maximum_isolated_ports_on_bridge.py | 4 +- 11 files changed, 524 insertions(+), 246 deletions(-) rename DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/{test_port_isolation_stress_incremental_mac_addresses.py => test_port_isolation_incremental_mac_addresses.py} (95%) create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_br_storm_control.py create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_mc_and_br_storm_control.py delete mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_storm_control.py create mode 100644 DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_unk_uc_storm_control.py diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_basic_functionality.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_basic_functionality.py index 39f7f0b88..e26c4ce28 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_basic_functionality.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_basic_functionality.py @@ -54,7 +54,7 @@ async def test_port_isolation_basic_functionality(testbed): device_host_name = dent_devices[0].host_name tg_ports = tgen_dev.links_dict[device_host_name][0] ports = tgen_dev.links_dict[device_host_name][1] - traffic_duration = 10 + traffic_duration = 15 pps_value = 1000 out = await IpLink.add( @@ -111,8 +111,8 @@ async def test_port_isolation_basic_functionality(testbed): 'stream_0': { 'ip_source': dev_groups[tg_ports[0]][0]['name'], 'ip_destination': dev_groups[tg_ports[3-x]][0]['name'], - 'srcMac': '28:be:0d:47:eb:2b', - 'dstMac': '1c:99:9f:fb:63:15', + 'srcMac': f'28:be:0d:4{x+4}:eb:2b', + 'dstMac': f'1c:99:9f:fb:63:1{x+2}', 'frameSize': 162, 'rate': pps_value, 'protocol': '0x0800', @@ -133,7 +133,7 @@ async def test_port_isolation_basic_functionality(testbed): 'stream_2': { 'ip_source': dev_groups[tg_ports[2]][0]['name'], 'ip_destination': dev_groups[tg_ports[3-x if x <= 0 else 2-x]][0]['name'], - 'srcMac': '2a:3e:13:88:e5:6d', + 'srcMac': f'2a:3e:13:88:e5:{x+3}d', 'dstMac': 'ff:ff:ff:ff:ff:ff', 'frameSize': 162, 'rate': pps_value, @@ -143,8 +143,8 @@ async def test_port_isolation_basic_functionality(testbed): 'stream_3': { 'ip_source': dev_groups[tg_ports[3]][0]['name'], 'ip_destination': dev_groups[tg_ports[2-x]][0]['name'], - 'srcMac': 'ea:f9:ca:10:1d:b6', - 'dstMac': 'ba:2e:c5:2c:fa:8d', + 'srcMac': f'ea:f9:ca:10:1d:b{x+2}', + 'dstMac': f'ba:2e:c5:2c:fa:{x+5}d', 'frameSize': 162, 'rate': pps_value, 'protocol': '0x0800', diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_enable_disable_feature_under_ws_traffic.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_enable_disable_feature_under_ws_traffic.py index 31a911c74..4edf9c75b 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_enable_disable_feature_under_ws_traffic.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_enable_disable_feature_under_ws_traffic.py @@ -12,6 +12,7 @@ tgen_utils_get_traffic_stats, tgen_utils_setup_streams, tgen_utils_start_traffic, + tgen_utils_stop_traffic, tgen_utils_get_loss ) @@ -160,3 +161,5 @@ async def test_port_isolation_enable_disable_feature_under_ws_traffic(testbed): await tgen_utils_clear_traffic_stats(tgen_dev) await asyncio.sleep(traffic_duration/2) + + await tgen_utils_stop_traffic(tgen_dev) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_stress_incremental_mac_addresses.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_incremental_mac_addresses.py similarity index 95% rename from DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_stress_incremental_mac_addresses.py rename to DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_incremental_mac_addresses.py index 14c6c0637..a533faabf 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_stress_incremental_mac_addresses.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_incremental_mac_addresses.py @@ -23,9 +23,9 @@ ] -async def test_port_isolation_stress_incremental_mac_addresses(testbed): +async def test_port_isolation_incremental_mac_addresses(testbed): """ - Test Name: test_port_isolation_stress_incremental_mac_addresses + Test Name: test_port_isolation_incremental_mac_addresses Test Suite: suite_functional_port_isolation Test Overview: Verify that isolated ports cannot send or receive packets containing incremental destination mac addresses to or from each other but still @@ -53,7 +53,7 @@ async def test_port_isolation_stress_incremental_mac_addresses(testbed): tg_ports = tgen_dev.links_dict[device_host_name][0] ports = tgen_dev.links_dict[device_host_name][1] ixia_vhost_mac_count = 4 - traffic_duration = 10 + traffic_duration = 15 pps_value = 4000 mac_count = 5000 tolerance = 0.8 @@ -116,7 +116,7 @@ async def test_port_isolation_stress_incremental_mac_addresses(testbed): 'start': '40:ee:65:26:69:46', 'step': '00:00:00:00:10:00', 'count': mac_count}, - 'dstMac': '56:18:0b:25:41:b5', + 'dstMac': f'56:18:0b:25:41:b{x+1}', 'frameSize': 1451, 'rate': pps_value, 'protocol': '0x0800', @@ -129,7 +129,7 @@ async def test_port_isolation_stress_incremental_mac_addresses(testbed): 'start': '78:d5:fc:e5:42:85', 'step': '00:00:00:00:10:00', 'count': mac_count}, - 'dstMac': 'f2:c9:74:75:45:38', + 'dstMac': f'f2:c9:74:75:45:3{x+5}', 'frameSize': 1451, 'rate': pps_value, 'protocol': '0x9200', @@ -142,7 +142,7 @@ async def test_port_isolation_stress_incremental_mac_addresses(testbed): 'start': 'ea:c5:88:37:c9:6f', 'step': '00:00:00:00:10:00', 'count': mac_count}, - 'dstMac': 'fa:f5:38:5e:1a:18', + 'dstMac': f'fa:f5:38:5e:1a:1{x+5}', 'frameSize': 1451, 'rate': pps_value, 'protocol': '0x88a8', @@ -155,7 +155,7 @@ async def test_port_isolation_stress_incremental_mac_addresses(testbed): 'start': '28:de:d0:df:49:ca', 'step': '00:00:00:00:10:00', 'count': mac_count}, - 'dstMac': '6a:e7:c4:5b:02:d1', + 'dstMac': f'6a:e7:c4:5b:02:d{x+1}', 'frameSize': 1451, 'rate': pps_value, 'protocol': '0x0800', diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_br_storm_control.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_br_storm_control.py new file mode 100644 index 000000000..154c95055 --- /dev/null +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_br_storm_control.py @@ -0,0 +1,165 @@ +import pytest +import asyncio + +from dent_os_testbed.test.test_suite.functional.storm_control.storm_control_utils import verify_expected_rx_rate +from dent_os_testbed.test.test_suite.functional.storm_control.storm_control_utils import devlink_rate_value +from dent_os_testbed.utils.test_utils.cleanup_utils import cleanup_kbyte_per_sec_rate_value +from dent_os_testbed.lib.bridge.bridge_link import BridgeLink +from dent_os_testbed.lib.ip.ip_link import IpLink +from random import randrange + +from dent_os_testbed.utils.test_utils.tgen_utils import ( + tgen_utils_get_dent_devices_with_tgen, + tgen_utils_traffic_generator_connect, + tgen_utils_dev_groups_from_config, + tgen_utils_get_traffic_stats, + tgen_utils_setup_streams, + tgen_utils_start_traffic, + tgen_utils_stop_traffic, + tgen_utils_get_loss +) + +pytestmark = [ + pytest.mark.suite_functional_port_isolation, + pytest.mark.asyncio, + pytest.mark.usefixtures('cleanup_bridges', 'cleanup_tgen') +] + + +async def test_port_isolation_interaction_br_storm_control(testbed): + """ + Test Name: test_port_isolation_interaction_br_storm_control + Test Suite: suite_functional_port_isolation + Test Overview: Verify traffic is limited on isolated ports. + Test Author: Kostiantyn Stavruk + Test Procedure: + 1. Init bridge entity br0. + 2. Set ports swp1, swp2, swp3, swp4 master br0. + 3. Set entities swp1, swp2, swp3, swp4 UP state. + 4. Set bridge br0 admin state UP. + 5. Set the first three bridge entities as isolated. + 6. Set up the following streams with random generated size of packet: + - broadcast stream on the first (isolated) TG port + - broadcast stream on the fourth (non-isolated) port + 7. Set storm control rate limit of broadcast streams. + 8. Transmit continues traffic by TG. + 9. Verify traffic sent from isolated ports that was received and limited by + storm control on a non-isolated port only. + 10. Verify traffic sent from a non-isolated port is received and limited by + storm control on all ports. + """ + + bridge = 'br0' + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 4) + if not tgen_dev or not dent_devices: + pytest.skip('The testbed does not have enough dent with tgen connections') + dent_dev = dent_devices[0] + device_host_name = dent_dev.host_name + tg_ports = tgen_dev.links_dict[device_host_name][0] + ports = tgen_dev.links_dict[device_host_name][1] + kbyte_value = [30277, 105367] + traffic_duration = 15 + deviation = 0.10 + + out = await IpLink.add( + input_data=[{device_host_name: [ + {'device': bridge, 'vlan_filtering': 1, 'type': 'bridge'}]}]) + err_msg = f"Verify that bridge created and vlan filtering set to 'ON'.\n{out}" + assert out[0][device_host_name]['rc'] == 0, err_msg + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': bridge, 'operstate': 'up'}]}]) + assert out[0][device_host_name]['rc'] == 0, f"Verify that bridge set to 'UP' state.\n{out}" + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': port, 'master': bridge, 'operstate': 'up'} for port in ports]}]) + err_msg = f"Verify that bridge entities set to 'UP' state and links enslaved to bridge.\n{out}" + assert out[0][device_host_name]['rc'] == 0, err_msg + + out = await BridgeLink.set( + input_data=[{device_host_name: [ + {'device': port, 'isolated': True} for port in ports[:3]]}]) + assert out[0][device_host_name]['rc'] == 0, f"Verify that entities set to isolated state 'ON'.\n{out}" + + await devlink_rate_value(dev=f'pci/0000:01:00.0/{ports[0].replace("swp","")}', + name='bc_kbyte_per_sec_rate ', value=kbyte_value[0], + cmode='runtime', device_host_name=device_host_name, set=True, verify=True) + await devlink_rate_value(dev=f'pci/0000:01:00.0/{ports[3].replace("swp","")}', + name='bc_kbyte_per_sec_rate ', value=kbyte_value[1], + cmode='runtime', device_host_name=device_host_name, set=True, verify=True) + + try: + address_map = ( + # swp port, tg port, tg ip, gw, plen + (ports[0], tg_ports[0], '1.1.1.2', '1.1.1.1', 24), + (ports[1], tg_ports[1], '1.1.1.3', '1.1.1.1', 24), + (ports[2], tg_ports[2], '1.1.1.4', '1.1.1.1', 24), + (ports[3], tg_ports[3], '1.1.1.5', '1.1.1.1', 24) + ) + + dev_groups = tgen_utils_dev_groups_from_config( + {'ixp': port, 'ip': ip, 'gw': gw, 'plen': plen} + for _, port, ip, gw, plen in address_map + ) + + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + """ + Set up the following streams: + — stream_1-3 — | — stream_4-6 — + swp1 -> swp4 | swp4 -> swp3 + swp1 -> swp3 | swp4 -> swp2 + swp1 -> swp2 | swp4 -> swp1 + """ + + streams = { + f'stream_{x+1}': { + 'ip_source': dev_groups[tg_ports[0]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3-x]][0]['name'], + 'srcMac': f'16:ea:c3:{x+5}d:1e:ec', + 'dstMac': 'ff:ff:ff:ff:ff:ff', + 'frameSize': randrange(100, 1500), + 'frame_rate_type': 'line_rate', + 'rate': 100, + 'protocol': '0x0800', + 'type': 'raw' + } for x in range(3) + } + streams.update({ + f'stream_{x+4}': { + 'ip_source': dev_groups[tg_ports[3]][0]['name'], + 'ip_destination': dev_groups[tg_ports[2-x]][0]['name'], + 'srcMac': f'84:fc:70:36:2a:7{x+3}', + 'dstMac': 'ff:ff:ff:ff:ff:ff', + 'frameSize': randrange(100, 1500), + 'frame_rate_type': 'line_rate', + 'rate': 100, + 'protocol': '0x0800', + 'type': 'raw' + } for x in range(3) + }) + + await tgen_utils_setup_streams(tgen_dev, config_file_name=None, streams=streams) + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(traffic_duration) + + # check the traffic stats + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Port Statistics') + await verify_expected_rx_rate(kbyte_value[0], stats, + rx_ports=[streams['stream_1']['ip_destination']], deviation=deviation) + for x in range(3): + await verify_expected_rx_rate(kbyte_value[1], stats, + rx_ports=[streams[f'stream_{x+4}']['ip_destination']], deviation=deviation) + + # check the traffic stats + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Flow Statistics') + for row in stats.Rows: + for x in range(2): + if row['Traffic Item'] == f'stream_{x+2}': + assert tgen_utils_get_loss(row) == 100.000, \ + f"Verify that traffic from {row['Tx Port']} to {row['Rx Port']} not forwarded.\n{out}" + finally: + await tgen_utils_stop_traffic(tgen_dev) + await cleanup_kbyte_per_sec_rate_value(dent_dev, tgen_dev, bc=True) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_mc_and_br_storm_control.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_mc_and_br_storm_control.py new file mode 100644 index 000000000..c3be22b4d --- /dev/null +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_mc_and_br_storm_control.py @@ -0,0 +1,163 @@ +import pytest +import asyncio + +from dent_os_testbed.test.test_suite.functional.storm_control.storm_control_utils import verify_expected_rx_rate +from dent_os_testbed.test.test_suite.functional.storm_control.storm_control_utils import devlink_rate_value +from dent_os_testbed.utils.test_utils.cleanup_utils import cleanup_kbyte_per_sec_rate_value +from dent_os_testbed.lib.bridge.bridge_link import BridgeLink +from dent_os_testbed.lib.ip.ip_link import IpLink +from random import randrange + +from dent_os_testbed.utils.test_utils.tgen_utils import ( + tgen_utils_get_dent_devices_with_tgen, + tgen_utils_traffic_generator_connect, + tgen_utils_dev_groups_from_config, + tgen_utils_get_traffic_stats, + tgen_utils_setup_streams, + tgen_utils_start_traffic, + tgen_utils_stop_traffic, + tgen_utils_get_loss +) + +pytestmark = [ + pytest.mark.suite_functional_port_isolation, + pytest.mark.asyncio, + pytest.mark.usefixtures('cleanup_bridges', 'cleanup_tgen') +] + + +async def test_port_isolation_interaction_mc_and_br_storm_control(testbed): + """ + Test Name: test_port_isolation_interaction_mc_and_br_storm_control + Test Suite: suite_functional_port_isolation + Test Overview: Verify traffic is limited on isolated ports. + Test Author: Kostiantyn Stavruk + Test Procedure: + 1. Init bridge entity br0. + 2. Set ports swp1, swp2, swp3, swp4 master br0. + 3. Set entities swp1, swp2, swp3, swp4 UP state. + 4. Set bridge br0 admin state UP. + 5. Set the first three bridge entities as isolated. + 6. Set up the following streams with random generated size of packet: + - multicast stream on the second (isolated) TG port + - broadcast stream on the third (isolated) TG port + 7. Set storm control rate limit of multicast and broadcast streams. + 8. Transmit continues traffic by TG. + 9. Verify traffic sent from isolated ports that was received and limited by + storm control on a non-isolated port only. + """ + + bridge = 'br0' + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 4) + if not tgen_dev or not dent_devices: + pytest.skip('The testbed does not have enough dent with tgen connections') + dent_dev = dent_devices[0] + device_host_name = dent_dev.host_name + tg_ports = tgen_dev.links_dict[device_host_name][0] + ports = tgen_dev.links_dict[device_host_name][1] + kbyte_value = [9042, 65394] + traffic_duration = 15 + deviation = 0.10 + + out = await IpLink.add( + input_data=[{device_host_name: [ + {'device': bridge, 'vlan_filtering': 1, 'type': 'bridge'}]}]) + err_msg = f"Verify that bridge created and vlan filtering set to 'ON'.\n{out}" + assert out[0][device_host_name]['rc'] == 0, err_msg + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': bridge, 'operstate': 'up'}]}]) + assert out[0][device_host_name]['rc'] == 0, f"Verify that bridge set to 'UP' state.\n{out}" + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': port, 'master': bridge, 'operstate': 'up'} for port in ports]}]) + err_msg = f"Verify that bridge entities set to 'UP' state and links enslaved to bridge.\n{out}" + assert out[0][device_host_name]['rc'] == 0, err_msg + + out = await BridgeLink.set( + input_data=[{device_host_name: [ + {'device': port, 'isolated': True} for port in ports[:3]]}]) + assert out[0][device_host_name]['rc'] == 0, f"Verify that entities set to isolated state 'ON'.\n{out}" + + await devlink_rate_value(dev=f'pci/0000:01:00.0/{ports[1].replace("swp","")}', + name='unreg_mc_kbyte_per_sec_rate', value=kbyte_value[0], + cmode='runtime', device_host_name=device_host_name, set=True, verify=True) + await devlink_rate_value(dev=f'pci/0000:01:00.0/{ports[2].replace("swp","")}', + name='bc_kbyte_per_sec_rate', value=kbyte_value[1], + cmode='runtime', device_host_name=device_host_name, set=True, verify=True) + + try: + address_map = ( + # swp port, tg port, tg ip, gw, plen + (ports[0], tg_ports[0], '1.1.1.2', '1.1.1.1', 24), + (ports[1], tg_ports[1], '1.1.1.3', '1.1.1.1', 24), + (ports[2], tg_ports[2], '1.1.1.4', '1.1.1.1', 24), + (ports[3], tg_ports[3], '1.1.1.5', '1.1.1.1', 24) + ) + + dev_groups = tgen_utils_dev_groups_from_config( + {'ixp': port, 'ip': ip, 'gw': gw, 'plen': plen} + for _, port, ip, gw, plen in address_map + ) + + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + """ + Set up the following streams: + — stream_1-3 — | — stream_4-6 — + swp1 -> swp4 | swp4 -> swp3 + swp1 -> swp3 | swp4 -> swp2 + swp1 -> swp2 | swp4 -> swp1 + """ + + streams = { + 'stream_1': { + 'ip_source': dev_groups[tg_ports[1]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3]][0]['name'], + 'srcIp': '147.147.96.74', + 'dstIp': '229.112.223.59', + 'srcMac': 'f2:de:a4:35:bd:4b', + 'dstMac': '01:00:5E:70:df:3b', + 'frameSize': randrange(100, 1500), + 'frame_rate_type': 'line_rate', + 'rate': 100, + 'protocol': '0x0800', + 'type': 'raw' + } + } + streams.update({ + f'stream_{x+2}': { + 'ip_source': dev_groups[tg_ports[2]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3-x if x <= 0 else 2-x]][0]['name'], + 'srcMac': f'16:ea:c3:{x+5}d:1e:ec', + 'dstMac': 'ff:ff:ff:ff:ff:ff', + 'frameSize': randrange(100, 1500), + 'frame_rate_type': 'line_rate', + 'rate': 100, + 'protocol': '0x0800', + 'type': 'raw' + } for x in range(3) + }) + + await tgen_utils_setup_streams(tgen_dev, config_file_name=None, streams=streams) + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(traffic_duration) + + # check the traffic stats + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Port Statistics') + kbyte_value = kbyte_value[0] + kbyte_value[1] + await verify_expected_rx_rate(kbyte_value, stats, + rx_ports=[streams['stream_1']['ip_destination']], deviation=deviation) + + # check the traffic stats + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Flow Statistics') + for row in stats.Rows: + for x in range(2): + if row['Traffic Item'] == f'stream_{x+3}': + assert tgen_utils_get_loss(row) == 100.000, \ + f"Verify that traffic from {row['Tx Port']} to {row['Rx Port']} not forwarded.\n{out}" + finally: + await tgen_utils_stop_traffic(tgen_dev) + await cleanup_kbyte_per_sec_rate_value(dent_dev, tgen_dev, bc=True) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_ports_inside_lag.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_ports_inside_lag.py index 92a1494bc..a65981544 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_ports_inside_lag.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_ports_inside_lag.py @@ -50,7 +50,7 @@ async def test_port_isolation_interaction_ports_inside_lag(testbed): device_host_name = dent_devices[0].host_name tg_ports = tgen_dev.links_dict[device_host_name][0] ports = tgen_dev.links_dict[device_host_name][1] - traffic_duration = 10 + traffic_duration = 15 pps_value = 1000 for x in range(2): diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_route_between_vlan_devices.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_route_between_vlan_devices.py index f026ab9b3..13cfcc6fb 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_route_between_vlan_devices.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_route_between_vlan_devices.py @@ -53,7 +53,7 @@ async def test_port_isolation_interaction_route_between_vlan_devices(testbed): device_host_name = dent_devices[0].host_name tg_ports = tgen_dev.links_dict[device_host_name][0] ports = tgen_dev.links_dict[device_host_name][1] - traffic_duration = 10 + traffic_duration = 15 pps_value = 1000 out = await IpLink.add( @@ -167,7 +167,7 @@ async def test_port_isolation_interaction_route_between_vlan_devices(testbed): await tgen_utils_stop_traffic(tgen_dev) # check the traffic stats - stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Traffic Item Statistics') + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Flow Statistics') for row in stats.Rows: assert tgen_utils_get_loss(row) == 0.000, \ f"Verify that traffic from {row['Tx Port']} to {row['Rx Port']} forwarded.\n{out}" diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_span_rule.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_span_rule.py index 263c45d61..0dcbef2d7 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_span_rule.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_span_rule.py @@ -39,11 +39,10 @@ async def test_port_isolation_interaction_span_rule(testbed): 6. Define SPAN rule with one isolated port as source port and the other isolated port as monitor port. 7. Set up stream. 8. Transmit traffic by TG. - 9. Verify all packets from the source port are copied to the monitor port. - 10. Verify traffic is not doubled (regular bridge traffic is not received on the isolated monitor port). - 11. Delete the SPAN rule. - 12. Transmit traffic by TG again. - 13. Verify that traffic is not received on the isolated port. + 9. Verify all packets from the source port are copied to the monitor port and traffic is not doubled. + 10. Delete the SPAN rule. + 11. Transmit traffic by TG again. + 12. Verify that traffic is not received on the isolated port. """ bridge = 'br0' @@ -126,10 +125,14 @@ async def test_port_isolation_interaction_span_rule(testbed): await tgen_utils_stop_traffic(tgen_dev) # check the traffic stats - stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Traffic Item Statistics') + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Flow Statistics') for row in stats.Rows: assert tgen_utils_get_loss(row) == 0.000, \ f"Verify that traffic from {row['Tx Port']} to {row['Rx Port']} forwarded.\n{out}" + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Traffic Item Statistics') + collected = {row['Traffic Item']: + {'tx_frames': row['Tx Frames'], 'rx_frames': row['Rx Frames']} for row in stats.Rows} + assert collected['bridgeStream']['tx_frames'] == collected['bridgeStream']['rx_frames'], 'Traffic is doubled.' out = await TcFilter.delete( input_data=[{device_host_name: [ @@ -141,7 +144,7 @@ async def test_port_isolation_interaction_span_rule(testbed): await tgen_utils_stop_traffic(tgen_dev) # check the traffic stats - stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Traffic Item Statistics') + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Flow Statistics') for row in stats.Rows: assert tgen_utils_get_loss(row) == 100.000, \ f"Verify that traffic from {row['Tx Port']} to {row['Rx Port']} not forwarded.\n{out}" diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_storm_control.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_storm_control.py deleted file mode 100644 index 9292f2e81..000000000 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_storm_control.py +++ /dev/null @@ -1,221 +0,0 @@ -import pytest -import random -import asyncio - -from dent_os_testbed.utils.test_utils.cleanup_utils import cleanup_kbyte_per_sec_rate_value -from dent_os_testbed.lib.devlink.devlink_port import DevlinkPort -from dent_os_testbed.lib.bridge.bridge_link import BridgeLink -from dent_os_testbed.lib.ip.ip_link import IpLink - -from dent_os_testbed.utils.test_utils.tgen_utils import ( - tgen_utils_get_dent_devices_with_tgen, - tgen_utils_traffic_generator_connect, - tgen_utils_dev_groups_from_config, - tgen_utils_clear_traffic_items, - tgen_utils_get_traffic_stats, - tgen_utils_setup_streams, - tgen_utils_start_traffic, - tgen_utils_stop_traffic, - tgen_utils_get_loss -) - -pytestmark = [ - pytest.mark.suite_functional_port_isolation, - pytest.mark.asyncio, - pytest.mark.usefixtures('cleanup_bridges', 'cleanup_tgen') -] - - -async def devlink_rate_value(dev, name, value, cmode=False, device_host_name=True, - set_and_verify=False, verify=False): - if set_and_verify: - # set kbyte_per_sec_rate value - out = await DevlinkPort.set(input_data=[{device_host_name: [ - {'dev': dev, 'name': name, 'value': value, 'cmode': cmode}]}]) - assert out[0][device_host_name]['rc'] == 0, f"Failed to set rate value '{value}'.\n{out}" - - # verify kbyte_per_sec_rate value - out = await DevlinkPort.show(input_data=[{device_host_name: [ - {'options': '-j', 'dev': dev, 'name': name}]}], parse_output=True) - err_msg = f"Failed to execute the command 'DevlinkPort.show'.\n{out}" - assert out[0][device_host_name]['rc'] == 0, err_msg - devlink_info = out[0][device_host_name]['parsed_output'] - kbyte_value = devlink_info['param'][dev][0]['values'][0]['value'] - assert kbyte_value == value, f"Verify that storm control rate configured is '{value}' kbps.\n" - - if verify: - # verify kbyte_per_sec_rate value - out = await DevlinkPort.show(input_data=[{device_host_name: [ - {'options': '-j', 'dev': dev, 'name': name}]}], parse_output=True) - err_msg = f"Failed to execute the command 'DevlinkPort.show'.\n{out}" - assert out[0][device_host_name]['rc'] == 0, err_msg - devlink_info = out[0][device_host_name]['parsed_output'] - kbyte_value = devlink_info['param'][dev][0]['values'][0]['value'] - assert kbyte_value == value, f"Verify that storm control rate configured is '{value}' kbps.\n" - - -async def test_port_isolation_interaction_storm_control(testbed): - """ - Test Name: test_port_isolation_interaction_storm_control - Test Suite: suite_functional_port_isolation - Test Overview: Verify traffic is limited on isolated ports. - Test Author: Kostiantyn Stavruk - Test Procedure: - 1. Init bridge entity br0. - 2. Set ports swp1, swp2, swp3, swp4 master br0. - 3. Set entities swp1, swp2, swp3, swp4 UP state. - 4. Set bridge br0 admin state UP. - 5. Set the first three bridge entities as isolated. - 6. Set up the following streams with random generated size of packet: - - unicast stream on the first (isolated) TG port - - multicast stream on the second (isolated) TG port - - broadcast stream on the third (isolated) TG port - - stream on the fourth (non-isolated) port - 7. Set storm control rate limit of broadcast, multicast and unknown unicast - traffic on one isolated port and one non-isolated port. - 8. Transmit traffic by TG. - 9. Verify traffic sent from isolated ports that was received and limited by - storm control on a non-isolated port only. - 10. Verify traffic sent from a non-isolated port is received and limited by - storm control on all ports. - """ - - bridge = 'br0' - tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 4) - if not tgen_dev or not dent_devices: - pytest.skip('The testbed does not have enough dent with tgen connections') - dent_dev = dent_devices[0] - device_host_name = dent_dev.host_name - tg_ports = tgen_dev.links_dict[device_host_name][0] - ports = tgen_dev.links_dict[device_host_name][1] - traffic_duration = 10 - pps_value = 1000 - - out = await IpLink.add( - input_data=[{device_host_name: [ - {'device': bridge, 'vlan_filtering': 1, 'type': 'bridge'}]}]) - err_msg = f"Verify that bridge created and vlan filtering set to 'ON'.\n{out}" - assert out[0][device_host_name]['rc'] == 0, err_msg - - out = await IpLink.set( - input_data=[{device_host_name: [ - {'device': bridge, 'operstate': 'up'}]}]) - assert out[0][device_host_name]['rc'] == 0, f"Verify that bridge set to 'UP' state.\n{out}" - - out = await IpLink.set( - input_data=[{device_host_name: [ - {'device': port, 'master': bridge, 'operstate': 'up'} for port in ports]}]) - err_msg = f"Verify that bridge entities set to 'UP' state and links enslaved to bridge.\n{out}" - assert out[0][device_host_name]['rc'] == 0, err_msg - - out = await BridgeLink.set( - input_data=[{device_host_name: [ - {'device': port, 'isolated': True} for port in ports[:3]]}]) - assert out[0][device_host_name]['rc'] == 0, f"Verify that entities set to isolated state 'ON'.\n{out}" - - await devlink_rate_value(dev='pci/0000:01:00.0/1', name='unreg_mc_kbyte_per_sec_rate', - value=30277, cmode='runtime', device_host_name=device_host_name, set_and_verify=True) - await devlink_rate_value(dev='pci/0000:01:00.0/4', name='unreg_mc_kbyte_per_sec_rate', - value=105367, cmode='runtime', device_host_name=device_host_name, set_and_verify=True) - - address_map = ( - # swp port, tg port, tg ip, gw, plen - (ports[0], tg_ports[0], '1.1.1.2', '1.1.1.1', 24), - (ports[1], tg_ports[1], '1.1.1.3', '1.1.1.1', 24), - (ports[2], tg_ports[2], '1.1.1.4', '1.1.1.1', 24), - (ports[3], tg_ports[3], '1.1.1.5', '1.1.1.1', 24) - ) - - dev_groups = tgen_utils_dev_groups_from_config( - {'ixp': port, 'ip': ip, 'gw': gw, 'plen': plen} - for _, port, ip, gw, plen in address_map - ) - - await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) - - """ - Set up the following streams: - — stream_0 — | — stream_1 — | — stream_2 — | — stream_3 — - swp1 -> swp4 | swp2 -> swp4 | swp3 -> swp4 | swp4 -> swp3 - - — stream_0 — | — stream_1 — | — stream_2 — | — stream_3 — - swp1 -> swp3 | swp2 -> swp3 | swp3 -> swp2 | swp4 -> swp2 - - — stream_0 — | — stream_1 — | — stream_2 — | — stream_3 — - swp1 -> swp2 | swp2 -> swp1 | swp3 -> swp1 | swp4 -> swp1 - """ - - for x in range(3): - streams = { - 'stream_0': { - 'ip_source': dev_groups[tg_ports[0]][0]['name'], - 'ip_destination': dev_groups[tg_ports[3-x]][0]['name'], - 'srcMac': '28:be:0d:47:eb:2b', - 'dstMac': '1c:99:9f:fb:63:15', - 'frameSize': random.randint(128, 512), - 'rate': pps_value, - 'protocol': '0x0800', - 'type': 'raw' - }, - 'stream_1': { - 'ip_source': dev_groups[tg_ports[1]][0]['name'], - 'ip_destination': dev_groups[tg_ports[3-x if x < 2 else 0]][0]['name'], - 'srcIp': '147.147.96.74', - 'dstIp': '229.112.223.59', - 'srcMac': 'f2:de:a4:35:bd:4b', - 'dstMac': '01:00:5E:70:df:3b', - 'frameSize': random.randint(128, 512), - 'rate': pps_value, - 'protocol': '0x0800', - 'type': 'raw' - }, - 'stream_2': { - 'ip_source': dev_groups[tg_ports[2]][0]['name'], - 'ip_destination': dev_groups[tg_ports[3-x if x <= 0 else 2-x]][0]['name'], - 'srcMac': '2a:3e:13:88:e5:6d', - 'dstMac': 'ff:ff:ff:ff:ff:ff', - 'frameSize': random.randint(128, 512), - 'rate': pps_value, - 'protocol': '0x0800', - 'type': 'raw' - }, - 'stream_3': { - 'ip_source': dev_groups[tg_ports[3]][0]['name'], - 'ip_destination': dev_groups[tg_ports[2-x]][0]['name'], - 'srcMac': 'ea:f9:ca:10:1d:b6', - 'dstMac': 'ba:2e:c5:2c:fa:8d', - 'frameSize': random.randint(128, 512), - 'rate': pps_value, - 'protocol': '0x0800', - 'type': 'raw' - } - } - - await tgen_utils_setup_streams(tgen_dev, config_file_name=None, streams=streams) - await tgen_utils_start_traffic(tgen_dev) - await asyncio.sleep(traffic_duration) - await tgen_utils_stop_traffic(tgen_dev) - - if x == 0: - expected_loss = { - 'stream_0': 0, - 'stream_1': 0, - 'stream_2': 0, - 'stream_3': 0 - } - else: - expected_loss = { - 'stream_0': 100, - 'stream_1': 100, - 'stream_2': 100, - 'stream_3': 0 - } - # check the traffic stats - stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Flow Statistics') - for row in stats.Rows: - assert tgen_utils_get_loss(row) == expected_loss[row['Traffic Item']], \ - 'Verify that traffic is forwarded/not forwarded in accordance.' - - await tgen_utils_clear_traffic_items(tgen_dev) - - await cleanup_kbyte_per_sec_rate_value(dent_dev, unreg_mc=True) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_unk_uc_storm_control.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_unk_uc_storm_control.py new file mode 100644 index 000000000..ff2c54f05 --- /dev/null +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_interaction_unk_uc_storm_control.py @@ -0,0 +1,165 @@ +import pytest +import asyncio + +from dent_os_testbed.test.test_suite.functional.storm_control.storm_control_utils import verify_expected_rx_rate +from dent_os_testbed.test.test_suite.functional.storm_control.storm_control_utils import devlink_rate_value +from dent_os_testbed.utils.test_utils.cleanup_utils import cleanup_kbyte_per_sec_rate_value +from dent_os_testbed.lib.bridge.bridge_link import BridgeLink +from dent_os_testbed.lib.ip.ip_link import IpLink +from random import randrange + +from dent_os_testbed.utils.test_utils.tgen_utils import ( + tgen_utils_get_dent_devices_with_tgen, + tgen_utils_traffic_generator_connect, + tgen_utils_dev_groups_from_config, + tgen_utils_get_traffic_stats, + tgen_utils_setup_streams, + tgen_utils_start_traffic, + tgen_utils_stop_traffic, + tgen_utils_get_loss +) + +pytestmark = [ + pytest.mark.suite_functional_port_isolation, + pytest.mark.asyncio, + pytest.mark.usefixtures('cleanup_bridges', 'cleanup_tgen') +] + + +async def test_port_isolation_interaction_unk_uc_storm_control(testbed): + """ + Test Name: test_port_isolation_interaction_unk_uc_storm_control + Test Suite: suite_functional_port_isolation + Test Overview: Verify traffic is limited on isolated ports. + Test Author: Kostiantyn Stavruk + Test Procedure: + 1. Init bridge entity br0. + 2. Set ports swp1, swp2, swp3, swp4 master br0. + 3. Set entities swp1, swp2, swp3, swp4 UP state. + 4. Set bridge br0 admin state UP. + 5. Set the first three bridge entities as isolated. + 6. Set up the following streams with random generated size of packet: + - unknown unicast stream on the first (isolated) TG port + - stream on the fourth (non-isolated) port + 7. Set storm control rate limit of unknown unicast stream. + 8. Transmit continues traffic by TG. + 9. Verify traffic sent from isolated ports that was received and limited by + storm control on a non-isolated port only. + 10. Verify traffic sent from a non-isolated port is received and limited by + storm control on all ports. + """ + + bridge = 'br0' + tgen_dev, dent_devices = await tgen_utils_get_dent_devices_with_tgen(testbed, [], 4) + if not tgen_dev or not dent_devices: + pytest.skip('The testbed does not have enough dent with tgen connections') + dent_dev = dent_devices[0] + device_host_name = dent_dev.host_name + tg_ports = tgen_dev.links_dict[device_host_name][0] + ports = tgen_dev.links_dict[device_host_name][1] + kbyte_value = [15277, 97367] + traffic_duration = 15 + deviation = 0.10 + + out = await IpLink.add( + input_data=[{device_host_name: [ + {'device': bridge, 'vlan_filtering': 1, 'type': 'bridge'}]}]) + err_msg = f"Verify that bridge created and vlan filtering set to 'ON'.\n{out}" + assert out[0][device_host_name]['rc'] == 0, err_msg + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': bridge, 'operstate': 'up'}]}]) + assert out[0][device_host_name]['rc'] == 0, f"Verify that bridge set to 'UP' state.\n{out}" + + out = await IpLink.set( + input_data=[{device_host_name: [ + {'device': port, 'master': bridge, 'operstate': 'up'} for port in ports]}]) + err_msg = f"Verify that bridge entities set to 'UP' state and links enslaved to bridge.\n{out}" + assert out[0][device_host_name]['rc'] == 0, err_msg + + out = await BridgeLink.set( + input_data=[{device_host_name: [ + {'device': port, 'isolated': True} for port in ports[:3]]}]) + assert out[0][device_host_name]['rc'] == 0, f"Verify that entities set to isolated state 'ON'.\n{out}" + + await devlink_rate_value(dev=f'pci/0000:01:00.0/{ports[0].replace("swp","")}', + name='unk_uc_kbyte_per_sec_rate', value=kbyte_value[0], + cmode='runtime', device_host_name=device_host_name, set=True, verify=True) + await devlink_rate_value(dev=f'pci/0000:01:00.0/{ports[3].replace("swp","")}', + name='unk_uc_kbyte_per_sec_rate', value=kbyte_value[1], + cmode='runtime', device_host_name=device_host_name, set=True, verify=True) + + try: + address_map = ( + # swp port, tg port, tg ip, gw, plen + (ports[0], tg_ports[0], '1.1.1.2', '1.1.1.1', 24), + (ports[1], tg_ports[1], '1.1.1.3', '1.1.1.1', 24), + (ports[2], tg_ports[2], '1.1.1.4', '1.1.1.1', 24), + (ports[3], tg_ports[3], '1.1.1.5', '1.1.1.1', 24) + ) + + dev_groups = tgen_utils_dev_groups_from_config( + {'ixp': port, 'ip': ip, 'gw': gw, 'plen': plen} + for _, port, ip, gw, plen in address_map + ) + + await tgen_utils_traffic_generator_connect(tgen_dev, tg_ports, ports, dev_groups) + + """ + Set up the following streams: + — stream_1-3 — | — stream_4-6 — + swp1 -> swp4 | swp4 -> swp3 + swp1 -> swp3 | swp4 -> swp2 + swp1 -> swp2 | swp4 -> swp1 + """ + + streams = { + f'stream_{x+1}': { + 'ip_source': dev_groups[tg_ports[0]][0]['name'], + 'ip_destination': dev_groups[tg_ports[3-x]][0]['name'], + 'srcMac': f'16:ea:c3:{x+5}d:1e:ec', + 'dstMac': f'3e:f4:7a:e8:10:3{x+1}', + 'frameSize': randrange(100, 1500), + 'frame_rate_type': 'line_rate', + 'rate': 100, + 'protocol': '0x0800', + 'type': 'raw' + } for x in range(3) + } + streams.update({ + f'stream_{x+4}': { + 'ip_source': dev_groups[tg_ports[3]][0]['name'], + 'ip_destination': dev_groups[tg_ports[2-x]][0]['name'], + 'srcMac': f'84:fc:70:36:2a:7{x+3}', + 'dstMac': f'68:16:3d:2e:b4:c{x+5}', + 'frameSize': randrange(100, 1500), + 'frame_rate_type': 'line_rate', + 'rate': 100, + 'protocol': '0x0800', + 'type': 'raw' + } for x in range(3) + }) + + await tgen_utils_setup_streams(tgen_dev, config_file_name=None, streams=streams) + await tgen_utils_start_traffic(tgen_dev) + await asyncio.sleep(traffic_duration) + + # check the traffic stats + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Port Statistics') + await verify_expected_rx_rate(kbyte_value[0], stats, + rx_ports=[streams['stream_1']['ip_destination']], deviation=deviation) + for x in range(3): + await verify_expected_rx_rate(kbyte_value[1], stats, + rx_ports=[streams[f'stream_{x+4}']['ip_destination']], deviation=deviation) + + # check the traffic stats + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Flow Statistics') + for row in stats.Rows: + for x in range(2): + if row['Traffic Item'] == f'stream_{x+2}': + assert tgen_utils_get_loss(row) == 100.000, \ + f"Verify that traffic from {row['Tx Port']} to {row['Rx Port']} not forwarded.\n{out}" + finally: + await tgen_utils_stop_traffic(tgen_dev) + await cleanup_kbyte_per_sec_rate_value(dent_dev, tgen_dev, bc=True) diff --git a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_maximum_isolated_ports_on_bridge.py b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_maximum_isolated_ports_on_bridge.py index d4dfe57b7..dbb335e5a 100644 --- a/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_maximum_isolated_ports_on_bridge.py +++ b/DentOS_Framework/DentOsTestbed/src/dent_os_testbed/test/test_suite/functional/port_isolation/test_port_isolation_maximum_isolated_ports_on_bridge.py @@ -52,7 +52,7 @@ async def test_port_isolation_maximum_isolated_ports_on_bridge(testbed): device_host_name = dent_dev.host_name tg_ports = tgen_dev.links_dict[device_host_name][0] ports = tgen_dev.links_dict[device_host_name][1] - traffic_duration = 10 + traffic_duration = 15 pps_value = 1000 timeout = 30 @@ -152,7 +152,7 @@ async def test_port_isolation_maximum_isolated_ports_on_bridge(testbed): await asyncio.sleep(traffic_duration) # check the traffic stats - stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Traffic Item Statistics') + stats = await tgen_utils_get_traffic_stats(tgen_dev, 'Flow Statistics') for row in stats.Rows: assert tgen_utils_get_loss(row) == 100.000, \ f"Verify that traffic from {row['Tx Port']} to {row['Rx Port']} not forwarded.\n{out}" From 2d8049ee0749ad7416643895b918968068369d84 Mon Sep 17 00:00:00 2001 From: Andriy Lozovyy <124686118+AndriyLozovyyPLV@users.noreply.github.com> Date: Fri, 5 May 2023 07:25:40 +0300 Subject: [PATCH 9/9] Add support to set etherType (#316) Signed-off-by: Andriy Lozovyy --- .../lib/traffic/ixnetwork/ixnetwork_ixia_client_impl.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DentOS_Framework/DentOsTestbedLib/src/dent_os_testbed/lib/traffic/ixnetwork/ixnetwork_ixia_client_impl.py b/DentOS_Framework/DentOsTestbedLib/src/dent_os_testbed/lib/traffic/ixnetwork/ixnetwork_ixia_client_impl.py index cad776155..6a393587f 100644 --- a/DentOS_Framework/DentOsTestbedLib/src/dent_os_testbed/lib/traffic/ixnetwork/ixnetwork_ixia_client_impl.py +++ b/DentOS_Framework/DentOsTestbedLib/src/dent_os_testbed/lib/traffic/ixnetwork/ixnetwork_ixia_client_impl.py @@ -391,6 +391,9 @@ def __configure_l2_stack(self, config_element, pkt_data, track_by): if 'srcMac' in pkt_data: self.__update_field(eth_stack.Field.find(FieldTypeId='ethernet.header.sourceAddress'), pkt_data['srcMac']) + if 'protocol' in pkt_data and pkt_data['protocol'] not in ['ipv6', 'ipv4', 'ip', '802.1Q']: + self.__update_field(eth_stack.Field.find(FieldTypeId='ethernet.header.etherType'), + pkt_data['protocol']) if 'vlanID' in pkt_data: vlan_stack = config_element.Stack.read( eth_stack.AppendProtocol(self.stack_template['vlan'])