From 866825a0f442c5ec9a6261d4e25baa49f15ccda7 Mon Sep 17 00:00:00 2001 From: Longxiang Lyu Date: Mon, 3 Aug 2020 06:44:18 +0000 Subject: [PATCH 1/2] [conn_graph_facts] Add check for links between DUTs For graph files that includes links between DUTs(`fullmesh` topo for Spytest), `conn_graph_facts` should skip those links when parsing vlan configs for links(connected to Fanout switch). Signed-off-by: Longxiang Lyu --- ansible/library/conn_graph_facts.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ansible/library/conn_graph_facts.py b/ansible/library/conn_graph_facts.py index de01ce16e27..191064ff46e 100755 --- a/ansible/library/conn_graph_facts.py +++ b/ansible/library/conn_graph_facts.py @@ -188,6 +188,8 @@ def get_host_vlan(self, hostname): self.vlanport[hostname] = {} for port in self.links[hostname]: peerdevice = self.links[hostname][port]['peerdevice'] + if self.devices[peerdevice]["Type"].lower() == "devsonic": + continue peerport = self.links[hostname][port]['peerport'] peerportmode = self.vlanport[peerdevice][peerport]['mode'] peervlanids = self.vlanport[peerdevice][peerport]['vlanids'] From d8f4ad1815373c0865c97cdc2afb48d991684fa2 Mon Sep 17 00:00:00 2001 From: Longxiang Lyu Date: Mon, 3 Aug 2020 06:48:11 +0000 Subject: [PATCH 2/2] [pytest] Add testcase to generate Spytest testbed file * Add template file `tests/template/spytest_testbed.yaml.j2` as the testbed template. * Add a testcase: `tests/testbed_setup/test_gen_spy_testbed.py::test_gen_spy_testbed` to generate the testbed file for Spytest and save as `spytest/testbeds/spytest_testbed.yaml`. Signed-off-by: Longxiang Lyu --- tests/templates/spytest_testbed.yaml.j2 | 41 ++++++++ tests/testbed_setup/test_gen_spy_testbed.py | 109 ++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 tests/templates/spytest_testbed.yaml.j2 create mode 100644 tests/testbed_setup/test_gen_spy_testbed.py diff --git a/tests/templates/spytest_testbed.yaml.j2 b/tests/templates/spytest_testbed.yaml.j2 new file mode 100644 index 00000000000..d98f5b6d58a --- /dev/null +++ b/tests/templates/spytest_testbed.yaml.j2 @@ -0,0 +1,41 @@ +version: 2.0 + +services: {default: !include sonic_services.yaml} + +params: !include sonic_params.yaml +builds: !include sonic_builds.yaml +speeds: !include sonic_speeds.yaml +errors: !include sonic_errors.yaml +instrument: !include sonic_instrument.yaml + +configs: + default: !include sonic_configs.yaml + empty: {current: [], restore: []} + +devices: + {% for hostname, login_info in devices %} + {{ hostname }}: + device_type: DevSonic + access: {{ login_info.login_access }} + credentials: {{ login_info.login_credentials }} + properties: {config: default, build: default, services: default, params: def_dut, speed: default, errors: default} + {% endfor %} + ptf-01: + device_type: TGEN + properties: {type: scapy, version: 1.0, ip: {{ testbed.ptf_ip }}, params: def_tg} + +topology: + ptf-01: + interfaces: + {% for ptf_conn in ptf_connections %} + {{ ptf_conn.start_port }}: {{ "{" }}EndDevice: {{ ptf_conn.end_device }}, EndPort: {{ ptf_conn.end_port }}, params: def_tg_link{{ "}" }} + {% endfor %} + {% for hostname in testbed.duts %} + {% if hostname in dev_connections %} + {{ hostname }}: + interfaces: + {% for dev_conn in dev_connections[hostname] %} + {{ dev_conn.start_port }}: {{ "{" }}EndDevice: {{ dev_conn.end_device }}, EndPort: {{ dev_conn.end_port }}, params: def_tg_link{{ "}" }} + {% endfor %} + {% endif %} + {% endfor %} diff --git a/tests/testbed_setup/test_gen_spy_testbed.py b/tests/testbed_setup/test_gen_spy_testbed.py new file mode 100644 index 00000000000..03e3e6c2a10 --- /dev/null +++ b/tests/testbed_setup/test_gen_spy_testbed.py @@ -0,0 +1,109 @@ +import logging +import json +import os +import pytest +import yaml + +from collections import defaultdict +from jinja2 import Template + + +TESTBED_TEMPLATE = "templates/spytest_testbed.yaml.j2" +PTF_INTERFACE_TEMPLATE = "1/%d" + +pytestmark = [ + pytest.mark.topology("util"), + pytest.mark.sanity_check(skip_sanity=True) +] + + +@pytest.fixture(scope="function") +def hostvars(duthosts): + """Return host variables dicts for DUTs defined in testbed.""" + if not duthosts: + return {} + var_manager = duthosts[-1].host.options["variable_manager"] + hostvars_all = var_manager.get_vars()["hostvars"] + return {duthost.hostname: hostvars_all[duthost.hostname] + for duthost in duthosts} + + +def test_gen_spy_testbed(conn_graph_facts_multi_duts, hostvars, testbed, + pytestconfig): + """Generate spytest testbed file.""" + + def _interface_key(interface): + """Get interface key to sort.""" + return list(map(int, interface.lstrip("Ethernet").split("/"))) + + def _to_string(obj): + """Convert unicodes in obj to strings""" + return yaml.safe_load(json.dumps(obj)) + + hostnames = testbed["duts"] + device_conn = _to_string(conn_graph_facts_multi_duts["device_conn"]) + connections = dict( + zip(hostnames, conn_graph_facts_multi_duts["device_conn"])) + + # devices section + devices = [] + for hostname in hostnames: + hostvar = hostvars[hostname] + login_info = {} + login_info["login_access"] = \ + json.dumps(hostvar["login_access"]).replace('"', '') + login_info["login_credentials"] = \ + json.dumps(hostvar["login_credentials"]).replace('"', '') + devices.append((hostname, login_info)) + + # topology section + ptf_connections = [] + intf = 1 + for hostname in hostnames: + end_device = hostname + conns = connections[hostname] + end_ports = sorted( + (_ for _ in conns if conns[_]['peerdevice'] not in connections), + key=_interface_key) + for end_port in end_ports: + ptf_conn = { + "start_port": PTF_INTERFACE_TEMPLATE % intf, + "end_device": hostname, + "end_port": end_port + } + ptf_connections.append(ptf_conn) + conns.pop(end_port) + intf += 1 + + dev_connections = defaultdict(list) + for hostname in hostnames: + conns = connections[hostname] + for start_port in sorted(conns.keys(), key=_interface_key): + end_device = conns[start_port]["peerdevice"] + end_port = conns[start_port]["peerport"] + dev_connections[hostname].append( + { + "start_port": start_port, + "end_device": end_device, + "end_port": end_port + } + ) + connections[end_device].pop(end_port) + + # write to testbed dest file + with open(TESTBED_TEMPLATE) as tmpl_fd: + testbed_tmpl = Template( + tmpl_fd.read(), trim_blocks=True, lstrip_blocks=True) + testbed_file = os.path.join(str(pytestconfig.rootdir), + "../spytest/testbeds/spytest_testbed.yaml") + testbed_file = os.path.normpath(testbed_file) + logging.info("testbed save path: %s", testbed_file) + if os.path.exists(testbed_file): + logging.warn("testbed file(%s) exists, overwrite!", testbed_file) + testbed_stream = testbed_tmpl.stream( + devices=devices, + testbed=testbed, + ptf_connections=ptf_connections, + dev_connections=dev_connections + ) + testbed_stream.dump(testbed_file)