Skip to content

Commit cb3a047

Browse files
authored
Support option --ports of config qos reload for reloading ports' QoS and buffer configuration to default (sonic-net#2125)
What I did CLI: config qos reload --ports <ports_list> --ports <ports_list>: a set of interfaces with empty QoS and buffer configurations (typically they have just been created via DPB). Format: <port>{,port}, like “Ethernet0” or “Ethernet4,Ethernet5,Ethernet6,Ethernet7” Each port in the list should exist in the CONFIG_DB.PORT table The flow: Render the template qos_config.j2 and buffer_config.j2, generating a temporary json file. (This is one step in “config qos reload”). Parse the json file, extracting all the items on the ports in the port_list Apply all the extracted items into the CONFIG_DB Signed-off-by: Stephen Sun <[email protected]>
1 parent 154a801 commit cb3a047

File tree

6 files changed

+419
-1
lines changed

6 files changed

+419
-1
lines changed

config/main.py

+166-1
Original file line numberDiff line numberDiff line change
@@ -2258,6 +2258,7 @@ def _update_buffer_calculation_model(config_db, model):
22582258

22592259
@qos.command('reload')
22602260
@click.pass_context
2261+
@click.option('--ports', is_flag=False, required=False, help="List of ports that needs to be updated")
22612262
@click.option('--no-dynamic-buffer', is_flag=True, help="Disable dynamic buffer calculation")
22622263
@click.option(
22632264
'--json-data', type=click.STRING,
@@ -2267,8 +2268,13 @@ def _update_buffer_calculation_model(config_db, model):
22672268
'--dry_run', type=click.STRING,
22682269
help="Dry run, writes config to the given file"
22692270
)
2270-
def reload(ctx, no_dynamic_buffer, dry_run, json_data):
2271+
def reload(ctx, no_dynamic_buffer, dry_run, json_data, ports):
22712272
"""Reload QoS configuration"""
2273+
if ports:
2274+
log.log_info("'qos reload --ports {}' executing...".format(ports))
2275+
_qos_update_ports(ctx, ports, dry_run, json_data)
2276+
return
2277+
22722278
log.log_info("'qos reload' executing...")
22732279
_clear_qos()
22742280

@@ -2340,6 +2346,165 @@ def reload(ctx, no_dynamic_buffer, dry_run, json_data):
23402346
if buffer_model_updated:
23412347
print("Buffer calculation model updated, restarting swss is required to take effect")
23422348

