Common functions for Multi ASIC (#4973)

Signed-off-by: Arvindsrinivasan Lakshmi Narasimhan arlakshm@microsoft.com

The following common APIs are added for multi ASIC
- an  API to check if a given port is a internal or external port
- an  API to check if a given port-channel is internal or external
- an API to check if a bgp-session is internal or external
- an  API to connect to the config and other dbs in the a given namespace
- added common APIs to the sonic_py_common library.
- update the sample port-config.ini with role column and add corresponding test to verify the ports configuration is - generated properly.
This commit is contained in:
arlakshm 2020-08-14 07:36:00 -07:00 committed by GitHub
parent bf3c901c6c
commit 6c895513ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 355 additions and 42 deletions

View File

@ -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

View File

@ -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

View File

@ -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
# 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

View File

@ -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
# 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

View File

@ -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
# 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

View File

@ -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
# 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

View File

@ -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))

View File

@ -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