From a92da8304795533fa89dd7516e5cc3c3c09099d3 Mon Sep 17 00:00:00 2001 From: Maxime Lorrillere Date: Tue, 27 Apr 2021 12:18:45 -0700 Subject: [PATCH] [chassis] VoQ configuration using minigraph.xml file (#5991) This commit contains the following changes to support for configuring a VoQ switch using a minigraph.xml file.: - Add support for system ports configuration to minigraph - Add support for SwitchId, SwitchType and MaxCores to minigraph - Add support for inband vlan configuration in minigraph - `asic_name` is now a mandatory attribute in CONFIG_DB on VoQ switches Co-authored-by: Maxime Lorrillere --- src/sonic-config-engine/minigraph.py | 102 +++++- .../tests/sample-voq-graph.xml | 338 ++++++++++++++++++ src/sonic-config-engine/tests/test_cfggen.py | 42 +++ 3 files changed, 470 insertions(+), 12 deletions(-) create mode 100644 src/sonic-config-engine/tests/sample-voq-graph.xml diff --git a/src/sonic-config-engine/minigraph.py b/src/sonic-config-engine/minigraph.py index 8c5b21fe1437..3bbf4095a32f 100644 --- a/src/sonic-config-engine/minigraph.py +++ b/src/sonic-config-engine/minigraph.py @@ -456,6 +456,17 @@ def parse_dpg(dpg, hname): gwaddr = ipaddress.ip_address(next(mgmtipn.hosts())) mgmt_intf[(intfname, ipprefix)] = {'gwaddr': gwaddr} + voqinbandintfs = child.find(str(QName(ns, "VoqInbandInterfaces"))) + voq_inband_intfs = {} + if voqinbandintfs: + for voqintf in voqinbandintfs.findall(str(QName(ns1, "VoqInbandInterface"))): + intfname = voqintf.find(str(QName(ns, "Name"))).text + intftype = voqintf.find(str(QName(ns, "Type"))).text + ipprefix = voqintf.find(str(QName(ns1, "PrefixStr"))).text + if intfname not in voq_inband_intfs: + voq_inband_intfs[intfname] = {'inband_type': intftype} + voq_inband_intfs["%s|%s" % (intfname, ipprefix)] = {} + pcintfs = child.find(str(QName(ns, "PortChannelInterfaces"))) pc_intfs = [] pcs = {} @@ -667,8 +678,8 @@ def parse_dpg(dpg, hname): 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, dpg_ecmp_content - return None, None, None, None, None, None, None, None, None, None + return intfs, lo_intfs, mvrf, mgmt_intf, voq_inband_intfs, vlans, vlan_members, pcs, pc_members, acls, vni, tunnelintfs, dpg_ecmp_content + return None, None, None, None, None, None, None, None, None, None, None, None, None def parse_host_loopback(dpg, hname): for child in dpg: @@ -794,6 +805,9 @@ def parse_meta(meta, hname): cloudtype = None resource_type = None downstream_subrole = None + switch_id = None + switch_type = None + max_cores = None kube_data = {} device_metas = meta.find(str(QName(ns, "Devices"))) for device in device_metas.findall(str(QName(ns1, "DeviceMetadata"))): @@ -825,11 +839,17 @@ def parse_meta(meta, hname): resource_type = value elif name == "DownStreamSubRole": downstream_subrole = value + elif name == "SwitchId": + switch_id = value + elif name == "SwitchType": + switch_type = value + elif name == "MaxCores": + max_cores = value elif name == "KubernetesEnabled": kube_data["enable"] = value elif name == "KubernetesServerIp": kube_data["ip"] = value - return syslog_servers, dhcp_servers, ntp_servers, tacacs_servers, mgmt_routes, erspan_dst, deployment_id, region, cloudtype, resource_type, downstream_subrole, kube_data + return syslog_servers, dhcp_servers, ntp_servers, tacacs_servers, mgmt_routes, erspan_dst, deployment_id, region, cloudtype, resource_type, downstream_subrole, switch_id, switch_type, max_cores, kube_data def parse_linkmeta(meta, hname): @@ -886,6 +906,9 @@ def parse_linkmeta(meta, hname): def parse_asic_meta(meta, hname): sub_role = None + switch_id = None + switch_type = None + max_cores = None device_metas = meta.find(str(QName(ns, "Devices"))) for device in device_metas.findall(str(QName(ns1, "DeviceMetadata"))): if device.find(str(QName(ns1, "Name"))).text.lower() == hname.lower(): @@ -895,7 +918,13 @@ def parse_asic_meta(meta, hname): value = device_property.find(str(QName(ns1, "Value"))).text if name == "SubRole": sub_role = value - return sub_role + elif name == "SwitchId": + switch_id = value + elif name == "SwitchType": + switch_type = value + elif name == "MaxCores": + max_cores = value + return sub_role, switch_id, switch_type, max_cores def parse_deviceinfo(meta, hwsku): port_speeds = {} @@ -912,7 +941,28 @@ def parse_deviceinfo(meta, hwsku): if desc != None: port_descriptions[port_alias_map.get(alias, alias)] = desc.text port_speeds[port_alias_map.get(alias, alias)] = speed - return port_speeds, port_descriptions + + sysports = device_info.find(str(QName(ns, "SystemPorts"))) + sys_ports = {} + if sysports is not None: + for sysport in sysports.findall(str(QName(ns, "SystemPort"))): + portname = sysport.find(str(QName(ns, "Name"))).text + hostname = sysport.find(str(QName(ns, "Hostname"))) + asic_name = sysport.find(str(QName(ns, "AsicName"))) + system_port_id = sysport.find(str(QName(ns, "SystemPortId"))).text + switch_id = sysport.find(str(QName(ns, "SwitchId"))).text + core_id = sysport.find(str(QName(ns, "CoreId"))).text + core_port_id = sysport.find(str(QName(ns, "CorePortId"))).text + speed = sysport.find(str(QName(ns, "Speed"))).text + num_voq = sysport.find(str(QName(ns, "NumVoq"))).text + key = portname + if asic_name is not None: + key = "%s|%s" % (asic_name.text, key) + if hostname is not None: + key = "%s|%s" % (hostname.text, key) + sys_ports[key] = {"system_port_id": system_port_id, "switch_id": switch_id, "core_index": core_id, "core_port_index": core_port_id, "speed": speed, "num_voq": num_voq} + + return port_speeds, port_descriptions, sys_ports # Function to check if IP address is present in the key. # If it is present, then the key would be a tuple. @@ -1097,6 +1147,7 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw vlan_members = None pcs = None mgmt_intf = None + voq_inband_intfs = None lo_intfs = None neighbors = None devices = None @@ -1107,6 +1158,7 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw port_speeds_default = {} port_speed_png = {} port_descriptions = {} + sys_ports = {} console_ports = {} mux_cable_ports = {} syslog_servers = [] @@ -1119,6 +1171,9 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw deployment_id = None region = None cloudtype = None + switch_id = None + switch_type = None + max_cores = None hostname = None linkmetas = {} host_lo_intfs = None @@ -1153,7 +1208,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, tunnel_intfs, dpg_ecmp_content) = parse_dpg(child, hostname) + (intfs, lo_intfs, mvrf, mgmt_intf, voq_inband_intfs, vlans, vlan_members, pcs, pc_members, acls, vni, tunnel_intfs, dpg_ecmp_content) = parse_dpg(child, hostname) elif child.tag == str(QName(ns, "CpgDec")): (bgp_sessions, bgp_internal_sessions, bgp_voq_chassis_sessions, bgp_asn, bgp_peers_with_range, bgp_monitors) = parse_cpg(child, hostname) elif child.tag == str(QName(ns, "PngDec")): @@ -1161,25 +1216,25 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw elif child.tag == str(QName(ns, "UngDec")): (u_neighbors, u_devices, _, _, _, _, _, _) = parse_png(child, hostname, None) elif child.tag == str(QName(ns, "MetadataDeclaration")): - (syslog_servers, dhcp_servers, ntp_servers, tacacs_servers, mgmt_routes, erspan_dst, deployment_id, region, cloudtype, resource_type, downstream_subrole, kube_data) = parse_meta(child, hostname) + (syslog_servers, dhcp_servers, ntp_servers, tacacs_servers, mgmt_routes, erspan_dst, deployment_id, region, cloudtype, resource_type, downstream_subrole, switch_id, switch_type, max_cores, kube_data) = parse_meta(child, hostname) elif child.tag == str(QName(ns, "LinkMetadataDeclaration")): linkmetas = parse_linkmeta(child, hostname) elif child.tag == str(QName(ns, "DeviceInfos")): - (port_speeds_default, port_descriptions) = parse_deviceinfo(child, hwsku) + (port_speeds_default, port_descriptions, sys_ports) = 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, tunnel_intfs, dpg_ecmp_content) = parse_dpg(child, asic_name) + (intfs, lo_intfs, mvrf, mgmt_intf, voq_inband_intfs, vlans, vlan_members, pcs, pc_members, acls, vni, tunnel_intfs, dpg_ecmp_content) = 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_voq_chassis_sessions, bgp_asn, bgp_peers_with_range, bgp_monitors) = parse_cpg(child, asic_name, local_devices) elif child.tag == str(QName(ns, "PngDec")): (neighbors, devices, port_speed_png) = parse_asic_png(child, asic_name, hostname) elif child.tag == str(QName(ns, "MetadataDeclaration")): - (sub_role) = parse_asic_meta(child, asic_name) + (sub_role, switch_id, switch_type, max_cores ) = parse_asic_meta(child, asic_name) elif child.tag == str(QName(ns, "LinkMetadataDeclaration")): linkmetas = parse_linkmeta(child, hostname) elif child.tag == str(QName(ns, "DeviceInfos")): - (port_speeds_default, port_descriptions) = parse_deviceinfo(child, hwsku) + (port_speeds_default, port_descriptions, sys_ports) = parse_deviceinfo(child, hwsku) # set the host device type in asic metadata also device_type = [devices[key]['type'] for key in devices if key.lower() == hostname.lower()][0] @@ -1232,6 +1287,26 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw current_device['sub_role'] = sub_role results['DEVICE_METADATA']['localhost']['sub_role'] = sub_role results['DEVICE_METADATA']['localhost']['asic_name'] = asic_name + elif switch_type == "voq": + # On Voq switches asic_name is mandatory even on single-asic devices + results['DEVICE_METADATA']['localhost']['asic_name'] = 'Asic0' + + # on Voq system each asic has a switch_id + if switch_id is not None: + results['DEVICE_METADATA']['localhost']['switch_id'] = switch_id + # on Voq system each asic has a switch_type + if switch_type is not None: + results['DEVICE_METADATA']['localhost']['switch_type'] = switch_type + # on Voq system each asic has a max_cores + if max_cores is not None: + results['DEVICE_METADATA']['localhost']['max_cores'] = max_cores + + # Voq systems have an inband interface + if voq_inband_intfs is not None: + results['VOQ_INBAND_INTERFACE'] = {} + for key in voq_inband_intfs: + results['VOQ_INBAND_INTERFACE'][key] = voq_inband_intfs[key] + if resource_type is not None: results['DEVICE_METADATA']['localhost']['resource_type'] = resource_type @@ -1312,6 +1387,9 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw results['INTERFACE'] = phyport_intfs results['VLAN_INTERFACE'] = vlan_intfs + if sys_ports: + results['SYSTEM_PORT'] = sys_ports + for port_name in port_speeds_default: # ignore port not in port_config.ini if port_name not in ports: @@ -1597,7 +1675,7 @@ def parse_asic_sub_role(filename, asic_name): root = ET.parse(filename).getroot() for child in root: if child.tag == str(QName(ns, "MetadataDeclaration")): - sub_role = parse_asic_meta(child, asic_name) + sub_role, _, _, _ = parse_asic_meta(child, asic_name) return sub_role def parse_asic_meta_get_devices(root): diff --git a/src/sonic-config-engine/tests/sample-voq-graph.xml b/src/sonic-config-engine/tests/sample-voq-graph.xml new file mode 100644 index 000000000000..648f0b2b17a7 --- /dev/null +++ b/src/sonic-config-engine/tests/sample-voq-graph.xml @@ -0,0 +1,338 @@ + + + + + + + + + + + HostIP + Loopback0 + + 10.1.0.32/32 + + 10.1.0.32/32 + + + HostIP1 + Loopback0 + + FC00:1::32/128 + + FC00:1::32/128 + + + + + HostIP + eth0 + + 10.0.0.100/24 + + 10.0.0.100/24 + + + + + + + linecard-1 + + + + + + Vlan3094 + Vlan + 1.1.1.1/24 + + + + + + + + + + + + + linecard-1 + Force10-S6000 + + + + + + + linecard-1 + + + DeploymentId + + 1 + + + Region + + usfoo + + + CloudType + + Public + + + ErspanDestinationIpv4 + + 10.0.100.1 + + + NtpResources + + 10.0.10.1;10.0.10.2 + + + + SnmpResources + + 10.0.10.3;10.0.10.4 + + + + SyslogResources + + 10.0.10.5;10.0.10.6; + + + + TacacsServer + + 10.0.10.7;10.0.10.8 + + + ResourceType + + resource_type_x + + + SwitchId + + 0 + + + SwitchType + + voq + + + MaxCores + + 16 + + + + + + + + + + + DeviceInterface + + true + 1 + fortyGigE0/0 + + false + 0 + 0 + 10000 + + + DeviceInterface + + true + 1 + fortyGigE0/4 + + false + 0 + 0 + 25000 + + + DeviceInterface + + true + 1 + fortyGigE0/8 + + false + 0 + 0 + 40000 + Interface description + + + DeviceInterface + + true + 1 + fortyGigE0/12 + + false + 0 + 0 + 100000 + Interface description + + + true + 0 + Force10-S6000 + + + DeviceInterface + + true + 1 + Management1 + false + mgmt1 + 1000 + + + + + Cpu0 + linecard-1 + Asic0 + 1000 + 1 + 0 + 0 + 0 + 8 + + + Ethernet1/1 + linecard-1 + Asic0 + 40000 + 2 + 0 + 0 + 1 + 8 + + + Ethernet1/2 + linecard-1 + Asic0 + 40000 + 3 + 0 + 0 + 2 + 8 + + + Ethernet1/3 + linecard-1 + Asic0 + 40000 + 4 + 0 + 1 + 3 + 8 + + + Ethernet1/4 + linecard-1 + Asic0 + 40000 + 5 + 0 + 1 + 4 + 8 + + + Cpu0 + linecard-2 + Asic0 + 1000 + Cpu0 + 256 + 2 + 0 + 0 + 8 + + + Ethernet1/5 + linecard-2 + Asic0 + 40000 + 257 + 2 + 0 + 1 + 8 + + + Ethernet1/6 + linecard-2 + Asic0 + 40000 + 258 + 2 + 1 + 2 + 8 + + + Cpu0 + linecard-2 + Asic1 + 1000 + Cpu0 + 259 + 4 + 0 + 0 + 8 + + + Ethernet1/7 + linecard-2 + Asic1 + 40000 + 260 + 4 + 0 + 1 + 8 + + + Ethernet1/8 + linecard-2 + Asic1 + 40000 + 261 + 4 + 1 + 2 + 8 + + + + + linecard-1 + Force10-S6000 + diff --git a/src/sonic-config-engine/tests/test_cfggen.py b/src/sonic-config-engine/tests/test_cfggen.py index 9e1a8f8523ba..69da794ca5a0 100644 --- a/src/sonic-config-engine/tests/test_cfggen.py +++ b/src/sonic-config-engine/tests/test_cfggen.py @@ -21,6 +21,7 @@ def setUp(self): self.sample_graph_metadata = os.path.join(self.test_dir, 'simple-sample-graph-metadata.xml') self.sample_graph_pc_test = os.path.join(self.test_dir, 'pc-test-graph.xml') self.sample_graph_bgp_speaker = os.path.join(self.test_dir, 't0-sample-bgp-speaker.xml') + self.sample_graph_voq = os.path.join(self.test_dir, 'sample-voq-graph.xml') self.sample_device_desc = os.path.join(self.test_dir, 'device.xml') self.port_config = os.path.join(self.test_dir, 't0-sample-port-config.ini') self.port_config_autoneg = os.path.join(self.test_dir, 't0-sample-autoneg-port-config.ini') @@ -68,6 +69,11 @@ def test_device_desc_mgmt_ip(self): output = self.run_script(argument) self.assertEqual(output.strip(), "('eth0', '10.0.1.5/28')") + def test_minigraph_hostname(self): + argument = '-v "DEVICE_METADATA[\'localhost\'][\'hostname\']" -m "' + self.sample_graph + '"' + output = self.run_script(argument) + self.assertEqual(output.strip(), 'OCPSCH01040DDLF') + def test_minigraph_sku(self): argument = '-v "DEVICE_METADATA[\'localhost\'][\'hwsku\']" -m "' + self.sample_graph + '"' output = self.run_script(argument) @@ -665,3 +671,39 @@ def test_show_run_interfaces(self): argument = '-a \'{"key1":"value"}\' --var-json INTERFACE' output = self.run_script(argument) self.assertEqual(output, '') + + def test_minigraph_voq_metadata(self): + argument = "-m {} --var-json DEVICE_METADATA".format(self.sample_graph_voq) + output = json.loads(self.run_script(argument)) + self.assertEqual(output['localhost']['asic_name'], 'Asic0') + self.assertEqual(output['localhost']['switch_id'], '0') + self.assertEqual(output['localhost']['switch_type'], 'voq') + self.assertEqual(output['localhost']['max_cores'], '16') + + def test_minigraph_voq_system_ports(self): + argument = "-m {} --var-json SYSTEM_PORT".format(self.sample_graph_voq) + self.assertDictEqual( + json.loads(self.run_script(argument)), + { + "linecard-1|Asic0|Cpu0": { "core_port_index": "0", "num_voq": "8", "switch_id": "0", "speed": "1000", "core_index": "0", "system_port_id": "1" }, + "linecard-1|Asic0|Ethernet1/1": { "core_port_index": "1", "num_voq": "8", "switch_id": "0", "speed": "40000", "core_index": "0", "system_port_id": "2" }, + "linecard-1|Asic0|Ethernet1/2": { "core_port_index": "2", "num_voq": "8", "switch_id": "0", "speed": "40000", "core_index": "0", "system_port_id": "3" }, + "linecard-1|Asic0|Ethernet1/3": { "core_port_index": "3", "num_voq": "8", "switch_id": "0", "speed": "40000", "core_index": "1", "system_port_id": "4" }, + "linecard-1|Asic0|Ethernet1/4": { "core_port_index": "4", "num_voq": "8", "switch_id": "0", "speed": "40000", "core_index": "1", "system_port_id": "5" }, + "linecard-2|Asic0|Cpu0": { "core_port_index": "0", "num_voq": "8", "switch_id": "2", "speed": "1000", "core_index": "0", "system_port_id": "256" }, + "linecard-2|Asic0|Ethernet1/5": { "core_port_index": "1", "num_voq": "8", "switch_id": "2", "speed": "40000", "core_index": "0", "system_port_id": "257" }, + "linecard-2|Asic0|Ethernet1/6": { "core_port_index": "2", "num_voq": "8", "switch_id": "2", "speed": "40000", "core_index": "1", "system_port_id": "258" }, + "linecard-2|Asic1|Cpu0": { "core_port_index": "0", "num_voq": "8", "switch_id": "4", "speed": "1000", "core_index": "0", "system_port_id": "259" }, + "linecard-2|Asic1|Ethernet1/7": { "core_port_index": "1", "num_voq": "8", "switch_id": "4", "speed": "40000", "core_index": "0", "system_port_id": "260" }, + "linecard-2|Asic1|Ethernet1/8": { "core_port_index": "2", "num_voq": "8", "switch_id": "4", "speed": "40000", "core_index": "1", "system_port_id": "261" } + } + ) + + def test_minigraph_voq_inband_interface(self): + argument = "-m {} --var-json VOQ_INBAND_INTERFACE".format(self.sample_graph_voq) + self.assertDictEqual( + json.loads(self.run_script(argument)), + { 'Vlan3094': {'inband_type': 'Vlan'}, + 'Vlan3094|1.1.1.1/24': {} + } + )