Skip to content

Commit aa1b072

Browse files
authored
[config] Add Initial draft of Breakout Mode subcommand (sonic-net#766)
Added breakout subcommand in config command Signed-off-by: Sangita Maity <[email protected]>
1 parent 995cf39 commit aa1b072

File tree

2 files changed

+306
-0
lines changed

2 files changed

+306
-0
lines changed

config/main.py

+275
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
import ipaddress
1616
from swsssdk import ConfigDBConnector, SonicV2Connector, SonicDBConfig
1717
from minigraph import parse_device_desc_xml
18+
from config_mgmt import ConfigMgmtDPB
1819
from utilities_common.intf_filter import parse_interface_in_filter
20+
from utilities_common.util_base import UtilHelper
21+
from portconfig import get_child_ports, get_port_config_file_name
1922

2023
import aaa
2124
import mlnx
@@ -30,6 +33,7 @@
3033
ASIC_CONF_FILENAME = 'asic.conf'
3134
DEFAULT_CONFIG_DB_FILE = '/etc/sonic/config_db.json'
3235
NAMESPACE_PREFIX = 'asic'
36+
INTF_KEY = "interfaces"
3337

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

@@ -118,6 +122,159 @@ def get_command(self, ctx, cmd_name):
118122
except (KeyError, TypeError):
119123
raise click.Abort()
120124

125+
#
126+
# Load breakout config file for Dynamic Port Breakout
127+
#
128+
129+
try:
130+
# Load the helper class
131+
helper = UtilHelper()
132+
(platform, hwsku) = helper.get_platform_and_hwsku()
133+
except Exception as e:
134+
click.secho("Failed to get platform and hwsku with error:{}".format(str(e)), fg='red')
135+
raise click.Abort()
136+
137+
try:
138+
breakout_cfg_file = get_port_config_file_name(hwsku, platform)
139+
except Exception as e:
140+
click.secho("Breakout config file not found with error:{}".format(str(e)), fg='red')
141+
raise click.Abort()
142+
143+
#
144+
# Breakout Mode Helper functions
145+
#
146+
147+
# Read given JSON file
148+
def readJsonFile(fileName):
149+
try:
150+
with open(fileName) as f:
151+
result = json.load(f)
152+
except Exception as e:
153+
raise Exception(str(e))
154+
return result
155+
156+
def _get_option(ctx,args,incomplete):
157+
""" Provides dynamic mode option as per user argument i.e. interface name """
158+
all_mode_options = []
159+
interface_name = args[-1]
160+
161+
if not os.path.isfile(breakout_cfg_file) or not breakout_cfg_file.endswith('.json'):
162+
return []
163+
else:
164+
breakout_file_input = readJsonFile(breakout_cfg_file)
165+
if interface_name in breakout_file_input[INTF_KEY]:
166+
breakout_mode_list = [v["breakout_modes"] for i ,v in breakout_file_input[INTF_KEY].items() if i == interface_name][0]
167+
breakout_mode_options = []
168+
for i in breakout_mode_list.split(','):
169+
breakout_mode_options.append(i)
170+
all_mode_options = [str(c) for c in breakout_mode_options if incomplete in c]
171+
return all_mode_options
172+
173+
def shutdown_interfaces(ctx, del_intf_dict):
174+
""" shut down all the interfaces before deletion """
175+
for intf in del_intf_dict.keys():
176+
config_db = ctx.obj['config_db']
177+
if get_interface_naming_mode() == "alias":
178+
interface_name = interface_alias_to_name(intf)
179+
if interface_name is None:
180+
click.echo("[ERROR] interface name is None!")
181+
return False
182+
183+
if interface_name_is_valid(intf) is False:
184+
click.echo("[ERROR] Interface name is invalid. Please enter a valid interface name!!")
185+
return False
186+
187+
port_dict = config_db.get_table('PORT')
188+
if not port_dict:
189+
click.echo("port_dict is None!")
190+
return False
191+
192+
if intf in port_dict.keys():
193+
config_db.mod_entry("PORT", intf, {"admin_status": "down"})
194+
else:
195+
click.secho("[ERROR] Could not get the correct interface name, exiting", fg='red')
196+
return False
197+
return True
198+
199+
def _validate_interface_mode(ctx, breakout_cfg_file, interface_name, target_brkout_mode, cur_brkout_mode):
200+
""" Validate Parent interface and user selected mode before starting deletion or addition process """
201+
breakout_file_input = readJsonFile(breakout_cfg_file)["interfaces"]
202+
203+
if interface_name not in breakout_file_input:
204+
click.secho("[ERROR] {} is not a Parent port. So, Breakout Mode is not available on this port".format(interface_name), fg='red')
205+
return False
206+
207+
# Check whether target breakout mode is available for the user-selected interface or not
208+
if target_brkout_mode not in breakout_file_input[interface_name]["breakout_modes"]:
209+
click.secho('[ERROR] Target mode {} is not available for the port {}'. format(target_brkout_mode, interface_name), fg='red')
210+
return False
211+
212+
# Get config db context
213+
config_db = ctx.obj['config_db']
214+
port_dict = config_db.get_table('PORT')
215+
216+
# Check whether there is any port in config db.
217+
if not port_dict:
218+
click.echo("port_dict is None!")
219+
return False
220+
221+
# Check whether the user-selected interface is part of 'port' table in config db.
222+
if interface_name not in port_dict.keys():
223+
click.secho("[ERROR] {} is not in port_dict".format(interface_name))
224+
return False
225+
click.echo("\nRunning Breakout Mode : {} \nTarget Breakout Mode : {}".format(cur_brkout_mode, target_brkout_mode))
226+
if (cur_brkout_mode == target_brkout_mode):
227+
click.secho("[WARNING] No action will be taken as current and desired Breakout Mode are same.", fg='magenta')
228+
sys.exit(0)
229+
return True
230+
231+
def load_ConfigMgmt(verbose):
232+
""" Load config for the commands which are capable of change in config DB. """
233+
try:
234+
cm = ConfigMgmtDPB(debug=verbose)
235+
return cm
236+
except Exception as e:
237+
raise Exception("Failed to load the config. Error: {}".format(str(e)))
238+
239+
def breakout_warnUser_extraTables(cm, final_delPorts, confirm=True):
240+
"""
241+
Function to warn user about extra tables while Dynamic Port Breakout(DPB).
242+
confirm: re-confirm from user to proceed.
243+
Config Tables Without Yang model considered extra tables.
244+
cm = instance of config MGMT class.
245+
"""
246+
try:
247+
# check if any extra tables exist
248+
eTables = cm.tablesWithOutYang()
249+
if len(eTables):
250+
# find relavent tables in extra tables, i.e. one which can have deleted
251+
# ports
252+
tables = cm.configWithKeys(configIn=eTables, keys=final_delPorts)
253+
click.secho("Below Config can not be verified, It may cause harm "\
254+
"to the system\n {}".format(json.dumps(tables, indent=2)))
255+
click.confirm('Do you wish to Continue?', abort=True)
256+
except Exception as e:
257+
raise Exception("Failed in breakout_warnUser_extraTables. Error: {}".format(str(e)))
258+
return
259+
260+
def breakout_Ports(cm, delPorts=list(), portJson=dict(), force=False, \
261+
loadDefConfig=False, verbose=False):
262+
263+
deps, ret = cm.breakOutPort(delPorts=delPorts, portJson=portJson, \
264+
force=force, loadDefConfig=loadDefConfig)
265+
# check if DPB failed
266+
if ret == False:
267+
if not force and deps:
268+
click.echo("Dependecies Exist. No further action will be taken")
269+
click.echo("*** Printing dependecies ***")
270+
for dep in deps:
271+
click.echo(dep)
272+
sys.exit(0)
273+
else:
274+
click.echo("[ERROR] Port breakout Failed!!! Opting Out")
275+
raise click.Abort()
276+
return
277+
121278
#
122279
# Helper functions
123280
#
@@ -2186,6 +2343,124 @@ def speed(ctx, interface_name, interface_speed, verbose):
21862343
command += " -vv"
21872344
run_command(command, display_cmd=verbose)
21882345

2346+
#
2347+
# 'breakout' subcommand
2348+
#
2349+
2350+
@interface.command()
2351+
@click.argument('interface_name', metavar='<interface_name>', required=True)
2352+
@click.argument('mode', required=True, type=click.STRING, autocompletion=_get_option)
2353+
@click.option('-f', '--force-remove-dependencies', is_flag=True, help='Clear all depenedecies internally first.')
2354+
@click.option('-l', '--load-predefined-config', is_flag=True, help='load predefied user configuration (alias, lanes, speed etc) first.')
2355+
@click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, expose_value=False, prompt='Do you want to Breakout the port, continue?')
2356+
@click.option('-v', '--verbose', is_flag=True, help="Enable verbose output")
2357+
@click.pass_context
2358+
def breakout(ctx, interface_name, mode, verbose, force_remove_dependencies, load_predefined_config):
2359+
""" Set interface breakout mode """
2360+
if not os.path.isfile(breakout_cfg_file) or not breakout_cfg_file.endswith('.json'):
2361+
click.secho("[ERROR] Breakout feature is not available without platform.json file", fg='red')
2362+
raise click.Abort()
2363+
2364+
# Connect to config db and get the context
2365+
config_db = ConfigDBConnector()
2366+
config_db.connect()
2367+
ctx.obj['config_db'] = config_db
2368+
2369+
target_brkout_mode = mode
2370+
2371+
# Get current breakout mode
2372+
cur_brkout_dict = config_db.get_table('BREAKOUT_CFG')
2373+
cur_brkout_mode = cur_brkout_dict[interface_name]["brkout_mode"]
2374+
2375+
# Validate Interface and Breakout mode
2376+
if not _validate_interface_mode(ctx, breakout_cfg_file, interface_name, mode, cur_brkout_mode):
2377+
raise click.Abort()
2378+
2379+
""" Interface Deletion Logic """
2380+
# Get list of interfaces to be deleted
2381+
del_ports = get_child_ports(interface_name, cur_brkout_mode, breakout_cfg_file)
2382+
del_intf_dict = {intf: del_ports[intf]["speed"] for intf in del_ports}
2383+
2384+
if del_intf_dict:
2385+
""" shut down all the interface before deletion """
2386+
ret = shutdown_interfaces(ctx, del_intf_dict)
2387+
if not ret:
2388+
raise click.Abort()
2389+
click.echo("\nPorts to be deleted : \n {}".format(json.dumps(del_intf_dict, indent=4)))
2390+
2391+
else:
2392+
click.secho("[ERROR] del_intf_dict is None! No interfaces are there to be deleted", fg='red')
2393+
raise click.Abort()
2394+
2395+
""" Interface Addition Logic """
2396+
# Get list of interfaces to be added
2397+
add_ports = get_child_ports(interface_name, target_brkout_mode, breakout_cfg_file)
2398+
add_intf_dict = {intf: add_ports[intf]["speed"] for intf in add_ports}
2399+
2400+
if add_intf_dict:
2401+
click.echo("Ports to be added : \n {}".format(json.dumps(add_intf_dict, indent=4)))
2402+
else:
2403+
click.secho("[ERROR] port_dict is None!", fg='red')
2404+
raise click.Abort()
2405+
2406+
""" Special Case: Dont delete those ports where the current mode and speed of the parent port
2407+
remains unchanged to limit the traffic impact """
2408+
2409+
click.secho("\nAfter running Logic to limit the impact", fg="cyan", underline=True)
2410+
matched_item = [intf for intf, speed in del_intf_dict.items() if intf in add_intf_dict.keys() and speed == add_intf_dict[intf]]
2411+
2412+
# Remove the interface which remains unchanged from both del_intf_dict and add_intf_dict
2413+
map(del_intf_dict.pop, matched_item)
2414+
map(add_intf_dict.pop, matched_item)
2415+
2416+
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))
2417+
if len(add_intf_dict.keys()) == 0:
2418+
click.secho("[ERROR] add_intf_dict is None! No interfaces are there to be added", fg='red')
2419+
raise click.Abort()
2420+
2421+
port_dict = {}
2422+
for intf in add_intf_dict:
2423+
if intf in add_ports.keys():
2424+
port_dict[intf] = add_ports[intf]
2425+
2426+
# writing JSON object
2427+
with open('new_port_config.json', 'w') as f:
2428+
json.dump(port_dict, f, indent=4)
2429+
2430+
# Start Interation with Dy Port BreakOut Config Mgmt
2431+
try:
2432+
""" Load config for the commands which are capable of change in config DB """
2433+
cm = load_ConfigMgmt(verbose)
2434+
2435+
""" Delete all ports if forced else print dependencies using ConfigMgmt API """
2436+
final_delPorts = [intf for intf in del_intf_dict.keys()]
2437+
""" Warn user if tables without yang models exist and have final_delPorts """
2438+
breakout_warnUser_extraTables(cm, final_delPorts, confirm=True)
2439+
2440+
# Create a dictionary containing all the added ports with its capabilities like alias, lanes, speed etc.
2441+
portJson = dict(); portJson['PORT'] = port_dict
2442+
2443+
# breakout_Ports will abort operation on failure, So no need to check return
2444+
breakout_Ports(cm, delPorts=final_delPorts, portJson=portJson, force=force_remove_dependencies, \
2445+
loadDefConfig=load_predefined_config, verbose=verbose)
2446+
2447+
# Set Current Breakout mode in config DB
2448+
brkout_cfg_keys = config_db.get_keys('BREAKOUT_CFG')
2449+
if interface_name.decode("utf-8") not in brkout_cfg_keys:
2450+
click.secho("[ERROR] {} is not present in 'BREAKOUT_CFG' Table!".\
2451+
format(interface_name), fg='red')
2452+
raise click.Abort()
2453+
config_db.set_entry("BREAKOUT_CFG", interface_name,\
2454+
{'brkout_mode': target_brkout_mode})
2455+
click.secho("Breakout process got successfully completed.".\
2456+
format(interface_name), fg="cyan", underline=True)
2457+
click.echo("Please note loaded setting will be lost after system reboot. To preserve setting, run `config save`.")
2458+
2459+
except Exception as e:
2460+
click.secho("Failed to break out Port. Error: {}".format(str(e)), \
2461+
fg='magenta')
2462+
sys.exit(0)
2463+
21892464
def _get_all_mgmtinterface_keys():
21902465
"""Returns list of strings containing mgmt interface keys
21912466
"""

