From 3bf2bf9a5266c92cf7bf91bc8ac6c73bcf228c7d Mon Sep 17 00:00:00 2001 From: SuvarnaMeenakshi <50386592+SuvarnaMeenakshi@users.noreply.github.com> Date: Mon, 4 May 2020 16:15:15 -0700 Subject: [PATCH] [config engine] Parser changes to support parsing of multi-asic device minigraph (#4222) - Changes to minigraph.py to parse minigraph.xml of a multi asic platform - Changes to portconfig.py to parse additional column "asic_port_name" in port_config.ini - Add a new option -n to sonic-cfggen for multi asic platforms - Add unit tests for config generation for multi asic platforms Signed-off-by: SuvarnaMeenakshi Signed-off-by: Arvindsrinivasan Lakshmi Narasimhan --- src/sonic-config-engine/minigraph.py | 223 ++- src/sonic-config-engine/portconfig.py | 22 +- src/sonic-config-engine/sonic-cfggen | 35 +- src/sonic-config-engine/sonic_device_util.py | 49 +- .../tests/multi_npu_data/sample-minigraph.xml | 1214 +++++++++++++++++ .../multi_npu_data/sample_port_config-0.ini | 9 + .../multi_npu_data/sample_port_config-1.ini | 9 + .../multi_npu_data/sample_port_config-2.ini | 9 + .../multi_npu_data/sample_port_config-3.ini | 9 + .../tests/test_multinpu_cfggen.py | 221 +++ 10 files changed, 1753 insertions(+), 47 deletions(-) create mode 100644 src/sonic-config-engine/tests/multi_npu_data/sample-minigraph.xml create mode 100644 src/sonic-config-engine/tests/multi_npu_data/sample_port_config-0.ini create mode 100644 src/sonic-config-engine/tests/multi_npu_data/sample_port_config-1.ini create mode 100644 src/sonic-config-engine/tests/multi_npu_data/sample_port_config-2.ini create mode 100644 src/sonic-config-engine/tests/multi_npu_data/sample_port_config-3.ini create mode 100644 src/sonic-config-engine/tests/test_multinpu_cfggen.py diff --git a/src/sonic-config-engine/minigraph.py b/src/sonic-config-engine/minigraph.py index 8f91fbe5c318..8ff4944c5d13 100644 --- a/src/sonic-config-engine/minigraph.py +++ b/src/sonic-config-engine/minigraph.py @@ -14,6 +14,7 @@ from lxml.etree import QName from portconfig import get_port_config +from sonic_device_util import get_npu_id_from_name """minigraph.py version_added: "1.9" @@ -118,14 +119,13 @@ def parse_png(png, hname): startport = link.find(str(QName(ns, "StartPort"))).text bandwidth_node = link.find(str(QName(ns, "Bandwidth"))) bandwidth = bandwidth_node.text if bandwidth_node is not None else None - if enddevice.lower() == hname.lower(): if port_alias_map.has_key(endport): endport = port_alias_map[endport] neighbors[endport] = {'name': startdevice, 'port': startport} if bandwidth: port_speeds[endport] = bandwidth - else: + elif startdevice.lower() == hname.lower(): if port_alias_map.has_key(startport): startport = port_alias_map[startport] neighbors[startport] = {'name': enddevice, 'port': endport} @@ -159,9 +159,103 @@ def parse_png(png, hname): return (neighbors, devices, console_dev, console_port, mgmt_dev, mgmt_port, port_speeds, console_ports) +def parse_asic_external_link(link, asic_name, hostname): + neighbors = {} + port_speeds = {} + enddevice = link.find(str(QName(ns, "EndDevice"))).text + endport = link.find(str(QName(ns, "EndPort"))).text + startdevice = link.find(str(QName(ns, "StartDevice"))).text + startport = link.find(str(QName(ns, "StartPort"))).text + bandwidth_node = link.find(str(QName(ns, "Bandwidth"))) + bandwidth = bandwidth_node.text if bandwidth_node is not None else None + # if chassis internal is false, the interface name will be + # interface alias which should be converted to asic port name + if (enddevice.lower() == hostname.lower()): + if ((port_alias_asic_map.has_key(endport)) and + (asic_name.lower() in port_alias_asic_map[endport].lower())): + endport = port_alias_asic_map[endport] + neighbors[port_alias_map[endport]] = {'name': startdevice, 'port': startport} + if bandwidth: + port_speeds[port_alias_map[endport]] = bandwidth + elif (startdevice.lower() == hostname.lower()): + if ((port_alias_asic_map.has_key(startport)) and + (asic_name.lower() in port_alias_asic_map[startport].lower())): + startport = port_alias_asic_map[startport] + neighbors[port_alias_map[startport]] = {'name': enddevice, 'port': endport} + if bandwidth: + port_speeds[port_alias_map[startport]] = bandwidth + + return neighbors, port_speeds + +def parse_asic_internal_link(link, asic_name, hostname): + neighbors = {} + port_speeds = {} + enddevice = link.find(str(QName(ns, "EndDevice"))).text + endport = link.find(str(QName(ns, "EndPort"))).text + startdevice = link.find(str(QName(ns, "StartDevice"))).text + startport = link.find(str(QName(ns, "StartPort"))).text + bandwidth_node = link.find(str(QName(ns, "Bandwidth"))) + bandwidth = bandwidth_node.text if bandwidth_node is not None else None + if ((enddevice.lower() == asic_name.lower()) and + (startdevice.lower() != hostname.lower())): + if port_alias_map.has_key(endport): + endport = port_alias_map[endport] + neighbors[endport] = {'name': startdevice, 'port': startport} + if bandwidth: + port_speeds[endport] = bandwidth + elif ((startdevice.lower() == asic_name.lower()) and + (enddevice.lower() != hostname.lower())): + if port_alias_map.has_key(startport): + startport = port_alias_map[startport] + neighbors[startport] = {'name': enddevice, 'port': endport} + if bandwidth: + port_speeds[startport] = bandwidth + + return neighbors, port_speeds + +def parse_asic_png(png, asic_name, hostname): + neighbors = {} + devices = {} + port_speeds = {} + for child in png: + if child.tag == str(QName(ns, "DeviceInterfaceLinks")): + for link in child.findall(str(QName(ns, "DeviceLinkBase"))): + # Chassis internal node is used in multi-asic device or chassis minigraph + # where the minigraph will contain the internal asic connectivity and + # external neighbor information. The ChassisInternal node will be used to + # determine if the link is internal to the device or chassis. + chassis_internal_node = link.find(str(QName(ns, "ChassisInternal"))) + chassis_internal = chassis_internal_node.text if chassis_internal_node is not None else "false" + + # If the link is an external link include the external neighbor + # information in ASIC ports table + if chassis_internal.lower() == "false": + ext_neighbors, ext_port_speeds = parse_asic_external_link(link, asic_name, hostname) + neighbors.update(ext_neighbors) + port_speeds.update(ext_port_speeds) + else: + int_neighbors, int_port_speeds = parse_asic_internal_link(link, asic_name, hostname) + neighbors.update(int_neighbors) + port_speeds.update(int_port_speeds) + + if child.tag == str(QName(ns, "Devices")): + for device in child.findall(str(QName(ns, "Device"))): + (lo_prefix, mgmt_prefix, name, hwsku, d_type, deployment_id) = parse_device(device) + device_data = {'lo_addr': lo_prefix, 'type': d_type, 'mgmt_addr': mgmt_prefix, 'hwsku': hwsku } + if deployment_id: + device_data['deployment_id'] = deployment_id + devices[name] = device_data + return (neighbors, devices, port_speeds) def parse_dpg(dpg, hname): for child in dpg: + """In Multi-NPU platforms the acl intfs are defined only for the host not for individual asic. + There is just one aclintf node in the minigraph + Get the aclintfs node first. + """ + if child.find(str(QName(ns, "AclInterfaces"))) is not None: + aclintfs = child.find(str(QName(ns, "AclInterfaces"))) + hostname = child.find(str(QName(ns, "Hostname"))) if hostname.text.lower() != hname.lower(): continue @@ -254,7 +348,6 @@ def parse_dpg(dpg, hname): vlan_attributes['alias'] = vintfname vlans[sonic_vlan_name] = vlan_attributes - aclintfs = child.find(str(QName(ns, "AclInterfaces"))) acls = {} for aclintf in aclintfs.findall(str(QName(ns, "AclInterface"))): if aclintf.find(str(QName(ns, "InAcl"))) is not None: @@ -369,7 +462,7 @@ def parse_cpg(cpg, hname): 'keepalive': keepalive, 'nhopself': nhopself } - else: + elif start_router.lower() == hname.lower(): bgp_sessions[end_peer.lower()] = { 'name': end_router, 'local_addr': start_peer.lower(), @@ -446,6 +539,19 @@ def parse_meta(meta, hname): region = value return syslog_servers, dhcp_servers, ntp_servers, tacacs_servers, mgmt_routes, erspan_dst, deployment_id, region +def parse_asic_meta(meta, hname): + sub_role = 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(): + properties = device.find(str(QName(ns1, "Properties"))) + for device_property in properties.findall(str(QName(ns1, "DeviceProperty"))): + name = device_property.find(str(QName(ns1, "Name"))).text + value = device_property.find(str(QName(ns1, "Value"))).text + if name == "SubRole": + sub_role = value + return sub_role + def parse_deviceinfo(meta, hwsku): port_speeds = {} port_descriptions = {} @@ -480,8 +586,7 @@ def parse_spine_chassis_fe(results, vni, lo_intfs, phyport_intfs, pc_intfs, pc_m lo_network = ipaddress.IPNetwork(lo[1]) if lo_network.version == 4: lo_addr = str(lo_network.ip) - break - + break results['VXLAN_TUNNEL'] = {chassis_vxlan_tunnel: { 'src_ip': lo_addr }} @@ -520,7 +625,7 @@ def parse_spine_chassis_fe(results, vni, lo_intfs, phyport_intfs, pc_intfs, pc_m for pc_member in pc_members: if pc_member[0] == pc_intf: intf_name = pc_member[1] - break + break if intf_name == None: print >> sys.stderr, 'Warning: cannot find any interfaces that belong to %s' % (pc_intf) @@ -567,8 +672,16 @@ def filter_acl_mirror_table_bindings(acls, neighbors, port_channels): # Main functions # ############################################################################### - -def parse_xml(filename, platform=None, port_config_file=None): +def parse_xml(filename, platform=None, port_config_file=None, asic_name=None): + """ Parse minigraph xml file. + + Keyword arguments: + filename -- minigraph file name + platform -- device platform + port_config_file -- port config file name + asic_name -- asic name; to parse multi-asic device minigraph to + generate asic specific configuration. + """ root = ET.parse(filename).getroot() mini_graph_path = filename @@ -588,7 +701,7 @@ def parse_xml(filename, platform=None, port_config_file=None): lo_intfs = None neighbors = None devices = None - hostname = None + sub_role = None docker_routing_config_mode = "separated" port_speeds_default = {} port_speed_png = {} @@ -603,6 +716,13 @@ def parse_xml(filename, platform=None, port_config_file=None): bgp_peers_with_range = None deployment_id = None region = None + hostname = None + + #hostname is the asic_name, get the asic_id from the asic_name + if asic_name is not None: + asic_id = get_npu_id_from_name(asic_name) + else: + asic_id = None hwsku_qn = QName(ns, "HwSku") hostname_qn = QName(ns, "Hostname") @@ -615,34 +735,59 @@ def parse_xml(filename, platform=None, port_config_file=None): if child.tag == str(docker_routing_config_mode_qn): docker_routing_config_mode = child.text - (ports, alias_map) = get_port_config(hwsku, platform, port_config_file) + (ports, alias_map, alias_asic_map) = get_port_config(hwsku=hwsku, platform=platform, port_config_file=port_config_file, asic=asic_id) port_alias_map.update(alias_map) + port_alias_asic_map.update(alias_asic_map) + for child in root: - 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) - elif child.tag == str(QName(ns, "CpgDec")): - (bgp_sessions, bgp_asn, bgp_peers_with_range, bgp_monitors) = parse_cpg(child, hostname) - elif child.tag == str(QName(ns, "PngDec")): - (neighbors, devices, console_dev, console_port, mgmt_dev, mgmt_port, port_speed_png, console_ports) = parse_png(child, hostname) - elif child.tag == str(QName(ns, "UngDec")): - (u_neighbors, u_devices, _, _, _, _, _, _) = parse_png(child, hostname) - elif child.tag == str(QName(ns, "MetadataDeclaration")): - (syslog_servers, dhcp_servers, ntp_servers, tacacs_servers, mgmt_routes, erspan_dst, deployment_id, region) = parse_meta(child, hostname) - elif child.tag == str(QName(ns, "DeviceInfos")): - (port_speeds_default, port_descriptions) = parse_deviceinfo(child, hwsku) - - current_device = [devices[key] for key in devices if key.lower() == hostname.lower()][0] + 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) + elif child.tag == str(QName(ns, "CpgDec")): + (bgp_sessions, bgp_asn, bgp_peers_with_range, bgp_monitors) = parse_cpg(child, hostname) + elif child.tag == str(QName(ns, "PngDec")): + (neighbors, devices, console_dev, console_port, mgmt_dev, mgmt_port, port_speed_png, console_ports) = parse_png(child, hostname) + elif child.tag == str(QName(ns, "UngDec")): + (u_neighbors, u_devices, _, _, _, _, _, _) = parse_png(child, device_hostname) + elif child.tag == str(QName(ns, "MetadataDeclaration")): + (syslog_servers, dhcp_servers, ntp_servers, tacacs_servers, mgmt_routes, erspan_dst, deployment_id, region) = parse_meta(child, hostname) + elif child.tag == str(QName(ns, "DeviceInfos")): + (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) + elif child.tag == str(QName(ns, "CpgDec")): + (bgp_sessions, bgp_asn, bgp_peers_with_range, bgp_monitors) = parse_cpg(child, asic_name) + 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) + elif child.tag == str(QName(ns, "DeviceInfos")): + (port_speeds_default, port_descriptions) = parse_deviceinfo(child, hwsku) + + if asic_name is None: + current_device = [devices[key] for key in devices if key.lower() == hostname.lower()][0] + name = hostname + else: + current_device = [devices[key] for key in devices if key.lower() == asic_name.lower()][0] + name = asic_name + results = {} results['DEVICE_METADATA'] = {'localhost': { 'bgp_asn': bgp_asn, 'deployment_id': deployment_id, 'region': region, 'docker_routing_config_mode': docker_routing_config_mode, - 'hostname': hostname, + 'hostname': name, 'hwsku': hwsku, 'type': current_device['type'] } } + # for this hostname, if sub_role is defined, add sub_role in + # device_metadata + if sub_role is not None: + current_device['sub_role'] = sub_role + results['DEVICE_METADATA']['localhost']['sub_role'] = sub_role results['BGP_NEIGHBOR'] = bgp_sessions results['BGP_MONITORS'] = bgp_monitors results['BGP_PEER_RANGE'] = bgp_peers_with_range @@ -703,9 +848,11 @@ def parse_xml(filename, platform=None, port_config_file=None): for port_name in port_speed_png: # not consider port not in port_config.ini - if port_name not in ports: - print >> sys.stderr, "Warning: ignore interface '%s' as it is not in the port_config.ini" % port_name - continue + #If no port_config_file is found ports is empty so ignore this error + if port_config_file is not None: + if port_name not in ports: + print >> sys.stderr, "Warning: ignore interface '%s' as it is not in the port_config.ini" % port_name + continue ports.setdefault(port_name, {})['speed'] = port_speed_png[port_name] @@ -809,11 +956,14 @@ def parse_xml(filename, platform=None, port_config_file=None): for nghbr in neighbors.keys(): # remove port not in port_config.ini if nghbr not in ports: - print >> sys.stderr, "Warning: ignore interface '%s' in DEVICE_NEIGHBOR as it is not in the port_config.ini" % nghbr + if port_config_file is not None: + print >> sys.stderr, "Warning: ignore interface '%s' in DEVICE_NEIGHBOR as it is not in the port_config.ini" % nghbr del neighbors[nghbr] - results['DEVICE_NEIGHBOR'] = neighbors - results['DEVICE_NEIGHBOR_METADATA'] = { key:devices[key] for key in devices if key.lower() != hostname.lower() } + if asic_name is None: + results['DEVICE_NEIGHBOR_METADATA'] = { key:devices[key] for key in devices if key.lower() != hostname.lower() } + else: + results['DEVICE_NEIGHBOR_METADATA'] = { key:devices[key] for key in devices if key in {device['name'] for device in neighbors.values()} } results['SYSLOG_SERVER'] = dict((item, {}) for item in syslog_servers) results['DHCP_SERVER'] = dict((item, {}) for item in dhcp_servers) results['NTP_SERVER'] = dict((item, {}) for item in ntp_servers) @@ -890,8 +1040,17 @@ def parse_device_desc_xml(filename): return results +def parse_asic_sub_role(filename, asic_name): + if not os.path.isfile(filename): + return None + root = ET.parse(filename).getroot() + for child in root: + if child.tag == str(QName(ns, "MetadataDeclaration")): + sub_role = parse_asic_meta(child, asic_name) + return sub_role port_alias_map = {} +port_alias_asic_map = {} def print_parse_xml(filename): diff --git a/src/sonic-config-engine/portconfig.py b/src/sonic-config-engine/portconfig.py index db2baa308174..87e13687ed55 100644 --- a/src/sonic-config-engine/portconfig.py +++ b/src/sonic-config-engine/portconfig.py @@ -3,11 +3,13 @@ import sys -def get_port_config_file_name(hwsku=None, platform=None): +def get_port_config_file_name(hwsku=None, platform=None, asic=None): port_config_candidates = [] port_config_candidates.append('/usr/share/sonic/hwsku/port_config.ini') if hwsku: if platform: + if asic: + port_config_candidates.append(os.path.join('/usr/share/sonic/device', platform, hwsku, asic,'port_config.ini')) port_config_candidates.append(os.path.join('/usr/share/sonic/device', platform, hwsku, 'port_config.ini')) port_config_candidates.append(os.path.join('/usr/share/sonic/platform', hwsku, 'port_config.ini')) port_config_candidates.append(os.path.join('/usr/share/sonic', hwsku, 'port_config.ini')) @@ -17,17 +19,19 @@ def get_port_config_file_name(hwsku=None, platform=None): return None -def get_port_config(hwsku=None, platform=None, port_config_file=None): + +def get_port_config(hwsku=None, platform=None, port_config_file=None, asic=None): if not port_config_file: - port_config_file = get_port_config_file_name(hwsku, platform) + port_config_file = get_port_config_file_name(hwsku, platform, asic) if not port_config_file: - return ({}, {}) + return ({}, {}, {}) return parse_port_config_file(port_config_file) def parse_port_config_file(port_config_file): ports = {} port_alias_map = {} + port_alias_asic_map = {} # Default column definition titles = ['name', 'lanes', 'alias', 'index'] with open(port_config_file) as data: @@ -49,6 +53,14 @@ def parse_port_config_file(port_config_file): data.setdefault('alias', name) ports[name] = data port_alias_map[data['alias']] = name - return (ports, port_alias_map) + # asic_port_name to sonic_name mapping also included in + # port_alias_map + if (('asic_port_name' in data) and + (data['asic_port_name'] != name)): + port_alias_map[data['asic_port_name']] = name + # alias to asic_port_name mapping + if 'asic_port_name' in data: + port_alias_asic_map[data['alias']] = data['asic_port_name'].strip() + return (ports, port_alias_map, port_alias_asic_map) diff --git a/src/sonic-config-engine/sonic-cfggen b/src/sonic-config-engine/sonic-cfggen index 53aa5a0ac31c..b254c30a24ef 100755 --- a/src/sonic-config-engine/sonic-cfggen +++ b/src/sonic-config-engine/sonic-cfggen @@ -37,10 +37,12 @@ from functools import partial from minigraph import minigraph_encoder from minigraph import parse_xml from minigraph import parse_device_desc_xml +from minigraph import parse_asic_sub_role from portconfig import get_port_config from sonic_device_util import get_machine_info from sonic_device_util import get_platform_info from sonic_device_util import get_system_mac +from sonic_device_util import get_npu_id_from_name from config_samples import generate_sample_config from config_samples import get_available_config from swsssdk import SonicV2Connector, ConfigDBConnector @@ -195,6 +197,7 @@ def main(): group.add_argument("-m", "--minigraph", help="minigraph xml file", nargs='?', const='/etc/sonic/minigraph.xml') group.add_argument("-M", "--device-description", help="device description xml file") group.add_argument("-k", "--hwsku", help="HwSKU") + parser.add_argument("-n", "--namespace", help="namespace name, used with -m or -k", nargs='?', const=None) parser.add_argument("-p", "--port-config", help="port config file, used with -m or -k", nargs='?', const=None) parser.add_argument("-y", "--yaml", help="yaml file that contains additional variables", action='append', default=[]) parser.add_argument("-j", "--json", help="json file that contains additional variables", action='append', default=[]) @@ -222,13 +225,18 @@ def main(): data = {} hwsku = args.hwsku + asic_name = args.namespace + asic_id = None + if asic_name is not None: + asic_id = get_npu_id_from_name(asic_name) + if hwsku is not None: hardware_data = {'DEVICE_METADATA': {'localhost': { 'hwsku': hwsku }}} deep_update(data, hardware_data) - (ports, _) = get_port_config(hwsku, platform, args.port_config) + (ports, _, _) = get_port_config(hwsku, platform, args.port_config, asic_id) if not ports: print('Failed to get port config', file=sys.stderr) sys.exit(1) @@ -242,11 +250,11 @@ def main(): minigraph = args.minigraph if platform: if args.port_config != None: - deep_update(data, parse_xml(minigraph, platform, args.port_config)) + deep_update(data, parse_xml(minigraph, platform, args.port_config, asic_name=asic_name)) else: - deep_update(data, parse_xml(minigraph, platform)) + deep_update(data, parse_xml(minigraph, platform, asic_name=asic_name)) else: - deep_update(data, parse_xml(minigraph, port_config_file=args.port_config)) + deep_update(data, parse_xml(minigraph, port_config_file=args.port_config, asic_name=asic_name)) if args.device_description != None: deep_update(data, parse_device_desc_xml(args.device_description)) @@ -267,11 +275,28 @@ def main(): configdb.connect() deep_update(data, FormatConverter.db_to_output(configdb.get_config())) + + # the minigraph file must be provided to get the mac address for backend asics if args.platform_info: + asic_role = None + if asic_name is not None: + if args.minigraph is not None: + asic_role = parse_asic_sub_role(args.minigraph, asic_name) + + if asic_role is not None and asic_role.lower() == "backend": + mac = get_system_mac(namespace=asic_name) + else: + mac = get_system_mac() + else: + mac = get_system_mac() + hardware_data = {'DEVICE_METADATA': {'localhost': { 'platform': platform, - 'mac': get_system_mac() + 'mac': mac, }}} + # The ID needs to be passed to the SAI to identify the asic. + if asic_name is not None: + hardware_data['DEVICE_METADATA']['localhost'].update(asic_id=asic_id) deep_update(data, hardware_data) if args.template is not None: diff --git a/src/sonic-config-engine/sonic_device_util.py b/src/sonic-config-engine/sonic_device_util.py index 34877600fd13..994eeb8ae395 100644 --- a/src/sonic-config-engine/sonic_device_util.py +++ b/src/sonic-config-engine/sonic_device_util.py @@ -3,7 +3,8 @@ import yaml import subprocess import re - +from natsort import natsorted +import glob DOCUMENTATION = ''' --- module: sonic_device_util @@ -17,6 +18,9 @@ TODO: this file shall be renamed and moved to other places in future to have it shared with multiple applications. ''' +SONIC_DEVICE_PATH = '/usr/share/sonic/device' +NPU_NAME_PREFIX = 'asic' +NAMESPACE_PATH_GLOB = '/run/netns/*' def get_machine_info(): if not os.path.isfile('/host/machine.conf'): return None @@ -27,7 +31,38 @@ def get_machine_info(): if len(tokens) < 2: continue machine_vars[tokens[0]] = tokens[1].strip() - return machine_vars + return machine_vars + +def get_npu_id_from_name(npu_name): + if npu_name.startswith(NPU_NAME_PREFIX): + return npu_name[len(NPU_NAME_PREFIX):] + else: + return None + +def get_num_npus(): + platform = get_platform_info(get_machine_info()) + asic_conf_file_path = os.path.join(SONIC_DEVICE_PATH, platform, 'asic.conf') + if not os.path.isfile(asic_conf_file_path): + return 1 + with open(asic_conf_file_path) as asic_conf_file: + for line in asic_conf_file: + tokens = line.split('=') + if len(tokens) < 2: + continue + if tokens[0].lower() == 'num_asic': + num_npus = tokens[1].strip() + return num_npus + +def get_namespaces(): + """ + In a multi NPU platform, each NPU is in a Linux Namespace. + This method returns list of all the Namespace present on the device + """ + ns_list = [] + for path in glob.glob(NAMESPACE_PATH_GLOB): + ns = os.path.basename(path) + ns_list.append(ns) + return natsorted(ns_list) def get_platform_info(machine_info): if machine_info != None: @@ -51,7 +86,7 @@ def get_sonic_version_info(): def valid_mac_address(mac): return bool(re.match("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$", mac)) -def get_system_mac(): +def get_system_mac(namespace=None): version_info = get_sonic_version_info() if (version_info['asic_type'] == 'mellanox'): @@ -73,10 +108,14 @@ def get_system_mac(): # Try valid mac in eeprom, else fetch it from eth0 platform = get_platform_info(get_machine_info()) hwsku = get_machine_info()['onie_machine'] - profile_cmd = 'cat /usr/share/sonic/device/' + platform +'/'+ hwsku +'/profile.ini | cut -f2 -d=' + profile_cmd = 'cat' + SONIC_DEVICE_PATH + '/' + platform +'/'+ hwsku +'/profile.ini | cut -f2 -d=' hw_mac_entry_cmds = [ profile_cmd, "sudo decode-syseeprom -m", "ip link show eth0 | grep ether | awk '{print $2}'" ] else: - hw_mac_entry_cmds = [ "ip link show eth0 | grep ether | awk '{print $2}'" ] + mac_address_cmd = "cat /sys/class/net/eth0/address" + if namespace is not None: + mac_address_cmd = "sudo ip netns exec {} {}".format(namespace, mac_address_cmd) + + hw_mac_entry_cmds = [mac_address_cmd] for get_mac_cmd in hw_mac_entry_cmds: proc = subprocess.Popen(get_mac_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) diff --git a/src/sonic-config-engine/tests/multi_npu_data/sample-minigraph.xml b/src/sonic-config-engine/tests/multi_npu_data/sample-minigraph.xml new file mode 100644 index 000000000000..118202d9b52e --- /dev/null +++ b/src/sonic-config-engine/tests/multi_npu_data/sample-minigraph.xml @@ -0,0 +1,1214 @@ + + + + + + false + multi_npu_platform_01 + 10.0.0.0 + 01T2 + 10.0.0.1 + 1 + 10 + 3 + + + multi_npu_platform_01 + FC00::1 + 01T2 + FC00::2 + 1 + 10 + 3 + + + false + multi_npu_platform_01 + 10.0.0.8 + 05T2 + 10.0.0.9 + 1 + 10 + 3 + + + multi_npu_platform_01 + FC00::9 + 05T2 + FC00::A + 1 + 10 + 3 + + + BGPSession + false + ASIC2 + 10.1.0.0 + ASIC0 + 10.1.0.1 + 1 + 0 + 0 + + + BGPSession + false + ASIC2 + 10.1.0.4 + ASIC1 + 10.1.0.5 + 1 + 0 + 0 + + + BGPSession + false + ASIC3 + 10.1.0.2 + ASIC0 + 10.1.0.3 + 1 + 0 + 0 + + + BGPSession + false + ASIC3 + 10.1.0.6 + ASIC1 + 10.1.0.7 + 1 + 0 + 0 + + + false + ASIC0 + 10.0.0.0 + 01T2 + 10.0.0.1 + 1 + 10 + 3 + + + ASIC0 + FC00::1 + 01T2 + FC00::2 + 1 + 10 + 3 + + + false + ASIC1 + 10.0.0.8 + 05T2 + 10.0.0.9 + 1 + 10 + 3 + + + ASIC1 + FC00::9 + 05T2 + FC00::A + 1 + 10 + 3 + + + + + 65100 + multi_npu_platform_01 + + +
10.0.0.1
+ + + +
+ +
10.0.0.9
+ + + +
+
+ +
+ + 65100 + + ASIC0 + + + BGPPeer +
10.1.0.1
+ + + +
+ + BGPPeer +
10.1.0.3
+ + + +
+ + BGPPeer +
10.0.0.1
+ + + +
+ + BGPPeer +
FC00::1
+ + + +
+
+ +
+ + 65100 + + ASIC1 + + + BGPPeer +
10.1.0.5
+ + + +
+ + BGPPeer +
10.1.0.7
+ + + +
+ + BGPPeer +
10.0.0.9
+ + + +
+ + BGPPeer +
FC00::A
+ + + +
+
+ +
+ + 65100 + + ASIC2 + + + BGPPeer +
10.1.0.0
+ + + +
+ + BGPPeer +
10.1.0.4
+ + + +
+
+ +
+ + 65100 + + ASIC3 + + + BGPPeer +
10.1.0.2
+ + + +
+ + BGPPeer +
10.1.0.6
+ + + +
+
+ +
+ + 65200 + 01T2 + + + + 65200 + 05T2 + + +
+
+ + + + + + HostIP + Loopback0 + + 10.1.0.32/32 + + 10.1.0.32/32 + + + HostIP1 + Loopback0 + + FC00:1::32/128 + + FC00:1::32/128 + + + + + HostIP + eth0 + + 3.10.147.150/23 + + 3.10.147.150/23 + + + V6HostIP + eth0 + + FC00:2::32/64 + + FC00:2::32/64 + + + + + + + multi_npu_platform_01 + + + PortChannel0002 + Ethernet1/1;Ethernet1/2 + + + + PortChannel0008 + Ethernet1/5;Ethernet1/6 + + + + + + + + PortChannel0002 + 10.0.0.0/31 + + + + PortChannel0002 + FC00::1/126 + + + + PortChannel0008 + 10.0.0.8/31 + + + + PortChannel0008 + FC00::9/126 + + + + + + SNMP_ACL + SNMP + SNMP + + + ERSPAN + Everflow + Everflow + + + ERSPANV6 + EverflowV6 + EverflowV6 + + + VTY_LINE + ssh-only + SSH + + + ;PortChannel0002;PortChannel0008 + DataAcl + DataPlane + + + + + + + + + + LoopbackInterface + HostIP + Loopback0 + + 8.0.0.0/32 + + 8.0.0.0/32 + + + + + + + + ASIC0 + + + PortChannelInterface + PortChannel4001 + Eth4-ASIC0;Eth5-ASIC0 + + + + PortChannelInterface + PortChannel4002 + Eth6-ASIC0;Eth7-ASIC0 + + + + PortChannelInterface + PortChannel0002 + Eth0-ASIC0;Eth1-ASIC0 + + + + + + + + IPInterface + + PortChannel4001 + 10.1.0.1/31 + + + IPInterface + + PortChannel4002 + 10.1.0.3/31 + + + + PortChannel0002 + 10.0.0.0/31 + + + + PortChannel0002 + FC00::1/126 + + + + + + + + + + + + LoopbackInterface + HostIP + Loopback0 + + 8.0.0.1/32 + + 8.0.0.1/32 + + + + + + + + ASIC1 + + + PortChannelInterface + PortChannel4003 + Eth4-ASIC1;Eth5-ASIC1 + + + + PortChannelInterface + PortChannel4004 + Eth6-ASIC1;Eth7-ASIC1 + + + + PortChannel0008 + Eth0-ASIC1;Eth1-ASIC1 + + + + + + + + IPInterface + + PortChannel4003 + 10.1.0.5/31 + + + IPInterface + + PortChannel4004 + 10.1.0.7/31 + + + + PortChannel0008 + 10.0.0.8/31 + + + + PortChannel0008 + FC00::9/126 + + + + + + + + + + + + LoopbackInterface + HostIP + Loopback0 + + 8.0.0.4/32 + + 8.0.0.4/32 + + + + + + + + ASIC2 + + + PortChannelInterface + PortChannel4009 + Eth0-ASIC2;Eth1-ASIC2 + + + + PortChannelInterface + PortChannel4010 + Eth2-ASIC2;Eth3-ASIC2 + + + + + + + + IPInterface + + PortChannel4009 + 10.1.0.0/31 + + + IPInterface + + PortChannel4010 + 10.1.0.4/31 + + + + + + + + + + + + LoopbackInterface + HostIP + Loopback0 + + 8.0.0.5/32 + + 8.0.0.5/32 + + + + + + + + ASIC3 + + + PortChannelInterface + PortChannel4013 + Eth0-ASIC3;Eth1-ASIC3 + + + + PortChannelInterface + PortChannel4014 + Eth2-ASIC3;Eth3-ASIC3 + + + + + + + + IPInterface + + PortChannel4013 + 10.1.0.2/31 + + + IPInterface + + PortChannel4014 + 10.1.0.6/31 + + + + + + + + + + + + DeviceInterfaceLink + 01T2 + Ethernet1 + multi_npu_platform_01 + Ethernet1/1 + + + DeviceInterfaceLink + 01T2 + Ethernet2 + multi_npu_platform_01 + Ethernet1/2 + + + DeviceInterfaceLink + 05T2 + Ethernet1 + multi_npu_platform_01 + Ethernet1/8 + + + DeviceInterfaceLink + 05T2 + Ethernet2 + multi_npu_platform_01 + Ethernet1/9 + + + DeviceInterfaceLink + 40000 + true + ASIC2 + Eth0-ASIC2 + true + ASIC0 + Eth4-ASIC0 + true + + + DeviceInterfaceLink + 40000 + true + ASIC2 + Eth1-ASIC2 + true + ASIC0 + Eth5-ASIC0 + true + + + DeviceInterfaceLink + 40000 + true + ASIC3 + Eth0-ASIC3 + true + ASIC0 + Eth6-ASIC0 + true + + + DeviceInterfaceLink + 40000 + true + ASIC3 + Eth1-ASIC3 + true + ASIC0 + Eth7-ASIC0 + true + + + DeviceInterfaceLink + 40000 + true + ASIC2 + Eth2-ASIC2 + true + ASIC1 + Eth4-ASIC1 + true + + + DeviceInterfaceLink + 40000 + true + ASIC2 + Eth3-ASIC2 + true + ASIC1 + Eth5-ASIC1 + true + + + DeviceInterfaceLink + 40000 + true + ASIC3 + Eth2-ASIC3 + true + ASIC1 + Eth6-ASIC1 + true + + + DeviceInterfaceLink + 40000 + true + ASIC3 + Eth3-ASIC3 + true + ASIC1 + Eth7-ASIC1 + true + + + DeviceInterfaceLink + 40000 + true + ASIC0 + Eth0-ASIC0 + true + multi_npu_platform_01 + Ethernet1/1 + true + + + DeviceInterfaceLink + 40000 + true + ASIC0 + Eth1-ASIC0 + true + multi_npu_platform_01 + Ethernet1/2 + true + + + DeviceInterfaceLink + 40000 + true + ASIC0 + Eth2-ASIC0 + true + multi_npu_platform_01 + Ethernet1/3 + true + + + DeviceInterfaceLink + 40000 + true + ASIC0 + Eth3-ASIC0 + true + multi_npu_platform_01 + Ethernet1/4 + true + + + DeviceInterfaceLink + 40000 + true + ASIC1 + Eth0-ASIC1 + true + multi_npu_platform_01 + Ethernet1/5 + true + + + DeviceInterfaceLink + 40000 + true + ASIC1 + Eth1-ASIC1 + true + multi_npu_platform_01 + Ethernet1/6 + true + + + DeviceInterfaceLink + 40000 + true + ASIC1 + Eth2-ASIC1 + true + multi_npu_platform_01 + Ethernet1/7 + true + + + DeviceInterfaceLink + 40000 + true + ASIC1 + Eth3-ASIC1 + true + multi_npu_platform_01 + Ethernet1/8 + true + + + + + multi_npu_platform_01 + multi-npu-01 + + 3.10.147.150 + + + + 07T2 + + 89.139.132.43 + + VM + + + 01T2 + + 89.139.132.40 + + VM + + + 05T2 + + 89.139.132.42 + + VM + + + 03T2 + + 89.139.132.41 + + VM + + + Asic +
+ 0.0.0.0/0 +
+ + ::/0 + + + + + + + + + + 0.0.0.0/0 + + + ::/0 + + + ASIC0 + multi-npu-asic +
+ + Asic +
+ 0.0.0.0/0 +
+ + ::/0 + + + + + + + + + + 0.0.0.0/0 + + + ::/0 + + + ASIC1 + multi-npu-asic +
+ + Asic +
+ 0.0.0.0/0 +
+ + ::/0 + + + + + + + + + + 0.0.0.0/0 + + + ::/0 + + + ASIC2 + multi-npu-asic +
+ + Asic +
+ 0.0.0.0/0 +
+ + ::/0 + + + + + + + + + + 0.0.0.0/0 + + + ::/0 + + + ASIC3 + multi-npu-asic +
+
+
+ + + true + + + DeviceInterface + + true + true + 1 + Ethernet1/1 + + false + 0 + 0 + 40000 + + + DeviceInterface + + true + true + 1 + Ethernet1/2 + + false + 0 + 0 + 40000 + + + DeviceInterface + + true + true + 1 + Ethernet1/3 + + false + 0 + 0 + 40000 + + + DeviceInterface + + true + true + 1 + Ethernet1/4 + + false + 0 + 0 + 40000 + + + DeviceInterface + + true + true + 1 + Ethernet1/5 + + false + 0 + 0 + 40000 + + + DeviceInterface + + true + true + 1 + Ethernet1/6 + + false + 0 + 0 + 40000 + + + DeviceInterface + + true + true + 1 + Ethernet1/7 + + false + 0 + 0 + 40000 + + + DeviceInterface + + true + true + 1 + Ethernet1/8 + + false + 0 + 0 + 40000 + + + true + 0 + multi-npu-01 + + + + + + + multi_npu_platform_01 + + + DeploymentId + + 1 + + + QosProfile + + Profile0 + + + DhcpResources + + 169.118.23.1;169.118.23.2;169.118.23.3;169.118.23.4;169.118.23.5;169.118.23.6;169.118.23.7;169.118.23.8;169.118.23.9;169.118.23.10;169.118.23.11;169.118.23.12;169.118.23.13;169.118.23.14;169.118.23.15;169.118.23.16;169.118.23.17;169.118.23.18;169.118.23.19;169.118.23.20;169.118.23.21;169.118.23.22;169.118.23.23;169.118.23.24;169.118.23.25;169.118.23.26;169.118.23.27;169.118.23.28;169.118.23.29;169.118.23.30;169.118.23.31;169.118.23.32;169.118.23.33;169.118.23.34;169.118.23.35;169.118.23.36;169.118.23.37;169.118.23.38;169.118.23.39;169.118.23.40;169.118.23.41;169.118.23.42;169.118.23.43;169.118.23.44;169.118.23.45;169.118.23.46;169.118.23.47;169.118.23.48 + + + NtpResources + + 17.39.1.129;17.39.1.130 + + + SnmpResources + + 71.49.219.98 + + + SyslogResources + + 71.49.219.8;123.46.98.21 + + + TacacsGroup + + Starlab + + + TacacsServer + + 123.46.98.21 + + + ForcedMgmtRoutes + + 71.49.219.98/31;71.49.219.8;123.46.98.16/28;10.3.149.170/31;40.122.216.24;13.91.48.226;71.49.219.14 + + + ErspanDestinationIpv4 + + 10.20.6.16 + + + + + ASIC0 + + + SubRole + + FrontEnd + + + + + ASIC1 + + + SubRole + + FrontEnd + + + + + ASIC2 + + + SubRole + + FrontEnd + + + + + ASIC3 + + + SubRole + + FrontEnd + + + + + ASIC2 + + + SubRole + + BackEnd + + + + + ASIC3 + + + SubRole + + BackEnd + + + + + + + multi_npu_platform_01 + multi-npu-01 +
diff --git a/src/sonic-config-engine/tests/multi_npu_data/sample_port_config-0.ini b/src/sonic-config-engine/tests/multi_npu_data/sample_port_config-0.ini new file mode 100644 index 000000000000..3fe912c98c45 --- /dev/null +++ b/src/sonic-config-engine/tests/multi_npu_data/sample_port_config-0.ini @@ -0,0 +1,9 @@ +# name lanes alias asic_port_name +Ethernet0 33,34,35,36 Ethernet1/1 Eth0-ASIC0 +Ethernet4 29,30,31,32 Ethernet1/2 Eth1-ASIC0 +Ethernet8 41,42,43,44 Ethernet1/3 Eth2-ASIC0 +Ethernet12 37,38,39,40 Ethernet1/4 Eth3-ASIC0 +Ethernet-BP0 13,14,15,16 Ethernet-BP0 Eth4-ASIC0 +Ethernet-BP4 17,18,19,20 Ethernet-BP4 Eth5-ASIC0 +Ethernet-BP8 21,22,23,24 Ethernet-BP8 Eth6-ASIC0 +Ethernet-BP12 25,26,27,28 Ethernet-BP12 Eth7-ASIC0 \ No newline at end of file diff --git a/src/sonic-config-engine/tests/multi_npu_data/sample_port_config-1.ini b/src/sonic-config-engine/tests/multi_npu_data/sample_port_config-1.ini new file mode 100644 index 000000000000..c496e0712a49 --- /dev/null +++ b/src/sonic-config-engine/tests/multi_npu_data/sample_port_config-1.ini @@ -0,0 +1,9 @@ +# name lanes alias asic_port_name +Ethernet16 33,34,35,36 Ethernet1/5 Eth0-ASIC1 +Ethernet20 29,30,31,32 Ethernet1/6 Eth1-ASIC1 +Ethernet24 41,42,43,44 Ethernet1/7 Eth2-ASIC1 +Ethernet28 37,38,39,40 Ethernet1/8 Eth3-ASIC1 +Ethernet-BP16 13,14,15,16 Ethernet-BP16 Eth4-ASIC1 +Ethernet-BP20 17,18,19,20 Ethernet-BP20 Eth5-ASIC1 +Ethernet-BP24 21,22,23,24 Ethernet-BP24 Eth6-ASIC1 +Ethernet-BP28 25,26,27,28 Ethernet-BP28 Eth7-ASIC1 \ No newline at end of file diff --git a/src/sonic-config-engine/tests/multi_npu_data/sample_port_config-2.ini b/src/sonic-config-engine/tests/multi_npu_data/sample_port_config-2.ini new file mode 100644 index 000000000000..4ae0575835a7 --- /dev/null +++ b/src/sonic-config-engine/tests/multi_npu_data/sample_port_config-2.ini @@ -0,0 +1,9 @@ +# name lanes alias asic_port_name +Ethernet-BP256 61,62,63,64 Ethernet-BP256 Eth0-ASIC2 +Ethernet-BP260 57,58,59,60 Ethernet-BP260 Eth1-ASIC2 +Ethernet-BP264 53,54,55,56 Ethernet-BP264 Eth2-ASIC2 +Ethernet-BP268 49,50,51,52 Ethernet-BP268 Eth3-ASIC2 +Ethernet-BP272 45,46,47,48 Ethernet-BP272 Eth4-ASIC2 +Ethernet-BP276 41,42,43,44 Ethernet-BP276 Eth5-ASIC2 +Ethernet-BP280 37,38,39,40 Ethernet-BP280 Eth6-ASIC2 +Ethernet-BP284 33,34,35,36 Ethernet-BP284 Eth7-ASIC2 \ No newline at end of file diff --git a/src/sonic-config-engine/tests/multi_npu_data/sample_port_config-3.ini b/src/sonic-config-engine/tests/multi_npu_data/sample_port_config-3.ini new file mode 100644 index 000000000000..8f45ed14946e --- /dev/null +++ b/src/sonic-config-engine/tests/multi_npu_data/sample_port_config-3.ini @@ -0,0 +1,9 @@ +# name lanes alias asic_port_name +Ethernet-BP384 29,30,31,32 Ethernet-BP384 Eth0-ASIC3 +Ethernet-BP388 25,26,27,28 Ethernet-BP388 Eth1-ASIC3 +Ethernet-BP392 21,22,23,24 Ethernet-BP392 Eth2-ASIC3 +Ethernet-BP396 17,18,19,20 Ethernet-BP396 Eth3-ASIC3 +Ethernet-BP400 13,14,15,16 Ethernet-BP400 Eth4-ASIC3 +Ethernet-BP404 9,10,11,12 Ethernet-BP404 Eth5-ASIC3 +Ethernet-BP408 5,6,7,8 Ethernet-BP408 Eth6-ASIC3 +Ethernet-BP412 1,2,3,4 Ethernet-BP412 Eth7-ASIC3 \ No newline at end of file diff --git a/src/sonic-config-engine/tests/test_multinpu_cfggen.py b/src/sonic-config-engine/tests/test_multinpu_cfggen.py new file mode 100644 index 000000000000..6facae0451db --- /dev/null +++ b/src/sonic-config-engine/tests/test_multinpu_cfggen.py @@ -0,0 +1,221 @@ +import unittest +from unittest import TestCase +import subprocess +import os +import json +import yaml + +SKU = 'multi-npu-01' +ASIC_SKU = 'multi-npu-asic' +NUM_ASIC = 4 +HOSTNAME = 'multi_npu_platform_01' + + +class TestMultiNpuCfgGen(TestCase): + + def setUp(self): + self.test_dir = os.path.dirname(os.path.realpath(__file__)) + self.test_data_dir = os.path.join(self.test_dir, 'multi_npu_data') + self.script_file = os.path.join(self.test_dir, '..', 'sonic-cfggen') + self.sample_graph = os.path.join(self.test_data_dir, 'sample-minigraph.xml') + self.port_config = [] + for asic in range(NUM_ASIC): + self.port_config.append(os.path.join(self.test_data_dir, "sample_port_config-{}.ini".format(asic))) + + def run_script(self, argument, check_stderr=False): + print '\n Running sonic-cfggen ' + argument + if check_stderr: + output = subprocess.check_output(self.script_file + ' ' + argument, stderr=subprocess.STDOUT, shell=True) + else: + output = subprocess.check_output(self.script_file + ' ' + argument, shell=True) + + linecount = output.strip().count('\n') + if linecount <= 0: + print ' Output: ' + output.strip() + else: + print ' Output: ({0} lines, {1} bytes)'.format(linecount + 1, len(output)) + return output + + def run_diff(self, file1, file2): + return subprocess.check_output('diff -u {} {} || true'.format(file1, file2), shell=True) + + def run_script_for_asic(self,argument,asic, port_config=None): + argument = "{} -n asic{} ".format(argument, asic) + if port_config: + argument += "-p {}".format(port_config) + output = self.run_script(argument) + return output + + def test_dummy_run(self): + argument = '' + output = self.run_script(argument) + self.assertEqual(output, '') + + def test_hwsku(self): + argument = "-v \"DEVICE_METADATA[\'localhost\'][\'hwsku\']\" -m \"{}\"".format(self.sample_graph) + output = self.run_script(argument) + self.assertEqual(output.strip(), SKU) + for asic in range(NUM_ASIC): + output = self.run_script_for_asic(argument, asic) + self.assertEqual(output.strip(), SKU) + + def test_print_data(self): + argument = "-m \"{}\" --print-data".format(self.sample_graph) + output = self.run_script(argument) + self.assertGreater(len(output.strip()) , 0) + for asic in range(NUM_ASIC): + output = self.run_script_for_asic(argument, asic) + self.assertGreater(len(output.strip()) , 0) + + def test_additional_json_data(self): + argument = '-a \'{"key1":"value1"}\' -v key1' + output = self.run_script(argument) + self.assertEqual(output.strip(), 'value1') + for asic in range(NUM_ASIC): + output = self.run_script_for_asic(argument, asic) + self.assertEqual(output.strip(), 'value1') + + def test_read_yaml(self): + argument = '-v yml_item -y ' + os.path.join(self.test_dir, 'test.yml') + output = yaml.load(self.run_script(argument)) + self.assertListEqual(output, ['value1', 'value2']) + for asic in range(NUM_ASIC): + output = yaml.load(self.run_script_for_asic(argument, asic)) + self.assertListEqual(output, ['value1', 'value2']) + + def test_render_template(self): + argument = '-y ' + os.path.join(self.test_dir, 'test.yml') + ' -t ' + os.path.join(self.test_dir, 'test.j2') + output = self.run_script(argument) + self.assertEqual(output.strip(), 'value1\nvalue2') + for asic in range(NUM_ASIC): + output = self.run_script_for_asic(argument, asic) + self.assertEqual(output.strip(), 'value1\nvalue2') + + def test_metadata_tacacs(self): + argument = '-m "' + self.sample_graph + '" --var-json "TACPLUS_SERVER"' + output = json.loads(self.run_script(argument)) + self.assertDictEqual(output, {'123.46.98.21': {'priority': '1', 'tcp_port': '49'}}) + #TACPLUS_SERVER not present in the asic configuration. + for asic in range(NUM_ASIC): + output = json.loads(self.run_script_for_asic(argument, asic, self.port_config[asic])) + self.assertDictEqual(output, {}) + + def test_metadata_ntp(self): + argument = '-m "' + self.sample_graph + '" --var-json "NTP_SERVER"' + output = json.loads(self.run_script(argument)) + self.assertDictEqual(output, {'17.39.1.130': {}, '17.39.1.129': {}}) + #NTP data is present only in the host config + for asic in range(NUM_ASIC): + output = json.loads(self.run_script_for_asic(argument, asic, self.port_config[asic])) + print "Log:asic{} sku {}".format(asic,output) + self.assertDictEqual(output, {}) + + def test_mgmt_port(self): + argument = '-m "' + self.sample_graph + '" --var-json "MGMT_PORT"' + output = json.loads(self.run_script(argument)) + self.assertDictEqual(output, {'eth0': {'alias': 'eth0', 'admin_status': 'up'}}) + for asic in range(NUM_ASIC): + output = json.loads(self.run_script_for_asic(argument, asic, self.port_config[asic])) + self.assertDictEqual(output, {}) + + def test_frontend_asic_portchannels(self): + argument = "-m {} -p {} -n asic0 --var-json \"PORTCHANNEL\"".format(self.sample_graph, self.port_config[0]) + output = json.loads(self.run_script(argument)) + self.assertDictEqual(output, \ + {'PortChannel0002': {'admin_status': 'up', 'min_links': '2', 'members': ['Ethernet0', 'Ethernet4'], 'mtu': '9100'}, + 'PortChannel4001': {'admin_status': 'up', 'min_links': '2', 'members': ['Ethernet-BP0', 'Ethernet-BP4'], 'mtu': '9100'}, + 'PortChannel4002': {'admin_status': 'up', 'min_links': '2', 'members': ['Ethernet-BP8', 'Ethernet-BP12'], 'mtu': '9100'}}) + + def test_backend_asic_portchannels(self): + argument = "-m {} -p {} -n asic3 --var-json \"PORTCHANNEL\"".format(self.sample_graph, self.port_config[3]) + output = json.loads(self.run_script(argument)) + self.assertDictEqual(output, \ + {'PortChannel4013': {'admin_status': 'up', 'min_links': '2', 'members': ['Ethernet-BP384', 'Ethernet-BP388'], 'mtu': '9100'}, + 'PortChannel4014': {'admin_status': 'up', 'min_links': '2', 'members': ['Ethernet-BP392', 'Ethernet-BP396'], 'mtu': '9100'}}) + + def test_frontend_asic_portchannel_mem(self): + argument = "-m {} -p {} -n asic0 --var-json \"PORTCHANNEL_MEMBER\"".format(self.sample_graph, self.port_config[0]) + output = json.loads(self.run_script(argument)) + self.assertListEqual(output.keys(), \ + ['PortChannel4002|Ethernet-BP8', 'PortChannel0002|Ethernet0', 'PortChannel0002|Ethernet4', 'PortChannel4002|Ethernet-BP12', 'PortChannel4001|Ethernet-BP0', 'PortChannel4001|Ethernet-BP4']) + + def test_backend_asic_portchannels_mem(self): + argument = "-m {} -p {} -n asic3 --var-json \"PORTCHANNEL_MEMBER\"".format(self.sample_graph, self.port_config[3]) + output = json.loads(self.run_script(argument)) + self.assertListEqual(output.keys(), \ + ['PortChannel4013|Ethernet-BP384', 'PortChannel4014|Ethernet-BP392', 'PortChannel4014|Ethernet-BP396', 'PortChannel4013|Ethernet-BP388']) + + def test_frontend_asic_portchannel_intf(self): + argument = "-m {} -p {} -n asic0 --var-json \"PORTCHANNEL_INTERFACE\"".format(self.sample_graph, self.port_config[0]) + output = json.loads(self.run_script(argument)) + self.assertListEqual(output.keys(), \ + ['PortChannel4001|10.1.0.1/31', 'PortChannel0002|FC00::1/126', 'PortChannel4002|10.1.0.3/31', 'PortChannel0002', 'PortChannel0002|10.0.0.0/31', 'PortChannel4001', 'PortChannel4002']) + + def test_backend_asic_portchannel_intf(self): + argument = "-m {} -p {} -n asic3 --var-json \"PORTCHANNEL_INTERFACE\"".format(self.sample_graph, self.port_config[3]) + output = json.loads(self.run_script(argument)) + self.assertListEqual(output.keys(), \ + ['PortChannel4013', 'PortChannel4013|10.1.0.2/31', 'PortChannel4014', 'PortChannel4014|10.1.0.6/31']) + + def test_frontend_asic_device_neigh(self): + argument = "-m {} -p {} -n asic0 --var-json \"DEVICE_NEIGHBOR\"".format(self.sample_graph, self.port_config[0]) + output = json.loads(self.run_script(argument)) + self.assertDictEqual(output, \ + {'Ethernet0': {'name': '01T2', 'port': 'Ethernet1'}, + 'Ethernet4': {'name': '01T2', 'port': 'Ethernet2'}, + 'Ethernet-BP4': {'name': 'ASIC2', 'port': 'Eth1-ASIC2'}, + 'Ethernet-BP12': {'name': 'ASIC3', 'port': 'Eth1-ASIC3'}, + 'Ethernet-BP0': {'name': 'ASIC2', 'port': 'Eth0-ASIC2'}, + 'Ethernet-BP8': {'name': 'ASIC3', 'port': 'Eth0-ASIC3'}}) + + def test_frontend_asic_device_neigh_metadata(self): + argument = "-m {} -p {} -n asic0 --var-json \"DEVICE_NEIGHBOR_METADATA\"".format(self.sample_graph, self.port_config[0]) + output = json.loads(self.run_script(argument)) + self.assertDictEqual(output, \ + {'01T2': {'lo_addr': None, 'mgmt_addr': '89.139.132.40', 'hwsku': 'VM', 'type': 'SpineRouter'}, + 'ASIC3': {'lo_addr': '0.0.0.0/0', 'mgmt_addr': '0.0.0.0/0', 'hwsku': 'multi-npu-asic', 'type': 'Asic'}, + 'ASIC2': {'lo_addr': '0.0.0.0/0', 'mgmt_addr': '0.0.0.0/0', 'hwsku': 'multi-npu-asic', 'type': 'Asic'}}) + + def test_backend_asic_device_neigh(self): + argument = "-m {} -p {} -n asic3 --var-json \"DEVICE_NEIGHBOR\"".format(self.sample_graph, self.port_config[3]) + output = json.loads(self.run_script(argument)) + self.assertDictEqual(output, \ + {'Ethernet-BP396': {'name': 'ASIC1', 'port': 'Eth7-ASIC1'}, + 'Ethernet-BP384': {'name': 'ASIC0', 'port': 'Eth6-ASIC0'}, + 'Ethernet-BP392': {'name': 'ASIC1', 'port': 'Eth6-ASIC1'}, + 'Ethernet-BP388': {'name': 'ASIC0', 'port': 'Eth7-ASIC0'}}) + + def test_backend_device_neigh_metadata(self): + argument = "-m {} -p {} -n asic3 --var-json \"DEVICE_NEIGHBOR_METADATA\"".format(self.sample_graph, self.port_config[3]) + output = json.loads(self.run_script(argument)) + self.assertDictEqual(output, \ + {'ASIC1': {'lo_addr': '0.0.0.0/0', 'mgmt_addr': '0.0.0.0/0', 'hwsku': 'multi-npu-asic', 'type': 'Asic'}, + 'ASIC0': {'lo_addr': '0.0.0.0/0', 'mgmt_addr': '0.0.0.0/0', 'hwsku': 'multi-npu-asic', 'type': 'Asic'}}) + + def test_frontend_bgp_neighbor(self): + argument = "-m {} -p {} -n asic0 --var-json \"BGP_NEIGHBOR\"".format(self.sample_graph, self.port_config[0]) + output = json.loads(self.run_script(argument)) + self.assertDictEqual(output, \ + {'10.0.0.1': {'rrclient': 0, 'name': '01T2', 'local_addr': '10.0.0.0', 'nhopself': 0, 'holdtime': '10', 'asn': '65200', 'keepalive': '3'}, + '10.1.0.0': {'rrclient': 0, 'name': 'ASIC2', 'local_addr': '10.1.0.1', 'nhopself': 0, 'holdtime': '0', 'asn': '65100', 'keepalive': '0'}, + 'fc00::2': {'rrclient': 0, 'name': '01T2', 'local_addr': 'fc00::1', 'nhopself': 0, 'holdtime': '10', 'asn': '65200', 'keepalive': '3'}, + '10.1.0.2': {'rrclient': 0, 'name': 'ASIC3', 'local_addr': '10.1.0.3', 'nhopself': 0, 'holdtime': '0', 'asn': '65100', 'keepalive': '0'}}) + + def test_backend_asic_bgp_neighbor(self): + argument = "-m {} -p {} -n asic3 --var-json \"BGP_NEIGHBOR\"".format(self.sample_graph, self.port_config[3]) + output = json.loads(self.run_script(argument)) + self.assertDictEqual(output, \ + {'10.1.0.7': {'rrclient': 0, 'name': 'ASIC1', 'local_addr': '10.1.0.6', 'nhopself': 0, 'holdtime': '0', 'asn': '65100', 'keepalive': '0'}, + '10.1.0.3': {'rrclient': 0, 'name': 'ASIC0', 'local_addr': '10.1.0.2', 'nhopself': 0, 'holdtime': '0', 'asn': '65100', 'keepalive': '0'}}) + + def test_device_asic_metadata(self): + argument = "-m {} --var-json DEVICE_METADATA".format(self.sample_graph) + for asic in range(NUM_ASIC): + output = json.loads(self.run_script_for_asic(argument, asic,self.port_config[asic])) + asic_name = "asic{}".format(asic) + self.assertEqual(output['localhost']['hostname'], asic_name) + self.assertEqual(output['localhost']['type'], 'Asic') + if asic == 0 or asic == 1: + self.assertEqual(output['localhost']['sub_role'], 'FrontEnd') + else: + self.assertEqual(output['localhost']['sub_role'], 'BackEnd')