2349+
def _qos_update_ports(ctx, ports, dry_run, json_data):
2350+
"""Reload QoS configuration"""
2351+
_, hwsku_path = device_info.get_paths_to_platform_and_hwsku_dirs()
2352+
sonic_version_file = device_info.get_sonic_version_file()
2353+
2354+
portlist = ports.split(',')
2355+
portset_to_handle = set(portlist)
2356+
portset_handled = set()
2357+
2358+
namespace_list = [DEFAULT_NAMESPACE]
2359+
if multi_asic.get_num_asics() > 1:
2360+
namespace_list = multi_asic.get_namespaces_from_linux()
2361+
2362+
# Tables whose key is port only
2363+
tables_single_index = [
2364+
'PORT_QOS_MAP',
2365+
'BUFFER_PORT_INGRESS_PROFILE_LIST',
2366+
'BUFFER_PORT_EGRESS_PROFILE_LIST']
2367+
# Tables whose key is port followed by other element
2368+
tables_multi_index = [
2369+
'QUEUE',
2370+
'BUFFER_PG',
2371+
'BUFFER_QUEUE']
2372+
2373+
if json_data:
2374+
from_db = "--additional-data \'{}\'".format(json_data) if json_data else ""
2375+
else:
2376+
from_db = "-d"
2377+
2378+
items_to_update = {}
2379+
config_dbs = {}
2380+
2381+
for ns in namespace_list:
2382+
if ns is DEFAULT_NAMESPACE:
2383+
asic_id_suffix = ""
2384+
config_db = ConfigDBConnector()
2385+
else:
2386+
asic_id = multi_asic.get_asic_id_from_name(ns)
2387+
if asic_id is None:
2388+
click.secho("Command 'qos update' failed with invalid namespace '{}'".format(ns), fg="yellow")
2389+
raise click.Abort()
2390+
asic_id_suffix = str(asic_id)
2391+
2392+
config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=ns)
2393+
2394+
config_db.connect()
2395+
config_dbs[ns] = config_db
2396+
if is_dynamic_buffer_enabled(config_db):
2397+
buffer_template_file = os.path.join(hwsku_path, asic_id_suffix, "buffers_dynamic.json.j2")
2398+
else:
2399+
buffer_template_file = os.path.join(hwsku_path, asic_id_suffix, "buffers.json.j2")
2400+
2401+
if not os.path.isfile(buffer_template_file):
2402+
click.secho("Buffer definition template not found at {}".format(buffer_template_file), fg="yellow")
2403+
ctx.abort()
2404+
2405+
qos_template_file = os.path.join(hwsku_path, asic_id_suffix, "qos.json.j2")
2406+
2407+
if not os.path.isfile(qos_template_file):
2408+
click.secho("QoS definition template not found at {}".format(qos_template_file), fg="yellow")
2409+
ctx.abort()
2410+
2411+
# Remove multi indexed entries first
2412+
for table_name in tables_multi_index:
2413+
entries = config_db.get_keys(table_name)
2414+
for key in entries:
2415+
port, _ = key
2416+
if not port in portset_to_handle:
2417+
continue
2418+
config_db.set_entry(table_name, '|'.join(key), None)
2419+
2420+
cmd_ns = "" if ns is DEFAULT_NAMESPACE else "-n {}".format(ns)
2421+
command = "{} {} {} -t {},config-db -t {},config-db -y {} --print-data".format(
2422+
SONIC_CFGGEN_PATH, cmd_ns, from_db, buffer_template_file, qos_template_file, sonic_version_file
2423+
)
2424+
jsonstr = clicommon.run_command(command, display_cmd=False, return_cmd=True)
2425+
2426+
jsondict = json.loads(jsonstr)
2427+
port_table = jsondict.get('PORT')
2428+
if port_table:
2429+
ports_to_update = set(port_table.keys()).intersection(portset_to_handle)
2430+
if not ports_to_update:
2431+
continue
2432+
else:
2433+
continue
2434+
2435+
portset_handled.update(ports_to_update)
2436+
2437+
items_to_apply = {}
2438+
2439+
for table_name in tables_single_index:
2440+
table_items_rendered = jsondict.get(table_name)
2441+
if table_items_rendered:
2442+
for key, data in table_items_rendered.items():
2443+
port = key
2444+
if not port in ports_to_update:
2445+
continue
2446+
# Push the rendered data to config-db
2447+
if not items_to_apply.get(table_name):
2448+
items_to_apply[table_name] = {}
2449+
items_to_apply[table_name][key] = data
2450+
2451+
for table_name in tables_multi_index:
2452+
table_items_rendered = jsondict.get(table_name)
2453+
if table_items_rendered:
2454+
for key, data in table_items_rendered.items():
2455+
port = key.split('|')[0]
2456+
if not port in ports_to_update:
2457+
continue
2458+
# Push the result to config-db
2459+
if not items_to_apply.get(table_name):
2460+
items_to_apply[table_name] = {}
2461+
items_to_apply[table_name][key] = data
2462+
2463+
# Handle CABLE_LENGTH
2464+
# This table needs to be specially handled because the port is not the index but the field name
2465+
# The idea is for all the entries in template, the same entries in CONFIG_DB will be merged together
2466+
# Eg. there is entry AZURE rendered from template for ports Ethernet0, Ethernet4 with cable length "5m":
2467+
# and entry AZURE in CONFIG_DB for ports Ethernet8, Ethernet12, Ethernet16 with cable length "40m"
2468+
# The entry that will eventually be pushed into CONFIG_DB is
2469+
# {"AZURE": {"Ethernet0": "5m", "Ethernet4": "5m", "Ethernet8": "40m", "Ethernet12": "40m", "Ethernet16": "40m"}}
2470+
table_name = 'CABLE_LENGTH'
2471+
cable_length_table = jsondict.get(table_name)
2472+
if cable_length_table:
2473+
for key, item in cable_length_table.items():
2474+
cable_length_from_db = config_db.get_entry(table_name, key)
2475+
cable_length_from_template = {}
2476+
for port in ports_to_update:
2477+
cable_len = item.get(port)
2478+
if cable_len:
2479+
cable_length_from_template[port] = cable_len
2480+
# Reaching this point,
2481+
# - cable_length_from_template contains cable length rendered from the template, eg Ethernet0 and Ethernet4 in the above example
2482+
# - cable_length_from_db contains cable length existing in the CONFIG_DB, eg Ethernet8, Ethernet12, and Ethernet16 in the above exmaple
2483+
2484+
if not items_to_apply.get(table_name):
2485+
items_to_apply[table_name] = {}
2486+
2487+
if cable_length_from_db:
2488+
cable_length_from_db.update(cable_length_from_template)
2489+
items_to_apply[table_name][key] = cable_length_from_db
2490+
else:
2491+
items_to_apply[table_name][key] = cable_length_from_template
2492+
2493+
if items_to_apply:
2494+
items_to_update[ns] = items_to_apply
2495+
2496+
if dry_run:
2497+
with open(dry_run + ns, "w+") as f:
2498+
json.dump(items_to_apply, f, sort_keys=True, indent=4)
2499+
else:
2500+
jsonstr = json.dumps(items_to_apply)
2501+
cmd_ns = "" if ns is DEFAULT_NAMESPACE else "-n {}".format(ns)
2502+
command = "{} {} --additional-data '{}' --write-to-db".format(SONIC_CFGGEN_PATH, cmd_ns, jsonstr)
2503+
clicommon.run_command(command, display_cmd=False)
2504+
2505+
if portset_to_handle != portset_handled:
2506+
click.echo("The port(s) {} are not updated because they do not exist".format(portset_to_handle - portset_handled))
2507+
23432508
def is_dynamic_buffer_enabled(config_db):
23442509
"""Return whether the current system supports dynamic buffer calculation"""
23452510
device_metadata = config_db.get_entry('DEVICE_METADATA', 'localhost')

