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 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", 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 diff --git a/CLI/actioner/show_config_ospfv2.py b/CLI/actioner/show_config_ospfv2.py index 14a2f9ee8d..408ea3946b 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: @@ -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) @@ -256,9 +299,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 +369,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 +487,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 +551,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 +678,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 +762,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: @@ -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 = "" @@ -814,9 +918,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 +1018,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 +1195,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: 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: 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') 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) 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) 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: 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:]) 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/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/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_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( 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/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/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: diff --git a/CLI/actioner/sonic_cli_vxlan.py b/CLI/actioner/sonic_cli_vxlan.py index c8f656b15b..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: @@ -674,11 +678,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"] = natsorted(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: @@ -746,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: @@ -782,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: 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 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 + + + + + + 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/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/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/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", 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..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 @@ -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 (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)); +> } 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 = "" 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 %} 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']%} {{' '}} 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 %} + + 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 %} 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/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 e8f4b63239..128765ffb6 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,13 +43,16 @@ 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 // 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 ( @@ -70,9 +69,13 @@ 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") + flag.DurationVar(&apiTimeout, "apitimeout", apiTimeout, "REST API timeout; value 0 disables it") +} + +// Start REST server +func main() { flag.Parse() @@ -88,34 +91,6 @@ func init() { clientAuth = server.UserAuth{"password": true, "cert": false, "jwt": true} } -} - -// 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") @@ -160,6 +135,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) @@ -217,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 } @@ -302,60 +279,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/main/main_test.go b/rest/main/main_test.go index f42847814d..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.SIGUSR1) + 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) + } } 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}) + } + } +} 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/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) 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) +} 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