Skip to content

Commit

Permalink
[config] Add Initial draft of Breakout Mode subcommand (sonic-net#766)
Browse files Browse the repository at this point in the history
Added breakout subcommand in config command

Signed-off-by: Sangita Maity <sangitamaity0211@gmail.com>
  • Loading branch information
samaity authored and vincentchiang-ec committed Sep 11, 2020
1 parent e87d2fb commit 6570f85
Show file tree
Hide file tree
Showing 2 changed files with 306 additions and 0 deletions.
275 changes: 275 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
import ipaddress
from swsssdk import ConfigDBConnector, SonicV2Connector, SonicDBConfig
from minigraph import parse_device_desc_xml
from config_mgmt import ConfigMgmtDPB
from utilities_common.intf_filter import parse_interface_in_filter
from utilities_common.util_base import UtilHelper
from portconfig import get_child_ports, get_port_config_file_name

import aaa
import mlnx
Expand All @@ -29,6 +32,7 @@
ASIC_CONF_FILENAME = 'asic.conf'
DEFAULT_CONFIG_DB_FILE = '/etc/sonic/config_db.json'
NAMESPACE_PREFIX = 'asic'
INTF_KEY = "interfaces"

INIT_CFG_FILE = '/etc/sonic/init_cfg.json'

Expand Down Expand Up @@ -117,6 +121,159 @@ def get_command(self, ctx, cmd_name):
except (KeyError, TypeError):
raise click.Abort()

#
# Load breakout config file for Dynamic Port Breakout
#

try:
# Load the helper class
helper = UtilHelper()
(platform, hwsku) = helper.get_platform_and_hwsku()
except Exception as e:
click.secho("Failed to get platform and hwsku with error:{}".format(str(e)), fg='red')
raise click.Abort()

try:
breakout_cfg_file = get_port_config_file_name(hwsku, platform)
except Exception as e:
click.secho("Breakout config file not found with error:{}".format(str(e)), fg='red')
raise click.Abort()

#
# Breakout Mode Helper functions
#

# Read given JSON file
def readJsonFile(fileName):
try:
with open(fileName) as f:
result = json.load(f)
except Exception as e:
raise Exception(str(e))
return result

def _get_option(ctx,args,incomplete):
""" Provides dynamic mode option as per user argument i.e. interface name """
all_mode_options = []
interface_name = args[-1]

if not os.path.isfile(breakout_cfg_file) or not breakout_cfg_file.endswith('.json'):
return []
else:
breakout_file_input = readJsonFile(breakout_cfg_file)
if interface_name in breakout_file_input[INTF_KEY]:
breakout_mode_list = [v["breakout_modes"] for i ,v in breakout_file_input[INTF_KEY].items() if i == interface_name][0]
breakout_mode_options = []
for i in breakout_mode_list.split(','):
breakout_mode_options.append(i)
all_mode_options = [str(c) for c in breakout_mode_options if incomplete in c]
return all_mode_options

def shutdown_interfaces(ctx, del_intf_dict):
""" shut down all the interfaces before deletion """
for intf in del_intf_dict.keys():
config_db = ctx.obj['config_db']
if get_interface_naming_mode() == "alias":
interface_name = interface_alias_to_name(intf)
if interface_name is None:
click.echo("[ERROR] interface name is None!")
return False

if interface_name_is_valid(intf) is False:
click.echo("[ERROR] Interface name is invalid. Please enter a valid interface name!!")
return False

port_dict = config_db.get_table('PORT')
if not port_dict:
click.echo("port_dict is None!")
return False

if intf in port_dict.keys():
config_db.mod_entry("PORT", intf, {"admin_status": "down"})
else:
click.secho("[ERROR] Could not get the correct interface name, exiting", fg='red')
return False
return True

def _validate_interface_mode(ctx, breakout_cfg_file, interface_name, target_brkout_mode, cur_brkout_mode):
""" Validate Parent interface and user selected mode before starting deletion or addition process """
breakout_file_input = readJsonFile(breakout_cfg_file)["interfaces"]

if interface_name not in breakout_file_input:
click.secho("[ERROR] {} is not a Parent port. So, Breakout Mode is not available on this port".format(interface_name), fg='red')
return False

# Check whether target breakout mode is available for the user-selected interface or not
if target_brkout_mode not in breakout_file_input[interface_name]["breakout_modes"]:
click.secho('[ERROR] Target mode {} is not available for the port {}'. format(target_brkout_mode, interface_name), fg='red')
return False

# Get config db context
config_db = ctx.obj['config_db']
port_dict = config_db.get_table('PORT')

# Check whether there is any port in config db.
if not port_dict:
click.echo("port_dict is None!")
return False

# Check whether the user-selected interface is part of 'port' table in config db.
if interface_name not in port_dict.keys():
click.secho("[ERROR] {} is not in port_dict".format(interface_name))
return False
click.echo("\nRunning Breakout Mode : {} \nTarget Breakout Mode : {}".format(cur_brkout_mode, target_brkout_mode))
if (cur_brkout_mode == target_brkout_mode):
click.secho("[WARNING] No action will be taken as current and desired Breakout Mode are same.", fg='magenta')
sys.exit(0)
return True

def load_ConfigMgmt(verbose):
""" Load config for the commands which are capable of change in config DB. """
try:
cm = ConfigMgmtDPB(debug=verbose)
return cm
except Exception as e:
raise Exception("Failed to load the config. Error: {}".format(str(e)))

def breakout_warnUser_extraTables(cm, final_delPorts, confirm=True):
"""
Function to warn user about extra tables while Dynamic Port Breakout(DPB).
confirm: re-confirm from user to proceed.
Config Tables Without Yang model considered extra tables.
cm = instance of config MGMT class.
"""
try:
# check if any extra tables exist
eTables = cm.tablesWithOutYang()
if len(eTables):
# find relavent tables in extra tables, i.e. one which can have deleted
# ports
tables = cm.configWithKeys(configIn=eTables, keys=final_delPorts)
click.secho("Below Config can not be verified, It may cause harm "\
"to the system\n {}".format(json.dumps(tables, indent=2)))
click.confirm('Do you wish to Continue?', abort=True)
except Exception as e:
raise Exception("Failed in breakout_warnUser_extraTables. Error: {}".format(str(e)))
return

def breakout_Ports(cm, delPorts=list(), portJson=dict(), force=False, \
loadDefConfig=False, verbose=False):

deps, ret = cm.breakOutPort(delPorts=delPorts, portJson=portJson, \
force=force, loadDefConfig=loadDefConfig)
# check if DPB failed
if ret == False:
if not force and deps:
click.echo("Dependecies Exist. No further action will be taken")
click.echo("*** Printing dependecies ***")
for dep in deps:
click.echo(dep)
sys.exit(0)
else:
click.echo("[ERROR] Port breakout Failed!!! Opting Out")
raise click.Abort()
return

#
# Helper functions
#
Expand Down Expand Up @@ -1978,6 +2135,124 @@ def speed(ctx, interface_name, interface_speed, verbose):
command += " -vv"
run_command(command, display_cmd=verbose)

#
# 'breakout' subcommand
#

@interface.command()
@click.argument('interface_name', metavar='<interface_name>', required=True)
@click.argument('mode', required=True, type=click.STRING, autocompletion=_get_option)
@click.option('-f', '--force-remove-dependencies', is_flag=True, help='Clear all depenedecies internally first.')
@click.option('-l', '--load-predefined-config', is_flag=True, help='load predefied user configuration (alias, lanes, speed etc) first.')
@click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, expose_value=False, prompt='Do you want to Breakout the port, continue?')
@click.option('-v', '--verbose', is_flag=True, help="Enable verbose output")
@click.pass_context
def breakout(ctx, interface_name, mode, verbose, force_remove_dependencies, load_predefined_config):
""" Set interface breakout mode """
if not os.path.isfile(breakout_cfg_file) or not breakout_cfg_file.endswith('.json'):
click.secho("[ERROR] Breakout feature is not available without platform.json file", fg='red')
raise click.Abort()