doc/Command-Reference.md

+30
Original file line numberDiff line numberDiff line change
@@ -7586,6 +7586,36 @@ Some of the example QOS configurations that users can modify are given below.
75867586
When there are no changes in the platform specific configutation files, they internally use the file "/usr/share/sonic/templates/buffers_config.j2" and "/usr/share/sonic/templates/qos_config.j2" to generate the configuration.
75877587
```
75887588
7589+
**config qos reload --ports port_list**
7590+
7591+
This command is used to reload the default QoS configuration on a group of ports.
7592+
Typically, the default QoS configuration is in the following tables.
7593+
1) PORT_QOS_MAP
7594+
2) QUEUE
7595+
3) BUFFER_PG
7596+
4) BUFFER_QUEUE
7597+
5) BUFFER_PORT_INGRESS_PROFILE_LIST
7598+
6) BUFFER_PORT_EGRESS_PROFILE_LIST
7599+
7) CABLE_LENGTH
7600+
7601+
If there was QoS configuration in the above tables for the ports:
7602+
7603+
- if `--force` option is provied, the existing QoS configuration will be replaced by the default QoS configuration,
7604+
- otherwise, the command will exit with nothing updated.
7605+
7606+
- Usage:
7607+
```
7608+
config qos reload --ports <port>[,port]
7609+
```
7610+
7611+
- Example:
7612+
```
7613+
admin@sonic:~$ sudo config qos reload --ports Ethernet0,Ethernet4
7614+
7615+
In this example, it updates the QoS configuration on port Ethernet0 and Ethernet4 to default.
7616+
If there was QoS configuration on the ports, the command will clear the existing QoS configuration on the port and reload to default.
7617+
```
7618+
75897619
Go Back To [Beginning of the document](#) or [Beginning of this section](#qos)
75907620
75917621
## sFlow

tests/config_test.py

+52
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,24 @@ def test_qos_reload_single(
442442
)
443443
assert filecmp.cmp(output_file, expected_result, shallow=False)
444444

445+
def test_qos_update_single(
446+
self, get_cmd_module, setup_qos_mock_apis
447+
):
448+
(config, show) = get_cmd_module
449+
json_data = '{"DEVICE_METADATA": {"localhost": {}}, "PORT": {"Ethernet0": {}}}'
450+
runner = CliRunner()
451+
output_file = os.path.join(os.sep, "tmp", "qos_config_update.json")
452+
cmd_vector = ["reload", "--ports", "Ethernet0", "--json-data", json_data, "--dry_run", output_file]
453+
result = runner.invoke(config.config.commands["qos"], cmd_vector)
454+
print(result.exit_code)
455+
print(result.output)
456+
assert result.exit_code == 0
457+
cwd = os.path.dirname(os.path.realpath(__file__))
458+
expected_result = os.path.join(
459+
cwd, "qos_config_input", "update_qos.json"
460+
)
461+
assert filecmp.cmp(output_file, expected_result, shallow=False)
462+
445463
@classmethod
446464
def teardown_class(cls):
447465
print("TEARDOWN")
@@ -495,6 +513,40 @@ def test_qos_reload_masic(
495513
file = "{}{}".format(output_file, asic)
496514
assert filecmp.cmp(file, expected_result, shallow=False)
497515

516+
def test_qos_update_masic(
517+
self, get_cmd_module, setup_qos_mock_apis,
518+
setup_multi_broadcom_masic
519+
):
520+
(config, show) = get_cmd_module
521+
runner = CliRunner()
522+
523+
output_file = os.path.join(os.sep, "tmp", "qos_update_output")
524+
print("Saving output in {}<0,1,2..>".format(output_file))
525+
num_asic = device_info.get_num_npus()
526+
for asic in range(num_asic):
527+
try:
528+
file = "{}{}".format(output_file, asic)
529+
os.remove(file)
530+
except OSError:
531+
pass
532+
json_data = '{"DEVICE_METADATA": {"localhost": {}}, "PORT": {"Ethernet0": {}}}'
533+
result = runner.invoke(
534+
config.config.commands["qos"],
535+
["reload", "--ports", "Ethernet0,Ethernet4", "--json-data", json_data, "--dry_run", output_file]
536+
)
537+
print(result.exit_code)
538+
print(result.output)
539+
assert result.exit_code == 0
540+
541+
cwd = os.path.dirname(os.path.realpath(__file__))
542+
543+
for asic in range(num_asic):
544+
expected_result = os.path.join(
545+
cwd, "qos_config_input", str(asic), "update_qos.json"
546+
)
547+
548+
assert filecmp.cmp(output_file + "asic{}".format(asic), expected_result, shallow=False)
549+
498550
@classmethod
499551
def teardown_class(cls):
500552
print("TEARDOWN")
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"BUFFER_PG": {
3+
"Ethernet0|0": {
4+
"profile": "ingress_lossy_profile"
5+
}
6+
},
7+
"BUFFER_QUEUE": {
8+
"Ethernet0|0-2": {
9+
"profile": "egress_lossy_profile"
10+
},
11+
"Ethernet0|3-4": {
12+
"profile": "egress_lossless_profile"
13+
},
14+
"Ethernet0|5-6": {
15+
"profile": "egress_lossy_profile"
16+
}
17+
},
18+
"CABLE_LENGTH": {
19+
"AZURE": {
20+
"Ethernet0": "300m"
21+
}
22+
},
23+
"PORT_QOS_MAP": {
24+
"Ethernet0": {
25+
"dscp_to_tc_map": "AZURE",
26+
"pfc_enable": "3,4",
27+
"pfc_to_queue_map": "AZURE",
28+
"tc_to_pg_map": "AZURE",
29+
"tc_to_queue_map": "AZURE"
30+
}
31+
},
32+
"QUEUE": {
33+
"Ethernet0|0": {
34+
"scheduler": "scheduler.0"
35+
},
36+
"Ethernet0|1": {
37+
"scheduler": "scheduler.0"
38+
},
39+
"Ethernet0|2": {
40+
"scheduler": "scheduler.0"
41+
},
42+
"Ethernet0|3": {
43+
"scheduler": "scheduler.1",
44+
"wred_profile": "AZURE_LOSSLESS"
45+
},
46+
"Ethernet0|4": {
47+
"scheduler": "scheduler.1",
48+
"wred_profile": "AZURE_LOSSLESS"
49+
},
50+
"Ethernet0|5": {
51+
"scheduler": "scheduler.0"
52+
},
53+
"Ethernet0|6": {
54+
"scheduler": "scheduler.0"
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)