From 23b0e07d48cd635e2649207eb08d1b470240a9ec Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Sat, 7 Nov 2020 13:18:40 -0800 Subject: [PATCH] [minigraph.py]: Parse IP-in-IP tunnels from minigraph (#5742) Take tunnel info from `` tag in minigraph, and create tables in config_DB: ``` "TUNNEL": { "MUX_TUNNEL_0": { "tunnel_type": "IPINIP", "dst_ip": "26.1.1.10", "dscp_mode": "uniform", "encap_ecn_mode": "standard", "ecn_mode": "copy_from_outer", "ttl_mode": "pipe" } } ``` Signed-off-by: Lawrence Lee --- src/sonic-config-engine/minigraph.py | 45 ++++++++++++++-- .../tests/simple-sample-graph-case.xml | 54 ++++++++++++++++++- .../tests/test_minigraph_case.py | 35 +++++++++++- 3 files changed, 129 insertions(+), 5 deletions(-) diff --git a/src/sonic-config-engine/minigraph.py b/src/sonic-config-engine/minigraph.py index 444c0ce52fa8..96b68d7d22ef 100644 --- a/src/sonic-config-engine/minigraph.py +++ b/src/sonic-config-engine/minigraph.py @@ -1,6 +1,7 @@ from __future__ import print_function import calendar +from ipaddress import IPv4Address, IPv4Network, ip_address, ip_network import math import os import sys @@ -280,6 +281,7 @@ def parse_loopback_intf(child): def parse_dpg(dpg, hname): aclintfs = None mgmtintfs = None + tunnelintfs = defaultdict(dict) for child in dpg: """ In Multi-NPU platforms the acl intfs are defined only for the host not for individual asic. @@ -473,7 +475,25 @@ def parse_dpg(dpg, hname): except: print("Warning: Ignoring Control Plane ACL %s without type" % aclname, file=sys.stderr) - return intfs, lo_intfs, mvrf, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls, vni + mg_tunnels = child.find(str(QName(ns, "TunnelInterfaces"))) + if mg_tunnels is not None: + table_key_to_mg_key_map = {"encap_ecn_mode": "EcnEncapsulationMode", + "ecn_mode": "EcnDecapsulationMode", + "dscp_mode": "DifferentiatedServicesCodePointMode", + "ttl_mode": "TtlMode"} + for mg_tunnel in mg_tunnels.findall(str(QName(ns, "TunnelInterface"))): + tunnel_type = mg_tunnel.attrib["Type"] + tunnel_name = mg_tunnel.attrib["Name"] + tunnelintfs[tunnel_type][tunnel_name] = { + "tunnel_type": mg_tunnel.attrib["Type"].upper(), + } + + for table_key, mg_key in table_key_to_mg_key_map.items(): + # If the minigraph has the key, add the corresponding config DB key to the table + if mg_key in mg_tunnel.attrib: + tunnelintfs[tunnel_type][tunnel_name][table_key] = mg_tunnel.attrib[mg_key] + + return intfs, lo_intfs, mvrf, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls, vni, tunnelintfs return None, None, None, None, None, None, None, None, None, None def parse_host_loopback(dpg, hname): @@ -866,6 +886,7 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw intfs = None vlan_intfs = None pc_intfs = None + tunnel_intfs = None vlans = None vlan_members = None pcs = None @@ -923,7 +944,7 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw for child in root: if asic_name is None: if child.tag == str(QName(ns, "DpgDec")): - (intfs, lo_intfs, mvrf, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls, vni) = parse_dpg(child, hostname) + (intfs, lo_intfs, mvrf, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls, vni, tunnel_intfs) = parse_dpg(child, hostname) elif child.tag == str(QName(ns, "CpgDec")): (bgp_sessions, bgp_internal_sessions, bgp_asn, bgp_peers_with_range, bgp_monitors) = parse_cpg(child, hostname) elif child.tag == str(QName(ns, "PngDec")): @@ -938,7 +959,7 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw (port_speeds_default, port_descriptions) = parse_deviceinfo(child, hwsku) else: if child.tag == str(QName(ns, "DpgDec")): - (intfs, lo_intfs, mvrf, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls, vni) = parse_dpg(child, asic_name) + (intfs, lo_intfs, mvrf, mgmt_intf, vlans, vlan_members, pcs, pc_members, acls, vni, tunnel_intfs) = parse_dpg(child, asic_name) host_lo_intfs = parse_host_loopback(child, hostname) elif child.tag == str(QName(ns, "CpgDec")): (bgp_sessions, bgp_internal_sessions, bgp_asn, bgp_peers_with_range, bgp_monitors) = parse_cpg(child, asic_name, local_devices) @@ -1167,6 +1188,8 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw results['VLAN'] = vlans results['VLAN_MEMBER'] = vlan_members + results['TUNNEL'] = get_tunnel_entries(tunnel_intfs, lo_intfs, hostname) + for nghbr in list(neighbors.keys()): # remove port not in port_config.ini if nghbr not in ports: @@ -1234,6 +1257,22 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw return results +def get_tunnel_entries(tunnel_intfs, lo_intfs, hostname): + lo_addr = '' + # Use the first IPv4 loopback as the tunnel destination IP + for addr in lo_intfs.keys(): + ip_addr = ip_network(UNICODE_TYPE(addr[1])) + if isinstance(ip_addr, IPv4Network): + lo_addr = str(ip_addr.network_address) + break + + tunnels = {} + for type, tunnel_dict in tunnel_intfs.items(): + for tunnel_key, tunnel_attr in tunnel_dict.items(): + tunnel_attr['dst_ip'] = lo_addr + tunnels[tunnel_key] = tunnel_attr + return tunnels + def parse_device_desc_xml(filename): root = ET.parse(filename).getroot() diff --git a/src/sonic-config-engine/tests/simple-sample-graph-case.xml b/src/sonic-config-engine/tests/simple-sample-graph-case.xml index 6c7729fd0696..a3383a094602 100644 --- a/src/sonic-config-engine/tests/simple-sample-graph-case.xml +++ b/src/sonic-config-engine/tests/simple-sample-graph-case.xml @@ -126,6 +126,9 @@ + + + ab1 @@ -232,12 +235,36 @@ U true + + LogicalLink + 0 + false + switch-t0 + MuxTunnel0 + false + switch2-t0 + MuxTunnel0 + true + +
+ 26.1.1.10 +
switch-t0 Force10-S6000
+ +
+ 25.1.1.10 +
+ + 10.7.0.196/26 + + switch2-t0 + Force10-S6000 +
switch-01t1
@@ -252,6 +279,31 @@ + + + + + + + DevicePeeringLink + + True + + + UpperTOR + + switch-t0 + + + LowerTOR + + switch2-t0 + + + switch2-t0:MuxTunnel0;switch-t0:MuxTunnel0 + + + @@ -372,6 +424,6 @@ - switch-T0 + switch-t0 Force10-S6000 diff --git a/src/sonic-config-engine/tests/test_minigraph_case.py b/src/sonic-config-engine/tests/test_minigraph_case.py index e149ff64670b..de4f6b77ce50 100644 --- a/src/sonic-config-engine/tests/test_minigraph_case.py +++ b/src/sonic-config-engine/tests/test_minigraph_case.py @@ -121,7 +121,21 @@ def test_minigraph_neighbor_metadata(self): output = self.run_script(argument) self.assertEqual( utils.to_dict(output.strip()), - utils.to_dict("{'switch-01t1': {'lo_addr': '10.1.0.186/32', 'mgmt_addr': '10.7.0.196/26', 'hwsku': 'Force10-S6000', 'type': 'LeafRouter', 'deployment_id': '2'}}") + utils.to_dict("{" \ + "'switch-01t1': {" \ + "'lo_addr': '10.1.0.186/32'," \ + "'mgmt_addr': '10.7.0.196/26'," \ + "'hwsku': 'Force10-S6000'," \ + "'type': 'LeafRouter'," \ + "'deployment_id': '2'" \ + "}," \ + "'switch2-t0': {" \ + "'hwsku': 'Force10-S6000'," \ + "'lo_addr': '25.1.1.10'," \ + "'mgmt_addr': '10.7.0.196/26'," \ + "'type': 'ToRRouter'" \ + "}" \ + "}") ) # everflow portion is not used @@ -170,3 +184,22 @@ def test_mux_cable_parsing(self): self.assertTrue(port["mux_cable"]) else: self.assertTrue("mux_cable" not in port) + + def test_minigraph_tunnel_table(self): + argument = '-m "' + self.sample_graph + '" -p "' + self.port_config + '" -v "TUNNEL"' + expected_tunnel = { + "MuxTunnel0": { + "tunnel_type": "IPINIP", + "dst_ip": "10.1.0.32", + "dscp_mode": "uniform", + "encap_ecn_mode": "standard", + "ecn_mode": "copy_from_outer", + "ttl_mode": "pipe" + } + } + + output = self.run_script(argument) + self.assertEqual( + utils.to_dict(output.strip()), + expected_tunnel + )