From dd20cee822647b0c7e788e4c03418e82993a29f9 Mon Sep 17 00:00:00 2001 From: Dante Su Date: Fri, 18 Mar 2022 08:41:56 +0000 Subject: [PATCH 01/31] [JIRA SONIC-59238] SONIC4.0.0: AS5835-54T gearbox speed auto config issue Signed-off-by: Dante Su Change-Id: I8e384ba2252cfa11908d52976cafff05e8687f26 --- CLI/actioner/show_config_interface.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/CLI/actioner/show_config_interface.py b/CLI/actioner/show_config_interface.py index 58c2cd4793..b8b4b602d5 100644 --- a/CLI/actioner/show_config_interface.py +++ b/CLI/actioner/show_config_interface.py @@ -20,6 +20,7 @@ from itertools import groupby from operator import itemgetter +from sonic_cli_if_autoneg import getPortValidSpeeds def show_if_channel_group_cmd(render_tables): cmd_str = "" @@ -279,13 +280,21 @@ def show_if_autoneg(render_tables): continue if port.get("autoneg") != "on": continue - if "adv_speeds" in port: - if port["adv_speeds"] in [None, '', 'all', 'none']: + adv_speeds = port.get('adv_speeds') + if adv_speeds in [None, '', 'all', 'none']: + cmd_str = "speed auto" + else: + valid_speeds = getPortValidSpeeds(ifname_key) + admin_speeds = adv_speeds.split(',') + all = True + for spd in valid_speeds: + if spd not in admin_speeds: + all = False + break + if all: cmd_str = "speed auto" else: - cmd_str = "speed auto {}".format(port["adv_speeds"]) - else: - cmd_str = "speed auto" + cmd_str = "speed auto {}".format(adv_speeds) return "CB_SUCCESS", cmd_str From b79e09ae9a4873b2f0a7d0c3adae25f23333b42d Mon Sep 17 00:00:00 2001 From: Rajendra Dendukuri Date: Thu, 17 Mar 2022 20:04:40 -0700 Subject: [PATCH 02/31] [JIRA SONIC-59228] Modify messages displayed when kdump commands are executed - kdump configuration commands no longer automatically save the changes to startup-config. This has been changed to allow a single command to save the startup-config and not have multiple actors edit the config which may result in a corrupted config if not done in a mutually exclusive manner. - The messages displayed when kdump config is modified has been modified to notify user to save the config and reboot for them to take effect. Change-Id: I3f3cb4bbabbd7e3788e6a85c0d9f79b27a9cfe76 --- CLI/actioner/sonic_cli_oc_kdump.py | 25 ++++++++++++------------- CLI/clitree/cli-xml/kdump.xml | 25 ++++++++++++++----------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/CLI/actioner/sonic_cli_oc_kdump.py b/CLI/actioner/sonic_cli_oc_kdump.py index cc055d8a71..f67439a696 100644 --- a/CLI/actioner/sonic_cli_oc_kdump.py +++ b/CLI/actioner/sonic_cli_oc_kdump.py @@ -24,10 +24,10 @@ def invoke(func, args): ) return cl.get(path) else: - msg = "Kdump configuration has been updated in the startup configuration" - mem_msg = ( - "\nKdump updated memory will be only operational after the system reboots" - ) + msg = "Kdump configuration changes will be applied after the system reboots.\n" + save_msg = "Save SONiC configuration using 'write memory' before issuing the reboot command." + mem_msg = "Kdump updated memory will be only operational after the system reboots.\n" + msg = msg + save_msg path = cc.Path( "/restconf/data/openconfig-system:system/openconfig-system-ext:kdump/config" ) @@ -37,37 +37,36 @@ def invoke(func, args): "enable": False if func == "disable" else True } } - msg = ( - msg + - "\nKdump configuration changes will be applied after the system reboots" - ) elif func == "config": param = args[0] value = args[1] if param == "memory": - msg = msg + mem_msg + msg = mem_msg + save_msg elif param == "max-dumps": value = int(args[1]) + msg = "" else: return None body = {"openconfig-system-ext:config": {param: value}} elif func == "reset": if args[0] == "memory": - msg = msg + mem_msg - elif args[0] != "max-dumps": + msg = mem_msg + save_msg + elif args[0] == "max-dumps": + msg = "" + else: return None path = cc.Path( "/restconf/data/openconfig-system:system/openconfig-system-ext:kdump/config/{}" .format(args[0])) res = cl.delete(path) - if res.ok(): + if res.ok() and len(msg): print(msg) return res else: return None res = cl.patch(path, body) - if res.ok(): + if res.ok() and len(msg): print(msg) return res diff --git a/CLI/clitree/cli-xml/kdump.xml b/CLI/clitree/cli-xml/kdump.xml index ca6a9f7e4a..cc93a31b10 100644 --- a/CLI/clitree/cli-xml/kdump.xml +++ b/CLI/clitree/cli-xml/kdump.xml @@ -61,13 +61,12 @@ sonic# configure terminal - sonic(config)# enable kdump - KDUMP configuration has been updated in the startup configuration - Kdump configuration changes will be applied after the system reboots + sonic(config)# kdump enable + Kdump configuration changes will be applied after the system reboots. + Save SONiC configuration using 'write memory' before issuing the reboot command. sonic(config)# no kdump - KDUMP configuration has been updated in the startup configuration - ALERT! A system reboot is highly recommended. - Kdump configuration changes will be applied after the system reboots + Kdump configuration changes will be applied after the system reboots. + Save SONiC configuration using 'write memory' before issuing the reboot command. KDUMP @@ -93,9 +92,11 @@ sonic# configure terminal sonic(config)# kdump memory 512M - KDUMP configuration has been updated in the startup configuration - kdump updated memory will be only operational after the system reboots - sonic(config)# no kdump memory + Kdump updated memory will be only operational after the system reboots. + Save SONiC configuration using 'write memory' before issuing the reboot command. + sonic(config)# no kdump memory + Kdump updated memory will be only operational after the system reboots. + Save SONiC configuration using 'write memory' before issuing the reboot command. KDUMP @@ -190,14 +191,16 @@ - Show the amount of memory reserved for kdump operation. + Show the amount of memory reserved and allocated for kdump operation. - Use this command to show the amount of memory reserved for kdump operation. + Use this command to show the configured amount of memory reserved for kdump operation. + It also displays the actual memory allocated. sonic# show kdump memory Memory Reserved: 0M-2G:256M,2G-4G:256M,4G-8G:384M,8G-:448M + Memory Allocated: 448M KDUMP From 3cd7e7fdcb55c8a98cde0546177f3844019b8998 Mon Sep 17 00:00:00 2001 From: Shiva Kumar Boddula Date: Tue, 22 Mar 2022 01:38:07 -0700 Subject: [PATCH 03/31] [JIRA SONIC-58381] Added changed display switchport config revert info when port-control mode is auto/fource-unauth and dot1x role is authenticator. Change-Id: I8f581ddd01eb6997937655886932214763e85ce5 --- CLI/actioner/sonic_cli_authmgr.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CLI/actioner/sonic_cli_authmgr.py b/CLI/actioner/sonic_cli_authmgr.py index c0e0ed7aeb..717355569e 100644 --- a/CLI/actioner/sonic_cli_authmgr.py +++ b/CLI/actioner/sonic_cli_authmgr.py @@ -89,6 +89,14 @@ def invoke_api(func, args): portCtlMode = 'FORCE_AUTHORIZED' elif args[0] == 'force-unauthorized': portCtlMode = 'FORCE_UNAUTHORIZED' + if portCtlMode == 'AUTO' or portCtlMode == 'FORCE_UNAUTHORIZED': + func = "get_switchport_access_trunk_mode" + response = {} + response = invoke_api(func, args[1]) + if response and response.ok() and (response.content is not None) and ('sonic-port:PORT_LIST' in response.content): + if "access_vlan" in response.content['sonic-port:PORT_LIST'][0] or "tagged_vlans" in response.content['sonic-port:PORT_LIST'][0]: + print("%Info: Enabling PAC on the port will revert all switchport configurations on the port,\n\ + if port control mode is auto/force-unauthorized and port pae role is authenticator.") path = cc.Path('/restconf/data/openconfig-authmgr:authmgr/authmgr-port-config/interface') body = collections.defaultdict(dict) body["interface"] = [{ @@ -497,7 +505,8 @@ def invoke_api(func, args): response = invoke_api(func, args[1]) if response and response.ok() and (response.content is not None) and ('sonic-port:PORT_LIST' in response.content): if "access_vlan" in response.content['sonic-port:PORT_LIST'][0] or "tagged_vlans" in response.content['sonic-port:PORT_LIST'][0]: - print("%Info: Enabling PAC on the port will revert all switchport configurations on the port") + print("%Info: Enabling PAC on the port will revert all switchport configurations on the port,\n\ + if port control mode is auto/force-unauthorized and port pae role is authenticator.") elif args[0] == 'none': dot1xPaeRole = 'NONE' path = cc.Path('/restconf/data/openconfig-authmgr:authmgr/authmgr-port-config/interface') From 743b6b17d10f8b093d19a06b29ee4ead343ce70b Mon Sep 17 00:00:00 2001 From: leeprecy Date: Tue, 22 Mar 2022 17:37:49 -0700 Subject: [PATCH 04/31] [JIRA SONIC-59442] SONIC_4.0.0 - Klish show logging memory optimization Change-Id: Ib1fe26530bf34b24c4e05c7d38539e00d482a116 --- CLI/actioner/sonic_cli_in_memory_logging.py | 19 ++++++++++++++----- CLI/actioner/sonic_cli_logging.py | 19 ++++++++++++++----- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/CLI/actioner/sonic_cli_in_memory_logging.py b/CLI/actioner/sonic_cli_in_memory_logging.py index 74a47050bc..e045bfc522 100755 --- a/CLI/actioner/sonic_cli_in_memory_logging.py +++ b/CLI/actioner/sonic_cli_in_memory_logging.py @@ -20,6 +20,9 @@ import cli_client as cc from scripts.render_cli import show_cli_output +import os +from rpipe_utils import pipestr +import scripts.render_cli as cli def get_sonic_inmemory_logging(args): @@ -30,14 +33,20 @@ def get_sonic_inmemory_logging(args): body = {"openconfig-infra-logging:input": {"num-lines": int(args[1])}} else: body = {"openconfig-infra-logging:input": {"num-lines": 0}} - templ = args[0] - api_response = aa.post(keypath, body) + api_response = aa.post(keypath, body, response_type='string') try: if api_response.ok(): - response = api_response.content - if response is not None and "openconfig-infra-logging:output" in response: - show_cli_output(templ, response) + response = api_response.content + if response is not None and 'openconfig-infra-logging:output' in response: + full_cmd = os.getenv('USER_COMMAND', None) + if full_cmd is not None: + pipestr().write(full_cmd.split()) + data = response.split("\"status-detail\":[\"") + if len(data) > 1: + cli.write(data[1].split("\"]}}")[0]) + else: + print("%Error:{}".format(data)) except Exception as e: raise e except KeyboardInterrupt: diff --git a/CLI/actioner/sonic_cli_logging.py b/CLI/actioner/sonic_cli_logging.py index c3738fa22d..0d4b9f632b 100755 --- a/CLI/actioner/sonic_cli_logging.py +++ b/CLI/actioner/sonic_cli_logging.py @@ -20,6 +20,9 @@ import cli_client as cc from scripts.render_cli import show_cli_output +import os +from rpipe_utils import pipestr +import scripts.render_cli as cli SYSTEM = "/restconf/data/openconfig-system:system/" LOG_SERVERS = SYSTEM + "logging/remote-servers" @@ -202,21 +205,27 @@ def get_sonic_logging(args): body = { "openconfig-infra-logging:input": { "num-lines": num, - "logtype": "null", + "logtype": "syslog", "loglevel": "null", "severity": "null", "since": "null" } } - templ = args[0] - api_response = aa.post(keypath, body) + api_response = aa.post(keypath, body, response_type='string') try: if api_response.ok(): response = api_response.content if response is not None and "openconfig-infra-logging:output" in response: - show_cli_output(templ, response) + full_cmd = os.getenv('USER_COMMAND', None) + if full_cmd is not None: + pipestr().write(full_cmd.split()) + data = response.split("\"status-detail\":[\"") + if len(data) > 1: + cli.write(data[1].split("\"]}}")[0]) + else: + print("%Error:{}".format(data)) except Exception as e: print(e) raise e @@ -272,7 +281,7 @@ def get_openconfig_system_logging_filter(args): body = { "openconfig-infra-logging:input": { "num-lines": 0, - "logtype": args[1], + "logtype": "syslog" if args[1] == "null" else args[1], "loglevel": args[2], "severity": args[3], "since": ' '.join(args[4:]) From 189c229cfa1104cf49f2e06eae18fbcaf24490d5 Mon Sep 17 00:00:00 2001 From: Naveen K Suvarna Date: Thu, 24 Mar 2022 00:38:11 -0700 Subject: [PATCH 05/31] [JIRA SONIC-59496] OSPFv2 - commenting out debug function call to format render_tables Change-Id: I37da86cb55626e17c6f6e26ea7f822da4ddfa0f9 --- CLI/actioner/show_config_ospfv2.py | 54 +++++++++++++++--------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/CLI/actioner/show_config_ospfv2.py b/CLI/actioner/show_config_ospfv2.py index 14a2f9ee8d..d1f674e01c 100644 --- a/CLI/actioner/show_config_ospfv2.py +++ b/CLI/actioner/show_config_ospfv2.py @@ -68,8 +68,8 @@ def show_router_ospf_config(render_tables): # ospf_debug_log_disable() # ospf_debug_log_enable() - ospf_debug_print( - "show_router_ospf_config: render_tables {}".format(render_tables)) + #ospf_debug_print( + # "show_router_ospf_config: render_tables {}".format(render_tables)) present, _, vrf_name = ospf_get_key_value(render_tables, "vrf-name") if present == False: @@ -256,9 +256,9 @@ def show_router_ospf_passive_interface_config(render_tables): # ospf_debug_log_enable() # ospf_debug_log_disable() - ospf_debug_print( - "show_router_ospf_passive_interface_config: render_tables {}".format( - render_tables)) + #ospf_debug_print( + # "show_router_ospf_passive_interface_config: render_tables {}".format( + # render_tables)) present, _, vrf_name = ospf_get_key_value(render_tables, "vrf-name") if present == False: @@ -326,8 +326,8 @@ def show_router_ospf_area_config(render_tables): # ospf_debug_log_enable() # ospf_debug_log_disable() - ospf_debug_print( - "show_router_ospf_area_config: render_tables {}".format(render_tables)) + #ospf_debug_print( + # "show_router_ospf_area_config: render_tables {}".format(render_tables)) present, _, vrf_name = ospf_get_key_value(render_tables, "vrf-name") if present == False: @@ -444,9 +444,9 @@ def show_router_ospf_area_network_config(render_tables): # ospf_debug_log_enable() # ospf_debug_log_disable() - ospf_debug_print( - "show_router_ospf_area_network_config: render_tables {}".format( - render_tables)) + #ospf_debug_print( + # "show_router_ospf_area_network_config: render_tables {}".format( + # render_tables)) present, _, vrf_name = ospf_get_key_value(render_tables, "vrf-name") if present == False: @@ -508,9 +508,9 @@ def show_router_ospf_area_vlink_config(render_tables): # ospf_debug_log_enable() # ospf_debug_log_disable() - ospf_debug_print( - "show_router_ospf_area_vlink_config: render_tables {}".format( - render_tables)) + #ospf_debug_print( + # "show_router_ospf_area_vlink_config: render_tables {}".format( + # render_tables)) present, _, vrf_name = ospf_get_key_value(render_tables, "vrf-name") if present == False: @@ -635,9 +635,9 @@ def show_router_ospf_area_vlmd_auth_config(render_tables): # ospf_debug_log_enable() # ospf_debug_log_disable() - ospf_debug_print( - "show_router_ospf_area_vlmd_auth_config: render_tables {}".format( - render_tables)) + #ospf_debug_print( + # "show_router_ospf_area_vlmd_auth_config: render_tables {}".format( + # render_tables)) present, name, vrf_name = ospf_get_key_value(render_tables, "vrf-name") if present == False: @@ -719,9 +719,9 @@ def show_router_ospf_area_addr_range_config(render_tables): # ospf_debug_log_enable() # ospf_debug_log_disable() - ospf_debug_print( - "show_router_ospf_area_addr_range_config: render_tables {}".format( - render_tables)) + #ospf_debug_print( + # "show_router_ospf_area_addr_range_config: render_tables {}".format( + # render_tables)) present, name, vrf_name = ospf_get_key_value(render_tables, "vrf-name") if present == False: @@ -814,9 +814,9 @@ def show_router_ospf_distribute_route_config(render_tables): # ospf_debug_log_enable() # ospf_debug_log_disable() - ospf_debug_print( - "show_router_ospf_distribute_route_config: render_tables {}".format( - render_tables)) + #ospf_debug_print( + # "show_router_ospf_distribute_route_config: render_tables {}".format( + # render_tables)) present, name, vrf_name = ospf_get_key_value(render_tables, "vrf-name") if present == False: @@ -914,8 +914,8 @@ def show_interface_ip_ospf_config(render_tables): # ospf_debug_log_enable() # ospf_debug_log_disable() - ospf_debug_print("show_interface_ip_ospf_config: render_tables {}".format( - render_tables)) + #ospf_debug_print("show_interface_ip_ospf_config: render_tables {}".format( + # render_tables)) present, name, intf_name = ospf_get_key_value(render_tables, "name") if present == False: @@ -1091,9 +1091,9 @@ def show_interface_ip_ospf_md_auth_config(render_tables): # ospf_debug_log_enable() # ospf_debug_log_disable() - ospf_debug_print( - "show_interface_ip_ospf_md_auth_config: render_tables {}".format( - render_tables)) + #ospf_debug_print( + # "show_interface_ip_ospf_md_auth_config: render_tables {}".format( + # render_tables)) present, name, intf_name = ospf_get_key_value(render_tables, "name") if present == False: From 87499b9fd66a894ad2ffebe26e930d991f1604ef Mon Sep 17 00:00:00 2001 From: "Nisar, Bhavesh" Date: Fri, 25 Mar 2022 07:11:27 -0700 Subject: [PATCH 06/31] [SNC-17566] Add Managment interface as an option for sflow agent-id configuration in CLI (#78) * Add Managment interface as an option for sflow agent-id configuration in CLI. * For "show sflow" command output replace 'eth0' with 'Management0'. Co-authored-by: bnisar <95-bnisar@users.noreply.gitlab-eqx-01> --- CLI/actioner/sonic_cli_sflow.py | 2 ++ CLI/clitree/cli-xml/sflow.xml | 3 ++- CLI/renderer/templates/show_sflow.j2 | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CLI/actioner/sonic_cli_sflow.py b/CLI/actioner/sonic_cli_sflow.py index 0df59b8397..5216253e9e 100755 --- a/CLI/actioner/sonic_cli_sflow.py +++ b/CLI/actioner/sonic_cli_sflow.py @@ -129,6 +129,8 @@ def invoke_api(func, args=[]): path = cc.Path( "/restconf/data/openconfig-sampling-sflow:sampling/sflow/config/agent" ) + if args[0] == "Management0": + args[0] = "eth0" body = {"openconfig-sampling-sflow:agent": args[0]} return api.patch(path, body) elif func == "patch_sonic_sflow_sonic_sflow_sflow_sflow_list_polling_interval": diff --git a/CLI/clitree/cli-xml/sflow.xml b/CLI/clitree/cli-xml/sflow.xml index 7ebf70aedb..dcd3db0cd2 100644 --- a/CLI/clitree/cli-xml/sflow.xml +++ b/CLI/clitree/cli-xml/sflow.xml @@ -120,8 +120,9 @@ limitations under the License. + - sonic_cli_sflow patch_sonic_sflow_sonic_sflow_sflow_sflow_list_agent_id ${phy-if-name}${vlan-if-name}${loop-if-name} + sonic_cli_sflow patch_sonic_sflow_sonic_sflow_sflow_sflow_list_agent_id ${phy-if-name}${vlan-if-name}${loop-if-name}${mgmt-if-name} Configure sFlow agent interface diff --git a/CLI/renderer/templates/show_sflow.j2 b/CLI/renderer/templates/show_sflow.j2 index 83f22a8765..3f4f4f4fa9 100755 --- a/CLI/renderer/templates/show_sflow.j2 +++ b/CLI/renderer/templates/show_sflow.j2 @@ -20,6 +20,9 @@ {% endif %} {% if 'agent' in sfl_gbl %} +{% if sfl_gbl['agent'] == 'eth0' %} + {% do sfl_gbl.update({'agent': "Management 0"}) %} +{% endif %} {% if vars.update({'agentId' : sfl_gbl['agent']}) %}{% endif %} {% else %} {% if vars.update({'agentId' : "default"}) %} {% endif %} From e1fccaaa07ffae7dd80f29fd3aa689db11264888 Mon Sep 17 00:00:00 2001 From: Sachin Holla Date: Sun, 27 Mar 2022 23:13:45 -0700 Subject: [PATCH 07/31] [JIRA SONIC-59682] handle "FEATURE_" prefix in mock feature check API Change-Id: I6522bd98e545ba693549ca32f7a9da092c9fe0c0 --- tools/mock/sonic_py_common/device_info.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/mock/sonic_py_common/device_info.py b/tools/mock/sonic_py_common/device_info.py index 9e8e952db4..428e2a485d 100644 --- a/tools/mock/sonic_py_common/device_info.py +++ b/tools/mock/sonic_py_common/device_info.py @@ -32,5 +32,11 @@ def __has_feature(name): b_info = get_sonic_branding_info() if b_info is None or "features" not in b_info: return True + name = to_feature_name(name) f_list = b_info["features"] return f_list is not None and name in f_list + + +def to_feature_name(name): + f_name = name.upper() + return f_name[len("FEATURE_"):] if f_name.startswith("FEATURE_") else f_name From 22e5ac76d9b4d6ccb24de94d70424426d7b3e0b1 Mon Sep 17 00:00:00 2001 From: Dante Su Date: Mon, 28 Mar 2022 03:58:28 +0000 Subject: [PATCH 08/31] [JIRA SONIC-59621] Autoneg: "Type" column for "show interface advertise/link-training" has no information Signed-off-by: Dante Su Change-Id: Ie5ff156354e4aea28f458f1bc16e73f1e870db21 --- CLI/renderer/templates/show_interface_advertise.j2 | 4 ++-- CLI/renderer/templates/show_interface_advertise_one.j2 | 4 ++-- CLI/renderer/templates/show_interface_link_training.j2 | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CLI/renderer/templates/show_interface_advertise.j2 b/CLI/renderer/templates/show_interface_advertise.j2 index 89dbef8cfb..aab356b8f7 100644 --- a/CLI/renderer/templates/show_interface_advertise.j2 +++ b/CLI/renderer/templates/show_interface_advertise.j2 @@ -15,8 +15,8 @@ {% if 'present' in data %} {% set present = data['present'] %} {% endif %} -{% if present == 'PRESENT' and 'openconfig-platform-ext:display-name' in data %} -{% set type = data['openconfig-platform-ext:display-name'] %} +{% if present == 'PRESENT' and 'display-name' in data %} +{% set type = data['display-name'] %} {% endif %} {% endif %} diff --git a/CLI/renderer/templates/show_interface_advertise_one.j2 b/CLI/renderer/templates/show_interface_advertise_one.j2 index 550ed111c9..ff977c66fb 100644 --- a/CLI/renderer/templates/show_interface_advertise_one.j2 +++ b/CLI/renderer/templates/show_interface_advertise_one.j2 @@ -13,8 +13,8 @@ {% if 'present' in data %} {% set present = data['present'] %} {% endif %} -{% if present == 'PRESENT' and 'openconfig-platform-ext:display-name' in data %} -{% set type = data['openconfig-platform-ext:display-name'] %} +{% if present == 'PRESENT' and 'display-name' in data %} +{% set type = data['display-name'] %} {% endif %} {% endif %} diff --git a/CLI/renderer/templates/show_interface_link_training.j2 b/CLI/renderer/templates/show_interface_link_training.j2 index 358780af8f..97788ffd0b 100644 --- a/CLI/renderer/templates/show_interface_link_training.j2 +++ b/CLI/renderer/templates/show_interface_link_training.j2 @@ -15,8 +15,8 @@ {% if 'present' in data %} {% set present = data['present'] %} {% endif %} -{% if present == 'PRESENT' and 'openconfig-platform-ext:display-name' in data %} -{% set type = data['openconfig-platform-ext:display-name'] %} +{% if present == 'PRESENT' and 'display-name' in data %} +{% set type = data['display-name'] %} {% endif %} {% endif %} From 10ff3f1c820a8466d53c5e20bb7675f9f3f8c972 Mon Sep 17 00:00:00 2001 From: Sachin Holla Date: Mon, 28 Mar 2022 22:36:35 +0530 Subject: [PATCH 09/31] [JIRA SONIC-59703] REST APIs to start/collect go profiles Added following new debug REST APIs to access go profile data. - POST /debug/cpu-profile-start Starts cpu profiler and return immediately. - POST /debug/cpu-profile Stop cpu profiler and return the data in pprof binary format. - POST /debug/heap-profile Return heap profile data in pprof binary format. - POST /debug/goroutines Returns goroutine profile data as human readable text (includes stack traces of all goroutines) Other changes: - Do not start cpu profiler on REST server startup - No SIGUSR1 based start/stop trigger Change-Id: Id69899e476d73130affebc1b35c8ecf80570eddd --- go.mod | 1 - rest/main/main.go | 86 ++------------------------------------------ rest/server/debug.go | 62 ++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 84 deletions(-) diff --git a/go.mod b/go.mod index 91c7747b73..073f189ed3 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/gorilla/mux v1.7.4 github.com/leodido/go-urn v1.2.0 // indirect - github.com/pkg/profile v1.4.0 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect diff --git a/rest/main/main.go b/rest/main/main.go index e8f4b63239..6895feec07 100644 --- a/rest/main/main.go +++ b/rest/main/main.go @@ -28,15 +28,11 @@ import ( "net" "net/http" "os" - "os/signal" - "syscall" "time" "github.com/Azure/sonic-mgmt-common/translib/db" "github.com/Azure/sonic-mgmt-framework/rest/server" - "github.com/golang/glog" - "github.com/pkg/profile" ) // Command line parameters @@ -47,7 +43,6 @@ var ( caFile string // Client CA certificate file path noSocket bool // Do not start unix domain socket lister internal bool // Enable internal features on https listener - profDir string // CPU profiler reports directory clientAuth = server.NewUserAuth() funnel server.RequestFunnel // Controls number of concurrent requests @@ -70,10 +65,12 @@ func init() { flag.Var(clientAuth, "client_auth", "Client auth mode(s) - default: password,jwt") flag.BoolVar(&noSocket, "no-sock", false, "Do not start unix domain socket listener") flag.BoolVar(&internal, "internal", false, "Enable internal, non-standard features on https listener") - flag.StringVar(&profDir, "prof_dir", "", "CPU profiler reports directory") flag.UintVar(&funnel.Limit, "reqlimit", 0, "Max concurrent requests allowed") flag.DurationVar(&readTimeout, "readtimeout", readTimeout, "Maximum duration for reading entire request") + // dummy flag -- to be removed after cleaning up startup script + flag.String("prof_dir", "", "NOT USED") + flag.Parse() //Below is for setting the default client_auth to password,jwt. @@ -90,32 +87,9 @@ func init() { } -// theProfiler points to the currently active cpu profiler instance -var theProfiler interface{ Stop() } - // Start REST server func main() { - /* Enable profiling by default. Send SIGUSR1 signal to rest_server to - * stop profiling and save data to /tmp/profile/cpu.pprof file. - * Copy over the cpu.pprof file and rest_server to a Linux host and run - * any of the following commands to generate a report in needed format. - * go tool pprof --txt ./rest_server ./cpu.pprof > report.txt - * go tool pprof --pdf ./rest_server ./cpu.pprof > report.pdf - * Note: install graphviz to generate the graph on a pdf format - */ - startProfiler() - defer stopProfiler() - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGUSR1) - go func() { - for { - <-sigs - stopProfiler() - startProfiler() - } - }() - if caFile == "" && clientAuth.Enabled("cert") { clientAuth.Unset("cert") glog.Warning("Must specify -cacert with -client_auth cert") @@ -302,60 +276,6 @@ func getPreferredCipherSuites() []uint16 { } } -// startProfiler starts a cpu profiler. -func startProfiler() { - // Backup previous profiler reports in profDir. Creates a new - // temporary reports directory if profDir not specified. - if profDir != "" { - rotateFile(profDir+"/cpu.pprof", 5) - } else { - profDir = createTempProfileDir() - } - - var opts []func(*profile.Profile) - opts = append(opts, func(p *profile.Profile) { profile.Quiet(p) }) - opts = append(opts, profile.ProfilePath(profDir)) - - glog.Infof("Starting profiler.. reports dir %s", profDir) - theProfiler = profile.Start(opts...) -} - -// stopProfiler stops the cpu profiler. -func stopProfiler() { - if theProfiler != nil { - theProfiler.Stop() - glog.Infof("Profiler stopped") - theProfiler = nil - } -} - -func createTempProfileDir() string { - path, err := ioutil.TempDir(os.TempDir(), "profile") - if err != nil { - glog.Warning("Failed to create temp dir!", err) - path = os.TempDir() - } - - return path -} - -func rotateFile(file string, count int) { - info, err := os.Stat(file) - if err != nil { - return // file does not exists.. nothing to do - } - if info.Size() == 0 { - os.Remove(file) // remove empty file - return - } - for ; count > 1; count-- { - os.Rename(fmt.Sprintf("%s.%d", file, count-1), fmt.Sprintf("%s.%d", file, count)) - } - if count == 1 { - os.Rename(file, file+".1") - } -} - func check_if_rest_server_up(address string, serverConfig *tls.Config) { glog.Infof("Checking if rest_server started on %s", address) clientConfig := tls.Config{ diff --git a/rest/server/debug.go b/rest/server/debug.go index 6d4608c80c..f2916d0f11 100644 --- a/rest/server/debug.go +++ b/rest/server/debug.go @@ -25,10 +25,31 @@ import ( "net/http" "reflect" "sort" + + "github.com/Azure/sonic-mgmt-common/profile" + "github.com/Azure/sonic-mgmt-common/translib" + "github.com/Azure/sonic-mgmt-common/translib/tlerr" ) func init() { AddRoute("getRoutes", "GET", "/debug/rest-routes", getRoutesHandler, nil) + + // Handlers for go profilers + + startCpu := startProfileHandler(profile.CPU_PROFILE) + AddRoute("startProf_cpu", "POST", "/debug/cpu-profile-start", startCpu, nil) + + collectCpu := collectProfileHandler(profile.CPU_PROFILE) + AddRoute("cpuProf", "POST", "/debug/cpu-profile", collectCpu, nil) + AddRoute("cpuProf_", "GET", "/debug/cpu-profile", collectCpu, nil) + + collectHeap := collectProfileHandler(profile.HEAP_PROFILE) + AddRoute("memProf", "POST", "/debug/heap-profile", collectHeap, nil) + AddRoute("memProf_", "GET", "/debug/heap-profile", collectHeap, nil) + + collectBt := collectProfileHandler(profile.GOROUTINE_PROFILE) + AddRoute("goroutineProf", "POST", "/debug/goroutines", collectBt, nil) + AddRoute("goroutineProf_", "GET", "/debug/goroutines", collectBt, nil) } func getRoutesHandler(w http.ResponseWriter, r *http.Request) { @@ -72,3 +93,44 @@ func sortedMapKeys(m interface{}) []string { sort.Strings(keys) return keys } + +func startProfileHandler(t profile.Type) http.HandlerFunc { + h := func(w http.ResponseWriter, r *http.Request) { + err := profile.Start(t) + if err != nil { + writeErrorResponse(w, r, err) + } + } + return authorizeActionMiddleware("Debug/StartProfile", h) +} + +func collectProfileHandler(t profile.Type) http.HandlerFunc { + h := func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("X-Content-Type-Options", "nosniff") + err := profile.Collect(t, w) + if err != nil { + writeErrorResponse(w, r, err) + } + } + return authorizeActionMiddleware("Debug/CollectProfile", h) +} + +// authorizeActionMiddleware authorizes the debug actions that do not invoke +// translib Get/Set APIs. Action name is treated as the resource path. +// Typically, only admins are allowed to perform these actions. +func authorizeActionMiddleware(name string, inner http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + rc, r := GetContext(r) + aReq := translib.ActionRequest{ + Path: name, + User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}, + AuthEnabled: rc.ClientAuth.Any(), + } + if translib.IsAuthorizedForAction(aReq) { + inner(w, r) + } else { + writeErrorResponse(w, r, tlerr.AuthorizationError{Path: name}) + } + } +} From 36358f5c5780b9784a7046db0959f2530af3ea51 Mon Sep 17 00:00:00 2001 From: Rupesh Kumar Date: Fri, 1 Apr 2022 07:13:02 -0700 Subject: [PATCH 10/31] [JIRA SONIC-59975] Drop counter cli validation fix Change-Id: I515ec75de82cb02928e9dd885bb54e1020a9d450 --- CLI/actioner/sonic_cli_dropcounter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLI/actioner/sonic_cli_dropcounter.py b/CLI/actioner/sonic_cli_dropcounter.py index f159d077f3..fa3dc3c3ff 100755 --- a/CLI/actioner/sonic_cli_dropcounter.py +++ b/CLI/actioner/sonic_cli_dropcounter.py @@ -143,7 +143,7 @@ def invoke(func, args): if func == 'add_reason_config': reasons = deserialize_reason_list(args[1]) - keypath = cc.Path('/restconf/data/sonic-debugcounter:sonic-debugcounter/DEBUG_COUNTER/DEBUG_COUNTER_LIST={name}/type', name=args[0]) + keypath = cc.Path('/restconf/data/sonic-debugcounter:sonic-debugcounter/DEBUG_COUNTER/DEBUG_COUNTER_LIST={name}/reasons', name=args[0]) body = {"sonic-debugcounter:reasons" : [args[1]]} return aa.patch(keypath, body) From 3dbd40c8c350cdd78065e91bff7cfd8648453a98 Mon Sep 17 00:00:00 2001 From: Sachin Holla Date: Mon, 4 Apr 2022 00:21:40 -0700 Subject: [PATCH 11/31] [JIRA SONIC-59703] Remove unsued 'prof_dir' flag Change-Id: Ie9831e260a28607bfbecfbbf1318ccb4228773a1 --- rest/main/main.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/rest/main/main.go b/rest/main/main.go index 6895feec07..ad8189b08b 100644 --- a/rest/main/main.go +++ b/rest/main/main.go @@ -68,9 +68,6 @@ func init() { flag.UintVar(&funnel.Limit, "reqlimit", 0, "Max concurrent requests allowed") flag.DurationVar(&readTimeout, "readtimeout", readTimeout, "Maximum duration for reading entire request") - // dummy flag -- to be removed after cleaning up startup script - flag.String("prof_dir", "", "NOT USED") - flag.Parse() //Below is for setting the default client_auth to password,jwt. From 4f0293acfaea301eb90ebf510a6c8ef6cec0f5ac Mon Sep 17 00:00:00 2001 From: Phanindra TV Date: Tue, 5 Apr 2022 07:00:46 -0700 Subject: [PATCH 12/31] [JIRA SONIC-57576] Added logic to capture the return code and log error message. Change-Id: Id11c35eeeed776076b786fd5c111a281412a2356 --- CLI/actioner/sonic_cli_if_autoneg.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CLI/actioner/sonic_cli_if_autoneg.py b/CLI/actioner/sonic_cli_if_autoneg.py index 4f1bdf0713..3a56f3d320 100644 --- a/CLI/actioner/sonic_cli_if_autoneg.py +++ b/CLI/actioner/sonic_cli_if_autoneg.py @@ -358,7 +358,13 @@ def run(func, args): body = {"openconfig-interfaces:interfaces": {"interface": info}} path = cc.Path("/restconf/data/openconfig-interfaces:interfaces") - aa.patch(path, body) + response = {} + response = aa.patch(path, body) + if response.ok(): + return + else: + print((response.error_message())) + return elif func == "patch_ocif_eth_link_training": if if_name is not None: From 4715c2a47dd0923b71b826b0f408dbacdb8376ef Mon Sep 17 00:00:00 2001 From: Anurag Kumar Date: Tue, 29 Mar 2022 05:41:13 -0700 Subject: [PATCH 13/31] [JIRA SONIC-57627] Moved code to add default range after deletion from actioner to transformer code. On creating/ updating reserved vlan range, moved the code to delete the old range in actioner script Change-Id: Id6bd6b0e8df49c74de056786d54428ce63db6e69 --- CLI/actioner/sonic_cli_vlan.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/CLI/actioner/sonic_cli_vlan.py b/CLI/actioner/sonic_cli_vlan.py index cbfa556068..cdc2ce516b 100644 --- a/CLI/actioner/sonic_cli_vlan.py +++ b/CLI/actioner/sonic_cli_vlan.py @@ -109,7 +109,6 @@ def invoke_api(func, args=[]): elif (int(vlan) + 127 > 4094): return api._make_error_response("Invalid starting vlan id for 128 vlans to be reserved") path1 = cc.Path("/restconf/data/openconfig-vlan-ext:reserve-vlans") - response = api.delete(path1) keypath = cc.Path("/restconf/data/openconfig-vlan-ext:reserve-vlans") body = { @@ -138,26 +137,7 @@ def invoke_api(func, args=[]): path1 = cc.Path("/restconf/data/openconfig-vlan-ext:reserve-vlans") response = api.delete(path1) - if response.ok(): - j = 3967 - body = { - "openconfig-vlan-ext:reserve-vlans": { - "reserve-vlan": [] - } - } - i = 0 - while i <= 127: - vlan_name = "Vlan" + str(int(i+j)) - body["openconfig-vlan-ext:reserve-vlans"]["reserve-vlan"].append({ - "vlan-name": vlan_name, - "config": { - "vlan-name": vlan_name - } - }) - - i = i+1 - - response = api.patch(path1, body) + return response if func == "reserved_vlan_show": @@ -378,6 +358,7 @@ def run(func, args): if func == "get_sonic_vlan_sonic_vlan": if "sonic-vlan:sonic-vlan" in api_response: value = api_response["sonic-vlan:sonic-vlan"] + reserved_vlan_list = [] if "RESERVED_VLAN" in value: reserved_vlan_list = value["RESERVED_VLAN"]["RESERVED_VLAN_LIST"] else: From 76afac82154edde4589567664a8c04990996c065 Mon Sep 17 00:00:00 2001 From: "KR, HaemanthiSree" Date: Wed, 6 Apr 2022 10:42:34 -0700 Subject: [PATCH 14/31] SNC-17768/buffer-init-error-message (#85) * update code to check for error * Update code for qos buffer error --- CLI/actioner/sonic_cli_qos_buffer.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/CLI/actioner/sonic_cli_qos_buffer.py b/CLI/actioner/sonic_cli_qos_buffer.py index caccf3ccdd..9a01bad0ea 100644 --- a/CLI/actioner/sonic_cli_qos_buffer.py +++ b/CLI/actioner/sonic_cli_qos_buffer.py @@ -22,6 +22,8 @@ import cli_client as cc from scripts.render_cli import show_cli_output from natsort import natsorted, ns +import os +import syslog as log buffer_types = {"ingress": "INGRESS", "egress": "EGRESS"} buffer_modes = {"static": "STATIC", "dynamic": "DYNAMIC"} @@ -37,6 +39,20 @@ def prompt(msg): else: return True +def qos_buffer_rpc_reboot(api,path,body): + try: + api_response = api.post(path, body) + except Exception as e: + log.syslog(log.LOG_NOTICE, str(e)) + print("% Error: Internal error") + return + + server_down_msg="Could not connect to Management REST Server" + if server_down_msg in api_response.error_message(): + return 1 + else: + print((api_response.error_message())) + def get_list_from_range(val_lists): lst = val_lists.split(",") lst = [sub.replace("-", "..") for sub in lst] @@ -65,9 +81,9 @@ def invoke_api(func, args=[]): body = {"openconfig-qos-private:input": {"operation": "INIT"}} path = cc.Path( "/restconf/operations/openconfig-qos-private:qos-buffer-config") - return api.post(path, body) - else: - return None + qos_buffer_rpc_reboot(api,path,body) + + return None elif func == "rpc_clear_qos_buffer": initMessage = prompt(msg) @@ -75,9 +91,9 @@ def invoke_api(func, args=[]): body = {"openconfig-qos-private:input": {"operation": "CLEAR"}} path = cc.Path( "/restconf/operations/openconfig-qos-private:qos-buffer-config") - return api.post(path, body) - else: - return None + qos_buffer_rpc_reboot(api,path,body) + + return None elif func == "create_qos_buffer_pool": path = cc.Path( From 43f0bea711ddfe4d98ae327d081128bbef283d4f Mon Sep 17 00:00:00 2001 From: SumitAgarwal123 Date: Thu, 7 Apr 2022 08:14:07 -0700 Subject: [PATCH 15/31] [JIRA SONIC-60336] Changing run cofig order, displaying profile before bfd peer Change-Id: If5a8d3cc89342f077881a95ac3fea411195c72ae --- CLI/actioner/show_config_bfd.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CLI/actioner/show_config_bfd.py b/CLI/actioner/show_config_bfd.py index 7366a200ad..33aa43ac4b 100644 --- a/CLI/actioner/show_config_bfd.py +++ b/CLI/actioner/show_config_bfd.py @@ -374,6 +374,11 @@ def show_bfd_config(render_tables): cmd_str += "bfd" + cmd_end + for profile_rec in profile_record_list: + cmd_str += bfd_generate_profile_config(profile_rec) + cmd_str += " !" + cmd_str += cmd_end + for shop_rec in shop_record_list: cmd_str += bfd_generate_shop_config(shop_rec) cmd_str += " !" @@ -384,11 +389,6 @@ def show_bfd_config(render_tables): cmd_str += " !" cmd_str += cmd_end - for profile_rec in profile_record_list: - cmd_str += bfd_generate_profile_config(profile_rec) - cmd_str += " !" - cmd_str += cmd_end - # cmd_str += cmd_end return "CB_SUCCESS", cmd_str From 6d910c6d447dc67ea26555ce7ceab3db0d8428a4 Mon Sep 17 00:00:00 2001 From: Sachin Holla Date: Thu, 7 Apr 2022 09:51:24 -0700 Subject: [PATCH 16/31] [JIRA SONIC-60241] Dump coverage info on SIGHUP SIGUSR1 was used as the treigger earlier, which conflicts with profile dump trigger. Changed to use SIGHUP as coverage dump trigger. It will also restart REST server (required for dumping the coverage info). Change-Id: Ia82ed083616130a1b43ae5f562c05011cefef78b --- rest/main/main_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest/main/main_test.go b/rest/main/main_test.go index f42847814d..1da81f002d 100644 --- a/rest/main/main_test.go +++ b/rest/main/main_test.go @@ -30,7 +30,7 @@ import ( func TestMain(t *testing.T) { go main() sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGUSR1) + signal.Notify(sigs, syscall.SIGHUP) fmt.Println("Listening on sig kill from TestMain") <-sigs fmt.Println("Returning from TestMain on sig kill") From 92634d7fba0add462f10bf26d48b46a7d56cabc5 Mon Sep 17 00:00:00 2001 From: "Lem, Eddy" Date: Thu, 7 Apr 2022 17:47:28 -0700 Subject: [PATCH 17/31] Remove configure-bfd-profile (also in configre-bfd) from show-run-cfg view list. (#88) --- CLI/actioner/show_config_data.py | 1 - 1 file changed, 1 deletion(-) diff --git a/CLI/actioner/show_config_data.py b/CLI/actioner/show_config_data.py index c3eb5319f5..31145f3562 100644 --- a/CLI/actioner/show_config_data.py +++ b/CLI/actioner/show_config_data.py @@ -146,7 +146,6 @@ "configure-mirror", "configure-ipsla", "configure-bfd", - "configure-bfd-profile", "renderCfg_ntp", "renderCfg_ipdns", "renderCfg_tacacs", From 89d2c53bb3dc2155a2a83b48340bd390a391867f Mon Sep 17 00:00:00 2001 From: Sachin Holla Date: Mon, 11 Apr 2022 14:51:24 +0530 Subject: [PATCH 18/31] [JIRA SONIC-60448] REST server coverage build * Fix go test's -coverpkg argument to include packages from both mgmt-common and mgmt-framework. * Set /var/log/rest_server/coverage.out as the default coverage report path in the TestMain. Can be overriden by passing -test.coverprofile argument to the rest_server. * Move flag.Parse() call to REST server's main function from init. Required to allow go test's flags (like -test.coverprofile) Change-Id: I4c1f0435a66aa5d2e99b091d08a51fbad10dc7e9 --- rest/Makefile | 3 +-- rest/main/main.go | 10 +++++----- rest/main/main_test.go | 27 ++++++++++++++++++++++++++- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/rest/Makefile b/rest/Makefile index 374913e02c..95693b3c53 100644 --- a/rest/Makefile +++ b/rest/Makefile @@ -73,8 +73,7 @@ all: $(DEFAULT_TARGETS) # REST Server binary %/rest/rest_server: $(REST_SRCS) $$($$(*F)_GEN_SRCS) | $$(@D)/. ifeq ($(SONIC_COVERAGE_ON),y) - touch $(@D)/.test - $(GO) test -mod=vendor -tags=glog_defaults,$(*F)_pkg -coverpkg="./..." -c -o $@ ../rest/main + $(GO) test -mod=vendor -tags=glog_defaults,$(*F)_pkg -coverpkg="github.com/Azure/..." -c -o $@ ../rest/main else $(GO) build -o=$@ -mod=vendor -tags=glog_defaults,$(*F)_pkg $(GCFLAGS) -v ../rest/main endif diff --git a/rest/main/main.go b/rest/main/main.go index ad8189b08b..2c1865a157 100644 --- a/rest/main/main.go +++ b/rest/main/main.go @@ -67,6 +67,10 @@ func init() { flag.BoolVar(&internal, "internal", false, "Enable internal, non-standard features on https listener") flag.UintVar(&funnel.Limit, "reqlimit", 0, "Max concurrent requests allowed") flag.DurationVar(&readTimeout, "readtimeout", readTimeout, "Maximum duration for reading entire request") +} + +// Start REST server +func main() { flag.Parse() @@ -82,11 +86,6 @@ func init() { clientAuth = server.UserAuth{"password": true, "cert": false, "jwt": true} } -} - -// Start REST server -func main() { - if caFile == "" && clientAuth.Enabled("cert") { clientAuth.Unset("cert") glog.Warning("Must specify -cacert with -client_auth cert") @@ -131,6 +130,7 @@ func main() { glog.Infof("Read timeout = %v", readTimeout) glog.Infof("Concurrent request limit = %v", funnel.Limit) + glog.Infof("Authentication modes = %v", clientAuth) go check_if_rest_server_up(address, &tlsConfig) diff --git a/rest/main/main_test.go b/rest/main/main_test.go index 1da81f002d..080795c873 100644 --- a/rest/main/main_test.go +++ b/rest/main/main_test.go @@ -20,18 +20,43 @@ package main import ( + "flag" "fmt" "os" "os/signal" + "path/filepath" "syscall" "testing" ) -func TestMain(t *testing.T) { +func TestMain(m *testing.M) { + // Update default test.coverprofile flag value as "/var/log/rest_server/coverage.out". + // Command line argument will overwrite it, if specified. + if f := flag.Lookup("test.coverprofile"); f != nil { + exeName := filepath.Base(os.Args[0]) + coverFile := filepath.Join("/var/log", exeName, "coverage.out") + f.DefValue = coverFile + f.Value.Set(coverFile) + } + + // -test.xxx flags will not be available if flag.Parse() was called by an init function. + // Everything will work; but coverage report path cannot be customized. + if flag.Parsed() { + fmt.Println(">>>> WARNING: flag.Parse() called too early. Test flags will not be available.") + } + + os.Exit(m.Run()) +} + +func Test_main(t *testing.T) { go main() sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGHUP) fmt.Println("Listening on sig kill from TestMain") <-sigs fmt.Println("Returning from TestMain on sig kill") + + if f := flag.Lookup("test.coverprofile"); f != nil { + fmt.Println("Coverage report file:", f.Value) + } } From d05d4bdb6ed3e4749a03908cfea8aa6a60439ecb Mon Sep 17 00:00:00 2001 From: Balachandar Mani Date: Mon, 11 Apr 2022 14:23:04 -0700 Subject: [PATCH 19/31] [JIRA SONIC-60497] GET Request: Added the changes to pass the context to the Translib GET API Change-Id: I3fbdc6df82b4fa0b703fa6c52469d89ca64b6c4c --- rest/server/context.go | 2 ++ rest/server/handler.go | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/rest/server/context.go b/rest/server/context.go index 0e7175c14d..898c97c74c 100644 --- a/rest/server/context.go +++ b/rest/server/context.go @@ -70,6 +70,8 @@ type RequestContext struct { Auth AuthInfo ClientAuth UserAuth + + Ctxt context.Context } type contextkey int diff --git a/rest/server/handler.go b/rest/server/handler.go index 263c50bc0f..7d3cb0c38e 100644 --- a/rest/server/handler.go +++ b/rest/server/handler.go @@ -20,6 +20,7 @@ package server import ( + "context" "fmt" "io/ioutil" "log/syslog" @@ -51,6 +52,7 @@ func Process(w http.ResponseWriter, r *http.Request) { reqID: reqID, AuthEnabled: rc.ClientAuth.Any(), User: translib.UserRoles{Name: rc.Auth.User, Roles: rc.Auth.Roles}, + Ctx: r.Context(), } var err error @@ -340,6 +342,7 @@ type translibArgs struct { deleteEmpty bool // Delete empty entry during field delete AuthEnabled bool // Enable Authorization User translib.UserRoles // User and role info for RBAC + Ctx context.Context } // invokeTranslib calls appropriate TransLib API for the given HTTP @@ -363,6 +366,7 @@ func invokeTranslib(args *translibArgs, rc *RequestContext) (int, []byte, error) Content: args.content, Fields: args.fields, }, + Ctxt: args.Ctx, } resp, err1 := translib.Get(req) From 4ec537d79bcbac1218a4bc9a2f6856fe058b8b3f Mon Sep 17 00:00:00 2001 From: Balachandar Mani Date: Wed, 13 Apr 2022 13:34:42 -0700 Subject: [PATCH 20/31] [JIRA SONIC-60497] Made changes to remove the context field Change-Id: Id10d1b90cc807cab6143bcb5cd3f2289603d88e8 --- rest/server/context.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/rest/server/context.go b/rest/server/context.go index 898c97c74c..0e7175c14d 100644 --- a/rest/server/context.go +++ b/rest/server/context.go @@ -70,8 +70,6 @@ type RequestContext struct { Auth AuthInfo ClientAuth UserAuth - - Ctxt context.Context } type contextkey int From d2fcc509870d9a02f5d1f42602845041cc6c28df Mon Sep 17 00:00:00 2001 From: Ashok Narayanasamy Date: Fri, 4 Mar 2022 16:00:57 -0800 Subject: [PATCH 21/31] [JIRA SONIC-58393] Openconfig ACL yang enhancement for Dynamic ACL. Code changes to separate the dynamic ACL from the user configured ACL in openconfig ACL yang Change-Id: I86efef5181121563bb226203ce6cd80e6c788d29 --- CLI/actioner/sonic_cli_acl.py | 164 ++++++++++++++++++++++++++-------- 1 file changed, 126 insertions(+), 38 deletions(-) diff --git a/CLI/actioner/sonic_cli_acl.py b/CLI/actioner/sonic_cli_acl.py index eb5a515ad7..aac2dcd35b 100644 --- a/CLI/actioner/sonic_cli_acl.py +++ b/CLI/actioner/sonic_cli_acl.py @@ -683,6 +683,12 @@ def handle_get_acl_details_request(args): if counter_mode == "AGGREGATE_ONLY": keypath = cc.Path('/restconf/data/openconfig-acl:acl/acl-sets') response = acl_client.get(keypath, depth=None, ignore404=False) + keypath = cc.Path('/restconf/data/openconfig-acl:acl/openconfig-acl-ext:dynamic-acl-sets/dynamic-acl-set={acl_type}', acl_type=args[0]) + response2 = acl_client.get(keypath, depth=None, ignore404=False) + if response.ok() and response2.ok(): + response.content.update(response2.content) + elif response2.ok(): + response = response2 else: keypath = cc.Path('/restconf/data/sonic-acl:sonic-acl/ACL_TABLE/ACL_TABLE_LIST') response = acl_client.get(keypath, depth=None, ignore404=False) @@ -696,6 +702,9 @@ def handle_get_acl_details_request(args): if counter_mode == "AGGREGATE_ONLY": keypath = cc.Path('/restconf/data/openconfig-acl:acl/acl-sets/acl-set={name},{acl_type}', name=args[1], acl_type=args[0]) response = acl_client.get(keypath, depth=None, ignore404=False) + if not response.ok() and response.status_code == 404: + keypath = cc.Path('/restconf/data/openconfig-acl:acl/openconfig-acl-ext:dynamic-acl-sets/dynamic-acl-set={acl_type}', acl_type=args[0]) + response = acl_client.get(keypath, depth=None, ignore404=False) else: keypath = cc.Path('/restconf/data/sonic-acl:sonic-acl/ACL_TABLE/ACL_TABLE_LIST={aclname}', aclname=args[1]) response = acl_client.get(keypath, depth=None, ignore404=False) @@ -1054,7 +1063,11 @@ def __convert_l2_rule_to_user_fmt(acl_entry, rule_data): def __parse_acl_entry(data, acl_entry, acl_type): - seq_id = acl_entry['sequence-id'] + if 'sequence-id' in acl_entry: + seq_id = acl_entry['sequence-id'] + else: + seq_id = acl_entry['state']['sequence-id'] + data[seq_id] = dict() log.log_debug("Parse {} rule {}".format(acl_type, str(acl_entry))) @@ -1090,8 +1103,14 @@ def __parse_acl_entry(data, acl_entry, acl_type): def __convert_oc_acl_set_to_user_fmt(acl_set, data): - acl_name = acl_set['name'] - acl_type = __convert_oc_acl_type_to_user_fmt(acl_set['type']) + if 'name' in acl_set: + acl_name = acl_set['name'] + else: + acl_name = acl_set['state']['name'] + if 'type' in acl_set: + acl_type = __convert_oc_acl_type_to_user_fmt(acl_set['type']) + else: + acl_type = __convert_oc_acl_type_to_user_fmt(acl_set['state']['type']) if not data.get(acl_type, None): data[acl_type] = OrderedDict() data[acl_type][acl_name] = OrderedDict() @@ -1123,14 +1142,29 @@ def __handle_get_acl_details_aggregate_mode_response(resp_content, args): acl_type = __convert_oc_acl_type_to_user_fmt(args[0]) data[acl_type] = OrderedDict() - for acl_set in resp_content["openconfig-acl:acl-sets"]["acl-set"]: - if not acl_set['type'].endswith(args[0]): - continue - - __convert_oc_acl_set_to_user_fmt(acl_set, data) + if "openconfig-acl:acl-sets" in resp_content: + resp_content_data = resp_content["openconfig-acl:acl-sets"] + if "acl-set" in resp_content_data: + for acl_set in resp_content_data["acl-set"]: + if not acl_set['type'].endswith(args[0]): + continue + __convert_oc_acl_set_to_user_fmt(acl_set, data) + + if 'openconfig-acl-ext:dynamic-acl-set' in resp_content: + resp_content_data = resp_content['openconfig-acl-ext:dynamic-acl-set'] + if 'acl-sets' in resp_content_data[0]: + for acl_set in resp_content_data[0]['acl-sets']['acl-set']: + __convert_oc_acl_set_to_user_fmt(acl_set, data) else: log.log_debug('Get details for ACL Type {}::{}'.format(args[0], args[1])) - __convert_oc_acl_set_to_user_fmt(resp_content['openconfig-acl:acl-set'][0], data) + if 'openconfig-acl-ext:dynamic-acl-set' in resp_content: + resp_content_data = resp_content['openconfig-acl-ext:dynamic-acl-set'] + if 'acl-sets' in resp_content_data[0]: + for acl_set in resp_content_data[0]['acl-sets']['acl-set']: + if acl_set['state']['name'] == args[1]: + __convert_oc_acl_set_to_user_fmt(acl_set, data) + else: + __convert_oc_acl_set_to_user_fmt(resp_content['openconfig-acl:acl-set'][0], data) log.log_debug(str(data)) show_cli_output('show_access_list.j2', data) @@ -1158,37 +1192,85 @@ def __get_and_show_acl_counters_by_name_and_intf(acl_name, acl_type, intf_name, if response.ok(): log.log_debug(response.content) __convert_oc_acl_set_to_user_fmt(response.content['openconfig-acl:acl-set'][0], output) - temp = OrderedDict() - __deep_copy(temp, output) - if cache: - cache[acl_name] = temp - else: + elif not response.ok() and response.status_code == 404: + keypath = cc.Path('/restconf/data/openconfig-acl:acl/openconfig-acl-ext:dynamic-acl-sets/dynamic-acl-set={acl_type}', acl_type=acl_type) + response = acl_client.get(keypath, depth=None, ignore404=False) + if response.ok(): + log.log_debug(response.content) + if 'acl-sets' in response.content['openconfig-acl-ext:dynamic-acl-set'][0]: + for acl_set in response.content['openconfig-acl-ext:dynamic-acl-set'][0]['acl-sets']['acl-set']: + if acl_set['state']['name'] == acl_name: + __convert_oc_acl_set_to_user_fmt(acl_set, output) + if not response.ok(): log.log_error("Error pulling ACL config for {}:{}".format(acl_name, acl_type)) raise SonicAclCLIError("{}".format(response.error_message())) + + temp = OrderedDict() + __deep_copy(temp, output) + if cache: + cache[acl_name] = temp else: log.log_debug("Cache present") __deep_copy(output, cache[acl_name]) user_acl_type = __convert_oc_acl_type_to_user_fmt(acl_type) output[user_acl_type][acl_name]['stage'] = stage.capitalize() + foundStaticBinding = False if intf_name != "CtrlPlane": keypath = cc.Path('/restconf/data/openconfig-acl:acl/interfaces/interface={id}/{stage}-acl-sets/{stage}-acl-set={setname},{acltype}', id=intf_name, stage=stage.lower(), setname=acl_name, acltype=acl_type) response = acl_client.get(keypath, depth=None, ignore404=False) - if not response.ok(): + + log.log_debug(response.content) + if response.ok(): + acl_set = response.content['openconfig-acl:{}-acl-set'.format(stage.lower())][0] + user_acl_type = __convert_oc_acl_type_to_user_fmt(acl_set['type']) + output[user_acl_type][acl_name]['stage'] = stage.capitalize() + foundStaticBinding = True + for acl_entry in acl_set['acl-entries']['acl-entry']: + try: + # Its possible that the counter may not be present for user configured ACLs when dynamic ACLs are active + output[user_acl_type][acl_name]['rules'][acl_entry['sequence-id']]['packets'] = acl_entry['state']['matched-packets'] + output[user_acl_type][acl_name]['rules'][acl_entry['sequence-id']]['octets'] = acl_entry['state']['matched-octets'] + except KeyError: + pass + elif response.status_code != 404: raise SonicAclCLIError("{}".format(response.error_message())) + if intf_name != "CtrlPlane" and intf_name != "Switch": + keypath = cc.Path('/restconf/data/openconfig-acl:acl/interfaces/interface={id}/openconfig-acl-ext:dynamic-{stage}-acl-sets/dynamic-{stage}-acl-set={acltype}', + id=intf_name, stage=stage.lower(), acltype=acl_type) + response = acl_client.get(keypath, depth=None, ignore404=False) log.log_debug(response.content) - acl_set = response.content['openconfig-acl:{}-acl-set'.format(stage.lower())][0] - user_acl_type = __convert_oc_acl_type_to_user_fmt(acl_set['type']) - output[user_acl_type][acl_name]['stage'] = stage.capitalize() - for acl_entry in acl_set['acl-entries']['acl-entry']: - try: - # Its possible that the counter may not be present for user configured ACLs when dynamic ACLs are active - output[user_acl_type][acl_name]['rules'][acl_entry['sequence-id']]['packets'] = acl_entry['state']['matched-packets'] - output[user_acl_type][acl_name]['rules'][acl_entry['sequence-id']]['octets'] = acl_entry['state']['matched-octets'] - except KeyError: - pass + if response.ok(): + acl_stage_set = response.content['openconfig-acl-ext:dynamic-{}-acl-set'.format(stage.lower())][0] + acl_set = {} + if '{}-acl-sets'.format(stage.lower()) in acl_stage_set: + for acl_stage_set_data in acl_stage_set['{}-acl-sets'.format(stage.lower())]['{}-acl-set'.format(stage.lower())]: + if acl_stage_set_data['state']['set-name'] == acl_name: + acl_set = acl_stage_set_data + break + if 'type' in acl_set: + user_acl_type = __convert_oc_acl_type_to_user_fmt(acl_set['type']) + elif 'state' in acl_set: + user_acl_type = __convert_oc_acl_type_to_user_fmt(acl_set['state']['type']) + output[user_acl_type][acl_name]['stage'] = stage.capitalize() + if 'acl-entries' in acl_set: + for acl_entry in acl_set['acl-entries']['acl-entry']: + sequence_id = acl_entry['state']['sequence-id'] + try: + if 'packets' not in output[user_acl_type][acl_name]['rules'][sequence_id]: + output[user_acl_type][acl_name]['rules'][sequence_id]['packets'] = acl_entry['state']['matched-packets'] + else: + output[user_acl_type][acl_name]['rules'][sequence_id]['packets'] = str(int(output[user_acl_type][acl_name]['rules'][sequence_id]['packets']) + int(acl_entry['state']['matched-packets'])) + if 'octets' not in output[user_acl_type][acl_name]['rules'][sequence_id]: + output[user_acl_type][acl_name]['rules'][sequence_id]['octets'] = acl_entry['state']['matched-octets'] + else: + output[user_acl_type][acl_name]['rules'][sequence_id]['octets'] = str(int(output[user_acl_type][acl_name]['rules'][sequence_id]['octets']) + int(acl_entry['state']['matched-octets'])) + except KeyError: + pass + elif not foundStaticBinding: + raise SonicAclCLIError("{}".format(response.error_message())) render_dict = dict() if intf_name != "Switch": @@ -1236,11 +1318,11 @@ def __process_acl_counters_request_by_name_and_inf(response, args): portBindingData = __get_port_binding_table_data(acl_type) stage = acl_data.get("stage", "INGRESS") - ports = [] + ports = set() if "ports" in acl_data: - ports = acl_data["ports"] - elif acl_data["aclname"] in portBindingData: - ports = portBindingData[acl_data["aclname"]] + ports.update(acl_data["ports"]) + if acl_data["aclname"] in portBindingData: + ports.update(portBindingData[acl_data["aclname"]]) intf_name = args[2] if len(args) == 3 else "".join(args[3:]) if intf_name in ports: @@ -1271,11 +1353,11 @@ def __process_acl_counters_request_by_type_and_name(response, args): portBindingData = __get_port_binding_table_data(acl_type) cache = dict() stage = acl_data.get("stage", "INGRESS") - ports = [] + ports = set() if "ports" in acl_data: - ports = acl_data["ports"] - elif acl_data["aclname"] in portBindingData: - ports = portBindingData[acl_data["aclname"]] + ports.update(acl_data["ports"]) + if acl_data["aclname"] in portBindingData: + ports.update(portBindingData[acl_data["aclname"]]) if len(ports) != 0: # Sort and show @@ -1289,6 +1371,9 @@ def __process_acl_counters_request_by_type_and_name(response, args): log.log_debug("ACL {} Type {} has ZERO ports. Show only ACL configuration.".format(acl_data["aclname"], acl_type)) keypath = cc.Path('/restconf/data/openconfig-acl:acl/acl-sets/acl-set={name},{acl_type}', name=args[1], acl_type=args[0]) response = acl_client.get(keypath, depth=None, ignore404=False) + if not response.ok() and response.status_code == 404: + keypath = cc.Path('/restconf/data/openconfig-acl:acl/openconfig-acl-ext:dynamic-acl-sets/dynamic-acl-set={acl_type}', acl_type=args[0]) + response = acl_client.get(keypath, depth=None, ignore404=False) if response.ok(): __handle_get_acl_details_aggregate_mode_response(response.content, args) else: @@ -1318,11 +1403,11 @@ def __process_acl_counters_request_by_type(response, args): cache = dict() for acl in filtered_acls: stage = acl.get("stage", "INGRESS") - ports = [] + ports = set() if "ports" in acl: - ports = acl["ports"] - elif acl["aclname"] in portBindingData: - ports = portBindingData[acl["aclname"]] + ports.update(acl["ports"]) + if acl["aclname"] in portBindingData: + ports.update(portBindingData[acl["aclname"]]) if len(ports) != 0: # Sort and show @@ -1338,6 +1423,9 @@ def __process_acl_counters_request_by_type(response, args): acl_name = acl["aclname"].replace("_" + args[0], "") keypath = cc.Path('/restconf/data/openconfig-acl:acl/acl-sets/acl-set={name},{acl_type}', name=acl_name, acl_type=args[0]) response = acl_client.get(keypath, depth=None, ignore404=False) + if not response.ok() and response.status_code == 404: + keypath = cc.Path('/restconf/data/openconfig-acl:acl/openconfig-acl-ext:dynamic-acl-sets/dynamic-acl-set={acl_type}', acl_type=args[0]) + response = acl_client.get(keypath, depth=None, ignore404=False) if response.ok(): __handle_get_acl_details_aggregate_mode_response(response.content, [args[0], acl_name]) else: @@ -1358,7 +1446,7 @@ def handle_get_acl_details_response(response, op_str, args): if response.ok(): if response.counter_mode == 'AGGREGATE_ONLY': __handle_get_acl_details_aggregate_mode_response(response.content, args) - else: + else: if bool(response.content): __handle_get_acl_details_interface_mode_response(response.content, args) elif len(args) > 1: From 89345b2fe6a97cc72bdb1b1be763fca7328fd264 Mon Sep 17 00:00:00 2001 From: Prasanth Kunjum Veettil Date: Wed, 27 Apr 2022 02:46:46 -0700 Subject: [PATCH 22/31] [JIRA SONIC-60591] DPB should not display message as an error when trying to breakout an already broken port. Signed-off-by: Prasanth Kunjum Veettil Change-Id: I8d22bef4a3af974242269c063f8a4faaef7ebc76 --- CLI/actioner/sonic_cli_breakout.py | 66 +++++++++++++----------------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/CLI/actioner/sonic_cli_breakout.py b/CLI/actioner/sonic_cli_breakout.py index 7c4647182c..c574cc0c4e 100644 --- a/CLI/actioner/sonic_cli_breakout.py +++ b/CLI/actioner/sonic_cli_breakout.py @@ -82,21 +82,14 @@ def invoke(func, args): "num-physical-channels": 0 } } - '''body = { - "openconfig-platform:components": { - "component": [{ - "name": interface, - "port": { - "openconfig-platform-port:breakout-mode": { - "config": { - "num-channels": int(args[1][0]), - "channel-speed": speed_map.get(args[1]), - } - } - }, - }] - } - }''' + get_resp = aa.get(path) + if get_resp.ok() and get_resp.content: + if 'openconfig-platform-port:config' in get_resp.content: + cfg = get_resp.content['openconfig-platform-port:config'] + if 'breakout-speed' in cfg: + if speed_map.get(args[1]) in cfg['breakout-speed'] and int(args[1][0]) is cfg['num-breakouts']: + print("No change in port breakout mode") + return None return aa.patch(path, body) elif (func == @@ -110,11 +103,9 @@ def invoke(func, args): get_resp = aa.get(path) if get_resp.ok() and get_resp.content: return aa.delete(path) - elif get_resp.error_message(): - return get_resp else: - return aa._make_error_response( - "%Error: No change in port breakout mode") + print("No change in port breakout mode") + return None else: path = cc.Path( "/restconf/data/sonic-port-breakout:sonic-port-breakout") @@ -166,25 +157,26 @@ def invoke(func, args): def run(func, args): try: api_response = invoke(func, args) - if api_response.ok(): - if (func.find( + if api_response is not None: + if api_response.ok(): + if (func.find( "openconfig_platform_port_components_component_port_breakout_mode_config" - ) != -1): - print(( - "Dynamic Port Breakout in-progress, use 'show interface breakout port {}' to check status." - .format(args[0]))) - if api_response.content is not None: - if func == "dependencies": - temp = args[1] - elif func == "modes": - temp = args[1] - elif len(args) > 3: - temp = args[3] - else: - temp = args[1] - show_cli_output(temp, api_response.content) - elif api_response.error_message(): - print((api_response.error_message())) + ) != -1): + print(( + "Dynamic Port Breakout in-progress, use 'show interface breakout port {}' to check status." + .format(args[0]))) + if api_response.content is not None: + if func == "dependencies": + temp = args[1] + elif func == "modes": + temp = args[1] + elif len(args) > 3: + temp = args[3] + else: + temp = args[1] + show_cli_output(temp, api_response.content) + elif api_response.error_message(): + print((api_response.error_message())) except Exception as ex: print(ex) From 65052606fc085efa8cf3a795a8dc902e2b894d27 Mon Sep 17 00:00:00 2001 From: Tapash Das Date: Thu, 28 Apr 2022 06:26:09 -0700 Subject: [PATCH 23/31] [JIRA SONIC-61010] management framework code is optimized to not sort the json data. This exposed one issue with the show CLI implementation. Change-Id: Ib127753015a960092293c36125af7677375c576b --- CLI/actioner/sonic_cli_vxlan.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CLI/actioner/sonic_cli_vxlan.py b/CLI/actioner/sonic_cli_vxlan.py index c8f656b15b..0d25de9280 100755 --- a/CLI/actioner/sonic_cli_vxlan.py +++ b/CLI/actioner/sonic_cli_vxlan.py @@ -674,11 +674,12 @@ def vxlan_show_vxlan_vrfvnimap(args): elif response is not None: if len(response) != 0: if args[1] == "show": - vrf_list = response["sonic-vrf:VRF_LIST"][0] - for iter in vrf_list: - iter_len = len(iter) - if iter_len == 3: + vrf_list = response["sonic-vrf:VRF_LIST"] + for vrf in vrf_list: + if "vni" in vrf: + response["sonic-vrf:VRF_LIST"] = sorted(vrf_list, key=lambda x: x['vrf_name']) show_cli_output(args[0], response) + break elif args[1] == "count": vrf_map_count = 0 if "sonic-vrf:VRF_LIST" in response: From a908b1b87263bb9740dc2b7d7d93e6af85ec9ea0 Mon Sep 17 00:00:00 2001 From: Senthil Kumar Bhava Dhamodharan Date: Thu, 5 May 2022 15:05:42 -0700 Subject: [PATCH 24/31] [JIRA SONIC-61053] Sorting vxlan klish output Sorted klish output for following vxlan commands show vxlan vrfvnimap show vxlan vlanvnimap show vxlan remote mac show vxlan remote vni UT: Verified klish commands. Change-Id: I5f90e7c8577f9cd79ce74b3bf343efecee6dc4c5 --- CLI/actioner/sonic_cli_vxlan.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CLI/actioner/sonic_cli_vxlan.py b/CLI/actioner/sonic_cli_vxlan.py index 0d25de9280..42a2a2bd32 100755 --- a/CLI/actioner/sonic_cli_vxlan.py +++ b/CLI/actioner/sonic_cli_vxlan.py @@ -23,6 +23,7 @@ from rpipe_utils import pipestr from scripts.render_cli import show_cli_output import sonic_intf_utils as ifutils +from natsort import natsorted # vxlan_global_info = [] def is_vlan_vni_mapping_exists(): @@ -649,6 +650,9 @@ def vxlan_show_vxlan_vlanvnimap(args): elif response is not None: if args[1] == "show": if len(response) != 0: + vlan_vni_map_list = response[ + "sonic-vxlan:VXLAN_TUNNEL_MAP_LIST"] + response["sonic-vxlan:VXLAN_TUNNEL_MAP_LIST"] = natsorted(vlan_vni_map_list, key=lambda x: x['vlan']) show_cli_output(args[0], response) elif args[1] == "count": if "sonic-vxlan:VXLAN_TUNNEL_MAP_LIST" in response: @@ -677,7 +681,7 @@ def vxlan_show_vxlan_vrfvnimap(args): vrf_list = response["sonic-vrf:VRF_LIST"] for vrf in vrf_list: if "vni" in vrf: - response["sonic-vrf:VRF_LIST"] = sorted(vrf_list, key=lambda x: x['vrf_name']) + response["sonic-vrf:VRF_LIST"] = natsorted(vrf_list, key=lambda x: x['vrf_name']) show_cli_output(args[0], response) break elif args[1] == "count": @@ -747,6 +751,9 @@ def vxlan_show_vxlan_remote_vni(args): else: index = index + 1 if args[1] == "show": + remote_map_list = response[ + "sonic-vxlan:VXLAN_REMOTE_VNI_TABLE_LIST"] + response["sonic-vxlan:VXLAN_REMOTE_VNI_TABLE_LIST"] = natsorted(remote_map_list, key=lambda x: x['vlan']) show_cli_output(args[0], response) elif args[1] == "count": if "sonic-vxlan:VXLAN_REMOTE_VNI_TABLE_LIST" in response: @@ -783,6 +790,9 @@ def vxlan_show_vxlan_remote_mac(args): else: index = index + 1 if args[1] == "show": + vxlan_fdb_list = response[ + "sonic-vxlan:VXLAN_FDB_TABLE_LIST"] + response["sonic-vxlan:VXLAN_FDB_TABLE_LIST"] = natsorted(vxlan_fdb_list, key=lambda x: x['vlan']) show_cli_output(args[0], response) elif args[1] == "count": if "sonic-vxlan:VXLAN_FDB_TABLE_LIST" in response: From cc955669662d3d80f349221cb27db74d39fee1e7 Mon Sep 17 00:00:00 2001 From: sabarivel sakthivel Date: Fri, 6 May 2022 15:33:58 -0700 Subject: [PATCH 25/31] [JIRA SONIC-57454] added klish command to show mclag remote macs Change-Id: Iaab2ecd5f49d4ff6d02d32246e034c2e401c9774 --- CLI/actioner/sonic_cli_mclag.py | 57 +++++++++++++++++++ CLI/clitree/cli-xml/mclag.xml | 44 ++++++++++++++ .../templates/show_mclag_remote_mac.j2 | 17 ++++++ 3 files changed, 118 insertions(+) create mode 100644 CLI/renderer/templates/show_mclag_remote_mac.j2 diff --git a/CLI/actioner/sonic_cli_mclag.py b/CLI/actioner/sonic_cli_mclag.py index 92158e748c..54ff1bcaa9 100755 --- a/CLI/actioner/sonic_cli_mclag.py +++ b/CLI/actioner/sonic_cli_mclag.py @@ -23,6 +23,7 @@ import cli_client as cc from rpipe_utils import pipestr from scripts.render_cli import show_cli_output +from natsort import natsorted MCLAG_DEFAULT_DELAY_RESTORE = 300 @@ -405,6 +406,14 @@ def invoke(func, args): ) return aa.get(keypath) + if func == "get_sonic_mclag_sonic_mclag_mclag_fdb_table_mclag_fdb_table_list": + keypath = cc.Path( + "/restconf/data/sonic-mclag:sonic-mclag/MCLAG_FDB_TABLE/MCLAG_FDB_TABLE_LIST" + ) + return aa.get(keypath) + + + ####################################### # Get APIs - END ####################################### @@ -733,6 +742,51 @@ def mclag_show_mclag_peer_gateway(args): return +# show mclag remote mac command +def mclag_show_mclag_remote_mac(args): + mclag_iface_info = {} + + api_response = invoke( + "get_sonic_mclag_sonic_mclag_mclag_fdb_table_mclag_fdb_table_list", + args[1:]) + arg_length = len(args) + if api_response.ok(): + response = api_response.content + if response is None: + print("no mclag fdb entries") + elif len(response) != 0 and "sonic-mclag:MCLAG_FDB_TABLE_LIST" in response: + index = 0 + while index < len(response["sonic-mclag:MCLAG_FDB_TABLE_LIST"]): + iter = response["sonic-mclag:MCLAG_FDB_TABLE_LIST"][index] + if iter["mac_type"] != "remote": + response["sonic-mclag:MCLAG_FDB_TABLE_LIST"].pop(index) + if arg_length > 2 and args[2] != iter["vlan"] and args[2] != iter["port"]: + response["sonic-mclag:MCLAG_FDB_TABLE_LIST"].pop(index) + else: + index = index + 1 + if args[1] == "show": + mclag_fdb_list = response[ + "sonic-mclag:MCLAG_FDB_TABLE_LIST"] + response["sonic-mclag:MCLAG_FDB_TABLE_LIST"] = natsorted(mclag_fdb_list, key=lambda x: x['vlan']) + show_cli_output(args[0], response) + elif args[1] == "count": + if "sonic-mclag:MCLAG_FDB_TABLE_LIST" in response: + remote_mac_list = response[ + "sonic-mclag:MCLAG_FDB_TABLE_LIST"] + remote_mac_list_len = str(len(remote_mac_list)) + print(("Total Count: " + remote_mac_list_len)) + else: + print("Total Count: 0") + else: + if api_response.status_code != 404: + print((api_response.error_message())) + else: + print(("No MCLAG remote mac ")) + + + return + + def run(func, args): @@ -751,6 +805,9 @@ def run(func, args): if func == "show_mclag_peer_gateway": mclag_show_mclag_peer_gateway(args) return 0 + if func == "show_mclag_remote_mac": + mclag_show_mclag_remote_mac(args) + return 0 except Exception as e: print((sys.exc_info()[1])) diff --git a/CLI/clitree/cli-xml/mclag.xml b/CLI/clitree/cli-xml/mclag.xml index 2a0023cb77..d8b5d3ba51 100644 --- a/CLI/clitree/cli-xml/mclag.xml +++ b/CLI/clitree/cli-xml/mclag.xml @@ -333,7 +333,51 @@ session-timeout 30 + + + + + + + + + + sonic_cli_mclag show_mclag_remote_mac show_mclag_remote_mac.j2 show ${__params} + + + Show command to display MCLAG remote mac count + + + sonic-cli# show mclag mac remote + + + sonic-cli# show mclag mac remote + ======================================================================== + Vlan Mac Port Type + ======================================================================== + Vlan300 00:0a:0a:0a:0a:0a PortChannel10 static + Vlan400 00:0a:0a:0a:0b:0b Ethernet12 dynamic + Vlan400 00:0a:0a:0a:0a:0a PortChannel101 dynamic + + + + + + sonic_cli_mclag show_mclag_remote_mac show_mclag_remote_mac.j2 count + + + Show command to display MCLAG remote mac count + + + sonic-cli# show mclag remote mac count + + + sonic-cli# show mclag remote mac count + + + + diff --git a/CLI/renderer/templates/show_mclag_remote_mac.j2 b/CLI/renderer/templates/show_mclag_remote_mac.j2 new file mode 100644 index 0000000000..8c3183c5d0 --- /dev/null +++ b/CLI/renderer/templates/show_mclag_remote_mac.j2 @@ -0,0 +1,17 @@ +{% set count = namespace(value=0) %} + +{{"========================================================================"}} +{{"Vlan".center(9)}} {{"Mac".center(20)}} {{"Port".center(15)}} {{"Type".center(9)}} +{{"========================================================================"}} + +{% if json_output %} + +{% for fdblist in json_output['sonic-mclag:MCLAG_FDB_TABLE_LIST']%} +{{fdblist['vlan'].center(9)}} {{fdblist['mac_addr'].center(20)}} {{fdblist['port'].center(15)}} {{fdblist['type'].center(9)}} +{% set count.value = count.value + 1 %} +{% endfor %} + +{{'\nTotal count : %4s\n'|format(count.value)}} +{% endif %} + + From f42671c4519692e8088886a58741baad78089f8c Mon Sep 17 00:00:00 2001 From: sabarivel sakthivel Date: Fri, 6 May 2022 17:45:31 -0700 Subject: [PATCH 26/31] [JIRA SONIC-57711] added show configuration support for link state track group Change-Id: If29c6d00aa8d5fc32b91c2c92bcb5a04d254747d --- CLI/clitree/cli-xml/link_track.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CLI/clitree/cli-xml/link_track.xml b/CLI/clitree/cli-xml/link_track.xml index c2a9f3adec..09f6cf5c7c 100644 --- a/CLI/clitree/cli-xml/link_track.xml +++ b/CLI/clitree/cli-xml/link_track.xml @@ -73,6 +73,27 @@ limitations under the License. + + sonic_cli_show_config show_view views=configure-link-state-track view_keys="group=${grp-name}" + + + Sh + + + Use this command to display running configurations within current link state track group + + + sonic(config-link-track)# show configuration + link state track track1 + description mlag + downstream all-mclag + threshold type percentage up 10 + + + + + + From 2ae2140313a2526eb5dab935349cd9f5b5825528 Mon Sep 17 00:00:00 2001 From: Ramachandran Sathianandan Date: Mon, 14 Mar 2022 21:39:15 -0700 Subject: [PATCH 27/31] [JIRA SONIC-60508] : OSPFv2 GR CLI changes Change-Id: I19b5e7dd1144f88095a5d2fda0831f1cde9f3980 --- CLI/actioner/show_config_ospfv2.py | 104 +++++++++ CLI/actioner/sonic_cli_ospfv2.py | 204 ++++++++++++++++++ CLI/actioner/sonic_cli_show_ospfv2.py | 157 +++++++++++++- CLI/clitree/cli-xml/ospf.xml | 150 ++++++++++++- .../show_ip_ospf_graceful_restart_helper.j2 | 84 ++++++++ .../templates/show_ip_ospf_neighbor_detail.j2 | 14 ++ 6 files changed, 706 insertions(+), 7 deletions(-) create mode 100644 CLI/renderer/templates/show_ip_ospf_graceful_restart_helper.j2 diff --git a/CLI/actioner/show_config_ospfv2.py b/CLI/actioner/show_config_ospfv2.py index d1f674e01c..408ea3946b 100644 --- a/CLI/actioner/show_config_ospfv2.py +++ b/CLI/actioner/show_config_ospfv2.py @@ -127,6 +127,14 @@ def show_router_ospf_config(render_tables): match_value=match_value, ) + match_value = {True: ""} + cmd_str += ospf_generate_command( + tbl_rec, + "opaque-lsa-capability", + " capability opaque", + match_value=match_value, + ) + cmd_str += ospf_generate_command(tbl_rec, "default-metric", " default-metric") @@ -240,6 +248,41 @@ def show_router_ospf_config(render_tables): cmd_str += ospf_generate_command(tbl_rec, "write-multiplier", " write-multiplier") + gr_p, _, gr_v = ospf_get_key_value( + tbl_rec, "gr-enabled") + grgp_p, _, grgp_v = ospf_get_key_value( + tbl_rec, "gr-grace-period") + if gr_p == True: + cmd_str += " graceful-restart" + if grgp_p == True: + cmd_str += " grace-period {}".format(grgp_v) + cmd_str += cmd_end + + match_value = {True: ""} + cmd_str += ospf_generate_command(tbl_rec, "gr-helper-only", + " graceful-restart helper enable", + match_value=match_value) + + grhelperlsa_p, _, grhelperlsa_v = ospf_get_key_value( + tbl_rec, "gr-helper-strict-lsa-checking") + if grhelperlsa_p == True and grhelperlsa_v == False: + match_value = {False: ""} + cmd_str += ospf_generate_command(tbl_rec, "gr-helper-strict-lsa-checking", + " no graceful-restart helper strict-lsa-checking", + match_value=match_value) + + match_value = {True: ""} + cmd_str += ospf_generate_command(tbl_rec, "gr-helper-planned-only", + " graceful-restart helper planned-only", + match_value=match_value) + + cmd_str += ospf_generate_command(tbl_rec, "gr-helper-supported-grace-time", + " graceful-restart helper supported-grace-time") + + status, gr_nbr_cmd = show_router_ospf_gr_helper_neighbor_config( + render_tables) + if status == "CB_SUCCESS": + cmd_str += gr_nbr_cmd status, sub_cmd_str = show_router_ospf_distribute_route_config( render_tables) @@ -807,6 +850,67 @@ def show_router_ospf_area_addr_range_config(render_tables): "show_router_ospf_area_addr_range_config: cmd_str {}".format(cmd_str)) return "CB_SUCCESS", cmd_str +def show_router_ospf_gr_helper_neighbor_config(render_tables): + cmd_str = "" + cmd_end = " ;" + + #ospf_debug_log_enable() + #ospf_debug_log_disable() + ospf_debug_print( + "show_router_ospf_gr_helper_neighbor_config: render_tables {}".format( + render_tables)) + + present, name, vrf_name = ospf_get_key_value(render_tables, "vrf-name") + if present == False: + ospf_debug_print( + "show_router_ospf_gr_helper_neighbor_config: vrf_name not present") + return "CB_SUCCESS", cmd_str + + ospf_debug_print( + "show_router_ospf_gr_helper_neighbor_config: vrf_name {} present".format( + vrf_name)) + tbl_path = "sonic-ospfv2:sonic-ospfv2/OSPFV2_ROUTER_GR_NEIGHBOUR_HELPER/OSPFV2_ROUTER_GR_NEIGHBOUR_HELPER_LIST" + present, tbl_path, tbl_rec_list = ospf_get_key_value( + render_tables, tbl_path) + if present == False: + ospf_debug_print( + "show_router_ospf_gr_helper_neighbor_config: OSPFV2_ROUTER_GR_NEIGHBOUR_HELPER not present" + ) + return "CB_SUCCESS", cmd_str + + ospf_debug_print( + "show_router_ospf_gr_helper_neighbor_config: OSPFV2_ROUTER_GR_NEIGHBOUR_HELPER {}".format( + tbl_rec_list)) + + if not isinstance(tbl_rec_list, list): + tbl_rec_list = [tbl_rec_list] + + for tbl_rec in tbl_rec_list: + vrf_p, _, vrf_v = ospf_get_key_value(tbl_rec, "vrf_name") + if vrf_p == False: + ospf_debug_print( + "show_router_ospf_gr_helper_neighbor_config : vrf_name field not present" + ) + continue + + if vrf_v != vrf_name: + ospf_debug_print( + "show_router_ospf_gr_helper_neighbor_config : vrf names {} != {}". + format(vrf_v, vrf_name)) + continue + + nbr_p, _, nbr_v = ospf_get_key_value(tbl_rec, "neighbour-id") + if nbr_p == False: + ospf_debug_print( + "show_router_ospf_gr_helper_neighbor_config: neighbour-id field not present" + ) + continue + cmd_str += " graceful-restart helper enable {}".format(nbr_v) + cmd_str += cmd_end + + ospf_debug_print( + "show_router_ospf_gr_helper_neighbor_config: cmd_str {}".format(cmd_str)) + return "CB_SUCCESS", cmd_str def show_router_ospf_distribute_route_config(render_tables): cmd_str = "" diff --git a/CLI/actioner/sonic_cli_ospfv2.py b/CLI/actioner/sonic_cli_ospfv2.py index 1bc98b75de..fd09f4421f 100644 --- a/CLI/actioner/sonic_cli_ospfv2.py +++ b/CLI/actioner/sonic_cli_ospfv2.py @@ -973,6 +973,22 @@ def invoke_api(func, args=[]): api, args[0], "config/openconfig-ospfv2-ext:ospf-rfc1583-compatible") + elif (func == + "patch_openconfig_ospfv2_ext_network_instances_network_instance_protocols_protocol_ospfv2_global_config_opaque_lsa_capability" + ): + body = { + "config": { + "openconfig-ospfv2-ext:opaque-lsa-capability": True + } + } + return patch_ospf_router_global_config(api, args[0], body) + + elif (func == + "delete_openconfig_ospfv2_ext_network_instances_network_instance_protocols_protocol_ospfv2_global_config_opaque_lsa_capability" + ): + return delete_ospf_router_global_config( + api, args[0], + "config/openconfig-ospfv2-ext:opaque-lsa-capability") elif (func == "patch_openconfig_network_instance_network_instances_network_instance_protocols_protocol_config_default_metric" ): @@ -1044,6 +1060,176 @@ def invoke_api(func, args=[]): return patch_ospf_router_passive_interface_config( api, args[0], args[1], args[2], body) + elif (func == + "patch_openconfig_network_instance_network_instances_network_instance_protocols_protocol_ospfv2_global_graceful_restart_config" + ): + vrf = args[0] + gr = False + grgraceperiod = "" + grtype = "" + helperoption = "" + grhelper = False + grhelpernbr = "" + grhelperlsa = False + grhelperplanned = False + grhelpersupportedgracetime = "" + grhelperoptions = False + + #print("gr-config: argslen {} args={}".format(len(args), args)) + + #i = 0 + #for arg in args: + #print(i, args[i]) + #i = i + 1 + + i = 0 + for arg in args: + if arg == "graceful-restart": + gr = True + if (len(args) > 2): + grtype = args[i + 1] + if grtype == "helper": + helperoption = args[i+2] + if helperoption == "enable": + grhelper = True + if (len(args) > 4): + grhelpernbr = args[i+3] + elif helperoption == "strict-lsa-checking": + grhelperoptions = True + grhelperlsa = True + elif helperoption == "planned-only": + grhelperoptions = True + grhelperplanned = True + elif helperoption == "supported-grace-time": + grhelperoptions = True + grhelpersupportedgracetime = args[i+3] + elif grtype == "grace-period": + grgraceperiod = args[i+2] + i = i + 1 + # Graceful-Restart enabled + if gr == True and grhelper != True and grhelperoptions != True: + body = { + "openconfig-network-instance:graceful-restart": { + "config": { "enabled": True} + } + } + if grgraceperiod != "": + body = { + "openconfig-network-instance:graceful-restart": { + "config": { "enabled": True, + "openconfig-ospfv2-ext:grace-period": int(grgraceperiod)} + } + } + return patch_ospf_router_global_config(api, args[0], body) + # Graceful-Restart helper for all neighbors + elif grhelper == True and grhelpernbr == "": + body = { + "openconfig-network-instance:graceful-restart": { + "config": { "helper-only": True} + } + } + return patch_ospf_router_global_config(api, args[0], body) + # Graceful-Restart helper for a specific neighbor + elif grhelper == True and grhelpernbr != "": + body = { + "openconfig-network-instance:graceful-restart": { + "openconfig-ospfv2-ext:helpers": { + "helper": [ + { + "neighbour-id": grhelpernbr, + "config": { + "vrf-name": vrf, + "neighbour-id": grhelpernbr, + "enabled": True + } + } + ] + } + } + } + return patch_ospf_router_global_config(api, args[0], body) + # Graceful-Restart helper options + elif grhelperoptions == True: + body = {"openconfig-network-instance:graceful-restart": {"config": {}}} + if grhelperlsa == True: + body["openconfig-network-instance:graceful-restart"].update({"config":{"openconfig-ospfv2-ext:strict-lsa-checking": True}}) + if grhelperplanned == True: + body["openconfig-network-instance:graceful-restart"].update({"config":{"openconfig-ospfv2-ext:planned-only": True}}) + if grhelpersupportedgracetime != "": + body["openconfig-network-instance:graceful-restart"].update({"config":{"openconfig-ospfv2-ext:supported-grace-time": int(grhelpersupportedgracetime)}}) + return patch_ospf_router_global_config(api, args[0], body) + + elif (func == + "delete_openconfig_network_instance_network_instances_network_instance_protocols_protocol_ospfv2_global_graceful_restart_config" + ): + vrf = args[0] + gr = False + grgraceperiod = False + grtype = "" + helperoption = "" + grhelper = False + grhelpernbr = "" + grhelperlsa = False + grhelperplanned = False + grhelpersupportedgracetime = False + grhelperoptions = False + + #print("gr-config(delete): argslen {} args={}".format(len(args), args)) + + #i = 0 + #for arg in args: + #print(i, args[i]) + #i = i + 1 + + i = 0 + for arg in args: + if arg == "graceful-restart": + gr = True + if (len(args) > 3): + grtype = args[i + 1] + if grtype == "helper": + helperoption = args[i+2] + if helperoption == "enable": + grhelper = True + if (len(args) > 5): + grhelpernbr = args[i+3] + elif helperoption == "strict-lsa-checking": + grhelperoptions = True + grhelperlsa = True + elif helperoption == "planned-only": + grhelperoptions = True + grhelperplanned = True + elif helperoption == "supported-grace-time": + grhelperoptions = True + grhelpersupportedgracetime = True + elif grtype == "grace-period": + grgraceperiod = True + i = i + 1 + if gr == True and grhelper != True and grhelperoptions != True: + response = delete_ospf_router_global_config( + api, args[0], "graceful-restart/config/enabled") + if response.ok() == False: + return response + return delete_ospf_router_global_config( + api, args[0], "graceful-restart/config/openconfig-ospfv2-ext:grace-period") + elif grhelper == True and grhelpernbr == "": + return delete_ospf_router_global_config( + api, args[0], "graceful-restart/config/helper-only") + elif grhelper == True and grhelpernbr != "": + ospf_gr_helper_nbr_uri = "graceful-restart/openconfig-ospfv2-ext:helpers/helper={}".format(grhelpernbr) + return delete_ospf_router_global_config( + api, args[0], ospf_gr_helper_nbr_uri) + elif grhelperoptions == True: + if grhelperlsa == True: + body = {"openconfig-network-instance:graceful-restart": {"config": {"openconfig-ospfv2-ext:strict-lsa-checking": False}}} + return patch_ospf_router_global_config(api, args[0], body) + if grhelperplanned == True: + return delete_ospf_router_global_config( + api, args[0], "graceful-restart/config/openconfig-ospfv2-ext:planned-only") + if grhelpersupportedgracetime == True: + return delete_ospf_router_global_config( + api, args[0], "graceful-restart/config/openconfig-ospfv2-ext:supported-grace-time") + elif (func == "patch_openconfig_network_instance_network_instances_network_instance_protocols_protocol_ospfv2_global_timers" ): @@ -2776,10 +2962,28 @@ def show_ospf_running_config(args): pipestr().write(full_cmd.split()) write(ospf_all_cfgs.replace("\t", "\n")) +def gr_prepare_ospf_api(args): + api = cc.ApiClient() + keypath = [] + body = None + + keypath = cc.Path("/restconf/operations/sonic-graceful-restart-prepare:gr-prepare") + + body = {"sonic-graceful-restart-prepare:input": {"ospfv2": True}} + + return api.post(keypath, body) def run(func, args): if func == "show_running_config_ospf": show_ospf_running_config(args) + elif func == "gr_prepare_ospf": + response = gr_prepare_ospf_api(args) + if response.ok(): + if response.content is not None: + api_response = response.content + if api_response is None: + print("Failed") + sys.exit(1) else: response = invoke_api(func, args) if response.ok(): diff --git a/CLI/actioner/sonic_cli_show_ospfv2.py b/CLI/actioner/sonic_cli_show_ospfv2.py index 51efa4f0a8..4032c3c992 100644 --- a/CLI/actioner/sonic_cli_show_ospfv2.py +++ b/CLI/actioner/sonic_cli_show_ospfv2.py @@ -478,12 +478,153 @@ def build_area_id_list(vrf_name): print("%Error: Internal error") return output -# The below function checks if the neighbor_name provided by user contains +def build_gr_helper_neighbor_list(vrf_name): + api = cc.ApiClient() + output = [] + tableNeighbour = ( + "/restconf/data/sonic-ospfv2:sonic-ospfv2/OSPFV2_ROUTER_GR_NEIGHBOUR_HELPER/OSPFV2_ROUTER_GR_NEIGHBOUR_HELPER_LIST", + "sonic-ospfv2:OSPFV2_ROUTER_GR_NEIGHBOUR_HELPER_LIST", + "vrf_name", + "neighbour-id", + ) + requests = [tableNeighbour] + for request in requests: + keypath = cc.Path(request[0]) + try: + response = api.get(keypath) + response = response.content + if response is None: + continue + neighbourList = response.get(request[1]) + if neighbourList is None: + continue + for neighbour in neighbourList: + vrfName = neighbour.get(request[2]) + neighbourId = neighbour.get(request[3]) + if neighbourId is None: + continue + if vrfName is None or vrfName != vrf_name: + continue + output.append(neighbourId) + output.sort() + except Exception as e: + log.syslog(log.LOG_ERR, str(e)) + print("%Error: Internal error") + return output + +def generate_show_ip_ospf_graceful_restart_helper(vrf, detail): + api = cc.ApiClient() + keypath = [] + vrfName = "" + vrfName = vrf + d = {} + dlist = [] + det = {} + d = {"vrfName": vrfName} + det = {"flagDetail": True} + dlist.append(d) + keypath = cc.Path( + "/restconf/data/openconfig-network-instance:network-instances/network-instance={name}/protocols/protocol=OSPF,ospfv2/ospfv2/global/state", + name=vrfName, + ) + response = api.get(keypath) + if response.ok(): + if response.content is not None: + # Get Command Output + api_response = response.content + if api_response is None or len(api_response) == 0: + print("% OSPF instance not found") + return + if ("openconfig-network-instance:state" in api_response + and api_response["openconfig-network-instance:state"] is + not None): + api_response[ + "openconfig-network-instance:global_state"] = api_response.pop( + "openconfig-network-instance:state") + dlist.append(api_response) + else: + print("% OSPF instance not found") + return + + keypath = cc.Path( + "/restconf/data/openconfig-network-instance:network-instances/network-instance={name}/protocols/protocol=OSPF,ospfv2/ospfv2/global/graceful-restart/state", + name=vrfName, + ) + response = api.get(keypath) + if response.ok(): + if response.content is not None: + # Get Command Output + api_response = response.content + if ("openconfig-network-instance:state" in api_response + and api_response["openconfig-network-instance:state"] is + not None): + api_response[ + "openconfig-network-instance:graceful_restart_state"] = api_response.pop( + "openconfig-network-instance:state") + dlist.append(api_response) + else: + return + + gr_helper_nbr_list = build_gr_helper_neighbor_list(vrfName) + if len(gr_helper_nbr_list) != 0: + neighbourInfoList = [] + for neighbourId in gr_helper_nbr_list: + neighbourInfoList.append(neighbourId) + neighboursInfo = OrderedDict() + neighboursInfo["openconfig-network-instance:gr_helper_neighbors"] = neighbourInfoList + dlist.append(neighboursInfo) + + area_id_list = [] + area_id_list = build_area_id_list(vrfName) + if len(area_id_list) == 0: + detail = False + + areaInfoList = [] + if detail == True: + for areaId in area_id_list: + areaConfig = OrderedDict() + areaConfig["identifier"] = areaId + areaInfo = OrderedDict() + areaInfo["config"] = areaConfig + areaInfo["identifier"] = areaId + areaInfoList.append(areaInfo) + keypath = cc.Path( + "/restconf/data/openconfig-network-instance:network-instances/network-instance={name}/protocols" + + + "/protocol=OSPF,ospfv2/ospfv2/areas/area={identifier1}/interfaces", + name=vrfName, + identifier1=areaId, + ) + response = api.get(keypath) + if response.ok(): + if response.content is not None: + # Get Command Output + api_response = response.content + if ("openconfig-network-instance:interfaces" in api_response + and + api_response["openconfig-network-instance:interfaces"] + is not None): + areaInfo["interfaces"] = api_response[ + "openconfig-network-instance:interfaces"] + + areasInfo = OrderedDict() + areasInfo["area"] = areaInfoList + areasOuter = OrderedDict() + areasOuter["openconfig-network-instance:areas"] = areasInfo + dlist.append(areasOuter) + + if detail == True: + dlist.append(det) + + show_cli_output("show_ip_ospf_graceful_restart_helper.j2", dlist) + + +# The below function checks if the neighbor_name provided by user contains # interface name like Vlan, Ethernet or portChannel or Loopback interface def check_interface_name(neighbor_name): if 'Eth' in neighbor_name or 'Vlan' in neighbor_name or 'PortChannel' in neighbor_name or 'Loopback' in neighbor_name: return True - return False + return False def invoke_show_api(func, args=[]): vrf = args[0] @@ -549,6 +690,18 @@ def invoke_show_api(func, args=[]): return generate_show_ip_ospf_interfaces( vrf, "show_ip_ospf_neighbor.j2", intfname) + elif arg == "graceful-restart": + j = i + 1 + detail = False + for grargs in args[j:]: + if grargs == "helper": + continue + if grargs == "detail": + detail = True + j = j + 1 + + return generate_show_ip_ospf_graceful_restart_helper(vrf, detail) + elif arg == "interface": j = i trafficcmd = False diff --git a/CLI/clitree/cli-xml/ospf.xml b/CLI/clitree/cli-xml/ospf.xml index 8391ccf001..f769706dc7 100644 --- a/CLI/clitree/cli-xml/ospf.xml +++ b/CLI/clitree/cli-xml/ospf.xml @@ -19,7 +19,7 @@ - + @@ -483,17 +483,39 @@ OSPFv2 - sonic_cli_ospfv2 patch_openconfig_ospfv2_ext_network_instances_network_instance_protocols_protocol_ospfv2_global_config_ospf_rfc1583_compatible ${vrf-name} @@ -528,6 +550,84 @@ + + + + + + + + + + + + + + + + + + + sonic_cli_ospfv2 patch_openconfig_network_instance_network_instances_network_instance_protocols_protocol_ospfv2_global_graceful_restart_config ${vrf-name} ${__full_line} + + + Configures OSPFv2 Graceful Restart. + + + Configure Graceful Restart (RFC 3623). + + + sonic-cli(config-router-ospf)# graceful-restart + sonic-cli(config-router-ospf)# graceful-restart grace-time 120 + sonic-cli(config-router-ospf)# graceful-restart helper enable + sonic-cli(config-router-ospf)# graceful-restart helper enable 192.168.1.1 + sonic-cli(config-router-ospf)# graceful-restart helper strict-lsa-checking + sonic-cli(config-router-ospf)# graceful-restart helper planned-only + sonic-cli(config-router-ospf)# graceful-restart helper supported-grace-time 120 + + OSPFv2 + + + + + + + + + + + + + + + + + + + + + + sonic_cli_ospfv2 delete_openconfig_network_instance_network_instances_network_instance_protocols_protocol_ospfv2_global_graceful_restart_config ${vrf-name} ${__full_line} + + + Unconfigures OSPFv2 Graceful Restart. + + + Unconfigure Graceful Restart (RFC 3623). + + + sonic-cli(config-router-ospf)# no graceful-restart + sonic-cli(config-router-ospf)# no graceful-restart grace-time + sonic-cli(config-router-ospf)# no graceful-restart helper enable + sonic-cli(config-router-ospf)# no graceful-restart helper enable 192.168.1.1 + sonic-cli(config-router-ospf)# no graceful-restart helper strict-lsa-checking + sonic-cli(config-router-ospf)# no graceful-restart helper planned-only + sonic-cli(config-router-ospf)# no graceful-restart helper supported-grace-time + + OSPFv2 + + + @@ -1385,6 +1485,27 @@ + + + + + + + + sonic_cli_ospfv2 gr_prepare_ospf ${__full_line} + + + OSPFv2 GR + + + OSPFv2 GR + + + sonic# graceful-restart prepare ip ospf + + + + @@ -1557,6 +1678,14 @@ + + + + + + + + sonic_cli_show_ospfv2 show_ospf_cmds ${vrf-name} ${__full_line} @@ -2051,6 +2180,16 @@ VRF Name: default Attached Router: 2.2.2.2 + + sonic# show ip ospf graceful-restart helper detail + + OSPF Router with ID (192.168.10.2) + + Graceful restart helper support enabled. + Strict LSA check is enabled. + Helper supported for planned restarts only. + Supported Graceful restart interval: 500(in seconds). + OSPFv2 @@ -2065,6 +2204,7 @@ VRF Name: default --> + diff --git a/CLI/renderer/templates/show_ip_ospf_graceful_restart_helper.j2 b/CLI/renderer/templates/show_ip_ospf_graceful_restart_helper.j2 new file mode 100644 index 0000000000..a76edc1f2c --- /dev/null +++ b/CLI/renderer/templates/show_ip_ospf_graceful_restart_helper.j2 @@ -0,0 +1,84 @@ +{% set vars = {'vrfname': "False", 'helper': "Graceful restart helper support disabled.", 'planned_only': "Helper supported for Planned and Unplanned Restarts.", 'strict_lsa_checking': "Strict LSA check is enabled.", 'supported_interval': "1800", 'neighbors': "", 'detail': "False", 'active_restart_count': "0", 'last_exit_reason':"", 'cnt': 0} %} +{% set ospf_global_list = json_output %} +{% for list_elem in ospf_global_list %} + {% if 'vrfName' in list_elem %} + {% if vars.update({'vrfname':list_elem['vrfName']}) %}{% endif -%} + {% endif -%} + {% if 'vrfName' in list_elem and list_elem['vrfName'] != 'default' %} + {% if vars.update({'vrfname':list_elem['vrfName']}) %}{% endif -%} + {% endif -%} + {% if 'flagDetail' in list_elem %} + {% if vars.update({'detail':list_elem['flagDetail']}) %}{% endif -%} + {% endif -%} + {% if 'openconfig-network-instance:global_state' in list_elem %} + {% if 'router-id' in list_elem['openconfig-network-instance:global_state'] %} + {% if vars.update({'router_id':list_elem['openconfig-network-instance:global_state' ]['router-id']}) %}{% endif -%} + {% endif -%} + {% endif -%} + {% if 'openconfig-network-instance:graceful_restart_state' in list_elem and 'helper-only' in list_elem['openconfig-network-instance:graceful_restart_state'] and list_elem['openconfig-network-instance:graceful_restart_state']['helper-only'] == True %} + {% if vars.update({'helper':'Graceful restart helper support enabled.'}) %}{% endif -%} + {% endif -%} + {% if 'openconfig-network-instance:graceful_restart_state' in list_elem and 'openconfig-ospfv2-ext:strict-lsa-checking' in list_elem['openconfig-network-instance:graceful_restart_state'] and list_elem['openconfig-network-instance:graceful_restart_state']['openconfig-ospfv2-ext:strict-lsa-checking'] == True %} + {% if vars.update({'strict_lsa_checking':'Strict LSA check is enabled.'}) %}{% endif -%} + {% endif -%} + {% if 'openconfig-network-instance:graceful_restart_state' in list_elem and 'openconfig-ospfv2-ext:planned-only' in list_elem['openconfig-network-instance:graceful_restart_state'] and list_elem['openconfig-network-instance:graceful_restart_state']['openconfig-ospfv2-ext:planned-only'] == True %} + {% if vars.update({'planned_only':'Helper supported for planned restarts only.'}) %}{% endif -%} + {% endif -%} + {% if 'openconfig-network-instance:graceful_restart_state' in list_elem and 'openconfig-ospfv2-ext:supported-grace-time' in list_elem['openconfig-network-instance:graceful_restart_state'] %} + {% if vars.update({'supported_interval':list_elem['openconfig-network-instance:graceful_restart_state']['openconfig-ospfv2-ext:supported-grace-time']}) %}{% endif -%} + {% endif -%} + {% if 'openconfig-network-instance:graceful_restart_state' in list_elem and 'openconfig-ospfv2-ext:gr-helper-active-restarter-count' in list_elem['openconfig-network-instance:graceful_restart_state'] and list_elem['openconfig-network-instance:graceful_restart_state']['openconfig-ospfv2-ext:gr-helper-active-restarter-count'] != 0 %} + {% if vars.update({'active_restart_count':list_elem['openconfig-network-instance:graceful_restart_state']['openconfig-ospfv2-ext:gr-helper-active-restarter-count']}) %}{% endif -%} + {% endif -%} + {% if 'openconfig-network-instance:graceful_restart_state' in list_elem and 'openconfig-ospfv2-ext:gr-last-exit-reason' in list_elem['openconfig-network-instance:graceful_restart_state'] and list_elem['openconfig-network-instance:graceful_restart_state']['openconfig-ospfv2-ext:gr-last-exit-reason'] != "" %} + {% if vars.update({'last_exit_reason':list_elem['openconfig-network-instance:graceful_restart_state']['openconfig-ospfv2-ext:gr-last-exit-reason']}) %}{% endif -%} + {% endif -%} + {% if 'openconfig-network-instance:gr_helper_neighbors' in list_elem %} + {% if vars.update({'neighbors':list_elem['openconfig-network-instance:gr_helper_neighbors']}) %}{% endif -%} + {% endif -%} +{% endfor -%} +{{' '}}VRF Name: {{vars.vrfname}} +{{' '}}OSPF Router with ID ({{vars.router_id}}) +{{' '}}{{vars.helper}} +{{' '}}{{vars.strict_lsa_checking}} +{{' '}}{{vars.planned_only}} +{{' '}}Supported Graceful restart interval: {{vars.supported_interval}}(in seconds). +{{' '}}Enable Router List: +{{' '}}{{vars.neighbors}} +{% if vars.detail == True %} + {% if vars.last_exit_reason != "" %} + {{' '}}Last Helper exit Reason: {{vars.last_exit_reason}} + {% endif -%} + {% if vars.active_restart_count != '0' %} + {{' '}}Number of Active neighbours in graceful restart: {{vars.active_restart_count}} + {% for list_elem in ospf_global_list %} + {% if 'openconfig-network-instance:areas' in list_elem and 'area' in list_elem['openconfig-network-instance:areas'] %} + {% set area_list =list_elem['openconfig-network-instance:areas']['area'] %} + {% endif -%} + {% for areainfo in area_list %} + {% if 'interfaces' in areainfo and 'interface' in areainfo['interfaces'] %} + {% set interface_list = areainfo['interfaces']['interface'] %} + {% for interfaceinfo in interface_list %} + {% if 'openconfig-ospfv2-ext:neighbours' in interfaceinfo and 'neighbour' in interfaceinfo['openconfig-ospfv2-ext:neighbours'] %} + {% set neighbor_list = interfaceinfo['openconfig-ospfv2-ext:neighbours']['neighbour'] %} + {% for neighborinfo in neighbor_list %} + {% if 'state' in neighborinfo %} + {% if 'gr-active-helper' in neighborinfo['state'] and neighborinfo['state']['gr-active-helper'] == True %} + {% if vars.update({'cnt': vars.cnt + 1}) %}{% endif -%} +{{' '}}Neighbour {{vars.cnt}}: +{{' '}}Address: {{neighborinfo['neighbor-address']}} +{{' '}}Routerid: {{neighborinfo['neighbor-id']}} +{{' '}}Received Grace period: {{neighborinfo['state']['gr-grace-interval']}}(in seconds). +{{' '}}Actual Grace period: {{neighborinfo['state']['gr-actual-grace-interval']}}(in seconds). +{{' '}}Remaining GraceTime: {{neighborinfo['state']['gr-remaining-grace-interval']}}(in seconds). +{{' '}}Graceful Restart reason: {{neighborinfo['state']['gr-restart-reason']}}. + {% endif -%} + {% endif -%} + {% endfor -%} + {% endif -%} + {% endfor -%} + {% endif -%} + {% endfor -%} + {% endfor -%} + {% endif -%} +{% endif -%} diff --git a/CLI/renderer/templates/show_ip_ospf_neighbor_detail.j2 b/CLI/renderer/templates/show_ip_ospf_neighbor_detail.j2 index 9b5196df1e..da4d742f76 100644 --- a/CLI/renderer/templates/show_ip_ospf_neighbor_detail.j2 +++ b/CLI/renderer/templates/show_ip_ospf_neighbor_detail.j2 @@ -72,6 +72,20 @@ VRF Name: {{list_elem['vrfName']}} Thread Link State Update Retransmission on {% else %} Thread Link State Update Retransmission off +{% endif %} + Graceful restart Helper info: + Graceful Restart HELPER Status: {{neighborinfo['state']['gr-helper-status']}} +{% if 'gr-grace-interval' in neighborinfo ['state'] %} + Graceful Restart grace period time: {{neighborinfo['state']['gr-grace-interval']}} (seconds). +{% endif %} +{% if 'gr-restart-reason' in neighborinfo ['state'] %} + Graceful Restart reason: {{neighborinfo['state']['gr-restart-reason']}} +{% endif %} +{% if 'gr-helper-reject-reason' in neighborinfo ['state'] %} + Graceful Restart HELPER Reject Reason: {{neighborinfo['state']['gr-helper-reject-reason']}} +{% endif %} +{% if 'gr-helper-exit-reason' in neighborinfo ['state'] %} + Graceful Restart HELPER Exit Reason: {{neighborinfo['state']['gr-helper-exit-reason']}} {% endif %} {% if 'bfd-state' in neighborinfo['state']%} {{' '}} From 3072d24b0c836880c14c1d150ca5c77a11336cc5 Mon Sep 17 00:00:00 2001 From: Sachin Holla Date: Mon, 9 May 2022 15:24:41 +0530 Subject: [PATCH 28/31] [JIRA SONIC-61139] REST API timeout Introduced context deadline based API timeout for all REST APIs. API timeout is disabled by default; can be enabled by specifying a timeout duration via -apitimeout command line flag. When enabled, the incoming http request's context is wrapped in a deadline context and passed to the handler API. They are expected to abort and return context.Err() error when context.Done() is reached. REST server maps this error to 503 Service Unavailable status. Not all API handlers honor context timeouts. Server cannot not support timeout for such APIs; server would wait indefinitely for normal completion. E.g, translib Set(), Action() and non-translib APIs. When concurrent request limit is enabled, API timeout counts the queue wait time also. If timeout elapses before a request gets picked up for processing, server immediately responds with 503 error status. Change-Id: I3410055394a557e70894aaa50a5be754f1a6e3e9 --- rest/main/main.go | 6 +++ rest/server/error.go | 25 ++++++++++++- rest/server/error_test.go | 19 ++++++++++ rest/server/rate_limit.go | 31 ++++++++++++++-- rest/server/rate_limit_test.go | 67 ++++++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 5 deletions(-) diff --git a/rest/main/main.go b/rest/main/main.go index 2c1865a157..128765ffb6 100644 --- a/rest/main/main.go +++ b/rest/main/main.go @@ -49,6 +49,10 @@ var ( // readTimeout is the max duration for reading the entire request (header+body). // Default value is 30 seconds.. Value 0 means no timeout. readTimeout time.Duration = 30 * time.Second + + // apiTimeout is the timeout hint for REST APIs, passed through the context. + // It does not include readTimeout; but includes the wait time in RequestFunnel. + apiTimeout time.Duration = 0 ) const ( @@ -67,6 +71,7 @@ func init() { flag.BoolVar(&internal, "internal", false, "Enable internal, non-standard features on https listener") flag.UintVar(&funnel.Limit, "reqlimit", 0, "Max concurrent requests allowed") flag.DurationVar(&readTimeout, "readtimeout", readTimeout, "Maximum duration for reading entire request") + flag.DurationVar(&apiTimeout, "apitimeout", apiTimeout, "REST API timeout; value 0 disables it") } // Start REST server @@ -188,6 +193,7 @@ func newRouter(cfg *server.RouterConfig) http.Handler { if funnel.Limit > 0 { r = funnel.Wrap(r) } + r = server.WithTimeout(r, &apiTimeout) return r } diff --git a/rest/server/error.go b/rest/server/error.go index 44cf8d59bc..711c93b32e 100644 --- a/rest/server/error.go +++ b/rest/server/error.go @@ -20,6 +20,7 @@ package server import ( + "context" "encoding/json" "fmt" "net/http" @@ -54,6 +55,7 @@ const ( // error-type values errtypeProtocol errtype = "protocol" errtypeApplication errtype = "application" + errtypeRpc errtype = "rpc" // error-tag values errtagInvalidValue errtag = "invalid-value" @@ -116,8 +118,6 @@ func prepareErrorResponse(err error, r *http.Request) (status int, data []byte, // toErrorEntry translates an error object into HTTP status and an // errorEntry object. func toErrorEntry(err error, r *http.Request) (status int, errInfo errorEntry) { - // By default everything is 500 Internal Server Error - status = http.StatusInternalServerError errInfo.Type = errtypeApplication errInfo.Tag = errtagOperationFailed @@ -199,6 +199,7 @@ func toErrorEntry(err error, r *http.Request) (status int, errInfo errorEntry) { errInfo.Message = "Transaction failed. Please try again." case tlerr.InternalError: + status = http.StatusInternalServerError errInfo.Message = e.Error() errInfo.Path = e.Path errInfo.AppTag = e.AppTag @@ -238,6 +239,26 @@ func toErrorEntry(err error, r *http.Request) (status int, errInfo errorEntry) { errInfo.AppTag = e.AppTag errInfo.Tag = errtagAccessDenied + case tlerr.RequestContextCancelledError: + err = e.CtxError + } + + if status == 0 { + switch { + case err == context.Canceled: + // Won't be sent back to client; but used in log message! + status = http.StatusBadRequest + errInfo.Type = errtypeRpc + errInfo.Message = "Cancelled" + + case err == context.DeadlineExceeded: + status = http.StatusServiceUnavailable + errInfo.Type = errtypeRpc + errInfo.Message = "Timed out" + + default: + status = http.StatusInternalServerError + } } return diff --git a/rest/server/error_test.go b/rest/server/error_test.go index 8b991f01f3..de7f35de26 100644 --- a/rest/server/error_test.go +++ b/rest/server/error_test.go @@ -20,6 +20,7 @@ package server import ( + "context" "errors" "fmt" "strings" @@ -186,6 +187,24 @@ func TestErrorEntry(t *testing.T) { ClientVersion: "1.2.3", ServerVersion: "1.2.0", ServerBaseVersion: "1.0.0"}, 400, "protocol", "operation-not-supported", "", "Unsupported client version 1.2.3")) + // context errors + + t.Run("Ctx_cancel", testErrorEntry( + context.Canceled, + 400, "rpc", "operation-failed", "", "*")) + + t.Run("Ctx_timeout", testErrorEntry( + context.DeadlineExceeded, + 503, "rpc", "operation-failed", "", "*")) + + t.Run("Ctx_wrap_cancel", testErrorEntry( + tlerr.RequestContextCancelled("Context error", context.Canceled), + 400, "rpc", "operation-failed", "", "*")) + + t.Run("Ctx_wrap_timeout", testErrorEntry( + tlerr.RequestContextCancelled("Context error", context.DeadlineExceeded), + 503, "rpc", "operation-failed", "", "*")) + } func testErrorEntry(err error, diff --git a/rest/server/rate_limit.go b/rest/server/rate_limit.go index bffeffaa02..7bb530352f 100644 --- a/rest/server/rate_limit.go +++ b/rest/server/rate_limit.go @@ -20,7 +20,10 @@ package server import ( + "context" "net/http" + "sync/atomic" + "time" ) // RequestFunnel controls the number of concurrent requests processed by @@ -30,7 +33,7 @@ type RequestFunnel struct { slots chan struct{} // empty slots } -// Wrap returns a http.Handler which applies request funnelling +// Wrap returns a http.HandlerFunc which applies request funnelling // to a given inner http.Handler. One RequestFunnel can wrap multiple // handlers, in which case they all share the same limit. func (rf *RequestFunnel) Wrap(inner http.Handler) http.HandlerFunc { @@ -39,8 +42,30 @@ func (rf *RequestFunnel) Wrap(inner http.Handler) http.HandlerFunc { } return func(w http.ResponseWriter, r *http.Request) { - rf.slots <- struct{}{} - defer func() { <-rf.slots }() + ctx := r.Context() + select { + case rf.slots <- struct{}{}: + defer func() { <-rf.slots }() + inner.ServeHTTP(w, r) + case <-ctx.Done(): + writeErrorResponse(w, r, ctx.Err()) + } + } +} + +// WithTimeout creates a http.HandlerFunc which wraps a http.Handler to add context timeouts. +// It has no effect if the timeout duration is <= 0. Changes to this duration will reflect in +// the subsequent requests. +// Unlike the standard library http.TimeoutHandler, it does not write a response immediately +// after timeout. It expects the inner Handler to manage context timeouts. +func WithTimeout(inner http.Handler, timeout *time.Duration) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + t := atomic.LoadInt64((*int64)(timeout)) + if t > 0 { + ctx, cancel := context.WithTimeout(r.Context(), time.Duration(t)) + defer cancel() + r = r.WithContext(ctx) + } inner.ServeHTTP(w, r) } } diff --git a/rest/server/rate_limit_test.go b/rest/server/rate_limit_test.go index a96eb04711..a716052317 100644 --- a/rest/server/rate_limit_test.go +++ b/rest/server/rate_limit_test.go @@ -110,3 +110,70 @@ func TestReqFunnel_mix(t *testing.T) { f := RequestFunnel{Limit: 2} runReqFunnelTests(t, &f, 50, time.Second, 71*time.Millisecond, 321*time.Millisecond) } + +// timeoutTest holds input and state info for API timeout tests +type timeoutTest struct { + Timeout time.Duration + Handler http.Handler + _h http.Handler // internal state, not to be set by test case +} + +func (test *timeoutTest) Run(t *testing.T, expStatus int, expDelay time.Duration) { + if test._h == nil { + test._h = WithTimeout(test.Handler, &test.Timeout) + } + + w := httptest.NewRecorder() + t0 := time.Now() + test._h.ServeHTTP(w, httptest.NewRequest("GET", "/test", nil)) + delay := time.Since(t0) + if delay.Round(500*time.Millisecond) != expDelay { + t.Errorf("Expected delay %v; actual %v", expDelay, delay) + } + if w.Code != expStatus { + t.Errorf("Expected status %d; got %d", expStatus, w.Code) + } +} + +// timeoutTestHandler returns status 200 after waiting for 1s. Returns status 503 if +// context timeout is reached earlier. +var timeoutTestHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { + select { + case <-time.After(time.Second): + w.WriteHeader(http.StatusOK) + case <-r.Context().Done(): + w.WriteHeader(http.StatusServiceUnavailable) + } +} + +func TestApiTimeout_0(t *testing.T) { + test := timeoutTest{Timeout: 0, Handler: timeoutTestHandler} + test.Run(t, http.StatusOK, time.Second) +} + +func TestApiTimeout_fast(t *testing.T) { + test := timeoutTest{Timeout: 2 * time.Second, Handler: timeoutTestHandler} + test.Run(t, http.StatusOK, time.Second) +} + +func TestApiTimeout_slow(t *testing.T) { + test := timeoutTest{Timeout: 500 * time.Millisecond, Handler: timeoutTestHandler} + test.Run(t, http.StatusServiceUnavailable, test.Timeout) +} + +func TestApiTimeout_update(t *testing.T) { + test := timeoutTest{Timeout: 0, Handler: timeoutTestHandler} + test.Run(t, http.StatusOK, time.Second) + test.Timeout = 500 * time.Millisecond + test.Run(t, http.StatusServiceUnavailable, test.Timeout) + test.Timeout = 2 * time.Second + test.Run(t, http.StatusOK, time.Second) +} + +func TestApiTimeout_ignore(t *testing.T) { + var h http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { + time.Sleep(2 * time.Second) + } + test := timeoutTest{Timeout: time.Second, Handler: h} + test.Run(t, http.StatusOK, 2*time.Second) +} From 0b3c72d5dd89fb22bc150bf68705ba857cb636d0 Mon Sep 17 00:00:00 2001 From: "Kim, Kwan" Date: Thu, 12 May 2022 13:34:23 -0700 Subject: [PATCH 29/31] create pipestr-file per session to keep a seperate pipe token (#90) --- .../patches/klish-2.1.4/bin/clish.c.diff | 24 +++++++++++++++---- CLI/renderer/scripts/rpipe_utils.py | 3 +-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CLI/klish/patches/klish-2.1.4/bin/clish.c.diff b/CLI/klish/patches/klish-2.1.4/bin/clish.c.diff index 037d4b3571..7143e7d345 100644 --- a/CLI/klish/patches/klish-2.1.4/bin/clish.c.diff +++ b/CLI/klish/patches/klish-2.1.4/bin/clish.c.diff @@ -1,25 +1,39 @@ -41a42,43 +18a19,20 +> #include +> #include +41a44,45 > #include > -48a51,56 +48a53,58 > bool _nos_use_alt_name = false; > > bool nos_use_alt_name() { > return _nos_use_alt_name; > } > -88a97,101 +54a65 +> char pipestr_file[PATH_MAX]; +88a100,104 > char *mode = getenv("SONIC_CLI_IFACE_MODE"); > if (mode) { > _nos_use_alt_name = (strcmp(mode, "standard") == 0); > } > -307c320 +307c323 < clish_shell__set_idle_timeout(shell, timeout); --- > clish_shell__set_timeout(shell, timeout); -336a350,353 +336a353,356 > if (tmpfd == -1) { > fprintf(stderr, "Error: Can't dup stdin.\n"); > goto end; > } +380a401,408 +> +> /* Delete pipestr file */ +> snprintf(pipestr_file, sizeof(pipestr_file), +> "%s-%u", "/tmp/pipestr", getppid()); +> if (unlink(pipestr_file) < 0) { +> syslog(LOG_ERR, "Can't remove pipestr-file %s: %s\n", +> pipestr_file, strerror(errno)); +> } diff --git a/CLI/renderer/scripts/rpipe_utils.py b/CLI/renderer/scripts/rpipe_utils.py index 89a17de5bd..c7ca276db6 100644 --- a/CLI/renderer/scripts/rpipe_utils.py +++ b/CLI/renderer/scripts/rpipe_utils.py @@ -14,8 +14,7 @@ class pipestr: """ def __init__(self): - pwrec = pwd.getpwuid(os.getuid()) - self.pipestr = "/tmp/pipestr-" + pwrec.pw_name + self.pipestr = "/tmp/pipestr-" + str(os.getppid()) def write(self, argv): pipe_str = "" From dc9aeef3b6de807e8d7383129c9ee4bdf6b82a9a Mon Sep 17 00:00:00 2001 From: "Kim, Kwan" Date: Sun, 15 May 2022 14:17:29 -0700 Subject: [PATCH 30/31] fix segment fault at exit (#104) --- CLI/klish/patches/klish-2.1.4/bin/clish.c.diff | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLI/klish/patches/klish-2.1.4/bin/clish.c.diff b/CLI/klish/patches/klish-2.1.4/bin/clish.c.diff index 7143e7d345..8db2274254 100644 --- a/CLI/klish/patches/klish-2.1.4/bin/clish.c.diff +++ b/CLI/klish/patches/klish-2.1.4/bin/clish.c.diff @@ -33,7 +33,7 @@ > /* Delete pipestr file */ > snprintf(pipestr_file, sizeof(pipestr_file), > "%s-%u", "/tmp/pipestr", getppid()); -> if (unlink(pipestr_file) < 0) { +> if (access(pipestr_file, F_OK) == 0 && unlink(pipestr_file) < 0) { > syslog(LOG_ERR, "Can't remove pipestr-file %s: %s\n", > pipestr_file, strerror(errno)); > } From 54f437baaadbb0c9c3589334c551dda4f7aa48a7 Mon Sep 17 00:00:00 2001 From: Akhilesh Samineni Date: Mon, 16 May 2022 00:27:01 -0700 Subject: [PATCH 31/31] [JIRA SONIC-60551] - Added the sag.xml file to the running-config order Change-Id: Ib4272043b52da7b0541713871eada1ea9c843ac7 --- CLI/clitree/scripts/config_db_data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CLI/clitree/scripts/config_db_data.py b/CLI/clitree/scripts/config_db_data.py index 0160434480..db8464af7a 100644 --- a/CLI/clitree/scripts/config_db_data.py +++ b/CLI/clitree/scripts/config_db_data.py @@ -19,13 +19,14 @@ VIEW_PRIORITY_FILE_MAP = { "configure-if-view": ["interface.xml", "vrf.xml", "ipv4.xml", "ipv6.xml"], "configure-lag-view": ["interface.xml", "vrf.xml", "ipv4.xml", "ipv6.xml"], - "configure-vlan-view": ["interface.xml", "vrf.xml", "ipv4.xml", "ipv6.xml"], + "configure-vlan-view": ["interface.xml", "sag.xml", "vrf.xml", "ipv4.xml", "ipv6.xml"], "configure-if-mgmt-view": ["interface.xml", "vrf.xml", "ipv4.xml", "ipv6.xml"], "configure-lo-view": ["interface.xml", "vrf.xml", "ipv4.xml", "ipv6.xml"], "configure-vxlan-view": ["vxlan.xml"], "configure-subif-view": [ "interface.xml", "subinterface.xml", + "sag.xml", "vrf.xml", "ipv4.xml", "ipv6.xml",