# Connect to config db and get the context
config_db = ConfigDBConnector()
config_db.connect()
ctx.obj['config_db'] = config_db

target_brkout_mode = mode

# Get current breakout mode
cur_brkout_dict = config_db.get_table('BREAKOUT_CFG')
cur_brkout_mode = cur_brkout_dict[interface_name]["brkout_mode"]

# Validate Interface and Breakout mode
if not _validate_interface_mode(ctx, breakout_cfg_file, interface_name, mode, cur_brkout_mode):
raise click.Abort()

""" Interface Deletion Logic """
# Get list of interfaces to be deleted
del_ports = get_child_ports(interface_name, cur_brkout_mode, breakout_cfg_file)
del_intf_dict = {intf: del_ports[intf]["speed"] for intf in del_ports}

if del_intf_dict:
""" shut down all the interface before deletion """
ret = shutdown_interfaces(ctx, del_intf_dict)
if not ret:
raise click.Abort()
click.echo("\nPorts to be deleted : \n {}".format(json.dumps(del_intf_dict, indent=4)))

else:
click.secho("[ERROR] del_intf_dict is None! No interfaces are there to be deleted", fg='red')
raise click.Abort()

""" Interface Addition Logic """
# Get list of interfaces to be added
add_ports = get_child_ports(interface_name, target_brkout_mode, breakout_cfg_file)
add_intf_dict = {intf: add_ports[intf]["speed"] for intf in add_ports}

if add_intf_dict:
click.echo("Ports to be added : \n {}".format(json.dumps(add_intf_dict, indent=4)))
else:
click.secho("[ERROR] port_dict is None!", fg='red')
raise click.Abort()

""" Special Case: Dont delete those ports where the current mode and speed of the parent port
remains unchanged to limit the traffic impact """

click.secho("\nAfter running Logic to limit the impact", fg="cyan", underline=True)
matched_item = [intf for intf, speed in del_intf_dict.items() if intf in add_intf_dict.keys() and speed == add_intf_dict[intf]]

# Remove the interface which remains unchanged from both del_intf_dict and add_intf_dict
map(del_intf_dict.pop, matched_item)
map(add_intf_dict.pop, matched_item)

click.secho("\nFinal list of ports to be deleted : \n {} \nFinal list of ports to be added : \n {}".format(json.dumps(del_intf_dict, indent=4), json.dumps(add_intf_dict, indent=4), fg='green', blink=True))
if len(add_intf_dict.keys()) == 0:
click.secho("[ERROR] add_intf_dict is None! No interfaces are there to be added", fg='red')
raise click.Abort()

port_dict = {}
for intf in add_intf_dict:
if intf in add_ports.keys():
port_dict[intf] = add_ports[intf]

# writing JSON object
with open('new_port_config.json', 'w') as f:
json.dump(port_dict, f, indent=4)

# Start Interation with Dy Port BreakOut Config Mgmt
try:
""" Load config for the commands which are capable of change in config DB """
cm = load_ConfigMgmt(verbose)

""" Delete all ports if forced else print dependencies using ConfigMgmt API """
final_delPorts = [intf for intf in del_intf_dict.keys()]
""" Warn user if tables without yang models exist and have final_delPorts """
breakout_warnUser_extraTables(cm, final_delPorts, confirm=True)

# Create a dictionary containing all the added ports with its capabilities like alias, lanes, speed etc.
portJson = dict(); portJson['PORT'] = port_dict

# breakout_Ports will abort operation on failure, So no need to check return
breakout_Ports(cm, delPorts=final_delPorts, portJson=portJson, force=force_remove_dependencies, \
loadDefConfig=load_predefined_config, verbose=verbose)

# Set Current Breakout mode in config DB
brkout_cfg_keys = config_db.get_keys('BREAKOUT_CFG')
if interface_name.decode("utf-8") not in brkout_cfg_keys:
click.secho("[ERROR] {} is not present in 'BREAKOUT_CFG' Table!".\
format(interface_name), fg='red')
raise click.Abort()
config_db.set_entry("BREAKOUT_CFG", interface_name,\
{'brkout_mode': target_brkout_mode})
click.secho("Breakout process got successfully completed.".\
format(interface_name), fg="cyan", underline=True)
click.echo("Please note loaded setting will be lost after system reboot. To preserve setting, run `config save`.")

except Exception as e:
click.secho("Failed to break out Port. Error: {}".format(str(e)), \
fg='magenta')
sys.exit(0)

def _get_all_mgmtinterface_keys():
"""Returns list of strings containing mgmt interface keys
"""
Expand Down
31 changes: 31 additions & 0 deletions doc/Command-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2660,6 +2660,7 @@ This sub-section explains the following list of configuration on the interfaces.
3) shutdown - to administratively shut down the interface
4) speed - to set the interface speed
5) startup - to bring up the administratively shutdown interface
6) breakout - to set interface breakout mode
From 201904 release onwards, the “config interface” command syntax is changed and the format is as follows:
Expand Down Expand Up @@ -2962,6 +2963,36 @@ This command is used to configure the mtu for the Physical interface. Use the va
admin@sonic:~$ sudo config interface mtu Ethernet64 1500
```
**config interface breakout**
This command is used to set breakout mode available for user-specified interface.
kindly use, double tab i.e. <tab><tab> to see the available breakout option customized for each interface provided by the user.
- Usage:
```
sudo config interface breakout --help
Usage: config interface breakout [OPTIONS] <interface_name> MODE

Set interface breakout mode

Options:
-f, --force-remove-dependencies
Clear all depenedecies internally first.
-l, --load-predefined-config load predefied user configuration (alias,
lanes, speed etc) first.
-y, --yes
-v, --verbose Enable verbose output
-?, -h, --help Show this message and exit.
```
- Example :
```
admin@sonic:~$ sudo config interface breakout Ethernet0 <tab><tab>
<tab provides option for breakout mode>
1x100G[40G] 2x50G 4x25G[10G]

admin@sonic:~$ sudo config interface breakout Ethernet0 4x25G[10G] -f -l -v -y
```
Go Back To [Beginning of the document](#) or [Beginning of this section](#interfaces)
Expand Down

0 comments on commit 6570f85

Please sign in to comment.