From 01108a349df4e87a2422c3082f1b62eb42ff9849 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Wed, 24 Jul 2019 18:10:17 +0300 Subject: [PATCH 01/26] [tests/conftest.py] override pytest-ansible fixtures to overcome scope issues There are two issues with pytest-ansible I found: - ansible_adhoc is limited to function scope which makes it not usable for wider scope test fixtures - localhost fixture has some hidden dependency on ansible_adhoc (even without changing default scope) e.g.: def test_x(ansible_adhoc, testbed): duthost = AnsibleHost(ansible_adhoc, testbed['dut']) pass def test_y(ansible_adhoc, localhost, testbed): pass This snippet of test code fails to evaluate localhost fixture in test_y: test_x.py::test_x PASSED [ 50%] test_x.py::test_y ERROR [100%] Part of error log: host = self.get_host(hostname) if host is None: > raise AnsibleError("no host vars as host is not in inventory: %s" % hostname) E AnsibleError: ERROR! no host vars as host is not in inventory: arc-switch1025 Signed-off-by: Stepan Blyschak --- tests/conftest.py | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e50ffe29181..67ad83f6589 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,13 +2,15 @@ import csv import ipaddr as ipaddress -class TestbedInfo(): - ''' + +class TestbedInfo(object): + """ Parse the CSV file used to describe whole testbed info Please refer to the example of the CSV file format CSV file first line is title The topology name in title is using uniq-name | conf-name - ''' + """ + def __init__(self, testbed_file): self.testbed_filename = testbed_file self.testbed_topo = {} @@ -33,16 +35,51 @@ def __init__(self, testbed_file): if name: self.testbed_topo[name] = tb_prop + def pytest_addoption(parser): parser.addoption("--testbed", action="store", default=None, help="testbed name") parser.addoption("--testbed_file", action="store", default=None, help="testbed file name") + @pytest.fixture(scope="session") def testbed(request): + """ + Create and return testbed information + """ tbname = request.config.getoption("--testbed") tbfile = request.config.getoption("--testbed_file") - if tbname == None or tbfile == None: + if tbname is None or tbfile is None: raise ValueError("testbed and testbed_file are required!") tbinfo = TestbedInfo(tbfile) return tbinfo.testbed_topo[tbname] + + +# Here we override ansible_adhoc fixture from pytest-ansible plugin to overcome +# scope limitation issue; since we want to be able to use ansible_adhoc in module/class scope +# fixtures we have to override the scope here in global conftest.py +# Let's have it with module scope for now, so if something really breaks next test module run will have +# this fixture reevaluated +@pytest.fixture(scope='module') +def ansible_adhoc(request): + """Return an inventory initialization method.""" + plugin = request.config.pluginmanager.getplugin("ansible") + + def init_host_mgr(**kwargs): + return plugin.initialize(request.config, request, **kwargs) + return init_host_mgr + + +# Same as for ansible_adhoc, let's have localhost fixture with session scope +# as it feels that during session run the localhost object should persist unchanged. +# Also, we have autouse=True here to force pytest to evaluate localhost fixture to overcome +# some hidden dependency between localhost and ansible_adhoc (even with default scope) (FIXME) +@pytest.fixture(scope='session', autouse=True) +def localhost(request): + """Return a host manager representing localhost.""" + # NOTE: Do not use ansible_adhoc as a dependent fixture since that will assert specific command-line parameters have + # been supplied. In the case of localhost, the parameters are provided as kwargs below. + plugin = request.config.pluginmanager.getplugin("ansible") + return plugin.initialize(request.config, request, inventory='localhost,', connection='local', + host_pattern='localhost').localhost + From 44a3c2ddb71a0b8d3cdbeba007232c4addcb2782 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Wed, 24 Jul 2019 18:21:44 +0300 Subject: [PATCH 02/26] [pytest-infra] introduce PtfTestAdapter and ptfadapter fixture to control PTF traffic from sonic-mgmt node Signed-off-by: Stepan Blyschak --- tests/conftest.py | 3 + tests/ptf_fixtures.py | 67 ++++++++++++++ tests/ptfadapter/__init__.py | 3 + tests/ptfadapter/ptfadapter.py | 88 +++++++++++++++++++ .../templates/ptf_nn_agent.conf.ptf.j2 | 11 +++ 5 files changed, 172 insertions(+) create mode 100644 tests/ptf_fixtures.py create mode 100644 tests/ptfadapter/__init__.py create mode 100644 tests/ptfadapter/ptfadapter.py create mode 100644 tests/ptfadapter/templates/ptf_nn_agent.conf.ptf.j2 diff --git a/tests/conftest.py b/tests/conftest.py index 67ad83f6589..dceb3dd6b7c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,9 @@ import ipaddr as ipaddress +pytest_plugins = ('ptf_fixtures',) + + class TestbedInfo(object): """ Parse the CSV file used to describe whole testbed info diff --git a/tests/ptf_fixtures.py b/tests/ptf_fixtures.py new file mode 100644 index 00000000000..c389cfbe4fa --- /dev/null +++ b/tests/ptf_fixtures.py @@ -0,0 +1,67 @@ +"""This module provides ptfadapter fixture to be used by tests to send/receive traffic via PTF ports""" + +import pytest + +from ptfadapter import PtfTestAdapter +from ansible_host import ansible_host + +DEFAULT_PTF_NN_PORT = 10900 +DEFAULT_DEVICE_NUM = 0 +ETH_PFX = 'eth' + + +def get_ifaces(netdev_output): + """ parse /proc/net/dev content + :param netdev_output: content of /proc/net/dev + :return: interface names list + """ + + ifaces = [] + for line in netdev_output.split('\n'): + # Skip a header + if ':' not in line: + continue + + iface = line.split(':')[0].strip() + + # Skip not FP interfaces + if ETH_PFX not in iface: + continue + + ifaces.append(iface) + + return ifaces + + +@pytest.fixture(scope='module') +def ptfadapter(ansible_adhoc, testbed): + """return ptf test adapter object. + The fixture is module scope, because usually there is not need to + restart PTF nn agent and reinitialize data plane thread on every + test class or test function/method. Session scope should also be Ok, + however if something goes really wrong in one test module it is safer + to restart PTF before proceeding running other test modules + """ + + ptfhost = ansible_host(ansible_adhoc, testbed['ptf']) + # get the eth interfaces from PTF and initialize ifaces_map + res = ptfhost.command('cat /proc/net/dev') + ifaces = get_ifaces(res['stdout']) + ifaces_map = {int(ifname.replace(ETH_PFX, '')): ifname for ifname in ifaces} + + # generate supervisor configuration for ptf_nn_agent + ptfhost.host.options['variable_manager'].extra_vars = { + 'device_num': DEFAULT_DEVICE_NUM, + 'ptf_nn_port': DEFAULT_PTF_NN_PORT, + 'ifaces_map': ifaces_map, + } + ptfhost.template(src='ptfadapter/templates/ptf_nn_agent.conf.ptf.j2', + dest='/etc/supervisor/conf.d/ptf_nn_agent.conf') + + # reread configuration and update supervisor + ptfhost.command('supervisorctl reread') + ptfhost.command('supervisorctl update') + + with PtfTestAdapter(testbed['ptf_ip'], DEFAULT_PTF_NN_PORT, 0, len(ifaces_map)) as adapter: + yield adapter + diff --git a/tests/ptfadapter/__init__.py b/tests/ptfadapter/__init__.py new file mode 100644 index 00000000000..1803738798b --- /dev/null +++ b/tests/ptfadapter/__init__.py @@ -0,0 +1,3 @@ +from ptfadapter import PtfTestAdapter + +__all__ = ['PtfTestAdapter'] \ No newline at end of file diff --git a/tests/ptfadapter/ptfadapter.py b/tests/ptfadapter/ptfadapter.py new file mode 100644 index 00000000000..113f5e4bfe4 --- /dev/null +++ b/tests/ptfadapter/ptfadapter.py @@ -0,0 +1,88 @@ +import ptf +import ptf.base_tests as base_tests +import ptf.platforms.nn as nn +import ptf.dataplane as dataplane +import ptf.ptfutils as ptfutils + + +class PtfTestAdapter(base_tests.BaseTest): + """PtfTestAdapater class provides interface for pytest to use ptf.testutils functions """ + + DEFAULT_PTF_TIMEOUT = 2 + DEFAULT_PTF_NEG_TIMEOUT = 0.1 + + def __init__(self, ptf_ip, ptf_nn_port, device_num, ptf_ports_num): + """ initialize PtfTestAdapter + :param ptf_ip: PTF host IP + :param ptf_nn_port: PTF nanomessage agent port + :param device_num: device number + :param ptf_ports_num: PTF ports count + :return: + """ + self.runTest = lambda : None # set a no op runTest attribute to satisfy BaseTest interface + super(PtfTestAdapter, self).__init__() + self._init_ptf_dataplane(ptf_ip, ptf_nn_port, device_num, ptf_ports_num) + + def __enter__(self): + """ enter in 'with' block """ + + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """ exit from 'with' block """ + + self.kill() + + def _init_ptf_dataplane(self, ptf_ip, ptf_nn_port, device_num, ptf_ports_num, ptf_config=None): + """ + initialize ptf framework and establish connection to ptf_nn_agent + running on PTF host + :param ptf_ip: PTF host IP + :param ptf_nn_port: PTF nanomessage agent port + :param device_num: device number + :param ptf_ports_num: PTF ports count + :return: + """ + self.ptf_ip = ptf_ip + self.ptf_nn_port = ptf_nn_port + self.device_num = device_num + self.ptf_ports_num = ptf_ports_num + + ptfutils.default_timeout = self.DEFAULT_PTF_TIMEOUT + ptfutils.default_negative_timeout = self.DEFAULT_PTF_NEG_TIMEOUT + ptf.config = { + 'platform': 'nn', + 'device_sockets': [ + (device_num, range(ptf_ports_num), 'tcp://{}:{}'.format(ptf_ip, ptf_nn_port)) + ], + 'relax': True, + } + if ptf_config is not None: + ptf.config.update(ptf_config) + + # update ptf.config based on NN platform and create dataplane instance + nn.platform_config_update(ptf.config) + ptf.dataplane_instance = dataplane.DataPlane(config=ptf.config) + + # TODO: in case of multi PTF hosts topologies we'll have to manually call port_add on + # ptf.dataplane_instance to specify mapping between tcp://: and port tuple (device_id, port_id) + for id, ifname in ptf.config['port_map'].items(): + device_id, port_id = id + ptf.dataplane_instance.port_add(ifname, device_id, port_id) + + self.dataplane = ptf.dataplane_instance + + def kill(self): + """ kill data plane thread """ + self.dataplane.kill() + + def reinit(self, ptf_config=None): + """ reinitialize ptf data plane thread. + In case if test changes PTF host network configuration (like MAC change on interfaces) + reinit() method has to be called to restart data plane thread. + Also if test wants to restart PTF data plane specifying non-default PTF configuration + :param ptf_config: PTF configuration dictionary + """ + self.kill() + self._init_ptf_dataplane(self.ptf_ip, self.ptf_nn_port, self.device_num, self.ptf_ports_num, ptf_config) + diff --git a/tests/ptfadapter/templates/ptf_nn_agent.conf.ptf.j2 b/tests/ptfadapter/templates/ptf_nn_agent.conf.ptf.j2 new file mode 100644 index 00000000000..d0a4ffbe5df --- /dev/null +++ b/tests/ptfadapter/templates/ptf_nn_agent.conf.ptf.j2 @@ -0,0 +1,11 @@ +[program:ptf_nn_agent] +command=/usr/bin/python /opt/ptf_nn_agent.py --device-socket {{ device_num }}@tcp://0.0.0.0:{{ ptf_nn_port }} {% for id in ifaces_map -%} -i {{ device_num }}-{{ id }}@{{ ifaces_map[id] }} {% endfor %} + +process_name=ptf_nn_agent +stdout_logfile=/tmp/ptf_nn_agent.out.log +stderr_logfile=/tmp/ptf_nn_agent.err.log +redirect_stderr=false +autostart=true +autorestart=true +startsecs=1 +numprocs=1 \ No newline at end of file From 528ef72f50375b6fd8833a3b3d9a9b9efc01cb43 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 12:59:13 +0300 Subject: [PATCH 03/26] [ptfadapter] add default ptf config Signed-off-by: Stepan Blyschak --- tests/ptfadapter/__init__.py | 14 ++++++++++++++ tests/ptfadapter/ptfadapter.py | 14 +++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/tests/ptfadapter/__init__.py b/tests/ptfadapter/__init__.py index 1803738798b..4a70d9f3f42 100644 --- a/tests/ptfadapter/__init__.py +++ b/tests/ptfadapter/__init__.py @@ -1,3 +1,17 @@ +import ptf + +# config ptf with basic parameters before any other ptf module import +ptf.config = { + 'relax': True, + 'disable_ipv6': False, + 'disable_vxlan': False, + 'disable_erspan': False, + 'disable_geneve': False, + 'disable_mpls': False, + 'disable_nvgre': False, + 'qlen': 100000, +} + from ptfadapter import PtfTestAdapter __all__ = ['PtfTestAdapter'] \ No newline at end of file diff --git a/tests/ptfadapter/ptfadapter.py b/tests/ptfadapter/ptfadapter.py index 113f5e4bfe4..197fb8804a9 100644 --- a/tests/ptfadapter/ptfadapter.py +++ b/tests/ptfadapter/ptfadapter.py @@ -1,11 +1,11 @@ import ptf -import ptf.base_tests as base_tests +from ptf.base_tests import BaseTest +from ptf.dataplane import DataPlane import ptf.platforms.nn as nn -import ptf.dataplane as dataplane import ptf.ptfutils as ptfutils -class PtfTestAdapter(base_tests.BaseTest): +class PtfTestAdapter(BaseTest): """PtfTestAdapater class provides interface for pytest to use ptf.testutils functions """ DEFAULT_PTF_TIMEOUT = 2 @@ -50,19 +50,19 @@ def _init_ptf_dataplane(self, ptf_ip, ptf_nn_port, device_num, ptf_ports_num, pt ptfutils.default_timeout = self.DEFAULT_PTF_TIMEOUT ptfutils.default_negative_timeout = self.DEFAULT_PTF_NEG_TIMEOUT - ptf.config = { + + ptf.config.update({ 'platform': 'nn', 'device_sockets': [ (device_num, range(ptf_ports_num), 'tcp://{}:{}'.format(ptf_ip, ptf_nn_port)) ], - 'relax': True, - } + }) if ptf_config is not None: ptf.config.update(ptf_config) # update ptf.config based on NN platform and create dataplane instance nn.platform_config_update(ptf.config) - ptf.dataplane_instance = dataplane.DataPlane(config=ptf.config) + ptf.dataplane_instance = DataPlane(config=ptf.config) # TODO: in case of multi PTF hosts topologies we'll have to manually call port_add on # ptf.dataplane_instance to specify mapping between tcp://: and port tuple (device_id, port_id) From 1c6cd99ee26f864b3efad91079244ebd51379e02 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 13:00:48 +0300 Subject: [PATCH 04/26] [conftest] move overridden ansible fixture to seperate file Signed-off-by: Stepan Blyschak --- tests/ansible_fixtures.py | 31 +++++++++++++++++++++++++++++++ tests/ansible_host.py | 11 ++++++----- tests/conftest.py | 30 +----------------------------- 3 files changed, 38 insertions(+), 34 deletions(-) create mode 100644 tests/ansible_fixtures.py diff --git a/tests/ansible_fixtures.py b/tests/ansible_fixtures.py new file mode 100644 index 00000000000..ac31dd27012 --- /dev/null +++ b/tests/ansible_fixtures.py @@ -0,0 +1,31 @@ +""" This module provides few pytest-ansible fixtures overridden """ + +import pytest + +# Here we override ansible_adhoc fixture from pytest-ansible plugin to overcome +# scope limitation issue; since we want to be able to use ansible_adhoc in module/class scope +# fixtures we have to override the scope here in global conftest.py +# Let's have it with module scope for now, so if something really breaks next test module run will have +# this fixture reevaluated +@pytest.fixture(scope='module') +def ansible_adhoc(request): + """Return an inventory initialization method.""" + plugin = request.config.pluginmanager.getplugin("ansible") + + def init_host_mgr(**kwargs): + return plugin.initialize(request.config, request, **kwargs) + return init_host_mgr + + +# Same as for ansible_adhoc, let's have localhost fixture with session scope +# as it feels that during session run the localhost object should persist unchanged. +# Also, we have autouse=True here to force pytest to evaluate localhost fixture to overcome +# some hidden dependency between localhost and ansible_adhoc (even with default scope) (FIXME) +@pytest.fixture(scope='session', autouse=True) +def localhost(request): + """Return a host manager representing localhost.""" + # NOTE: Do not use ansible_adhoc as a dependent fixture since that will assert specific command-line parameters have + # been supplied. In the case of localhost, the parameters are provided as kwargs below. + plugin = request.config.pluginmanager.getplugin("ansible") + return plugin.initialize(request.config, request, inventory='localhost,', connection='local', + host_pattern='localhost').localhost diff --git a/tests/ansible_host.py b/tests/ansible_host.py index 864a66d195f..cb272b15f0b 100644 --- a/tests/ansible_host.py +++ b/tests/ansible_host.py @@ -16,9 +16,10 @@ def __init__(self, msg, results=None): def __str__(self): return "{}\nAnsible Results => {}".format(self.message, dump_ansible_results(self.results)) -class ansible_host(): - - def __init__(self, ansible_adhoc, hostname, is_local = False): +class ansible_host(object): + """ wrapper for ansible host object """ + + def __init__(self, ansible_adhoc, hostname, is_local=False): if is_local: self.host = ansible_adhoc(inventory='localhost', connection='local')[hostname] else: @@ -28,11 +29,11 @@ def __init__(self, ansible_adhoc, hostname, is_local = False): def __getattr__(self, item): self.module_name = item self.module = getattr(self.host, item) - + return self._run def _run(self, *module_args, **complex_args): - + module_ignore_errors = complex_args.pop('module_ignore_errors', False) res = self.module(*module_args, **complex_args)[self.hostname] diff --git a/tests/conftest.py b/tests/conftest.py index dceb3dd6b7c..1388155d356 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,7 @@ import ipaddr as ipaddress -pytest_plugins = ('ptf_fixtures',) +pytest_plugins = ('ptf_fixtures', 'ansible_fixtures') class TestbedInfo(object): @@ -58,31 +58,3 @@ def testbed(request): return tbinfo.testbed_topo[tbname] -# Here we override ansible_adhoc fixture from pytest-ansible plugin to overcome -# scope limitation issue; since we want to be able to use ansible_adhoc in module/class scope -# fixtures we have to override the scope here in global conftest.py -# Let's have it with module scope for now, so if something really breaks next test module run will have -# this fixture reevaluated -@pytest.fixture(scope='module') -def ansible_adhoc(request): - """Return an inventory initialization method.""" - plugin = request.config.pluginmanager.getplugin("ansible") - - def init_host_mgr(**kwargs): - return plugin.initialize(request.config, request, **kwargs) - return init_host_mgr - - -# Same as for ansible_adhoc, let's have localhost fixture with session scope -# as it feels that during session run the localhost object should persist unchanged. -# Also, we have autouse=True here to force pytest to evaluate localhost fixture to overcome -# some hidden dependency between localhost and ansible_adhoc (even with default scope) (FIXME) -@pytest.fixture(scope='session', autouse=True) -def localhost(request): - """Return a host manager representing localhost.""" - # NOTE: Do not use ansible_adhoc as a dependent fixture since that will assert specific command-line parameters have - # been supplied. In the case of localhost, the parameters are provided as kwargs below. - plugin = request.config.pluginmanager.getplugin("ansible") - return plugin.initialize(request.config, request, inventory='localhost,', connection='local', - host_pattern='localhost').localhost - From 448b4e06aee4a7eff1019b1f2032cd52cb5aa1db Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 13:03:34 +0300 Subject: [PATCH 05/26] [ptf_runner] rename ptf.py to ptf_runner.py to avoid conflict with ptf framework Signed-off-by: Stepan Blyschak --- tests/{ptf.py => ptf_runner.py} | 0 tests/test_bgp_speaker.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/{ptf.py => ptf_runner.py} (100%) diff --git a/tests/ptf.py b/tests/ptf_runner.py similarity index 100% rename from tests/ptf.py rename to tests/ptf_runner.py diff --git a/tests/test_bgp_speaker.py b/tests/test_bgp_speaker.py index 8e165d3cf4e..5195581aa14 100644 --- a/tests/test_bgp_speaker.py +++ b/tests/test_bgp_speaker.py @@ -3,7 +3,7 @@ import time import ipaddress from ansible_host import ansible_host -from ptf import ptf_runner +from ptf_runner import ptf_runner def generate_ips(num, prefix, exclude_ips): """ From 676041aec18d9d703b4fbfb1b2f626da2208464f Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 13:29:49 +0300 Subject: [PATCH 06/26] [gitignore] update with cpython compiled files Signed-off-by: Stepan Blyschak --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 16ed2fad4c0..aae273b6f86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ # Compiled Python files -ansible/plugins/filter/*.pyc +*.pyc +__pycache__/ From 96de109721554780efe5947cefab3c6ec9403594 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 13:33:34 +0300 Subject: [PATCH 07/26] [tests/fdb] rewrite fdb test with ptfadapter & fix change_mac.sh script Signed-off-by: Stepan Blyschak --- tests/fdb/__init__.py | 0 tests/fdb/change_mac.sh | 10 -- tests/fdb/conftest.py | 0 tests/fdb/fdb.j2 | 3 - tests/fdb/test_fdb.py | 139 ++++++++++++++++++++++++++++ tests/scripts/change_mac.sh | 13 +++ tests/{fdb => scripts}/remove_ip.sh | 0 tests/test_fdb.py | 79 ---------------- 8 files changed, 152 insertions(+), 92 deletions(-) create mode 100644 tests/fdb/__init__.py delete mode 100755 tests/fdb/change_mac.sh create mode 100644 tests/fdb/conftest.py delete mode 100644 tests/fdb/fdb.j2 create mode 100644 tests/fdb/test_fdb.py create mode 100755 tests/scripts/change_mac.sh rename tests/{fdb => scripts}/remove_ip.sh (100%) delete mode 100644 tests/test_fdb.py diff --git a/tests/fdb/__init__.py b/tests/fdb/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fdb/change_mac.sh b/tests/fdb/change_mac.sh deleted file mode 100755 index 3f8b9869faf..00000000000 --- a/tests/fdb/change_mac.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -for i in $(ifconfig | grep eth | cut -f 1 -d ' ') -do - prefix=$(ifconfig $i | grep HWaddr | cut -c39-53) - suffix=$( printf "%02x" ${i##eth}) - mac=$prefix$suffix - echo $i $mac - ifconfig $i hw ether $mac -done diff --git a/tests/fdb/conftest.py b/tests/fdb/conftest.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/fdb/fdb.j2 b/tests/fdb/fdb.j2 deleted file mode 100644 index e79d5e20f88..00000000000 --- a/tests/fdb/fdb.j2 +++ /dev/null @@ -1,3 +0,0 @@ -{% for vlan in mg_facts['minigraph_vlan_interfaces'] %} -{{ vlan['subnet'] }} {% for ifname in mg_facts['minigraph_vlans'][vlan['attachto']]['members'] %} {{ mg_facts['minigraph_port_indices'][ifname] }} {% endfor %} -{% endfor %} diff --git a/tests/fdb/test_fdb.py b/tests/fdb/test_fdb.py new file mode 100644 index 00000000000..c80fa114bbc --- /dev/null +++ b/tests/fdb/test_fdb.py @@ -0,0 +1,139 @@ +from ansible_host import ansible_host + +import pytest +import ptf.testutils as testutils + +import time +import itertools +import logging + +DEFAULT_FDB_ETHERNET_TYPE = 0x1234 +DUMMY_MAC_PREFIX = "02:11:22:33" +DUMMY_MAC_COUNT = 10 +FDB_POPULATE_SLEEP_TIMEOUT = 2 + +logger = logging.getLogger(__name__) + + +def send_eth(ptfadapter, source_port, source_mac, dest_mac): + pkt = testutils.simple_eth_packet( + eth_dst=dest_mac, + eth_src=source_mac, + eth_type=DEFAULT_FDB_ETHERNET_TYPE + ) + logger.debug('send packet source port id {} smac: {} dmac: {}'.format(source_port, source_mac, dest_mac)) + testutils.send(ptfadapter, source_port, pkt) + + +def send_recv_eth(ptfadapter, source_port, source_mac, dest_port, dest_mac): + pkt = testutils.simple_eth_packet( + eth_dst=dest_mac, + eth_src=source_mac, + eth_type=DEFAULT_FDB_ETHERNET_TYPE + ) + logger.debug('send packet src port {} smac: {} dmac: {} verifying on dst port {}'.format( + source_port, source_mac, dest_mac, dest_port)) + testutils.send(ptfadapter, source_port, pkt) + testutils.verify_packet_any_port(ptfadapter, pkt, [dest_port]) + + +def setup_fdb(ptfadapter, vlan_table, router_mac): + """ + :param ptfadapter: + :param vlan_table: + :return: + """ + + fdb = {} + + for vlan in vlan_table: + for member in vlan_table[vlan]: + mac = ptfadapter.dataplane.get_mac(0, member) + # send a packet to switch to populate layer 2 table with MAC of PTF interface + send_eth(ptfadapter, member, mac, router_mac) + + fdb[member] = [mac] + + # Send packets to switch to populate the layer 2 table with dummy MACs for each port + # Totally 10 dummy MACs for each port, send 1 packet for each dummy MAC + dummy_macs = ['{}:{:02x}:{:02x}'.format(DUMMY_MAC_PREFIX, member, i) + for i in range(DUMMY_MAC_COUNT)] + + for dummy_mac in dummy_macs: + send_eth(ptfadapter, member, dummy_mac, router_mac) + + fdb[member] += dummy_macs + + time.sleep(FDB_POPULATE_SLEEP_TIMEOUT) + + return fdb + + +@pytest.fixture +def fdb_cleanup(ansible_adhoc, testbed): + duthost = ansible_host(ansible_adhoc, testbed['dut']) + try: + duthost.command('sonic-clear fdb all') + yield + finally: + # in any case clear fdb after test + duthost.command('sonic-clear fdb all') + + +@pytest.mark.usefixtures('fdb_cleanup') +def test_fdb(ansible_adhoc, testbed, ptfadapter): + """ + 1. verify fdb forwarding in T0 topology. + 2. verify show mac command on DUT for learned mac. + """ + + if testbed['topo'] not in ['t0', 't0-64', 't0-116']: + pytest.skip("unsupported testbed type") + + duthost = ansible_host(ansible_adhoc, testbed['dut']) + ptfhost = ansible_host(ansible_adhoc, testbed['ptf']) + + host_facts = duthost.setup()['ansible_facts'] + mg_facts = duthost.minigraph_facts(host=duthost.hostname)['ansible_facts'] + + # remove existing IPs from PTF host + ptfhost.script("scripts/remove_ip.sh") + # set unique MACs to PTF interfaces + ptfhost.script("scripts/change_mac.sh") + # reinit data plane due to above changes on PTF interfaces + ptfadapter.reinit() + + router_mac = host_facts['ansible_Ethernet0']['macaddress'] + vlan_member_count = sum([len(v['members']) for k, v in mg_facts['minigraph_vlans'].items()]) + + vlan_table = {} + for vlan in mg_facts['minigraph_vlan_interfaces']: + vlan_table[vlan['subnet']] = [] + for ifname in mg_facts['minigraph_vlans'][vlan['attachto']]['members']: + vlan_table[vlan['subnet']].append(mg_facts['minigraph_port_indices'][ifname]) + + fdb = setup_fdb(ptfadapter, vlan_table, router_mac) + + for vlan in vlan_table: + for src, dst in itertools.combinations(vlan_table[vlan], 2): + for src_mac, dst_mac in itertools.product(fdb[src], fdb[dst]): + send_recv_eth(ptfadapter, src, src_mac, dst, dst_mac) + + res = duthost.command("show mac") + logger.info('"show mac" output on DUT') + for line in res['stdout_lines']: + logger.info(' {}'.format(line)) + + dummy_mac_count = 0 + total_mac_count = 0 + for l in res['stdout_lines']: + if DUMMY_MAC_PREFIX in l.lower(): + dummy_mac_count += 1 + if "dynamic" in l.lower(): + total_mac_count += 1 + + # Verify that the number of dummy MAC entries is expected + assert dummy_mac_count == DUMMY_MAC_COUNT * vlan_member_count + # Verify that total number of MAC entries is expected + assert total_mac_count == DUMMY_MAC_COUNT * vlan_member_count + vlan_member_count + diff --git a/tests/scripts/change_mac.sh b/tests/scripts/change_mac.sh new file mode 100755 index 00000000000..7845f3f2395 --- /dev/null +++ b/tests/scripts/change_mac.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +for INTF in $(ip -br link show | grep 'eth' | awk '{sub(/@.*/,"",$1); print $1}'); do + ADDR="$(ip -br link show dev ${INTF} | awk '{print $3}')" + PREFIX="$(cut -c1-15 <<< ${ADDR})" + SUFFIX="$(printf "%02x" ${INTF##eth})" + MAC="${PREFIX}${SUFFIX}" + + echo "Update ${INTF} MAC address: ${ADDR}->$MAC" + ip link set dev ${INTF} address ${MAC} +done diff --git a/tests/fdb/remove_ip.sh b/tests/scripts/remove_ip.sh similarity index 100% rename from tests/fdb/remove_ip.sh rename to tests/scripts/remove_ip.sh diff --git a/tests/test_fdb.py b/tests/test_fdb.py deleted file mode 100644 index 8983fd3c8f7..00000000000 --- a/tests/test_fdb.py +++ /dev/null @@ -1,79 +0,0 @@ -import pytest -from ansible_host import ansible_host -from ptf import ptf_runner - -def test_fdb(localhost, ansible_adhoc, testbed): - """ - 1. verify fdb forwarding in T0 topology. - 2. verify show mac command on DUT for learned mac. - """ - - if testbed['topo'] not in ['t0', 't0-64', 't0-116']: - pytest.skip("unsupported testbed type") - - hostname = testbed['dut'] - ptf_hostname = testbed['ptf'] - - duthost = ansible_host(ansible_adhoc, hostname) - ptfhost = ansible_host(ansible_adhoc, ptf_hostname) - - host_facts = duthost.setup()['ansible_facts'] - mg_facts = duthost.minigraph_facts(host=hostname)['ansible_facts'] - - # remove existing IPs from PTF host - ptfhost.script("fdb/remove_ip.sh") - - # Set unique MACs to PTF interfaces - res = ptfhost.script("fdb/change_mac.sh") - - root_dir = "/root" - - ptfhost.copy(src="scripts/arp_responder.py", dest="/opt") - extra_vars = { 'arp_responder_args': None } - ptfhost.host.options['variable_manager'].extra_vars = extra_vars - ptfhost.template(src="scripts/arp_responder.conf.j2", dest="/etc/supervisor/conf.d/arp_responder.conf") - ptfhost.shell("supervisorctl reread") - ptfhost.shell("supervisorctl update") - - extra_vars = { 'mg_facts': mg_facts } - ptfhost.host.options['variable_manager'].extra_vars = extra_vars - ptfhost.template(src="fdb/fdb.j2", dest="{}/fdb_info.txt".format(root_dir)) - - ptfhost.copy(src="ptftests", dest=root_dir) - - dummy_mac_prefix = "02:11:22:33" - dummy_mac_number = 10 - vlan_member_count = sum([len(v['members']) for k, v in mg_facts['minigraph_vlans'].items()]) - - duthost.command("sonic-clear fdb all") - - # run ptf test - ptf_runner(ptfhost, \ - "ptftests", - "fdb_test.FdbTest", - platform_dir="ptftests", - params={"testbed_type": "t0", - "router_mac": host_facts['ansible_Ethernet0']['macaddress'], - "fdb_info": "/root/fdb_info.txt", - "vlan_ip": mg_facts['minigraph_vlan_interfaces'][0]['addr'], - "dummy_mac_prefix": dummy_mac_prefix, - "dummy_mac_number": dummy_mac_number }, - log_file="/tmp/fdb_test.FdbTest.log") - - res = duthost.command("show mac") - - dummy_mac_count = 0 - total_mac_count = 0 - for l in res['stdout_lines']: - if dummy_mac_prefix in l.lower(): - dummy_mac_count += 1 - if "dynamic" in l.lower(): - total_mac_count += 1 - - # Verify that the number of dummy MAC entries is expected - assert dummy_mac_count == dummy_mac_number * vlan_member_count - - # Verify that total number of MAC entries is expected - assert total_mac_count == dummy_mac_number * vlan_member_count + vlan_member_count - - duthost.command("sonic-clear fdb all") From 7f981c0a3ed25d493690899be7e24c04788fe9de Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 13:49:09 +0300 Subject: [PATCH 08/26] [tests/ansible_host] rename to AnsibleHost to meet python naming convention Signed-off-by: Stepan Blyschak --- tests/ansible_host.py | 2 +- tests/ptf_fixtures.py | 4 ++-- tests/test_bgp_fact.py | 4 ++-- tests/test_bgp_speaker.py | 6 +++--- tests/test_lldp.py | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/ansible_host.py b/tests/ansible_host.py index cb272b15f0b..ff1f8ad79af 100644 --- a/tests/ansible_host.py +++ b/tests/ansible_host.py @@ -16,7 +16,7 @@ def __init__(self, msg, results=None): def __str__(self): return "{}\nAnsible Results => {}".format(self.message, dump_ansible_results(self.results)) -class ansible_host(object): +class AnsibleHost(object): """ wrapper for ansible host object """ def __init__(self, ansible_adhoc, hostname, is_local=False): diff --git a/tests/ptf_fixtures.py b/tests/ptf_fixtures.py index c389cfbe4fa..d6a21d341cb 100644 --- a/tests/ptf_fixtures.py +++ b/tests/ptf_fixtures.py @@ -3,7 +3,7 @@ import pytest from ptfadapter import PtfTestAdapter -from ansible_host import ansible_host +from ansible_host import AnsibleHost DEFAULT_PTF_NN_PORT = 10900 DEFAULT_DEVICE_NUM = 0 @@ -43,7 +43,7 @@ def ptfadapter(ansible_adhoc, testbed): to restart PTF before proceeding running other test modules """ - ptfhost = ansible_host(ansible_adhoc, testbed['ptf']) + ptfhost = AnsibleHost(ansible_adhoc, testbed['ptf']) # get the eth interfaces from PTF and initialize ifaces_map res = ptfhost.command('cat /proc/net/dev') ifaces = get_ifaces(res['stdout']) diff --git a/tests/test_bgp_fact.py b/tests/test_bgp_fact.py index b369799e4bd..5570b12f130 100644 --- a/tests/test_bgp_fact.py +++ b/tests/test_bgp_fact.py @@ -1,10 +1,10 @@ -from ansible_host import ansible_host +from ansible_host import AnsibleHost def test_bgp_facts(ansible_adhoc, testbed): """compare the bgp facts between observed states and target state""" hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) bgp_facts = ans_host.bgp_facts()['ansible_facts'] mg_facts = ans_host.minigraph_facts(host=hostname)['ansible_facts'] diff --git a/tests/test_bgp_speaker.py b/tests/test_bgp_speaker.py index 5195581aa14..c93128b1a63 100644 --- a/tests/test_bgp_speaker.py +++ b/tests/test_bgp_speaker.py @@ -2,7 +2,7 @@ import sys import time import ipaddress -from ansible_host import ansible_host +from ansible_host import AnsibleHost from ptf_runner import ptf_runner def generate_ips(num, prefix, exclude_ips): @@ -33,8 +33,8 @@ def test_bgp_speaker(localhost, ansible_adhoc, testbed): hostname = testbed['dut'] ptf_hostname = testbed['ptf'] - host = ansible_host(ansible_adhoc, hostname) - ptfhost = ansible_host(ansible_adhoc, ptf_hostname) + host = AnsibleHost(ansible_adhoc, hostname) + ptfhost = AnsibleHost(ansible_adhoc, ptf_hostname) mg_facts = host.minigraph_facts(host=hostname)['ansible_facts'] host_facts = host.setup()['ansible_facts'] diff --git a/tests/test_lldp.py b/tests/test_lldp.py index 75d61f993b1..abcdbe3b4e7 100644 --- a/tests/test_lldp.py +++ b/tests/test_lldp.py @@ -1,10 +1,10 @@ -from ansible_host import ansible_host +from ansible_host import AnsibleHost def test_lldp(localhost, ansible_adhoc, testbed): """verify the lldp message on DUT and neighbors""" hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) mg_facts = ans_host.minigraph_facts(host=hostname)['ansible_facts'] host_facts = ans_host.setup()['ansible_facts'] @@ -28,7 +28,7 @@ def test_lldp(localhost, ansible_adhoc, testbed): # Compare the LLDP neighbor interface with minigraph neigbhor interface (exclude the management port) assert v['port']['ifname'] == mg_facts['minigraph_neighbors'][k]['port'] - lhost = ansible_host(ansible_adhoc, 'localhost', True) + lhost = AnsibleHost(ansible_adhoc, 'localhost', True) for k, v in lldp_facts['lldp'].items(): hostip = v['chassis']['mgmt-ip'] From 753697f597869e07331a5415cc833a855ed051ba Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 13:55:43 +0300 Subject: [PATCH 09/26] [tests/ptfadapter] move qlen/relax to PtfTestAdapter object Signed-off-by: Stepan Blyschak --- tests/ptfadapter/__init__.py | 2 -- tests/ptfadapter/ptfadapter.py | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/ptfadapter/__init__.py b/tests/ptfadapter/__init__.py index 4a70d9f3f42..4953c06b642 100644 --- a/tests/ptfadapter/__init__.py +++ b/tests/ptfadapter/__init__.py @@ -2,14 +2,12 @@ # config ptf with basic parameters before any other ptf module import ptf.config = { - 'relax': True, 'disable_ipv6': False, 'disable_vxlan': False, 'disable_erspan': False, 'disable_geneve': False, 'disable_mpls': False, 'disable_nvgre': False, - 'qlen': 100000, } from ptfadapter import PtfTestAdapter diff --git a/tests/ptfadapter/ptfadapter.py b/tests/ptfadapter/ptfadapter.py index 197fb8804a9..0c6cf55beb4 100644 --- a/tests/ptfadapter/ptfadapter.py +++ b/tests/ptfadapter/ptfadapter.py @@ -8,6 +8,7 @@ class PtfTestAdapter(BaseTest): """PtfTestAdapater class provides interface for pytest to use ptf.testutils functions """ + DEFAULT_PTF_QUEUE_LEN = 100000 DEFAULT_PTF_TIMEOUT = 2 DEFAULT_PTF_NEG_TIMEOUT = 0.1 @@ -56,6 +57,8 @@ def _init_ptf_dataplane(self, ptf_ip, ptf_nn_port, device_num, ptf_ports_num, pt 'device_sockets': [ (device_num, range(ptf_ports_num), 'tcp://{}:{}'.format(ptf_ip, ptf_nn_port)) ], + 'qlen': self.DEFAULT_PTF_QUEUE_LEN, + 'relax': True, }) if ptf_config is not None: ptf.config.update(ptf_config) From 812a8eaf3b4589cfd25edb107db5a912d503fea6 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 15:45:20 +0300 Subject: [PATCH 10/26] [tests/ptfadapter] non need for disable_* config keys for newer ptf Signed-off-by: Stepan Blyschak --- tests/ptfadapter/__init__.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/ptfadapter/__init__.py b/tests/ptfadapter/__init__.py index 4953c06b642..1803738798b 100644 --- a/tests/ptfadapter/__init__.py +++ b/tests/ptfadapter/__init__.py @@ -1,15 +1,3 @@ -import ptf - -# config ptf with basic parameters before any other ptf module import -ptf.config = { - 'disable_ipv6': False, - 'disable_vxlan': False, - 'disable_erspan': False, - 'disable_geneve': False, - 'disable_mpls': False, - 'disable_nvgre': False, -} - from ptfadapter import PtfTestAdapter __all__ = ['PtfTestAdapter'] \ No newline at end of file From 31ec37e647d9c256660cb82c6e68cad477212072 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 15:47:04 +0300 Subject: [PATCH 11/26] [tests] change ansible_host to AnsibleHost Signed-off-by: Stepan Blyschak --- tests/fdb/test_fdb.py | 8 ++++---- tests/platform/check_critical_services.py | 8 ++++---- tests/platform/check_interface_status.py | 2 +- tests/platform/check_transceiver_status.py | 10 +++++----- tests/platform/test_platform_info.py | 10 +++++----- tests/platform/test_reboot.py | 8 ++++---- tests/platform/test_reload_config.py | 4 ++-- tests/platform/test_sequential_restart.py | 6 +++--- tests/platform/test_sfp.py | 6 +++--- tests/platform/test_xcvr_info_in_db.py | 4 ++-- 10 files changed, 33 insertions(+), 33 deletions(-) diff --git a/tests/fdb/test_fdb.py b/tests/fdb/test_fdb.py index c80fa114bbc..b6bafa99c6c 100644 --- a/tests/fdb/test_fdb.py +++ b/tests/fdb/test_fdb.py @@ -1,4 +1,4 @@ -from ansible_host import ansible_host +from ansible_host import AnsibleHost import pytest import ptf.testutils as testutils @@ -71,7 +71,7 @@ def setup_fdb(ptfadapter, vlan_table, router_mac): @pytest.fixture def fdb_cleanup(ansible_adhoc, testbed): - duthost = ansible_host(ansible_adhoc, testbed['dut']) + duthost = AnsibleHost(ansible_adhoc, testbed['dut']) try: duthost.command('sonic-clear fdb all') yield @@ -90,8 +90,8 @@ def test_fdb(ansible_adhoc, testbed, ptfadapter): if testbed['topo'] not in ['t0', 't0-64', 't0-116']: pytest.skip("unsupported testbed type") - duthost = ansible_host(ansible_adhoc, testbed['dut']) - ptfhost = ansible_host(ansible_adhoc, testbed['ptf']) + duthost = AnsibleHost(ansible_adhoc, testbed['dut']) + ptfhost = AnsibleHost(ansible_adhoc, testbed['ptf']) host_facts = duthost.setup()['ansible_facts'] mg_facts = duthost.minigraph_facts(host=duthost.hostname)['ansible_facts'] diff --git a/tests/platform/check_critical_services.py b/tests/platform/check_critical_services.py index d7b3ea084c0..30d0e6393ca 100644 --- a/tests/platform/check_critical_services.py +++ b/tests/platform/check_critical_services.py @@ -15,7 +15,7 @@ def get_service_status(dut, service): """ @summary: Get the ActiveState and SubState of a service. This function uses the systemctl tool to get the ActiveState and SubState of specified service. - @param dut: The ansible_host object of DUT. For interacting with DUT. + @param dut: The AnsibleHost object of DUT. For interacting with DUT. @param service: Service name. @return: Returns a dictionary containing ActiveState and SubState of the specified service, for example: { @@ -38,7 +38,7 @@ def service_fully_started(dut, service): instruction in service starting script is to run "docker wait ". This function take advantage of this design to check whether a service has been fully started. The trick is to check whether "docker wait " exists in current running processes. - @param dut: The ansible_host object of DUT. For interacting with DUT. + @param dut: The AnsibleHost object of DUT. For interacting with DUT. @param service: Service name. @return: Return True if the specified service is fully started. Otherwise return False. """ @@ -55,7 +55,7 @@ def service_fully_started(dut, service): def critical_services_fully_started(dut): """ @summary: Check whether all the critical service have been fully started. - @param dut: The ansible_host object of DUT. For interacting with DUT. + @param dut: The AnsibleHost object of DUT. For interacting with DUT. @return: Return True if all the critical services have been fully started. Otherwise return False. """ result = {} @@ -69,7 +69,7 @@ def check_critical_services(dut): """ @summary: Use systemctl to check whether all the critical services have expected status. ActiveState of all services must be "active". SubState of all services must be "running". - @param dut: The ansible_host object of DUT. For interacting with DUT. + @param dut: The AnsibleHost object of DUT. For interacting with DUT. """ logging.info("Wait until all critical services are fully started") assert wait_until(300, 20, critical_services_fully_started, dut), "Not all critical services are fully started" diff --git a/tests/platform/check_interface_status.py b/tests/platform/check_interface_status.py index 72d863007b5..a2aa4a4c578 100644 --- a/tests/platform/check_interface_status.py +++ b/tests/platform/check_interface_status.py @@ -34,7 +34,7 @@ def parse_intf_status(lines): def check_interface_status(dut, interfaces): """ @summary: Check the admin and oper status of the specified interfaces on DUT. - @param dut: The ansible_host object of DUT. For interacting with DUT. + @param dut: The AnsibleHost object of DUT. For interacting with DUT. @param hostname: @param interfaces: List of interfaces that need to be checked. """ diff --git a/tests/platform/check_transceiver_status.py b/tests/platform/check_transceiver_status.py index dec74349929..a2a87e0ffad 100644 --- a/tests/platform/check_transceiver_status.py +++ b/tests/platform/check_transceiver_status.py @@ -53,7 +53,7 @@ def all_transceivers_detected(dut, interfaces): def check_transceiver_basic(dut, interfaces): """ @summary: Check whether all the specified interface are in TRANSCEIVER_INFO redis DB. - @param dut: The ansible_host object of DUT. For interacting with DUT. + @param dut: The AnsibleHost object of DUT. For interacting with DUT. @param interfaces: List of interfaces that need to be checked. """ logging.info("Check whether transceiver information of all ports are in redis") @@ -66,7 +66,7 @@ def check_transceiver_basic(dut, interfaces): def check_transceiver_details(dut, interfaces): """ @summary: Check the detailed TRANSCEIVER_INFO content of all the specified interfaces. - @param dut: The ansible_host object of DUT. For interacting with DUT. + @param dut: The AnsibleHost object of DUT. For interacting with DUT. @param interfaces: List of interfaces that need to be checked. """ logging.info("Check detailed transceiver information of each connected port") @@ -81,7 +81,7 @@ def check_transceiver_details(dut, interfaces): def check_transceiver_dom_sensor_basic(dut, interfaces): """ @summary: Check whether all the specified interface are in TRANSCEIVER_DOM_SENSOR redis DB. - @param dut: The ansible_host object of DUT. For interacting with DUT. + @param dut: The AnsibleHost object of DUT. For interacting with DUT. @param interfaces: List of interfaces that need to be checked. """ logging.info("Check whether TRANSCEIVER_DOM_SENSOR of all ports in redis") @@ -94,7 +94,7 @@ def check_transceiver_dom_sensor_basic(dut, interfaces): def check_transceiver_dom_sensor_details(dut, interfaces): """ @summary: Check the detailed TRANSCEIVER_DOM_SENSOR content of all the specified interfaces. - @param dut: The ansible_host object of DUT. For interacting with DUT. + @param dut: The AnsibleHost object of DUT. For interacting with DUT. @param interfaces: List of interfaces that need to be checked. """ logging.info("Check detailed TRANSCEIVER_DOM_SENSOR information of each connected ports") @@ -110,7 +110,7 @@ def check_transceiver_dom_sensor_details(dut, interfaces): def check_transceiver_status(dut, interfaces): """ @summary: Check transceiver information of all the specified interfaces in redis DB. - @param dut: The ansible_host object of DUT. For interacting with DUT. + @param dut: The AnsibleHost object of DUT. For interacting with DUT. @param interfaces: List of interfaces that need to be checked. """ check_transceiver_basic(dut, interfaces) diff --git a/tests/platform/test_platform_info.py b/tests/platform/test_platform_info.py index 1873fb336cf..73e0edb4ecc 100644 --- a/tests/platform/test_platform_info.py +++ b/tests/platform/test_platform_info.py @@ -10,7 +10,7 @@ import pytest -from ansible_host import ansible_host +from ansible_host import AnsibleHost from psu_controller import psu_controller @@ -24,7 +24,7 @@ def test_show_platform_summary(localhost, ansible_adhoc, testbed): @summary: Check output of 'show platform summary' """ hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) logging.info("Check output of '%s'" % CMD_PLATFORM_SUMMARY) platform_summary = ans_host.command(CMD_PLATFORM_SUMMARY) @@ -44,7 +44,7 @@ def test_show_platform_psustatus(localhost, ansible_adhoc, testbed): @summary: Check output of 'show platform psustatus' """ hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) logging.info("Check PSU status using '%s', hostname: %s" % (CMD_PLATFORM_PSUSTATUS, hostname)) psu_status = ans_host.command(CMD_PLATFORM_PSUSTATUS) @@ -58,7 +58,7 @@ def test_turn_on_off_psu_and_check_psustatus(localhost, ansible_adhoc, testbed, @summary: Turn off/on PSU and check PSU status using 'show platform psustatus' """ hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) platform_info = parse_platform_summary(ans_host.command(CMD_PLATFORM_SUMMARY)["stdout_lines"]) psu_line_pattern = re.compile(r"PSU\s+\d+\s+(OK|NOT OK|NOT PRESENT)") @@ -151,7 +151,7 @@ def test_show_platform_syseeprom(localhost, ansible_adhoc, testbed): @summary: Check output of 'show platform syseeprom' """ hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) logging.info("Check output of '%s'" % CMD_PLATFORM_SYSEEPROM) platform_info = parse_platform_summary(ans_host.command(CMD_PLATFORM_SUMMARY)["stdout_lines"]) diff --git a/tests/platform/test_reboot.py b/tests/platform/test_reboot.py index 38444223a95..a402b408acf 100644 --- a/tests/platform/test_reboot.py +++ b/tests/platform/test_reboot.py @@ -15,7 +15,7 @@ import pytest -from ansible_host import ansible_host +from ansible_host import AnsibleHost from utilities import wait_until from check_critical_services import check_critical_services from check_interface_status import check_interface_status @@ -93,7 +93,7 @@ def test_cold_reboot(localhost, ansible_adhoc, testbed): @summary: This test case is to perform cold reboot and check platform status """ hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) reboot_and_check(localhost, ans_host, reboot_type="cold") @@ -103,7 +103,7 @@ def test_fast_reboot(localhost, ansible_adhoc, testbed): @summary: This test case is to perform cold reboot and check platform status """ hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) reboot_and_check(localhost, ans_host, reboot_type="fast") @@ -113,7 +113,7 @@ def test_warm_reboot(localhost, ansible_adhoc, testbed): @summary: This test case is to perform cold reboot and check platform status """ hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) asic_type = ans_host.shell("show platform summary | awk '/ASIC: / {print$2}'")["stdout"].strip() if asic_type in ["mellanox"]: diff --git a/tests/platform/test_reload_config.py b/tests/platform/test_reload_config.py index 83fe3544189..9b3bb583a80 100644 --- a/tests/platform/test_reload_config.py +++ b/tests/platform/test_reload_config.py @@ -10,7 +10,7 @@ import time import sys -from ansible_host import ansible_host +from ansible_host import AnsibleHost from utilities import wait_until from check_critical_services import check_critical_services from check_interface_status import check_interface_status @@ -23,7 +23,7 @@ def test_reload_configuration(localhost, ansible_adhoc, testbed): @summary: This test case is to reload the configuration and check platform status """ hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) ans_host.command("show platform summary") lab_conn_graph_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), \ "../../ansible/files/lab_connection_graph.xml") diff --git a/tests/platform/test_sequential_restart.py b/tests/platform/test_sequential_restart.py index 092bc614a62..8e715708a25 100644 --- a/tests/platform/test_sequential_restart.py +++ b/tests/platform/test_sequential_restart.py @@ -10,7 +10,7 @@ import time import sys -from ansible_host import ansible_host +from ansible_host import AnsibleHost from utilities import wait_until from check_critical_services import check_critical_services from check_interface_status import check_interface_status @@ -68,7 +68,7 @@ def test_restart_swss(localhost, ansible_adhoc, testbed): @summary: This test case is to restart the swss service and check platform status """ hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) restart_service_and_check(localhost, ans_host, "swss") @@ -77,5 +77,5 @@ def test_restart_syncd(localhost, ansible_adhoc, testbed): @summary: This test case is to restart the syncd service and check platform status """ hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) restart_service_and_check(localhost, ans_host, "syncd") diff --git a/tests/platform/test_sfp.py b/tests/platform/test_sfp.py index 078cf24170d..ea2c871c469 100644 --- a/tests/platform/test_sfp.py +++ b/tests/platform/test_sfp.py @@ -10,7 +10,7 @@ import time import copy -from ansible_host import ansible_host +from ansible_host import AnsibleHost def parse_output(output_lines): @@ -55,7 +55,7 @@ def test_check_sfp_status_and_configure_sfp(localhost, ansible_adhoc, testbed): * sfputil reset """ hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) localhost.command("who") lab_conn_graph_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), \ "../../ansible/files/lab_connection_graph.xml") @@ -120,7 +120,7 @@ def test_check_sfp_low_power_mode(localhost, ansible_adhoc, testbed): * sfputil lpmode on """ hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) localhost.command("who") lab_conn_graph_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), \ "../../ansible/files/lab_connection_graph.xml") diff --git a/tests/platform/test_xcvr_info_in_db.py b/tests/platform/test_xcvr_info_in_db.py index 908b5684409..0abedb83844 100644 --- a/tests/platform/test_xcvr_info_in_db.py +++ b/tests/platform/test_xcvr_info_in_db.py @@ -8,7 +8,7 @@ import re import os -from ansible_host import ansible_host +from ansible_host import AnsibleHost from check_transceiver_status import check_transceiver_status @@ -47,7 +47,7 @@ def test_xcvr_info_in_db(localhost, ansible_adhoc, testbed): @summary: This test case is to verify that xcvrd works as expected by checking transceiver information in DB """ hostname = testbed['dut'] - ans_host = ansible_host(ansible_adhoc, hostname) + ans_host = AnsibleHost(ansible_adhoc, hostname) localhost.command("who") lab_conn_graph_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), \ "../../ansible/files/lab_connection_graph.xml") From 659455be8426b94f2b2c31eddcc56a023db1980c Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 16:02:05 +0300 Subject: [PATCH 12/26] [conftest] remove extra new line Signed-off-by: Stepan Blyschak --- tests/conftest.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1388155d356..dd56286114a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -56,5 +56,3 @@ def testbed(request): tbinfo = TestbedInfo(tbfile) return tbinfo.testbed_topo[tbname] - - From 7da3f7dfe8735df7e0cb9a6a9459d6fb7bda1fbb Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 16:02:30 +0300 Subject: [PATCH 13/26] [tests/fdb] reduce info to debug Signed-off-by: Stepan Blyschak --- tests/fdb/test_fdb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/fdb/test_fdb.py b/tests/fdb/test_fdb.py index b6bafa99c6c..ddb62091d48 100644 --- a/tests/fdb/test_fdb.py +++ b/tests/fdb/test_fdb.py @@ -120,9 +120,9 @@ def test_fdb(ansible_adhoc, testbed, ptfadapter): send_recv_eth(ptfadapter, src, src_mac, dst, dst_mac) res = duthost.command("show mac") - logger.info('"show mac" output on DUT') + logger.debug('"show mac" output on DUT') for line in res['stdout_lines']: - logger.info(' {}'.format(line)) + logger.debug(' {}'.format(line)) dummy_mac_count = 0 total_mac_count = 0 From 70ec3702f977e57c044c34a2c9d39bb6ccffa97c Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 16:14:46 +0300 Subject: [PATCH 14/26] [tests/fdb] cleanup FDB test Signed-off-by: Stepan Blyschak --- tests/fdb/test_fdb.py | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/tests/fdb/test_fdb.py b/tests/fdb/test_fdb.py index ddb62091d48..1a4a5eb862b 100644 --- a/tests/fdb/test_fdb.py +++ b/tests/fdb/test_fdb.py @@ -16,6 +16,14 @@ def send_eth(ptfadapter, source_port, source_mac, dest_mac): + """ + send ethernet packet + :param ptfadapter: PTF adapter object + :param source_port: source port + :param source_mac: source MAC + :param dest_mac: destination MAC + :return: + """ pkt = testutils.simple_eth_packet( eth_dst=dest_mac, eth_src=source_mac, @@ -26,6 +34,15 @@ def send_eth(ptfadapter, source_port, source_mac, dest_mac): def send_recv_eth(ptfadapter, source_port, source_mac, dest_port, dest_mac): + """ + send ethernet packet and verify it on dest_port + :param ptfadapter: PTF adapter object + :param source_port: source port + :param source_mac: source MAC + :param dest_port: destination port to receive packet on + :param dest_mac: destination MAC + :return: + """ pkt = testutils.simple_eth_packet( eth_dst=dest_mac, eth_src=source_mac, @@ -39,9 +56,9 @@ def send_recv_eth(ptfadapter, source_port, source_mac, dest_port, dest_mac): def setup_fdb(ptfadapter, vlan_table, router_mac): """ - :param ptfadapter: - :param vlan_table: - :return: + :param ptfadapter: PTF adapter object + :param vlan_table: VLAN table map: VLAN subnet -> list of VLAN members + :return: FDB table map : VLAN member -> MAC addresses set """ fdb = {} @@ -52,7 +69,8 @@ def setup_fdb(ptfadapter, vlan_table, router_mac): # send a packet to switch to populate layer 2 table with MAC of PTF interface send_eth(ptfadapter, member, mac, router_mac) - fdb[member] = [mac] + # put in learned MAC + fdb[member] = { mac } # Send packets to switch to populate the layer 2 table with dummy MACs for each port # Totally 10 dummy MACs for each port, send 1 packet for each dummy MAC @@ -62,7 +80,8 @@ def setup_fdb(ptfadapter, vlan_table, router_mac): for dummy_mac in dummy_macs: send_eth(ptfadapter, member, dummy_mac, router_mac) - fdb[member] += dummy_macs + # put in set learned dummy MACs + fdb[member].update(dummy_macs) time.sleep(FDB_POPULATE_SLEEP_TIMEOUT) @@ -71,6 +90,7 @@ def setup_fdb(ptfadapter, vlan_table, router_mac): @pytest.fixture def fdb_cleanup(ansible_adhoc, testbed): + """ cleanup FDB before and after test run """ duthost = AnsibleHost(ansible_adhoc, testbed['dut']) try: duthost.command('sonic-clear fdb all') @@ -88,7 +108,7 @@ def test_fdb(ansible_adhoc, testbed, ptfadapter): """ if testbed['topo'] not in ['t0', 't0-64', 't0-116']: - pytest.skip("unsupported testbed type") + pytest.skip('unsupported testbed type') duthost = AnsibleHost(ansible_adhoc, testbed['dut']) ptfhost = AnsibleHost(ansible_adhoc, testbed['ptf']) @@ -97,10 +117,10 @@ def test_fdb(ansible_adhoc, testbed, ptfadapter): mg_facts = duthost.minigraph_facts(host=duthost.hostname)['ansible_facts'] # remove existing IPs from PTF host - ptfhost.script("scripts/remove_ip.sh") + ptfhost.script('scripts/remove_ip.sh') # set unique MACs to PTF interfaces - ptfhost.script("scripts/change_mac.sh") - # reinit data plane due to above changes on PTF interfaces + ptfhost.script('scripts/change_mac.sh') + # reinitialize data plane due to above changes on PTF interfaces ptfadapter.reinit() router_mac = host_facts['ansible_Ethernet0']['macaddress'] @@ -119,7 +139,7 @@ def test_fdb(ansible_adhoc, testbed, ptfadapter): for src_mac, dst_mac in itertools.product(fdb[src], fdb[dst]): send_recv_eth(ptfadapter, src, src_mac, dst, dst_mac) - res = duthost.command("show mac") + res = duthost.command('show mac') logger.debug('"show mac" output on DUT') for line in res['stdout_lines']: logger.debug(' {}'.format(line)) From b34493acf2b9047345d6d4699b54261200a900c9 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 16:25:39 +0300 Subject: [PATCH 15/26] [tests/ptfadapter] correct on the comment Signed-off-by: Stepan Blyschak --- tests/ptfadapter/__init__.py | 2 +- tests/ptfadapter/ptfadapter.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ptfadapter/__init__.py b/tests/ptfadapter/__init__.py index 1803738798b..c9eb27c8599 100644 --- a/tests/ptfadapter/__init__.py +++ b/tests/ptfadapter/__init__.py @@ -1,3 +1,3 @@ from ptfadapter import PtfTestAdapter -__all__ = ['PtfTestAdapter'] \ No newline at end of file +__all__ = ['PtfTestAdapter'] diff --git a/tests/ptfadapter/ptfadapter.py b/tests/ptfadapter/ptfadapter.py index 0c6cf55beb4..1c7c7b25953 100644 --- a/tests/ptfadapter/ptfadapter.py +++ b/tests/ptfadapter/ptfadapter.py @@ -67,8 +67,8 @@ def _init_ptf_dataplane(self, ptf_ip, ptf_nn_port, device_num, ptf_ports_num, pt nn.platform_config_update(ptf.config) ptf.dataplane_instance = DataPlane(config=ptf.config) - # TODO: in case of multi PTF hosts topologies we'll have to manually call port_add on - # ptf.dataplane_instance to specify mapping between tcp://: and port tuple (device_id, port_id) + # TODO: in case of multi PTF hosts topologies we'll have to provide custom platform that supports that + # and initialize port_map specifying mapping between tcp://: and port tuple (device_id, port_id) for id, ifname in ptf.config['port_map'].items(): device_id, port_id = id ptf.dataplane_instance.port_add(ifname, device_id, port_id) From 78b2907e417b21438a6104b262410724715f016a Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 18:22:02 +0300 Subject: [PATCH 16/26] [pytest] introduce pytest_runner playbook Signed-off-by: Stepan Blyschak --- ansible/roles/test/tasks/pytest_runner.yml | 64 ++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 ansible/roles/test/tasks/pytest_runner.yml diff --git a/ansible/roles/test/tasks/pytest_runner.yml b/ansible/roles/test/tasks/pytest_runner.yml new file mode 100644 index 00000000000..3ca3c332e36 --- /dev/null +++ b/ansible/roles/test/tasks/pytest_runner.yml @@ -0,0 +1,64 @@ +- name: print a warning + debug: + msg: + - "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + - "!!!!!! Ansible playbook for running {{ testcase_name }} is now deprecated !!!!!!" + - "!!!!!! This playbook is just a wrapper to run py.test in sonic-mgmt/tests !!!!!!" + - "!!!!!!!!!!!!!!!!!!!!!!!!!! Consider using py.test !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + - "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + +- fail: + msg: Test node is not defined + when: test_node is not defined + +- fail: + msg: Testbed is not defined + when: testbed_name is not defined + +- name: set py.test command variable + set_fact: + pytest_cmd: 'py.test {{ test_node }}' + +- name: append filter expression if needed + set_fact: + pytest_cmd: '{{ pytest_cmd }} -k "{{ test_filter }}"' + when: test_filter is defined + +- name: append mark if needed + set_fact: + pytest_cmd: '{{ pytest_cmd }} -m {{ test_mark }}' + when: test_mark is defined + +- name: append testbed name + set_fact: + pytest_cmd: '{{ pytest_cmd }} --testbed={{ testbed_name }}' + +# We could use ansible_inventory_sources magic variable but it is available in ansible >=2.5 +- name: append inventory file + set_fact: + pytest_cmd: '{{ pytest_cmd }} --inventory=../ansible/inventory' + +- name: append testbed file + set_fact: + pytest_cmd: '{{ pytest_cmd }} --testbed_file=../ansible/testbed.csv' + +- name: append host pattern + set_fact: + pytest_cmd: '{{ pytest_cmd }} --host-pattern={{ testbed_name }}' + +- name: append verbosity flag + set_fact: + pytest_cmd: '{{ pytest_cmd }} -v' + +- debug: var=pytest_cmd + +- name: run py.test + connection: local + shell: '{{ pytest_cmd }}' + args: + chdir: ../tests/ + environment: + ANSIBLE_LIBRARY: ../ansible/library/ + register: out + +- debug: msg='{{ out.stdout_lines }}' From bedbbc925f2f9a03999f8d45c77ea84f6c1951af Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 18:22:57 +0300 Subject: [PATCH 17/26] [test_lldp] divide into two test cases and put a TODO about SNMP community string Signed-off-by: Stepan Blyschak --- tests/test_lldp.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/tests/test_lldp.py b/tests/test_lldp.py index abcdbe3b4e7..6f791566ad4 100644 --- a/tests/test_lldp.py +++ b/tests/test_lldp.py @@ -1,16 +1,14 @@ from ansible_host import AnsibleHost + def test_lldp(localhost, ansible_adhoc, testbed): - """verify the lldp message on DUT and neighbors""" + """ verify the LLDP message on DUT """ hostname = testbed['dut'] ans_host = AnsibleHost(ansible_adhoc, hostname) mg_facts = ans_host.minigraph_facts(host=hostname)['ansible_facts'] - host_facts = ans_host.setup()['ansible_facts'] lldp_facts = ans_host.lldp()['ansible_facts'] - res = ans_host.shell("docker exec -i lldp lldpcli show chassis | grep \"SysDescr:\" | sed -e 's/^\\s*SysDescr:\\s*//g'") - dut_system_description = res['stdout'] minigraph_lldp_nei = {} for k, v in mg_facts['minigraph_neighbors'].items(): @@ -28,10 +26,26 @@ def test_lldp(localhost, ansible_adhoc, testbed): # Compare the LLDP neighbor interface with minigraph neigbhor interface (exclude the management port) assert v['port']['ifname'] == mg_facts['minigraph_neighbors'][k]['port'] + +def test_lldp_neighbor(localhost, ansible_adhoc, testbed): + """ verify LLDP information on neighbors """ + + hostname = testbed['dut'] + ans_host = AnsibleHost(ansible_adhoc, hostname) + mg_facts = ans_host.minigraph_facts(host=hostname)['ansible_facts'] + res = ans_host.shell("docker exec -i lldp lldpcli show chassis | grep \"SysDescr:\" | sed -e 's/^\\s*SysDescr:\\s*//g'") + dut_system_description = res['stdout'] + lldp_facts = ans_host.lldp()['ansible_facts'] + host_facts = ans_host.setup()['ansible_facts'] lhost = AnsibleHost(ansible_adhoc, 'localhost', True) for k, v in lldp_facts['lldp'].items(): + if k == 'eth0': + # skip test on management interface + continue + hostip = v['chassis']['mgmt-ip'] + # TODO: Need to read eos.yml to get SNMP version and community string nei_lldp_facts = lhost.lldp_facts(host=hostip, version='v2c', community='strcommunity')['ansible_facts'] print nei_lldp_facts neighbor_interface = v['port']['ifname'] From 295ea966b198d4fbd1895992a5728f81d2c2794d Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 18:24:52 +0300 Subject: [PATCH 18/26] [lldp] call py.test to run lldp test; from now on any change/new test cases for lldp should be done in pytest Signed-off-by: Stepan Blyschak --- ansible/roles/test/tasks/lldp.yml | 63 ++-------------------- ansible/roles/test/tasks/lldp_neighbor.yml | 25 --------- 2 files changed, 3 insertions(+), 85 deletions(-) delete mode 100644 ansible/roles/test/tasks/lldp_neighbor.yml diff --git a/ansible/roles/test/tasks/lldp.yml b/ansible/roles/test/tasks/lldp.yml index 050edd95a33..1f07e1cddf1 100644 --- a/ansible/roles/test/tasks/lldp.yml +++ b/ansible/roles/test/tasks/lldp.yml @@ -1,61 +1,4 @@ -# Gather minigraph facts -- name: Gathering minigraph facts about the device - minigraph_facts: - host: "{{ inventory_hostname }}" - -- name: Print neighbors in minigraph - debug: msg="{{ minigraph_neighbors }}" - -- name: find minigraph lldp neighbor - set_fact: - minigraph_lldp_nei: "{{ minigraph_lldp_nei|default({}) | combine({ item.key : item.value}) }}" - when: "'server' not in item.value['name'] | lower" - with_dict: minigraph_neighbors - -- name: Gather information from LLDP - lldp: +- name: run test + include: roles/test/tasks/pytest_runner.yml vars: - ansible_shell_type: docker - ansible_python_interpreter: docker exec -i lldp python - -- name: Print LLDP information - debug: msg="{{ lldp }}" - -- name: Verify LLDP information is available on most interfaces - assert: { that: "{{ lldp|length }} > {{ minigraph_lldp_nei|length * 0.8 }}"} - -- name: Compare the LLDP neighbor name with minigraph neigbhor name (exclude the management port) - assert: { that: "'{{ lldp[item]['chassis']['name'] }}' == '{{ minigraph_lldp_nei[item]['name'] }}'" } - with_items: "{{ lldp.keys() }}" - when: item != "eth0" - -- name: Compare the LLDP neighbor interface with minigraph neigbhor interface (exclude the management port) - assert: { that: "'{{ lldp[item]['port']['ifname'] }}' == '{{ minigraph_neighbors[item]['port'] }}'" } - with_items: "{{ lldp.keys() }}" - when: item != "eth0" - -- block: - - name: Obtain the system description of the DUT chassis - shell: "docker exec -i lldp lldpcli show chassis | grep \"SysDescr:\" | sed -e 's/^\\s*SysDescr:\\s*//g'" - register: result - - - name: Store system description of the DUT chassis as a fact - set_fact: - dut_system_description: "{{ result.stdout }}" - -###TODO: fix this lldp_neighbor validation, this part is not running -- name: Iterate through each LLDP neighbor and verify the information received by neighbor is correct - add_host: - name: "{{ lldp[item]['chassis']['mgmt-ip'] }}" - groups: "lldp_neighbors,eos" - neighbor_interface: "{{ lldp[item]['port']['ifname'] }}" - dut_interface: "{{ item }}" - hname: "{{ lldp[item]['chassis']['mgmt-ip'] }}" - dut_chassis_id: "0x{{ ansible_eth0['macaddress'] | replace(':', '') }}" - dut_hostname: "{{ inventory_hostname }}" - dut_port_alias: "{{ minigraph_ports[item]['alias'] }}" - dut_port_description: "{{ minigraph_neighbors[item]['name'] }}:{{ minigraph_neighbors[item]['port'] }}" - dut_system_description: "{{ dut_system_description }}" - with_items: "{{ lldp.keys() }}" - when: item != "eth0" - + test_node: test_lldp.py diff --git a/ansible/roles/test/tasks/lldp_neighbor.yml b/ansible/roles/test/tasks/lldp_neighbor.yml deleted file mode 100644 index 54b8db32963..00000000000 --- a/ansible/roles/test/tasks/lldp_neighbor.yml +++ /dev/null @@ -1,25 +0,0 @@ -- name: Gather LLDP information from all neighbors by performing a SNMP walk - lldp_facts: - host: "{{ hname }}" - version: "v2c" - community: "{{ snmp_rocommunity }}" - connection: local - -- name: Print LLDP facts from neighbors - debug: msg="{{ ansible_lldp_facts }}" - -- name: Verify the published DUT system name field is correct - assert: {that: "'{{ ansible_lldp_facts[neighbor_interface]['neighbor_sys_name'] }}' == '{{ dut_hostname }}'"} - -# FIXME: use more strict assertion -- name: Verify the published DUT chassis id field is not empty - assert: {that: "'{{ ansible_lldp_facts[neighbor_interface]['neighbor_chassis_id'] }}' == '{{ dut_chassis_id }}'"} - -- name: Verify the published DUT system description field is correct - assert: {that: "'{{ ansible_lldp_facts[neighbor_interface]['neighbor_sys_desc'] }}' == '{{ dut_system_description }}'"} - -- name: Verify the published DUT port id field is correct - assert: {that: "'{{ ansible_lldp_facts[neighbor_interface]['neighbor_port_id'] }}' == '{{ dut_port_alias }}'"} - -- name: Verify the published DUT port description field is correct - assert: {that: "'{{ ansible_lldp_facts[neighbor_interface]['neighbor_port_desc'] }}' == '{{ dut_port_description }}'"} From 82088f53bdb67ce82d2c40b94d91bdd90b8e126d Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 18:25:32 +0300 Subject: [PATCH 19/26] [tests/bgp_speaker] skip until test is fixed Signed-off-by: Stepan Blyschak --- tests/test_bgp_speaker.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_bgp_speaker.py b/tests/test_bgp_speaker.py index c93128b1a63..7e72b4183a2 100644 --- a/tests/test_bgp_speaker.py +++ b/tests/test_bgp_speaker.py @@ -1,3 +1,4 @@ +import pytest from netaddr import * import sys import time @@ -26,6 +27,7 @@ def generate_ips(num, prefix, exclude_ips): return generated_ips +@pytest.mark.skip(reason='test is broken') def test_bgp_speaker(localhost, ansible_adhoc, testbed): """setup bgp speaker on T0 topology and verify routes advertised by bgp speaker is received by T0 TOR From 0efe6282a2365b6ef202cbac00fad1b22fe31cc8 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 25 Jul 2019 19:52:08 +0300 Subject: [PATCH 20/26] [tests/ptfadapter] add README.md on how to use ptfadapter Signed-off-by: Stepan Blyschak --- tests/ptfadapter/README.md | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/ptfadapter/README.md diff --git a/tests/ptfadapter/README.md b/tests/ptfadapter/README.md new file mode 100644 index 00000000000..4a51cb15ecc --- /dev/null +++ b/tests/ptfadapter/README.md @@ -0,0 +1,47 @@ +# How to write traffic tests using PTF adapter + +## Overview + +```PtfTestAdapter``` provides an interface to send and receive traffic in the same way as ```ptf.base_tests.BaseTest``` object in PTF framework. +It makes use of ```ptf_nn_agent.py``` script running on PTF host, connectes to it over TCP and intialize PTF data plane thread. + +**NOTE** a good network connection between sonic-mgmt node and PTF host is requiered for traffic tests to be stable. + +## Usage in pytest + +You can use ```ptfadapter``` fixture which runs ```ptf_nn_agent.py``` on PTF and yields ```PtfTestAdapter``` object. + +Example test case code using PTF adapter: + +```python +import ptf.testutils as testutils +import ptf.mask as mask + +def test_some_traffic(ptfadapter): + pkt = testutils.simple_tcp_packet( + eth_dst=host_facts['ansible_Ethernet0']['macaddress'], + eth_src=ptfadapter.dataplane.get_mac(0, 0), + ip_src='1.1.1.1', + ip_dst='192.168.0.1', + ip_ttl=64, + tcp_sport=1234, + tcp_dport=4321) + + exp_pkt = pkt.copy() + exp_pkt = mask.Mask(exp_pkt) + exp_pkt.set_do_not_care_scapy(packet.Ether, 'dst') + exp_pkt.set_do_not_care_scapy(packet.Ether, 'src') + exp_pkt.set_do_not_care_scapy(packet.IP, 'ttl') + exp_pkt.set_do_not_care_scapy(packet.IP, 'chksum') + + testutils.send(ptfadapter, 5, pkt) + testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=[28, 29, 30, 31]) +``` + +If you have changed interface configuration on PTF host (like MAC address change) or you want to run PTF providing custom parameters you can use ```reinit``` method, e.g.: + +```python +def test_some_traffic(ptfadapter): + ptfadapter.reinit({'qlen': 1000}) + # rest of the test ... +``` \ No newline at end of file From 99cd272f48137af618a4646274de958551da44a4 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Mon, 29 Jul 2019 14:07:40 +0300 Subject: [PATCH 21/26] [.gitignore] ignore pytest_cached Signed-off-by: Stepan Blyschak --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index aae273b6f86..b05ece42aa5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ # Compiled Python files *.pyc __pycache__/ +.pytest_cache/ \ No newline at end of file From 3fcab3474d87efd821814f934b1bd599bbea9a68 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Mon, 29 Jul 2019 14:08:32 +0300 Subject: [PATCH 22/26] [tests/conftest] add shortcut fixtures for DUT and PTF Signed-off-by: Stepan Blyschak --- tests/conftest.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index dd56286114a..5b0fc8fbbec 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,8 @@ import csv import ipaddr as ipaddress +from ansible_host import AnsibleHost + pytest_plugins = ('ptf_fixtures', 'ansible_fixtures') @@ -56,3 +58,23 @@ def testbed(request): tbinfo = TestbedInfo(tbfile) return tbinfo.testbed_topo[tbname] + + +@pytest.fixture(scope="module") +def duthost(ansible_adhoc, testbed): + """ + Shortcut fixture for getting DUT host + """ + + hostname = testbed['dut'] + return AnsibleHost(ansible_adhoc, hostname) + + +@pytest.fixture(scope="module") +def ptfhost(ansible_adhoc, testbed): + """ + Shortcut fixture for getting PTF host + """ + + hostname = testbed['ptf'] + return AnsibleHost(ansible_adhoc, hostname) From 06486b3e02fab90d7752c1ab1f967dfddd64257d Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Mon, 29 Jul 2019 14:09:03 +0300 Subject: [PATCH 23/26] [tests/fdb] remove __init__.py from fdb test Signed-off-by: Stepan Blyschak --- tests/fdb/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/fdb/__init__.py diff --git a/tests/fdb/__init__.py b/tests/fdb/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 From 4547af7d387a3ae277c59d10b0f83c1c681b922d Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Mon, 29 Jul 2019 14:43:28 +0300 Subject: [PATCH 24/26] [tests/fdb] use pprint Signed-off-by: Stepan Blyschak --- tests/fdb/test_fdb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/fdb/test_fdb.py b/tests/fdb/test_fdb.py index 1a4a5eb862b..b3c39873bff 100644 --- a/tests/fdb/test_fdb.py +++ b/tests/fdb/test_fdb.py @@ -6,6 +6,7 @@ import time import itertools import logging +import pprint DEFAULT_FDB_ETHERNET_TYPE = 0x1234 DUMMY_MAC_PREFIX = "02:11:22:33" @@ -139,10 +140,9 @@ def test_fdb(ansible_adhoc, testbed, ptfadapter): for src_mac, dst_mac in itertools.product(fdb[src], fdb[dst]): send_recv_eth(ptfadapter, src, src_mac, dst, dst_mac) + # Should we have fdb_facts ansible module for this test? res = duthost.command('show mac') - logger.debug('"show mac" output on DUT') - for line in res['stdout_lines']: - logger.debug(' {}'.format(line)) + logger.info('"show mac" output on DUT:\n{}'.format(pprint.pformat(res['stdout_lines']))) dummy_mac_count = 0 total_mac_count = 0 From 8540053248c6d97e78d62e38358d8ef0bce66fb2 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Mon, 29 Jul 2019 17:57:15 +0300 Subject: [PATCH 25/26] [tests/lldp] read eos snmp community string Signed-off-by: Stepan Blyschak --- tests/conftest.py | 9 +++++++++ tests/eos | 1 + tests/test_lldp.py | 5 ++--- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 120000 tests/eos diff --git a/tests/conftest.py b/tests/conftest.py index 5b0fc8fbbec..e5380c840a6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ import pytest import csv +import yaml import ipaddr as ipaddress from ansible_host import AnsibleHost @@ -78,3 +79,11 @@ def ptfhost(ansible_adhoc, testbed): hostname = testbed['ptf'] return AnsibleHost(ansible_adhoc, hostname) + + +@pytest.fixture(scope='session') +def eos(): + """ read and yield eos configuration """ + with open('eos/eos.yml') as stream: + eos = yaml.safe_load(stream) + return eos diff --git a/tests/eos b/tests/eos new file mode 120000 index 00000000000..798ba784963 --- /dev/null +++ b/tests/eos @@ -0,0 +1 @@ +../ansible/group_vars/eos/ \ No newline at end of file diff --git a/tests/test_lldp.py b/tests/test_lldp.py index 6f791566ad4..fea40465033 100644 --- a/tests/test_lldp.py +++ b/tests/test_lldp.py @@ -27,7 +27,7 @@ def test_lldp(localhost, ansible_adhoc, testbed): assert v['port']['ifname'] == mg_facts['minigraph_neighbors'][k]['port'] -def test_lldp_neighbor(localhost, ansible_adhoc, testbed): +def test_lldp_neighbor(localhost, ansible_adhoc, testbed, eos): """ verify LLDP information on neighbors """ hostname = testbed['dut'] @@ -45,8 +45,7 @@ def test_lldp_neighbor(localhost, ansible_adhoc, testbed): continue hostip = v['chassis']['mgmt-ip'] - # TODO: Need to read eos.yml to get SNMP version and community string - nei_lldp_facts = lhost.lldp_facts(host=hostip, version='v2c', community='strcommunity')['ansible_facts'] + nei_lldp_facts = lhost.lldp_facts(host=hostip, version='v2c', community=eos['snmp_rocommunity'])['ansible_facts'] print nei_lldp_facts neighbor_interface = v['port']['ifname'] # Verify the published DUT system name field is correct From 323d9588b4ad0364975a5df8620b49c194ebac22 Mon Sep 17 00:00:00 2001 From: Stepan Blyschak Date: Thu, 1 Aug 2019 16:17:41 +0300 Subject: [PATCH 26/26] [pytest_runner] use inventory_file and testbed_file variables Signed-off-by: Stepan Blyschak --- ansible/roles/test/tasks/pytest_runner.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ansible/roles/test/tasks/pytest_runner.yml b/ansible/roles/test/tasks/pytest_runner.yml index 3ca3c332e36..01060bbc339 100644 --- a/ansible/roles/test/tasks/pytest_runner.yml +++ b/ansible/roles/test/tasks/pytest_runner.yml @@ -33,14 +33,13 @@ set_fact: pytest_cmd: '{{ pytest_cmd }} --testbed={{ testbed_name }}' -# We could use ansible_inventory_sources magic variable but it is available in ansible >=2.5 - name: append inventory file set_fact: - pytest_cmd: '{{ pytest_cmd }} --inventory=../ansible/inventory' + pytest_cmd: '{{ pytest_cmd }} --inventory=../ansible/{{ inventory_file }}' - name: append testbed file set_fact: - pytest_cmd: '{{ pytest_cmd }} --testbed_file=../ansible/testbed.csv' + pytest_cmd: '{{ pytest_cmd }} --testbed_file=../ansible/{{ testbed_file }}' - name: append host pattern set_fact: