diff --git a/src/sonic-config-engine/minigraph.py b/src/sonic-config-engine/minigraph.py index 24eeb20522..3500f8ba20 100644 --- a/src/sonic-config-engine/minigraph.py +++ b/src/sonic-config-engine/minigraph.py @@ -14,7 +14,7 @@ from lxml import etree as ET from lxml.etree import QName from portconfig import get_port_config -from sonic_py_common.device_info import get_npu_id_from_name +from sonic_py_common.multi_asic import get_asic_id_from_name """minigraph.py version_added: "1.9" @@ -840,7 +840,7 @@ def parse_xml(filename, platform=None, port_config_file=None, asic_name=None, hw # 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) + asic_id = get_asic_id_from_name(asic_name) else: asic_id = None diff --git a/src/sonic-config-engine/sonic-cfggen b/src/sonic-config-engine/sonic-cfggen index 9a65343a99..307c127bf9 100755 --- a/src/sonic-config-engine/sonic-cfggen +++ b/src/sonic-config-engine/sonic-cfggen @@ -43,7 +43,7 @@ from minigraph import parse_device_desc_xml from minigraph import parse_asic_sub_role from portconfig import get_port_config, get_port_config_file_name, get_breakout_mode from sonic_py_common.device_info import get_platform, get_system_mac -from sonic_py_common.device_info import get_npu_id_from_name, is_multi_npu +from sonic_py_common.multi_asic import get_asic_id_from_name, is_multi_asic from config_samples import generate_sample_config from config_samples import get_available_config from swsssdk import SonicV2Connector, ConfigDBConnector, SonicDBConfig @@ -240,8 +240,8 @@ def _get_jinja2_env(paths): env.filters['ip_network'] = ip_network for attr in ['ip', 'network', 'prefixlen', 'netmask', 'broadcast']: env.filters[attr] = partial(prefix_attr, attr) - # Pass the is_multi_npu function as global - env.globals['multi_asic'] = is_multi_npu + # Pass the is_multi_asic function as global + env.globals['multi_asic'] = is_multi_asic return env @@ -284,7 +284,7 @@ def main(): asic_name = args.namespace asic_id = None if asic_name is not None: - asic_id = get_npu_id_from_name(asic_name) + asic_id = get_asic_id_from_name(asic_name) # Load the database config for the namespace from global database json 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 index 3fe912c98c..5b2786fd8a 100644 --- 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 @@ -1,9 +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 +# name lanes alias index asic_port_name role +Ethernet0 33,34,35,36 Ethernet1/1 0 Eth0-ASIC0 Ext +Ethernet4 29,30,31,32 Ethernet1/2 1 Eth1-ASIC0 Ext +Ethernet8 41,42,43,44 Ethernet1/3 2 Eth2-ASIC0 Ext +Ethernet12 37,38,39,40 Ethernet1/4 3 Eth3-ASIC0 Ext +Ethernet-BP0 13,14,15,16 Ethernet-BP0 0 Eth4-ASIC0 Int +Ethernet-BP4 17,18,19,20 Ethernet-BP4 1 Eth5-ASIC0 Int +Ethernet-BP8 21,22,23,24 Ethernet-BP8 2 Eth6-ASIC0 Int +Ethernet-BP12 25,26,27,28 Ethernet-BP12 3 Eth7-ASIC0 Int \ 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 index c496e0712a..9764ede973 100644 --- 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 @@ -1,9 +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 +# name lanes alias index asic_port_name role +Ethernet16 33,34,35,36 Ethernet1/5 4 Eth0-ASIC1 Ext +Ethernet20 29,30,31,32 Ethernet1/6 5 Eth1-ASIC1 Ext +Ethernet24 41,42,43,44 Ethernet1/7 6 Eth2-ASIC1 Ext +Ethernet28 37,38,39,40 Ethernet1/8 7 Eth3-ASIC1 Ext +Ethernet-BP16 13,14,15,16 Ethernet-BP16 4 Eth4-ASIC1 Int +Ethernet-BP20 17,18,19,20 Ethernet-BP20 5 Eth5-ASIC1 Int +Ethernet-BP24 21,22,23,24 Ethernet-BP24 6 Eth6-ASIC1 Int +Ethernet-BP28 25,26,27,28 Ethernet-BP28 7 Eth7-ASIC1 Int \ 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 index 4ae0575835..b3bc03dbe9 100644 --- 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 @@ -1,9 +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 +# name lanes alias index asic_port_name role +Ethernet-BP256 61,62,63,64 Ethernet-BP256 8 Eth0-ASIC2 Int +Ethernet-BP260 57,58,59,60 Ethernet-BP260 9 Eth1-ASIC2 Int +Ethernet-BP264 53,54,55,56 Ethernet-BP264 10 Eth2-ASIC2 Int +Ethernet-BP268 49,50,51,52 Ethernet-BP268 11 Eth3-ASIC2 Int +Ethernet-BP272 45,46,47,48 Ethernet-BP272 12 Eth4-ASIC2 Int +Ethernet-BP276 41,42,43,44 Ethernet-BP276 13 Eth5-ASIC2 Int +Ethernet-BP280 37,38,39,40 Ethernet-BP280 14 Eth6-ASIC2 Int +Ethernet-BP284 33,34,35,36 Ethernet-BP284 15 Eth7-ASIC2 Int \ 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 index 8f45ed1494..2fb7f4ae6a 100644 --- 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 @@ -1,9 +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 +# name lanes alias index asic_port_name role +Ethernet-BP384 29,30,31,32 Ethernet-BP384 16 Eth0-ASIC3 Int +Ethernet-BP388 25,26,27,28 Ethernet-BP388 17 Eth1-ASIC3 Int +Ethernet-BP392 21,22,23,24 Ethernet-BP392 18 Eth2-ASIC3 Int +Ethernet-BP396 17,18,19,20 Ethernet-BP396 19 Eth3-ASIC3 Int +Ethernet-BP400 13,14,15,16 Ethernet-BP400 20 Eth4-ASIC3 Int +Ethernet-BP404 9,10,11,12 Ethernet-BP404 21 Eth5-ASIC3 Int +Ethernet-BP408 5,6,7,8 Ethernet-BP408 22 Eth6-ASIC3 Int +Ethernet-BP412 1,2,3,4 Ethernet-BP412 23 Eth7-ASIC3 Int \ 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 index 4b05541daa..13bda90314 100644 --- a/src/sonic-config-engine/tests/test_multinpu_cfggen.py +++ b/src/sonic-config-engine/tests/test_multinpu_cfggen.py @@ -158,6 +158,19 @@ class TestMultiNpuCfgGen(TestCase): self.assertListEqual(output.keys(), \ ['PortChannel4013', 'PortChannel4013|10.1.0.2/31', 'PortChannel4014', 'PortChannel4014|10.1.0.6/31']) + def test_frontend_asic_ports(self): + argument = "-m {} -p {} -n asic0 --var-json \"PORT\"".format(self.sample_graph, self.port_config[0]) + output = json.loads(self.run_script(argument)) + self.assertDictEqual(output, \ + {"Ethernet0": { "admin_status": "up", "alias": "Ethernet1/1", "asic_port_name": "Eth0-ASIC0", "description": "01T2:Ethernet1", "index": "0", "lanes": "33,34,35,36", "mtu": "9100", "pfc_asym": "off", "role": "Ext", "speed": "40000" }, + "Ethernet4": { "admin_status": "up", "alias": "Ethernet1/2", "asic_port_name": "Eth1-ASIC0", "description": "01T2:Ethernet2", "index": "1", "lanes": "29,30,31,32", "mtu": "9100", "pfc_asym": "off", "role": "Ext", "speed": "40000" }, + "Ethernet8": { "alias": "Ethernet1/3", "asic_port_name": "Eth2-ASIC0", "description": "Ethernet1/3", "index": "2", "lanes": "41,42,43,44", "mtu": "9100", "pfc_asym": "off", "role": "Ext", "speed": "40000" }, + "Ethernet12": { "alias": "Ethernet1/4", "asic_port_name": "Eth3-ASIC0", "description": "Ethernet1/4", "index": "3", "lanes": "37,38,39,40", "mtu": "9100", "pfc_asym": "off", "role": "Ext", "speed": "40000" }, + "Ethernet-BP0": { "admin_status": "up", "alias": "Ethernet-BP0", "asic_port_name": "Eth4-ASIC0", "description": "ASIC2:Eth0-ASIC2", "index": "0", "lanes": "13,14,15,16", "mtu": "9100", "pfc_asym": "off", "role": "Int", "speed": "40000" }, + "Ethernet-BP4": { "admin_status": "up", "alias": "Ethernet-BP4", "asic_port_name": "Eth5-ASIC0", "description": "ASIC2:Eth1-ASIC2", "index": "1", "lanes": "17,18,19,20", "mtu": "9100", "pfc_asym": "off", "role": "Int", "speed": "40000" }, + "Ethernet-BP8": { "admin_status": "up", "alias": "Ethernet-BP8", "asic_port_name": "Eth6-ASIC0", "description": "ASIC3:Eth0-ASIC3", "index": "2", "lanes": "21,22,23,24", "mtu": "9100", "pfc_asym": "off", "role": "Int", "speed": "40000" }, + "Ethernet-BP12": { "admin_status": "up", "alias": "Ethernet-BP12", "asic_port_name": "Eth7-ASIC0", "description": "ASIC3:Eth1-ASIC3", "index": "3", "lanes": "25,26,27,28", "mtu": "9100", "pfc_asym": "off", "role": "Int", "speed": "40000" }}) + 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)) diff --git a/src/sonic-py-common/sonic_py_common/multi_asic.py b/src/sonic-py-common/sonic_py_common/multi_asic.py new file mode 100644 index 0000000000..966b5d238e --- /dev/null +++ b/src/sonic-py-common/sonic_py_common/multi_asic.py @@ -0,0 +1,300 @@ +import glob +import os + +from natsort import natsorted +from swsssdk import ConfigDBConnector +from swsssdk import SonicDBConfig +from swsssdk import SonicV2Connector + +from .device_info import CONTAINER_PLATFORM_PATH +from .device_info import HOST_DEVICE_PATH +from .device_info import get_platform + +ASIC_NAME_PREFIX = 'asic' +NAMESPACE_PATH_GLOB = '/run/netns/*' +ASIC_CONF_FILENAME = 'asic.conf' +FRONTEND_ASIC_SUB_ROLE = 'FrontEnd' +BACKEND_ASIC_SUB_ROLE = 'BackEnd' +EXTERNAL_PORT = 'Ext' +INTERNAL_PORT = 'Int' +PORT_CHANNEL_CFG_DB_TABLE = 'PORTCHANNEL' +PORT_CFG_DB_TABLE = 'PORT' +BGP_NEIGH_CFG_DB_TABLE = 'BGP_NEIGHBOR' +NEIGH_DEVICE_METADATA_CFG_DB_TABLE = 'DEVICE_NEIGHBOR_METADATA' +DEFAULT_NAMESPACE = '' +PORT_ROLE = 'role' + + +def connect_config_db_for_ns(namespace=DEFAULT_NAMESPACE): + """ + The function connects to the config DB for a given namespace and + returns the handle + If no namespace is provided, it will connect to the db in the + default namespace. + In case of multi ASIC, the default namespace is the database + instance running the on the host + + Returns: + handle to the config_db for a namespace + """ + SonicDBConfig.load_sonic_global_db_config() + config_db = ConfigDBConnector(namespace=namespace) + config_db.connect() + return config_db + + +def connect_to_all_dbs_for_ns(namespace=DEFAULT_NAMESPACE): + """ + The function connects to the DBs for a given namespace and + returns the handle + If no namespace is provided, it will connect to the db in the + default namespace. + In case of multi ASIC, the default namespace is the + database instance running the on the host + In case of single ASIC, the namespace has to be DEFAULT_NAMESPACE + + Returns: + handle to all the dbs for a namespaces + """ + SonicDBConfig.load_sonic_global_db_config() + db = SonicV2Connector(namespace=namespace) + for db_id in db.get_db_list(): + db.connect(db_id) + return db + + +def get_asic_conf_file_path(): + """ + Retrieves the path to the ASIC conguration file on the device + + Returns: + A string containing the path to the ASIC conguration file on success, + None on failure + """ + asic_conf_path_candidates = [] + + asic_conf_path_candidates.append(os.path.join(CONTAINER_PLATFORM_PATH, + ASIC_CONF_FILENAME)) + + platform = get_platform() + if platform: + asic_conf_path_candidates.append(os.path.join( + HOST_DEVICE_PATH, platform, ASIC_CONF_FILENAME)) + + for asic_conf_file_path in asic_conf_path_candidates: + if os.path.isfile(asic_conf_file_path): + return asic_conf_file_path + + return None + + +def get_num_asics(): + """ + Retrieves the num of asics present in the multi ASIC platform + + Returns: + Num of asics + """ + asic_conf_file_path = get_asic_conf_file_path() + + if asic_conf_file_path is None: + 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_asics = tokens[1].strip() + return int(num_asics) + + +def is_multi_asic(): + """ + Checks if the device is multi asic or not + + Returns: + True: if the num of asic is more than 1 + """ + num_asics = get_num_asics() + + return (num_asics > 1) + + +def get_asic_id_from_name(asic_name): + + if asic_name.startswith(ASIC_NAME_PREFIX): + return asic_name[len(ASIC_NAME_PREFIX):] + else: + return None + + +def get_namespaces_from_linux(): + """ + In a multi asic platform, each ASIC is in a Linux Namespace. + This method returns list of all the Namespace present on the device + + Note: It is preferable to use this function can be used only + when the config_db is not available. + When configdb is available use get_all_namespaces() + + Returns: + List of the namespaces present in the system + """ + 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_all_namespaces(): + """ + In case of Multi-Asic platform, Each ASIC will have a linux network namespace created. + So we loop through the databases in different namespaces and depending on the sub_role + decide whether this is a front end ASIC/namespace or a back end one. + """ + front_ns = [] + back_ns = [] + num_asics = get_num_asics() + + if is_multi_asic(): + for asic in range(num_asics): + namespace = "{}{}".format(ASIC_NAME_PREFIX, asic) + config_db = connect_config_db_for_ns(namespace) + + metadata = config_db.get_table('DEVICE_METADATA') + if metadata['localhost']['sub_role'] == FRONTEND_ASIC_SUB_ROLE: + front_ns.append(namespace) + elif metadata['localhost']['sub_role'] == BACKEND_ASIC_SUB_ROLE: + back_ns.append(namespace) + + return {'front_ns': front_ns, 'back_ns': back_ns} + + +def get_namespace_list(namespace=None): + if not is_multi_asic(): + ns_list = [DEFAULT_NAMESPACE] + return ns_list + + if namespace is None: + # there are few commands that needs to work even if the + # config db is not present. So get the namespaces + # list from linux + ns_list = get_namespaces_from_linux() + else: + ns_list = [namespace] + + return ns_list + + +def get_port_table(namespace=None): + """ + Retrieves the ports from all the asic present on the devices + + Returns: + a dict of all the ports + """ + all_ports = {} + ns_list = get_namespace_list(namespace) + + for ns in ns_list: + ports = get_port_table_for_asic(ns) + all_ports.update(ports) + + return all_ports + + +def get_port_table_for_asic(namespace): + + config_db = connect_config_db_for_ns(namespace) + ports = config_db.get_table(PORT_CFG_DB_TABLE) + return ports + + +def get_namespace_for_port(port_name): + + ns_list = get_namespace_list() + port_namespace = None + + for ns in ns_list: + ports = get_port_table_for_asic(ns) + if port_name in ports: + port_namespace = ns + break + + if port_namespace is None: + raise ValueError('Unknown port name {}'.format(port_name)) + + return port_namespace + + +def get_port_role(port_name, namespace=None): + + ports_config = get_port_table(namespace) + if port_name not in ports_config: + raise ValueError('Unknown port name {}'.format(port_name)) + + if PORT_ROLE not in ports_config[port_name]: + return EXTERNAL_PORT + + role = ports_config[port_name][PORT_ROLE] + return role + + +def is_port_internal(port_name, namespace=None): + + role = get_port_role(port_name, namespace) + + if role == INTERNAL_PORT: + return True + + return False + + +def is_port_channel_internal(port_channel, namespace=None): + + if not is_multi_asic(): + return False + + ns_list = get_namespace_list(namespace) + + for ns in ns_list: + config_db = connect_config_db_for_ns(ns) + port_channels = config_db.get_table(PORT_CHANNEL_CFG_DB_TABLE) + + if port_channel in port_channels: + if 'members' in port_channel: + members = port_channels[port_channel]['members'] + if is_port_internal(members[0], namespace): + return True + + return False + + +def is_bgp_session_internal(bgp_neigh_ip, namespace=None): + + if not is_multi_asic(): + return False + + ns_list = get_namespace_list(namespace) + + for ns in ns_list: + + config_db = connect_config_db_for_ns(ns) + bgp_sessions = config_db.get_table(BGP_NEIGH_CFG_DB_TABLE) + if bgp_neigh_ip not in bgp_sessions: + continue + + bgp_neigh_name = bgp_sessions[bgp_neigh_ip]['name'] + neighbor_metadata = config_db.get_table( + NEIGH_DEVICE_METADATA_CFG_DB_TABLE) + + if ((neighbor_metadata) and + (neighbor_metadata[bgp_neigh_name]['type'].lower() == + ASIC_NAME_PREFIX)): + return True + else: + return False + return False