doc/Command-Reference.md

+31
Original file line numberDiff line numberDiff line change
@@ -2660,6 +2660,7 @@ This sub-section explains the following list of configuration on the interfaces.
26602660
3) shutdown - to administratively shut down the interface
26612661
4) speed - to set the interface speed
26622662
5) startup - to bring up the administratively shutdown interface
2663+
6) breakout - to set interface breakout mode
26632664
26642665
From 201904 release onwards, the “config interface” command syntax is changed and the format is as follows:
26652666
@@ -2962,6 +2963,36 @@ This command is used to configure the mtu for the Physical interface. Use the va
29622963
admin@sonic:~$ sudo config interface mtu Ethernet64 1500
29632964
```
29642965
2966+
**config interface breakout**
2967+
2968+
This command is used to set breakout mode available for user-specified interface.
2969+
kindly use, double tab i.e. <tab><tab> to see the available breakout option customized for each interface provided by the user.
2970+
2971+
- Usage:
2972+
```
2973+
sudo config interface breakout --help
2974+
Usage: config interface breakout [OPTIONS] <interface_name> MODE
2975+
2976+
Set interface breakout mode
2977+
2978+
Options:
2979+
-f, --force-remove-dependencies
2980+
Clear all depenedecies internally first.
2981+
-l, --load-predefined-config load predefied user configuration (alias,
2982+
lanes, speed etc) first.
2983+
-y, --yes
2984+
-v, --verbose Enable verbose output
2985+
-?, -h, --help Show this message and exit.
2986+
```
2987+
- Example :
2988+
```
2989+
admin@sonic:~$ sudo config interface breakout Ethernet0 <tab><tab>
2990+
<tab provides option for breakout mode>
2991+
1x100G[40G] 2x50G 4x25G[10G]
2992+
2993+
admin@sonic:~$ sudo config interface breakout Ethernet0 4x25G[10G] -f -l -v -y
2994+
```
2995+
29652996
Go Back To [Beginning of the document](#) or [Beginning of this section](#interfaces)
29662997
29672998

0 commit comments

Comments
 (0)