|
15 | 15 | import ipaddress
|
16 | 16 | from swsssdk import ConfigDBConnector, SonicV2Connector, SonicDBConfig
|
17 | 17 | from minigraph import parse_device_desc_xml
|
| 18 | +from config_mgmt import ConfigMgmtDPB |
18 | 19 | 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 |
19 | 22 |
|
20 | 23 | import aaa
|
21 | 24 | import mlnx
|
|
30 | 33 | ASIC_CONF_FILENAME = 'asic.conf'
|
31 | 34 | DEFAULT_CONFIG_DB_FILE = '/etc/sonic/config_db.json'
|
32 | 35 | NAMESPACE_PREFIX = 'asic'
|
| 36 | +INTF_KEY = "interfaces" |
33 | 37 |
|
34 | 38 | INIT_CFG_FILE = '/etc/sonic/init_cfg.json'
|
35 | 39 |
|
@@ -118,6 +122,159 @@ def get_command(self, ctx, cmd_name):
|
118 | 122 | except (KeyError, TypeError):
|
119 | 123 | raise click.Abort()
|
120 | 124 |
|
| 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 | + |
121 | 278 | #
|
122 | 279 | # Helper functions
|
123 | 280 | #
|
@@ -2186,6 +2343,124 @@ def speed(ctx, interface_name, interface_speed, verbose):
|
2186 | 2343 | command += " -vv"
|
2187 | 2344 | run_command(command, display_cmd=verbose)
|
2188 | 2345 |
|
| 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 | + |
2189 | 2464 | def _get_all_mgmtinterface_keys():
|
2190 | 2465 | """Returns list of strings containing mgmt interface keys
|
2191 | 2466 | """
|
|
0 commit comments