4b31e30924
Added support data for fabric monitoring in CONFIG_DB The CONFIG_DB now has the FABRIC_MONITOR|FABRIC_MONITOR_DATA table for default value for fabric port monitoring. An example output of getting this table is: sonic-db-cli CONFIG_DB hgetall "FABRIC_MONITOR|FABRIC_MONITOR_DATA" {'monErrThreshCrcCells': '1', 'monErrThreshRxCells': '61035156', 'monPollThreshIsolation': '1', 'monPollThreshRecovery': '8'} The CONFIG_DB now also has a table for each fabric port for its isolate status. An example output of getting this table is: sonic-db-cli CONFIG_DB hgetall "FABRIC_PORT|Fabric20" {'alias': 'Fabric20', 'isolateStatus': 'False', 'lanes': '20'}
465 lines
16 KiB
Python
465 lines
16 KiB
Python
try:
|
|
import ast
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
from swsscommon import swsscommon
|
|
from sonic_py_common import device_info
|
|
from sonic_py_common.multi_asic import get_asic_id_from_name
|
|
except ImportError as e:
|
|
raise ImportError("%s - required module not found" % str(e))
|
|
|
|
try:
|
|
if os.environ["CFGGEN_UNIT_TESTING"] == "2":
|
|
modules_path = os.path.join(os.path.dirname(__file__), ".")
|
|
tests_path = os.path.join(modules_path, "tests")
|
|
sys.path.insert(0, modules_path)
|
|
sys.path.insert(0, tests_path)
|
|
import mock_tables.dbconnector
|
|
mock_tables.dbconnector.load_namespace_config()
|
|
|
|
except KeyError:
|
|
pass
|
|
|
|
# Global Variable
|
|
PLATFORM_ROOT_PATH = '/usr/share/sonic/device'
|
|
PLATFORM_ROOT_PATH_DOCKER = '/usr/share/sonic/platform'
|
|
SONIC_ROOT_PATH = '/usr/share/sonic'
|
|
HWSKU_ROOT_PATH = '/usr/share/sonic/hwsku'
|
|
|
|
PLATFORM_JSON = 'platform.json'
|
|
PORT_CONFIG_INI = 'port_config.ini'
|
|
HWSKU_JSON = 'hwsku.json'
|
|
|
|
PORT_STR = "Ethernet"
|
|
BRKOUT_MODE = "default_brkout_mode"
|
|
CUR_BRKOUT_MODE = "brkout_mode"
|
|
INTF_KEY = "interfaces"
|
|
OPTIONAL_HWSKU_ATTRIBUTES = ["fec", "autoneg"]
|
|
|
|
BRKOUT_PATTERN = r'(\d{1,6})x(\d{1,6}G?)(\[(\d{1,6}G?,?)*\])?(\((\d{1,6})\))?'
|
|
BRKOUT_PATTERN_GROUPS = 6
|
|
|
|
#
|
|
# Helper Functions
|
|
#
|
|
|
|
# For python2 compatibility
|
|
def py2JsonStrHook(j):
|
|
if isinstance(j, unicode):
|
|
return j.encode('utf-8', 'backslashreplace')
|
|
if isinstance(j, list):
|
|
return [py2JsonStrHook(item) for item in j]
|
|
if isinstance(j, dict):
|
|
return {py2JsonStrHook(key): py2JsonStrHook(value)
|
|
for key, value in j.iteritems()}
|
|
return j
|
|
|
|
def readJson(filename):
|
|
# Read 'platform.json' or 'hwsku.json' file
|
|
try:
|
|
with open(filename) as fp:
|
|
if sys.version_info.major == 2:
|
|
return json.load(fp, object_hook=py2JsonStrHook)
|
|
return json.load(fp)
|
|
except Exception as e:
|
|
print("error occurred while parsing json: {}".format(sys.exc_info()[1]))
|
|
return None
|
|
|
|
def db_connect_configdb(namespace=None):
|
|
"""
|
|
Connect to configdb
|
|
"""
|
|
config_db = swsscommon.ConfigDBConnector(use_unix_socket_path=True, namespace=namespace)
|
|
if config_db is None:
|
|
return None
|
|
try:
|
|
"""
|
|
This could be blocking during the config load_minigraph phase,
|
|
as the CONFIG_DB_INITIALIZED is not yet set in the configDB.
|
|
We can ignore the check by using config_db.db_connect('CONFIG_DB') instead
|
|
"""
|
|
# Connect only if available & initialized
|
|
config_db.connect(wait_for_init=False)
|
|
except Exception as e:
|
|
config_db = None
|
|
return config_db
|
|
|
|
def get_hwsku_file_name(hwsku=None, platform=None):
|
|
hwsku_candidates_Json = []
|
|
hwsku_candidates_Json.append(os.path.join(HWSKU_ROOT_PATH, HWSKU_JSON))
|
|
if hwsku:
|
|
if platform:
|
|
hwsku_candidates_Json.append(os.path.join(PLATFORM_ROOT_PATH, platform, hwsku, HWSKU_JSON))
|
|
hwsku_candidates_Json.append(os.path.join(PLATFORM_ROOT_PATH_DOCKER, hwsku, HWSKU_JSON))
|
|
hwsku_candidates_Json.append(os.path.join(SONIC_ROOT_PATH, hwsku, HWSKU_JSON))
|
|
for candidate in hwsku_candidates_Json:
|
|
if os.path.isfile(candidate):
|
|
return candidate
|
|
return None
|
|
|
|
def get_fabric_monitor_config(hwsku=None, asic_name=None):
|
|
config_db = db_connect_configdb(asic_name)
|
|
if config_db is not None:
|
|
fabric_monitor_global = config_db.get_table("FABRIC_MONITOR")
|
|
if bool( fabric_monitor_global ):
|
|
return fabric_monitor_global
|
|
|
|
fabric_monitor_global = {}
|
|
if asic_name is not None:
|
|
asic_id = str(get_asic_id_from_name(asic_name))
|
|
else:
|
|
asic_id = None
|
|
fabric_monitor_config_file = device_info.get_path_to_fabric_monitor_config_file(hwsku, asic_id)
|
|
if not fabric_monitor_config_file:
|
|
return fabric_monitor_global
|
|
with open( fabric_monitor_config_file, "r" ) as openfile:
|
|
fabric_monitor_global = json.load( openfile )
|
|
openfile.close()
|
|
|
|
return fabric_monitor_global
|
|
|
|
|
|
def get_fabric_port_config(hwsku=None, platform=None, fabric_port_config_file=None, hwsku_config_file=None, asic_name=None):
|
|
config_db = db_connect_configdb(asic_name)
|
|
if config_db is not None and fabric_port_config_file is None:
|
|
port_data = config_db.get_table("FABRIC_PORT")
|
|
if bool(port_data):
|
|
ports = ast.literal_eval(json.dumps(port_data))
|
|
return ports
|
|
|
|
if asic_name is not None:
|
|
asic_id = str(get_asic_id_from_name(asic_name))
|
|
else:
|
|
asic_id = None
|
|
|
|
if not fabric_port_config_file:
|
|
fabric_port_config_file = device_info.get_path_to_fabric_port_config_file(hwsku, asic_id)
|
|
if not fabric_port_config_file:
|
|
return {}
|
|
# else parse fabric_port_config.ini
|
|
ports = {}
|
|
|
|
# Default column definition
|
|
# ../../device/arista/x86_64-arista_7800r3_48cq2_lc/Arista-7800R3-48CQ2-C48/fabric_port_config.ini
|
|
# # name lanes isolateStatus
|
|
# Fabric0 0 False
|
|
|
|
titles = ['name', 'lanes', 'isolateStatus']
|
|
with open(fabric_port_config_file) as data:
|
|
for line in data:
|
|
if line.startswith('#'):
|
|
if "name" in line:
|
|
titles = line.strip('#').split()
|
|
continue;
|
|
tokens = line.split()
|
|
if len(tokens) < 2:
|
|
continue
|
|
name_index = titles.index('name')
|
|
name = tokens[name_index]
|
|
data = {}
|
|
for i, item in enumerate(tokens):
|
|
if i == name_index:
|
|
continue
|
|
data[titles[i]] = item
|
|
data.setdefault('alias', name)
|
|
ports[name] = data
|
|
return ports
|
|
|
|
def get_port_config(hwsku=None, platform=None, port_config_file=None, hwsku_config_file=None, asic_name=None):
|
|
config_db = db_connect_configdb(asic_name)
|
|
# If available, Read from CONFIG DB first
|
|
if config_db is not None and port_config_file is None:
|
|
|
|
port_data = config_db.get_table("PORT")
|
|
if bool(port_data):
|
|
ports = ast.literal_eval(json.dumps(port_data))
|
|
port_alias_map = {}
|
|
port_alias_asic_map = {}
|
|
for intf_name in ports.keys():
|
|
if "alias" in ports[intf_name]:
|
|
port_alias_map[ports[intf_name]["alias"]] = intf_name
|
|
return (ports, port_alias_map, port_alias_asic_map)
|
|
|
|
if asic_name is not None:
|
|
asic_id = str(get_asic_id_from_name(asic_name))
|
|
else:
|
|
asic_id = None
|
|
|
|
if not port_config_file:
|
|
port_config_file = device_info.get_path_to_port_config_file(hwsku, asic_id)
|
|
|
|
if not port_config_file:
|
|
return ({}, {}, {})
|
|
|
|
|
|
# Read from 'platform.json' file
|
|
if port_config_file.endswith('.json'):
|
|
if not hwsku_config_file:
|
|
hwsku_json_file = get_hwsku_file_name(hwsku, platform)
|
|
if not hwsku_json_file:
|
|
return ({}, {}, {})
|
|
else:
|
|
hwsku_json_file = hwsku_config_file
|
|
|
|
return parse_platform_json_file(hwsku_json_file, port_config_file)
|
|
|
|
# If 'platform.json' file is not available, read from 'port_config.ini'
|
|
else:
|
|
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:
|
|
for line in data:
|
|
if line.startswith('#'):
|
|
if "name" in line:
|
|
titles = line.strip('#').split()
|
|
continue;
|
|
tokens = line.split()
|
|
if len(tokens) < 2:
|
|
continue
|
|
name_index = titles.index('name')
|
|
name = tokens[name_index]
|
|
data = {}
|
|
for i, item in enumerate(tokens):
|
|
if i == name_index:
|
|
continue
|
|
data[titles[i]] = item
|
|
data.setdefault('alias', name)
|
|
ports[name] = data
|
|
port_alias_map[data['alias']] = name
|
|
# 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)
|
|
|
|
class BreakoutCfg(object):
|
|
|
|
class BreakoutModeEntry:
|
|
def __init__(self, num_ports, default_speed, supported_speed, num_assigned_lanes=None):
|
|
self.num_ports = int(num_ports)
|
|
self.default_speed = self._speed_to_int(default_speed)
|
|
self.supported_speed = set((self.default_speed, ))
|
|
self._parse_supported_speed(supported_speed)
|
|
self.num_assigned_lanes = self._parse_num_assigned_lanes(num_assigned_lanes)
|
|
|
|
@classmethod
|
|
def _speed_to_int(cls, speed):
|
|
try:
|
|
if speed.endswith('G'):
|
|
return int(speed.replace('G', '')) * 1000
|
|
|
|
return int(speed)
|
|
except ValueError:
|
|
raise RuntimeError("Unsupported speed format '{}'".format(speed))
|
|
|
|
def _parse_supported_speed(self, speed):
|
|
if not speed:
|
|
return
|
|
|
|
if not speed.startswith('[') and not speed.endswith(']'):
|
|
raise RuntimeError("Unsupported port breakout format!")
|
|
|
|
for s in speed[1:-1].split(','):
|
|
self.supported_speed.add(self._speed_to_int(s.strip()))
|
|
|
|
def _parse_num_assigned_lanes(self, num_assigned_lanes):
|
|
if not num_assigned_lanes:
|
|
return
|
|
|
|
if isinstance(num_assigned_lanes, int):
|
|
return num_assigned_lanes
|
|
|
|
if not num_assigned_lanes.startswith('(') and not num_assigned_lanes.endswith(')'):
|
|
raise RuntimeError("Unsupported port breakout format!")
|
|
|
|
return int(num_assigned_lanes[1:-1])
|
|
|
|
def __eq__(self, other):
|
|
if isinstance(other, BreakoutCfg.BreakoutModeEntry):
|
|
if self.num_ports != other.num_ports:
|
|
return False
|
|
if self.supported_speed != other.supported_speed:
|
|
return False
|
|
if self.num_assigned_lanes != other.num_assigned_lanes:
|
|
return False
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def __ne__(self, other):
|
|
return not self == other
|
|
|
|
def __hash__(self):
|
|
return hash((self.num_ports, tuple(self.supported_speed), self.num_assigned_lanes))
|
|
|
|
def __init__(self, name, bmode, properties):
|
|
self._interface_base_id = int(name.replace(PORT_STR, ''))
|
|
self._properties = properties
|
|
self._lanes = properties ['lanes'].split(',')
|
|
self._indexes = properties ['index'].split(',')
|
|
self._breakout_mode_entry = self._str_to_entries(bmode)
|
|
self._breakout_capabilities = None
|
|
|
|
# Find specified breakout mode in port breakout mode capabilities
|
|
for supported_mode in self._properties['breakout_modes']:
|
|
if self._breakout_mode_entry == self._str_to_entries(supported_mode):
|
|
self._breakout_capabilities = self._properties['breakout_modes'][supported_mode]
|
|
break
|
|
|
|
if not self._breakout_capabilities:
|
|
raise RuntimeError("Unsupported breakout mode {}!".format(bmode))
|
|
|
|
def _re_group_to_entry(self, group):
|
|
if len(group) != BRKOUT_PATTERN_GROUPS:
|
|
raise RuntimeError("Unsupported breakout mode format!")
|
|
|
|
num_ports, default_speed, supported_speed, _, num_assigned_lanes, _ = group
|
|
if not num_assigned_lanes:
|
|
num_assigned_lanes = len(self._lanes)
|
|
|
|
return BreakoutCfg.BreakoutModeEntry(num_ports, default_speed, supported_speed, num_assigned_lanes)
|
|
|
|
def _str_to_entries(self, bmode):
|
|
"""
|
|
Example of match_list for some breakout_mode using regex
|
|
Breakout Mode -------> Match_list
|
|
-----------------------------
|
|
2x25G(2)+1x50G(2) ---> [('2', '25G', None, '(2)', '2'), ('1', '50G', None, '(2)', '2')]
|
|
1x50G(2)+2x25G(2) ---> [('1', '50G', None, '(2)', '2'), ('2', '25G', None, '(2)', '2')]
|
|
1x100G[40G] ---------> [('1', '100G', '[40G]', None, None)]
|
|
2x50G ---------------> [('2', '50G', None, None, None)]
|
|
"""
|
|
|
|
try:
|
|
groups_list = [re.match(BRKOUT_PATTERN, i).groups() for i in bmode.split("+")]
|
|
except Exception:
|
|
raise RuntimeError('Breakout mode "{}" validation failed!'.format(bmode))
|
|
|
|
return [self._re_group_to_entry(group) for group in groups_list]
|
|
|
|
def get_config(self):
|
|
# Ensure that we have corret number of configured lanes
|
|
lanes_used = 0
|
|
for entry in self._breakout_mode_entry:
|
|
lanes_used += entry.num_assigned_lanes
|
|
|
|
if lanes_used > len(self._lanes):
|
|
raise RuntimeError("Assigned lines count is more that available!")
|
|
|
|
ports = {}
|
|
|
|
lane_id = 0
|
|
alias_id = 0
|
|
|
|
for entry in self._breakout_mode_entry:
|
|
lanes_per_port = entry.num_assigned_lanes // entry.num_ports
|
|
|
|
for port in range(entry.num_ports):
|
|
interface_name = PORT_STR + str(self._interface_base_id + lane_id)
|
|
|
|
lanes = self._lanes[lane_id:lane_id + lanes_per_port]
|
|
|
|
ports[interface_name] = {
|
|
'alias': self._breakout_capabilities[alias_id],
|
|
'lanes': ','.join(lanes),
|
|
'speed': str(entry.default_speed),
|
|
'index': self._indexes[lane_id]
|
|
}
|
|
|
|
lane_id += lanes_per_port
|
|
alias_id += 1
|
|
|
|
return ports
|
|
|
|
|
|
"""
|
|
Given a port and breakout mode, this method returns
|
|
the list of child ports using platform_json file
|
|
"""
|
|
def get_child_ports(interface, breakout_mode, platform_json_file):
|
|
port_dict = readJson(platform_json_file)
|
|
|
|
mode_handler = BreakoutCfg(interface, breakout_mode, port_dict[INTF_KEY][interface])
|
|
|
|
return mode_handler.get_config()
|
|
|
|
def parse_platform_json_file(hwsku_json_file, platform_json_file):
|
|
ports = {}
|
|
port_alias_map = {}
|
|
port_alias_asic_map = {}
|
|
|
|
port_dict = readJson(platform_json_file)
|
|
hwsku_dict = readJson(hwsku_json_file)
|
|
|
|
if port_dict is None:
|
|
raise Exception("port_dict is none")
|
|
if hwsku_dict is None:
|
|
raise Exception("hwsku_dict is none")
|
|
|
|
if INTF_KEY not in port_dict or INTF_KEY not in hwsku_dict:
|
|
raise Exception("INTF_KEY is not present in appropriate file")
|
|
|
|
for intf in port_dict[INTF_KEY]:
|
|
if intf not in hwsku_dict[INTF_KEY]:
|
|
raise Exception("{} is not available in hwsku_dict".format(intf))
|
|
|
|
# take default_brkout_mode from hwsku.json
|
|
brkout_mode = hwsku_dict[INTF_KEY][intf][BRKOUT_MODE]
|
|
|
|
child_ports = get_child_ports(intf, brkout_mode, platform_json_file)
|
|
|
|
# take optional fields from hwsku.json
|
|
for key, item in hwsku_dict[INTF_KEY][intf].items():
|
|
if key in OPTIONAL_HWSKU_ATTRIBUTES:
|
|
child_ports.get(intf)[key] = item
|
|
|
|
ports.update(child_ports)
|
|
|
|
if ports is None:
|
|
raise Exception("Ports dictionary is None")
|
|
|
|
for i in ports.keys():
|
|
port_alias_map[ports[i]["alias"]]= i
|
|
return (ports, port_alias_map, port_alias_asic_map)
|
|
|
|
|
|
def get_breakout_mode(hwsku=None, platform=None, port_config_file=None):
|
|
if not port_config_file:
|
|
port_config_file = device_info.get_path_to_port_config_file(hwsku)
|
|
if not port_config_file:
|
|
return None
|
|
if port_config_file.endswith('.json'):
|
|
hwsku_json_file = get_hwsku_file_name(hwsku, platform)
|
|
if not hwsku_json_file:
|
|
raise Exception("'hwsku_json' file does not exist!!! This file is necessary to proceed forward.")
|
|
|
|
return parse_breakout_mode(hwsku_json_file)
|
|
else:
|
|
return None
|
|
|
|
def parse_breakout_mode(hwsku_json_file):
|
|
brkout_table = {}
|
|
hwsku_dict = readJson(hwsku_json_file)
|
|
if not hwsku_dict:
|
|
raise Exception("hwsku_dict is empty")
|
|
if INTF_KEY not in hwsku_dict:
|
|
raise Exception("INTF_KEY is not present in hwsku_dict")
|
|
|
|
for intf in hwsku_dict[INTF_KEY]:
|
|
brkout_table[intf] = {}
|
|
brkout_table[intf][CUR_BRKOUT_MODE] = hwsku_dict[INTF_KEY][intf][BRKOUT_MODE]
|
|
return brkout_table
|