sonic-buildimage/src/sonic-config-engine/portconfig.py

465 lines
16 KiB
Python
Raw Normal View History

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"
[sonic-cfggen] parse optional fec and autoneg fields from hwsku.json (#6155) **- Why I did it** For now `hwsku.json` and `platform.json` dont support optional fields. For example no way to add `fec` or `autoneg` field using `platform.json` and `hwsku.json`. **- How I did it** Added parsing of optional fields from hwsku.json. **- How to verify it** Add optional field to `hwsku.json`. After first boot will be generated new `config_db.json` or you can generate it using `sonic-cfggen` command. In this file must be optional field from `hwsku.json` or check using command `redis-cli hgetall PORT_TABLE:Ethernet0` Example of `hwsku.json`, that must be parsed: ``` { "interfaces": { "Ethernet0": { "default_brkout_mode": "1x100G[40G]", "fec": "rs", "autoneg": "0" }, ... } ``` Example of generated `config_db.json`: ``` "PORT": { "Ethernet0": { "alias": "Ethernet0", "lanes": "0,1,2,3", "speed": "100000", "index": "1", "admin_status": "up", "fec": "rs", "autoneg": "0", "mtu": "9100" }, ``` So, we can see this entries in redis db: ``` admin@sonic:~$ redis-cli hgetall PORT_TABLE:Ethernet0 1) "alias" 2) "Ethernet0" 3) "lanes" 4) "0,1,2,3" 5) "speed" 6) "100000" 7) "index" 8) "1" 9) "admin_status" 10) "up" 11) "fec" 12) "rs" 13) "autoneg" 14) "0" 15) "mtu" 16) "9100" 17) "description" 18) "" 19) "oper_status" 20) "up" ``` Also its way to fix `show interface status`, `FEC` field but also need add `FEC` field to `hwsku.json`. Before: ``` admin@sonic:~$ show interfaces status Interface Lanes Speed MTU FEC Alias Vlan Oper Admin Type Asym PFC ----------- --------------- ------- ----- ----- ----------- ------ ------ ------- --------------- ---------- Ethernet0 0,1,2,3 100G 9100 N/A Ethernet0 routed up up QSFP28 or later N/A ``` After: ``` admin@sonic:~$ show interfaces status Interface Lanes Speed MTU FEC Alias Vlan Oper Admin Type Asym PFC ----------- --------------- ------- ----- ----- ----------- ------ ------ ------- --------------- ---------- Ethernet0 0,1,2,3 100G 9100 rs Ethernet0 routed up up QSFP28 or later N/A ```
2021-01-24 19:46:33 -06:00
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)
[sonic-cfggen] parse optional fec and autoneg fields from hwsku.json (#6155) **- Why I did it** For now `hwsku.json` and `platform.json` dont support optional fields. For example no way to add `fec` or `autoneg` field using `platform.json` and `hwsku.json`. **- How I did it** Added parsing of optional fields from hwsku.json. **- How to verify it** Add optional field to `hwsku.json`. After first boot will be generated new `config_db.json` or you can generate it using `sonic-cfggen` command. In this file must be optional field from `hwsku.json` or check using command `redis-cli hgetall PORT_TABLE:Ethernet0` Example of `hwsku.json`, that must be parsed: ``` { "interfaces": { "Ethernet0": { "default_brkout_mode": "1x100G[40G]", "fec": "rs", "autoneg": "0" }, ... } ``` Example of generated `config_db.json`: ``` "PORT": { "Ethernet0": { "alias": "Ethernet0", "lanes": "0,1,2,3", "speed": "100000", "index": "1", "admin_status": "up", "fec": "rs", "autoneg": "0", "mtu": "9100" }, ``` So, we can see this entries in redis db: ``` admin@sonic:~$ redis-cli hgetall PORT_TABLE:Ethernet0 1) "alias" 2) "Ethernet0" 3) "lanes" 4) "0,1,2,3" 5) "speed" 6) "100000" 7) "index" 8) "1" 9) "admin_status" 10) "up" 11) "fec" 12) "rs" 13) "autoneg" 14) "0" 15) "mtu" 16) "9100" 17) "description" 18) "" 19) "oper_status" 20) "up" ``` Also its way to fix `show interface status`, `FEC` field but also need add `FEC` field to `hwsku.json`. Before: ``` admin@sonic:~$ show interfaces status Interface Lanes Speed MTU FEC Alias Vlan Oper Admin Type Asym PFC ----------- --------------- ------- ----- ----- ----------- ------ ------ ------- --------------- ---------- Ethernet0 0,1,2,3 100G 9100 N/A Ethernet0 routed up up QSFP28 or later N/A ``` After: ``` admin@sonic:~$ show interfaces status Interface Lanes Speed MTU FEC Alias Vlan Oper Admin Type Asym PFC ----------- --------------- ------- ----- ----- ----------- ------ ------ ------- --------------- ---------- Ethernet0 0,1,2,3 100G 9100 rs Ethernet0 routed up up QSFP28 or later N/A ```
2021-01-24 19:46:33 -06:00
# 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