diff --git a/.azure-pipelines/pr_test_scripts.yaml b/.azure-pipelines/pr_test_scripts.yaml index 527190a2124..be5463f544d 100644 --- a/.azure-pipelines/pr_test_scripts.yaml +++ b/.azure-pipelines/pr_test_scripts.yaml @@ -694,6 +694,7 @@ t1-lag-vpp: - lldp/test_lldp.py - lldp/test_lldp_syncd.py - log_fidelity/test_bgp_shutdown.py + - mpls/test_mpls.py - pc/test_po_voq.py - pc/test_lag_member.py - pc/test_po_update.py diff --git a/ansible/library/generate_golden_config_db.py b/ansible/library/generate_golden_config_db.py index 2fe6b6e284b..ac4da87a1a2 100644 --- a/ansible/library/generate_golden_config_db.py +++ b/ansible/library/generate_golden_config_db.py @@ -1338,6 +1338,21 @@ def generate(self): if self.has_otel_image(): config = self.overwrite_feature_golden_config_db_singleasic(config, "otel", "enabled", "enabled") + # Register the mpls feature on the sonic-vpp testbeds so the MPLS data-plane + # tests (tests/mpls) are not skipped by the "'mpls' not in feature_status" + # condition in tests_mark_conditions.yaml. The VPP SAI implementation + # supports MPLS (INSEG disposition/imposition). + # + # The state is deliberately "disabled": mpls is a config flag, not a + # containerized service, so there is no "mpls" docker. Registering it as + # "enabled" would make the image's monit container_checker report + # "Expected containers not running: mpls" and fail the sanity check for + # every test on this platform. The condition above only tests for the + # presence of the key, so "disabled" is enough to un-skip the tests. + if "vpp" in self.topo_name: + config = self.overwrite_feature_golden_config_db_singleasic( + config, "mpls", auto_restart="disabled", state="disabled") + # Disable dash-ha feature for all multi-asic platforms if multi_asic.is_multi_asic(): config = self.overwrite_feature_golden_config_db_multiasic(config, "dash-ha", feature_data={ diff --git a/tests/common/plugins/conditional_mark/tests_mark_conditions_sonic_vpp.yaml b/tests/common/plugins/conditional_mark/tests_mark_conditions_sonic_vpp.yaml index 6751c280243..5c6938bfc11 100644 --- a/tests/common/plugins/conditional_mark/tests_mark_conditions_sonic_vpp.yaml +++ b/tests/common/plugins/conditional_mark/tests_mark_conditions_sonic_vpp.yaml @@ -401,10 +401,12 @@ memory_checker/test_memory_checker.py::test_monit_reset_counter_failure: ####################################### ##### MPLS ##### ####################################### -mpls/test_mpls.py: +mpls/test_mpls.py::TestBasicMpls::test_push_label: skip: reason: > - Unsupported + MPLS push is not testable here: the test injects the push route + directly into ROUTE_TABLE and orchagent does not install that route + into ASIC_DB, so it never reaches the SAI backend. conditions_logical_operator: or conditions: - "asic_type in ['vpp']" diff --git a/tests/mpls/conftest.py b/tests/mpls/conftest.py index ce32578262b..0c1d6c83615 100644 --- a/tests/mpls/conftest.py +++ b/tests/mpls/conftest.py @@ -14,6 +14,22 @@ LABEL_DEL_ROUTES = 'label_del_routes' +def _resolve_ptf_port_ids(dut_port, mg_facts): + """Resolve a DUT L3 interface to its PTF port indices. + + On t1-lag the spine/tor facing interfaces can be PortChannels, which are not + present in minigraph_port_indices. Resolve such a PortChannel to the PTF port + indices of its physical member ports. A physical interface resolves to a + single-element list. + """ + portchannels = mg_facts.get('minigraph_portchannels', {}) + if dut_port in portchannels: + members = portchannels[dut_port]['members'] + else: + members = [dut_port] + return [mg_facts['minigraph_port_indices'][member] for member in members] + + @pytest.fixture(scope='module') def setup(duthost, tbinfo, ptfadapter): """ @@ -22,9 +38,24 @@ def setup(duthost, tbinfo, ptfadapter): :param tbinfo: fixture provides information about testbed :return: dictionary with all test required information """ - if tbinfo['topo']['name'] not in ('t1'): + if tbinfo['topo']['type'] != 't1': pytest.skip('Unsupported topology') + # Enabling MPLS on an interface makes intfmgrd run + # "sysctl -w net.mpls.conf..input=1", which needs the mpls_router kernel + # module. The module ships in the image but nothing loads it, so without this + # the sysctl fails and is logged as an ERR. sonic-swss's own MPLS test loads it + # the same way. Left loaded on teardown: modprobe is idempotent and unloading + # could disrupt anything else using MPLS. + if duthost.facts['asic_type'] == 'vpp': + result = duthost.shell('modprobe mpls_router', module_ignore_errors=True) + if result['rc'] != 0: + # Not fatal here: let the test itself fail on the resulting syslog + # error rather than hiding a genuine loss of kernel MPLS support + # behind a setup failure. + logger.warning('Failed to load mpls_router: %s. Enabling MPLS on an ' + 'interface will log a setIntfMpls error.', result['stderr']) + # gather ansible facts mg_facts = duthost.minigraph_facts(host=duthost.hostname)['ansible_facts'] host_facts = duthost.setup()['ansible_facts'] @@ -60,15 +91,20 @@ def setup(duthost, tbinfo, ptfadapter): logger.info('spine_ports: {}'.format(spine_ports)) logger.info('tor_addr: {}'.format(tor_addr)) + # The test needs both a T2-facing ingress and a T0-facing egress interface. + # Some t1 variants (e.g. t1-backend, whose neighbors are all BT0) have no T2 + # peer at all, so bail out cleanly instead of failing later in random.choice(). + if not spine_ports or not tor_ports: + pytest.skip('Topology has no T2-facing ({}) or T0-facing ({}) interface' + .format(len(spine_ports), len(tor_ports))) + for dut_port in tor_ports: - port_id = mg_facts['minigraph_port_indices'][dut_port] - tor_ports_ids[dut_port] = port_id + tor_ports_ids[dut_port] = _resolve_ptf_port_ids(dut_port, mg_facts) ansible_port = 'ansible_'+dut_port tor_mac[dut_port] = host_facts[ansible_port]['macaddress'] for dut_port in spine_ports: - port_id = mg_facts['minigraph_port_indices'][dut_port] - spine_ports_ids[dut_port] = port_id + spine_ports_ids[dut_port] = _resolve_ptf_port_ids(dut_port, mg_facts) ansible_port = 'ansible_'+dut_port spine_mac[dut_port] = host_facts[ansible_port]['macaddress'] @@ -78,8 +114,10 @@ def setup(duthost, tbinfo, ptfadapter): src_port = random.choice(spine_ports) dst_port = random.choice(tor_ports) + # dst_pid is the list of egress PortChannel member PTF ports (verify on any member). + # src_pid is a single ingress member PTF port used to inject the test packet. dst_pid = tor_ports_ids[dst_port] - src_pid = spine_ports_ids[src_port] + src_pid = spine_ports_ids[src_port][0] dst_mac = tor_mac[dst_port] src_mac = spine_mac[src_port] diff --git a/tests/mpls/test_mpls.py b/tests/mpls/test_mpls.py index fb0a5aa1b5f..e67972356c4 100644 --- a/tests/mpls/test_mpls.py +++ b/tests/mpls/test_mpls.py @@ -106,7 +106,6 @@ def expected_mask_ip_packet(self, pkt): exp_pkt['Ethernet'].remove_payload() exp_pkt /= pkt1 exp_pkt = mask.Mask(exp_pkt) - 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, 'chksum') @@ -198,7 +197,7 @@ def test_pop_label(self, setup, ptfadapter): ptfadapter.dataplane.flush() testutils.send(ptfadapter, src_pid, pkt) try: - res = testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=[dst_pid]) + res = testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=dst_pid) logger.info(res) except Exception as e: self.teardown_labels(setup) @@ -222,7 +221,7 @@ def test_swap_label(self, setup, ptfadapter): ptfadapter.dataplane.flush() testutils.send(ptfadapter, src_pid, pkt) try: - res = testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=[dst_pid]) + res = testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=dst_pid) logger.info(res) except Exception as e: self.teardown_labels(setup) @@ -255,7 +254,7 @@ def test_push_label(self, setup, ptfadapter): testutils.send(ptfadapter, src_pid, pkt) try: - res = testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=[dst_pid]) + res = testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=dst_pid) logger.info(res) except Exception as e: self.teardown_labels(setup) @@ -280,7 +279,7 @@ def test_swap_labelstack(self, setup, ptfadapter): testutils.send(ptfadapter, src_pid, pkt) try: - res = testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=[dst_pid]) + res = testutils.verify_packet_any_port(ptfadapter, exp_pkt, ports=dst_pid) logger.info(res) except Exception as e: self.teardown_labels(setup)