* Moved utility functions for multi-npu platforms from sonic-utilities config/main.py to here so that they can be used any module * Fix the issue with test run during compilation with acl-uploader PR#908 of sonic-utilities. * Fix get_num_npu as it was retuning string and not int * Address Review Comments * Address Review Comments
213 lines
7.2 KiB
Python
213 lines
7.2 KiB
Python
#!/usr/bin/env python
|
|
import os
|
|
import yaml
|
|
import subprocess
|
|
import re
|
|
from natsort import natsorted
|
|
import glob
|
|
from swsssdk import ConfigDBConnector, SonicDBConfig
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: sonic_device_util
|
|
version_added: "1.9"
|
|
short_description: Retrieve device related facts for a device.
|
|
description:
|
|
- Retrieve device related facts from config files.
|
|
'''
|
|
|
|
'''
|
|
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/*'
|
|
ASIC_CONF_FILENAME = 'asic.conf'
|
|
FRONTEND_ASIC_SUB_ROLE = 'FrontEnd'
|
|
BACKEND_ASIC_SUB_ROLE = 'BackEnd'
|
|
def get_machine_info():
|
|
if not os.path.isfile('/host/machine.conf'):
|
|
return None
|
|
machine_vars = {}
|
|
with open('/host/machine.conf') as machine_file:
|
|
for line in machine_file:
|
|
tokens = line.split('=')
|
|
if len(tokens) < 2:
|
|
continue
|
|
machine_vars[tokens[0]] = tokens[1].strip()
|
|
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())
|
|
if not platform:
|
|
return 1
|
|
asic_conf_file_path = os.path.join(SONIC_DEVICE_PATH, platform, ASIC_CONF_FILENAME)
|
|
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 int(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_hwsku():
|
|
config_db = ConfigDBConnector()
|
|
config_db.connect()
|
|
metadata = config_db.get_table('DEVICE_METADATA')
|
|
return metadata['localhost']['hwsku']
|
|
|
|
def get_platform():
|
|
if not os.path.isfile('/host/machine.conf'):
|
|
return ''
|
|
|
|
with open('/host/machine.conf') as machine_conf:
|
|
for line in machine_conf:
|
|
tokens = line.split('=')
|
|
if tokens[0].strip() == 'onie_platform' or tokens[0].strip() == 'aboot_platform':
|
|
return tokens[1].strip()
|
|
return ''
|
|
|
|
def is_multi_npu():
|
|
num_npus = get_num_npus()
|
|
return (num_npus > 1)
|
|
|
|
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_npus = get_num_npus()
|
|
SonicDBConfig.load_sonic_global_db_config()
|
|
|
|
if is_multi_npu():
|
|
for npu in range(num_npus):
|
|
namespace = "{}{}".format(NPU_NAME_PREFIX, npu)
|
|
config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace)
|
|
config_db.connect()
|
|
|
|
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_platform_info(machine_info):
|
|
if machine_info != None:
|
|
if machine_info.has_key('onie_platform'):
|
|
return machine_info['onie_platform']
|
|
elif machine_info.has_key('aboot_platform'):
|
|
return machine_info['aboot_platform']
|
|
return None
|
|
|
|
def get_sonic_version_info():
|
|
if not os.path.isfile('/etc/sonic/sonic_version.yml'):
|
|
return None
|
|
data = {}
|
|
with open('/etc/sonic/sonic_version.yml') as stream:
|
|
if yaml.__version__ >= "5.1":
|
|
data = yaml.full_load(stream)
|
|
else:
|
|
data = yaml.load(stream)
|
|
return data
|
|
|
|
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(namespace=None):
|
|
version_info = get_sonic_version_info()
|
|
|
|
if (version_info['asic_type'] == 'mellanox'):
|
|
# With Mellanox ONIE release(2019.05-5.2.0012) and above
|
|
# "onie_base_mac" was added to /host/machine.conf:
|
|
# onie_base_mac=e4:1d:2d:44:5e:80
|
|
# So we have another way to get the mac address besides decode syseeprom
|
|
# By this can mitigate the dependency on the hw-management service
|
|
base_mac_key = "onie_base_mac"
|
|
machine_vars = get_machine_info()
|
|
if machine_vars is not None and base_mac_key in machine_vars:
|
|
mac = machine_vars[base_mac_key]
|
|
mac = mac.strip()
|
|
if valid_mac_address(mac):
|
|
return mac
|
|
|
|
hw_mac_entry_cmds = [ "sudo decode-syseeprom -m" ]
|
|
elif (version_info['asic_type'] == 'marvell'):
|
|
# 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' + 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:
|
|
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)
|
|
(mac, err) = proc.communicate()
|
|
if err:
|
|
continue
|
|
mac = mac.strip()
|
|
if valid_mac_address(mac):
|
|
break
|
|
|
|
if not valid_mac_address(mac):
|
|
return None
|
|
|
|
# Align last byte of MAC if necessary
|
|
if version_info and version_info['asic_type'] == 'centec':
|
|
last_byte = mac[-2:]
|
|
aligned_last_byte = format(int(int(last_byte, 16) + 1), '02x')
|
|
mac = mac[:-2] + aligned_last_byte
|
|
return mac
|
|
|
|
#
|
|
# Function to obtain the routing-stack being utilized. Function is not
|
|
# platform-specific; it's being placed in this file temporarily till a more
|
|
# suitable location is identified as part of upcoming refactoring efforts.
|
|
#
|
|
def get_system_routing_stack():
|
|
command = "sudo docker ps | grep bgp | awk '{print$2}' | cut -d'-' -f3 | cut -d':' -f1"
|
|
|
|
try:
|
|
proc = subprocess.Popen(command,
|
|
stdout=subprocess.PIPE,
|
|
shell=True,
|
|
stderr=subprocess.STDOUT)
|
|
stdout = proc.communicate()[0]
|
|
proc.wait()
|
|
result = stdout.rstrip('\n')
|
|
|
|
except OSError, e:
|
|
raise OSError("Cannot detect routing-stack")
|
|
|
|
return result
|