From 99cbc4804ba5fa18adcf6a29271fc58a769e174d Mon Sep 17 00:00:00 2001 From: Sachin Holla <51310506+sachinholla@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:55:14 +0530 Subject: [PATCH] Use generic cli_client in klish actioners (#126) * Update all existing klish actioners to use the generic cli_client instead of swagger client sdk. Renderer templates are also updated wherever swagger client formatted json was different from yang json. * Disable swagger client sdk generation for all yang models * Error reporting enhancements in the cli_client * Fix crash in rpipe_utils while creating pipestr file. This was blocking all klish commands earlier. --- CLI/actioner/cli_client.py | 105 +++-- CLI/actioner/cli_log.py | 20 +- CLI/actioner/sonic-cli-acl.py | 399 +++++++----------- CLI/actioner/sonic-cli-if.py | 209 ++++----- CLI/actioner/sonic-cli-lldp.py | 93 ++-- CLI/actioner/sonic-cli-sys.py | 186 +++----- CLI/clitree/cli-xml/interface.xml | 2 +- CLI/renderer/scripts/rpipe_utils.py | 7 +- CLI/renderer/templates/lldp_neighbor_show.j2 | 12 +- CLI/renderer/templates/lldp_show.j2 | 2 +- CLI/renderer/templates/show_access_group.j2 | 16 +- CLI/renderer/templates/show_access_list.j2 | 26 +- .../templates/system_processes_show.j2 | 2 +- models/codegen.config | 4 - 14 files changed, 451 insertions(+), 632 deletions(-) diff --git a/CLI/actioner/cli_client.py b/CLI/actioner/cli_client.py index 403f4e83cc..771082e41c 100644 --- a/CLI/actioner/cli_client.py +++ b/CLI/actioner/cli_client.py @@ -1,4 +1,3 @@ -#!/usr/bin/python3 ################################################################################ # # # Copyright 2019 Broadcom. The term Broadcom refers to Broadcom Inc. and/or # @@ -25,25 +24,25 @@ from requests.structures import CaseInsensitiveDict from six.moves.urllib.parse import quote from collections import OrderedDict -from cli_log import log_warning +from cli_log import log_info, log_warning urllib3.disable_warnings() + class ApiClient(object): """REST API client to connect to the SONiC management REST server. Customized for CLI actioner use. """ - # Initialize API root and session + # Initialize API root and session __api_root = os.getenv('REST_API_ROOT', 'https://localhost') __session = requests.Session() - def request(self, method, path, data=None, headers={}, query=None, response_type=None): - url = '{0}{1}'.format(ApiClient.__api_root, path) + url = "{0}{1}".format(ApiClient.__api_root, path) - req_headers = CaseInsensitiveDict({ 'User-Agent': 'sonic-cli' }) + req_headers = CaseInsensitiveDict({'User-Agent': 'sonic-cli'}) req_headers.update(headers) body = None @@ -64,8 +63,8 @@ def request(self, method, path, data=None, headers={}, query=None, response_type return Response(r, response_type) except requests.RequestException as e: - log_warning('cli_client request exception: ' + str(e)) - #TODO have more specific error message based + log_info("cli_client request exception: {}", e) + # TODO have more specific error message based msg = '%Error: Could not connect to Management REST Server' return ApiClient.__new_error_response(msg) @@ -110,8 +109,8 @@ def prepare_query(depth=None, deleteEmptyEntry=None): @staticmethod def __new_error_response(errMessage, errType='client', errTag='operation-failed'): r = Response(requests.Response()) - r.content = {'ietf-restconf:errors':{ 'error':[ { - 'error-type':errType, 'error-tag':errTag, 'error-message':errMessage }]}} + r.content = {'ietf-restconf:errors': {'error': [{ + 'error-type': errType, 'error-tag': errTag, 'error-message': errMessage}]}} return r def cli_not_implemented(self, hint): @@ -126,6 +125,15 @@ def __init__(self, template, **kwargs): for k, v in list(kwargs.items()): self.path = self.path.replace('{%s}' % k, quote(v, safe='')) + def join(self, p, **kwargs): + if not isinstance(p, Path): + p = Path(p, **kwargs) + return Path(self.path + Path._withslash(p.path)) + + @staticmethod + def _withslash(s): + return "/"+s if s and not s.startswith("/") else s + def __str__(self): return self.path @@ -134,7 +142,7 @@ class Response(object): def __init__(self, response, response_type=None): self.response = response self.response_type = response_type - self.status_code = response.status_code + self.status_code = (response.status_code if response.status_code else 0) self.content = response.content try: @@ -145,7 +153,9 @@ def __init__(self, response, response_type=None): elif has_json_content(response): self.content = json.loads(response.content, object_pairs_hook=OrderedDict) except ValueError: - log_warning('Server returned invalid json! using raw content..') + # TODO Can we set status_code to 5XX in this case??? + # Json parsing can fail only if server returned bad json + log_warning("Server returned invalid json for url {}", self.response.url) self.content = response.content def ok(self): @@ -156,48 +166,73 @@ def errors(self): return {} errors = self.content - - if(not isinstance(errors, dict)): + if not isinstance(errors, dict): errors = {"error": errors} # convert to dict for consistency - elif('ietf-restconf:errors' in errors): - errors = errors['ietf-restconf:errors'] - + elif "ietf-restconf:errors" in errors: + errors = errors["ietf-restconf:errors"] return errors def error_message(self, formatter_func=None): - err = self.errors().get('error') + if hasattr(self, "err_message_override"): + return self.err_message_override + + err = self.errors().get("error") if err is None: return None if isinstance(err, list): err = err[0] if isinstance(err, dict): - if formatter_func is not None: - return formatter_func(self.status_code, err) - return default_error_message_formatter(self.status_code, err) + return format_error_message(self.status_code, err, formatter_func) return str(err) + def set_error_message(self, message): + self.err_message_override = add_error_prefix(message) + def __getitem__(self, key): return self.content[key] + def has_json_content(resp): - ctype = resp.headers.get('Content-Type') - return (ctype is not None and 'json' in ctype) + ctype = resp.headers.get("Content-Type") + return ctype is not None and "json" in ctype -def default_error_message_formatter(status_code, err_entry): - if 'error-message' in err_entry: - err_msg = err_entry['error-message'] + +def format_error_message(status_code, err_entry, formatter_func=None): + if formatter_func is not None: + err_msg = formatter_func(status_code, err_entry) + if err_msg: + return add_error_prefix(err_msg) + if "error-message" in err_entry: + err_msg = err_entry["error-message"] return add_error_prefix(err_msg) - err_tag = err_entry.get('error-tag') - if err_tag == 'invalid-value': - return '%Error: validation failed' - if err_tag == 'operation-not-supported': - return '%Error: not supported' - if err_tag == 'access-denied': - return '%Error: not authorized' - return '%Error: operation failed' + err_tag = err_entry.get("error-tag") + if err_tag == "invalid-value": + return "%Error: validation failed" + if err_tag == "operation-not-supported": + return "%Error: not supported" + if err_tag == "access-denied": + return "%Error: not authorized" + return "%Error: operation failed" + def add_error_prefix(err_msg): if not err_msg.startswith("%Error"): - return '%Error: ' + err_msg + return "%Error: " + err_msg return err_msg + +class ErrorData(object): + def __init__(self, response, index=0, formatter_func=None): + """Constructs an ErrorData object by parsing the RESTCONF error message + from a Response object. + """ + err_list = response.errors().get("error") + err = err_list[index] if err_list and len(err_list) > index else {} + err_info = err.get("error-info", {}) + self.status = response.status_code + self.type = err.get("error-type") + self.tag = err.get("error-tag") + self.app_tag = err.get("error-app-tag") + self.path = err.get("error-path") + self.cvl_err = err_info.get("cvl-error") + self.message = format_error_message(response.status_code, err, formatter_func) diff --git a/CLI/actioner/cli_log.py b/CLI/actioner/cli_log.py index cbd817b1d0..0412c7d417 100644 --- a/CLI/actioner/cli_log.py +++ b/CLI/actioner/cli_log.py @@ -28,23 +28,25 @@ __severity_str = [ 'EMERG', 'ALERT', 'CRIT', 'ERROR', 'WARN', 'NOTICE', 'INFO', 'DEBUG' ] -def log_debug(msg): +def log_debug(msg, *args, **kwargs): if __enable_debug: - __write_log(syslog.LOG_DEBUG, msg) + __write_log(syslog.LOG_DEBUG, msg, *args, **kwargs) -def log_info(msg): - __write_log(syslog.LOG_INFO, msg) +def log_info(msg, *args, **kwargs): + __write_log(syslog.LOG_INFO, msg, *args, **kwargs) -def log_warning(msg): - __write_log(syslog.LOG_WARNING, msg) +def log_warning(msg, *args, **kwargs): + __write_log(syslog.LOG_WARNING, msg, *args, **kwargs) -def log_error(msg): - __write_log(syslog.LOG_ERR, msg) +def log_error(msg, *args, **kwargs): + __write_log(syslog.LOG_ERR, msg, *args, **kwargs) -def __write_log(severity, msg): +def __write_log(severity, msg, *args, **kwargs): if not isinstance(msg, str): msg = str(msg) + else: + msg = msg.format(*args, **kwargs) syslog.syslog(severity, msg) diff --git a/CLI/actioner/sonic-cli-acl.py b/CLI/actioner/sonic-cli-acl.py index ff98b2e1c1..b2f678ca01 100755 --- a/CLI/actioner/sonic-cli-acl.py +++ b/CLI/actioner/sonic-cli-acl.py @@ -18,265 +18,174 @@ ########################################################################### import sys -import time -import json -import collections import re -import ast -import openconfig_acl_client +from cli_client import ApiClient, Path from rpipe_utils import pipestr -from openconfig_acl_client.rest import ApiException from scripts.render_cli import show_cli_output -import urllib3 -urllib3.disable_warnings() - -plugins = dict() - -def register(func): - """Register sdk client method as a plug-in""" - plugins[func.__name__] = func - return func - - -def call_method(name, args): - method = plugins[name] - return method(args) - -def generate_body(func, args): - body = None - # Get the rules of all ACL table entries. - if func.__name__ == 'get_openconfig_acl_acl_acl_sets': - keypath = [] - - # Get Interface binding to ACL table info - elif func.__name__ == 'get_openconfig_acl_acl_interfaces': - keypath = [] - - # Get all the rules specific to an ACL table. - elif func.__name__ == 'get_openconfig_acl_acl_acl_sets_acl_set_acl_entries': - keypath = [ args[0], args[1] ] - - # Configure ACL table - elif func.__name__ == 'patch_list_openconfig_acl_acl_acl_sets_acl_set' : - keypath = [] - body = { "openconfig-acl:acl-set": [{ - "name": args[0], - "type": args[1], - "config": { - "name": args[0], - "type": args[1], - "description": "" - } - }] - } - - # Configure ACL rule specific to an ACL table - elif func.__name__ == 'patch_list_openconfig_acl_acl_acl_sets_acl_set_acl_entries_acl_entry' : - keypath = [ args[0], args[1] ] - forwarding_action = "ACCEPT" if args[3] == 'permit' else 'DROP' - proto_number = {"icmp":"IP_ICMP","tcp":"IP_TCP","udp":"IP_UDP","6":"IP_TCP","17":"IP_UDP","1":"IP_ICMP", - "2":"IP_IGMP","103":"IP_PIM","46":"IP_RSVP","47":"IP_GRE","51":"IP_AUTH","115":"IP_L2TP"} - if args[4] not in list(proto_number.keys()): + +_proto_number_map = { + "icmp": "IP_ICMP", + "tcp": "IP_TCP", + "udp": "IP_UDP", + "6": "IP_TCP", + "17": "IP_UDP", + "1": "IP_ICMP", + "2": "IP_IGMP", + "103": "IP_PIM", + "46": "IP_RSVP", + "47": "IP_GRE", + "51": "IP_AUTH", + "115": "IP_L2TP", +} + +_action_map = { + "permit": "ACCEPT", + "deny": "DROP", +} + + +def acl_path(a_name=None, a_type=None): + p = Path("/restconf/data/openconfig-acl:acl/acl-sets") + if a_name is None or a_type is None: + return p.join("acl-set") + return p.join("acl-set={name},{type}", name=a_name, type=a_type) + + +def acl_if_path(if_name=None): + p = Path("/restconf/data/openconfig-acl:acl/interfaces") + if if_name is None: + return p.join("interface") + return p.join("interface={id}", id=if_name) + + +def check_ok(resp): + if not resp.ok(): + print(resp.error_message()) + return 1 + return 0 + + +def render(path, template): + resp = ApiClient().get(path, ignore404=True) + if not resp.ok(): + print(resp.error_message()) + return 1 + if resp.content: + show_cli_output(template, resp.content) + return 0 + + +class Handlers: + @staticmethod + def patch_list_openconfig_acl_acl_acl_sets_acl_set(a_name, a_type): + keys = {"name": a_name, "type": a_type} + body = {"acl-set": [{**keys, "config": {**keys}}]} + resp = ApiClient().patch(acl_path(), body) + return check_ok(resp) + + @staticmethod + def delete_openconfig_acl_acl_acl_sets_acl_set(a_name, a_type): + resp = ApiClient().delete(acl_path(a_name, a_type)) + return check_ok(resp) + + @staticmethod + def patch_list_openconfig_acl_acl_acl_sets_acl_set_acl_entries_acl_entry(a_name, a_type, seq, action, proto, *args): + if proto not in _proto_number_map: print("%Error: Invalid protocol number") - exit(1) - else: - protocol = proto_number.get(args[4]) - body=collections.defaultdict(dict) - body["acl-entry"]=[{ - "sequence-id": int(args[2]), - "config": { - "sequence-id": int(args[2]) - }, - "ipv4":{ - "config":{ - "protocol": protocol - } - }, - "transport": { - "config": { - } - }, - "actions": { - "config": { - "forwarding-action": forwarding_action - } - } - }] + return 1 + + action_config = {"forwarding-action": _action_map[action]} + ipv4_config = {"protocol": _proto_number_map[proto]} + xport_config = {} + xport_flags = [] + re_ip = re.compile("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}") - if re_ip.match(args[5]): - body["acl-entry"][0]["ipv4"]["config"]["source-address"]=args[5] - elif args[5]=="any": - body["acl-entry"][0]["ipv4"]["config"]["source-address"]="0.0.0.0/0" - flags_list=[] - i=6 - while(i%s : %s\n" %(func.__name__, e)) - if e.body != "": - body = json.loads(e.body) - if "ietf-restconf:errors" in body: - err = body["ietf-restconf:errors"] - if "error" in err: - errList = err["error"] - - errDict = {} - for dict in errList: - for k, v in dict.items(): - errDict[k] = v - - if "error-message" in errDict: - print("%Error: " + errDict["error-message"]) - return - print("%Error: Transaction Failure") - return - print("%Error: Transaction Failure") + @staticmethod + def get_openconfig_acl_acl_acl_sets_acl_set_acl_entries(a_name, a_type, template, *args): + return render(acl_path(a_name, a_type), template) + @staticmethod + def get_openconfig_acl_acl_interfaces(template, *args): + return render(acl_if_path(), template) -if __name__ == '__main__': +def run(func, args): + return getattr(Handlers, func)(*args) + + +if __name__ == '__main__': pipestr().write(sys.argv) - #pdb.set_trace() - func = eval(sys.argv[1], globals(), openconfig_acl_client.OpenconfigAclApi.__dict__) + func = sys.argv[1] run(func, sys.argv[2:]) diff --git a/CLI/actioner/sonic-cli-if.py b/CLI/actioner/sonic-cli-if.py index 7e0dffb3de..8ef7077c19 100755 --- a/CLI/actioner/sonic-cli-if.py +++ b/CLI/actioner/sonic-cli-if.py @@ -18,137 +18,102 @@ ########################################################################### import sys -import time -import json -import ast -import openconfig_interfaces_client +from natsort import natsorted + +from cli_client import ApiClient, Path from rpipe_utils import pipestr -from openconfig_interfaces_client.rest import ApiException from scripts.render_cli import show_cli_output -import urllib3 -urllib3.disable_warnings() - - -plugins = dict() - -def register(func): - """Register sdk client method as a plug-in""" - plugins[func.__name__] = func - return func - - -def call_method(name, args): - method = plugins[name] - return method(args) - -def generate_body(func, args): - body = None - # Get the rules of all ACL table entries. - if func.__name__ == 'patch_openconfig_interfaces_interfaces_interface_config_description': - keypath = [ args[0] ] - body = { "openconfig-interfaces:description": args[1] } - elif func.__name__ == 'patch_openconfig_interfaces_interfaces_interface_config_enabled': - keypath = [ args[0] ] - if args[1] == "True": - body = { "openconfig-interfaces:enabled": True } - else: - body = { "openconfig-interfaces:enabled": False } - elif func.__name__ == 'patch_openconfig_interfaces_interfaces_interface_config_mtu': - keypath = [ args[0] ] - body = { "openconfig-interfaces:mtu": int(args[1]) } - elif func.__name__ == 'patch_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv4_addresses_address_config': - sp = args[1].split('/') - keypath = [ args[0], 0, sp[0] ] - body = { "openconfig-if-ip:config": {"ip" : sp[0], "prefix-length" : int(sp[1])} } - elif func.__name__ == 'patch_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv6_addresses_address_config': - sp = args[1].split('/') - keypath = [ args[0], 0, sp[0] ] - body = { "openconfig-if-ip:config": {"ip" : sp[0], "prefix-length" : int(sp[1])} } - elif func.__name__ == 'delete_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv4_addresses_address_config_prefix_length': - keypath = [args[0], 0, args[1]] - elif func.__name__ == 'delete_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv6_addresses_address_config_prefix_length': - keypath = [args[0], 0, args[1]] - elif func.__name__ == 'get_openconfig_interfaces_interfaces_interface': - keypath = [args[0]] - elif func.__name__ == 'get_openconfig_interfaces_interfaces': - keypath = [] - else: - body = {} - - return keypath,body -def getId(item): - prfx = "Ethernet" - state_dict = item['state'] - ifName = state_dict['name'] +def config_intf(name, sub_path, value): + path = Path("/restconf/data/openconfig-interfaces:interfaces/interface={name}", name=name).join(sub_path) + if value is None: + resp = ApiClient().delete(path, ignore404=True) + else: + resp = ApiClient().patch(path, value) + if not resp.ok(): + print(resp.error_message()) + return 1 + return 0 + + +def ipaddr_payload(ip): + addr, mask = ip.split("/") + return {"address": [{ + "ip": addr, + "config": { + "ip": addr, + "prefix-length": int(mask), + }, + }]} + + +class Handlers: + @staticmethod + def patch_openconfig_interfaces_interfaces_interface_config_description(name, value, *args): + return config_intf(name, "config/description", {"description": value}) + + @staticmethod + def patch_openconfig_interfaces_interfaces_interface_config_enabled(name, value, *args): + return config_intf(name, "config/enabled", {"enabled": (value == "True")}) + + @staticmethod + def patch_openconfig_interfaces_interfaces_interface_config_mtu(name, mtu, *args): + return config_intf(name, "config/mtu", {"mtu": int(mtu)}) + + @staticmethod + def patch_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv4_addresses_address_config(name, ip4, *args): + ip4_path = "subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address" + return config_intf(name, ip4_path, ipaddr_payload(ip4)) + + @staticmethod + def delete_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv4_addresses_address_config_prefix_length(name, ip4): + ip4_path = Path("subinterfaces/subinterface=0/openconfig-if-ip:ipv4/addresses/address={ip}", ip=ip4) + return config_intf(name, ip4_path, None) + + @staticmethod + def patch_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv6_addresses_address_config(name, ip6, *args): + ip6_path = "subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address" + return config_intf(name, ip6_path, ipaddr_payload(ip6)) + + @staticmethod + def delete_openconfig_if_ip_interfaces_interface_subinterfaces_subinterface_ipv6_addresses_address_config_prefix_length(name, ip6): + ip6_path = Path("subinterfaces/subinterface=0/openconfig-if-ip:ipv6/addresses/address={ip}", ip=ip6) + return config_intf(name, ip6_path, None) + + @staticmethod + def get_openconfig_interfaces_interfaces(template, *args): + resp = ApiClient().get("/restconf/data/openconfig-interfaces:interfaces") + if not resp.ok(): + print(resp.error_message()) + return 1 + if not resp.content: + return 0 + # Sort interface records by name + intf_list = resp.content.get("openconfig-interfaces:interfaces", {}).get("interface", []) + if len(intf_list) > 1: + sorted_list = natsorted(intf_list, key=lambda x: x["name"]) + resp.content["openconfig-interfaces:interfaces"]["interface"] = sorted_list + show_cli_output(template, resp.content) + return 0 + + @staticmethod + def get_openconfig_interfaces_interfaces_interface(name, template, *args): + path = Path("/restconf/data/openconfig-interfaces:interfaces/interface={name}", name=name) + resp = ApiClient().get(path, ignore404=False) + if not resp.ok(): + print(resp.error_message()) + return 1 + if resp.content: + show_cli_output(template, resp.content) + return 0 - if ifName.startswith(prfx): - ifId = ifName[len(prfx):] - return ifId - return ifName def run(func, args): + return getattr(Handlers, func)(*args) - c = openconfig_interfaces_client.Configuration() - c.verify_ssl = False - aa = openconfig_interfaces_client.OpenconfigInterfacesApi(api_client=openconfig_interfaces_client.ApiClient(configuration=c)) - - # create a body block - keypath, body = generate_body(func, args) - - try: - if body is not None: - api_response = getattr(aa,func.__name__)(*keypath, body=body) - else : - api_response = getattr(aa,func.__name__)(*keypath) - - if api_response is None: - print ("Success") - else: - # Get Command Output - api_response = aa.api_client.sanitize_for_serialization(api_response) - if 'openconfig-interfaces:interfaces' in api_response: - value = api_response['openconfig-interfaces:interfaces'] - if 'interface' in value: - tup = value['interface'] - value['interface'] = sorted(tup, key=getId) - - if api_response is None: - print("Failed") - else: - if func.__name__ == 'get_openconfig_interfaces_interfaces_interface': - show_cli_output(args[1], api_response) - elif func.__name__ == 'get_openconfig_interfaces_interfaces': - show_cli_output(args[0], api_response) - else: - return - except ApiException as e: - #print("Exception when calling OpenconfigInterfacesApi->%s : %s\n" %(func.__name__, e)) - if e.body != "": - body = json.loads(e.body) - if "ietf-restconf:errors" in body: - err = body["ietf-restconf:errors"] - if "error" in err: - errList = err["error"] - - errDict = {} - for dict in errList: - for k, v in dict.items(): - errDict[k] = v - - if "error-message" in errDict: - print("%Error: " + errDict["error-message"]) - return - print("%Error: Transaction Failure") - return - print("%Error: Transaction Failure") - else: - print("Failed") if __name__ == '__main__': - pipestr().write(sys.argv) - func = eval(sys.argv[1], globals(), openconfig_interfaces_client.OpenconfigInterfacesApi.__dict__) - + func = sys.argv[1] run(func, sys.argv[2:]) diff --git a/CLI/actioner/sonic-cli-lldp.py b/CLI/actioner/sonic-cli-lldp.py index e87b31e31b..13ed7c9fb2 100644 --- a/CLI/actioner/sonic-cli-lldp.py +++ b/CLI/actioner/sonic-cli-lldp.py @@ -18,82 +18,47 @@ ########################################################################### import sys -import time -import json -import ast -import openconfig_lldp_client +from natsort import natsorted + +from cli_client import ApiClient, Path from rpipe_utils import pipestr -from openconfig_lldp_client.rest import ApiException from scripts.render_cli import show_cli_output -import urllib3 -urllib3.disable_warnings() -plugins = dict() - - -def register(func): - """Register sdk client method as a plug-in""" - plugins[func.__name__] = func - return func +def show_lldp_interface(path, template): + resp = ApiClient().get(path, ignore404=False) + if not resp.ok(): + print(resp.error_message()) + return 1 + if "openconfig-lldp:interfaces" in resp.content: + data = resp.content["openconfig-lldp:interfaces"].get("interface", []) + else: + data = resp.content.get("openconfig-lldp:interface", []) + # Server returns junk value for not found case!! Identify valid resp here + neigh = data[0].get("neighbors", {}).get("neighbor", []) if data else None + if neigh and "state" in neigh[0]: + sorted_data = natsorted(data, key=lambda x: x["name"]) + show_cli_output(template, sorted_data) + return 0 -def call_method(name, args): - method = plugins[name] - return method(args) -def generate_body(func, args): - body = None - if func.__name__ == 'get_openconfig_lldp_lldp_interfaces': - keypath = [] - elif func.__name__ == 'get_openconfig_lldp_lldp_interfaces_interface': - keypath = [args[1]] - else: - body = {} +class Handlers: + @staticmethod + def get_openconfig_lldp_lldp_interfaces(template, *args): + allif_path = Path("/restconf/data/openconfig-lldp:lldp/interfaces") + return show_lldp_interface(allif_path, template) - return keypath,body + @staticmethod + def get_openconfig_lldp_lldp_interfaces_interface(template, ifname, *args): + oneif_path = Path("/restconf/data/openconfig-lldp:lldp/interfaces/interface={name}", name=ifname) + return show_lldp_interface(oneif_path, template) def run(func, args): - c = openconfig_lldp_client.Configuration() - c.verify_ssl = False - aa = openconfig_lldp_client.OpenconfigLldpApi(api_client=openconfig_lldp_client.ApiClient(configuration=c)) - - # create a body block - keypath, body = generate_body(func, args) + return getattr(Handlers, func)(*args) - try: - if body is not None: - api_response = getattr(aa,func.__name__)(*keypath, body=body) - else : - api_response = getattr(aa,func.__name__)(*keypath) - if api_response is None: - print ("Success") - else: - response = api_response.to_dict() - if 'openconfig_lldpinterfaces' in list(response.keys()): - if not response['openconfig_lldpinterfaces']: - return - neigh_list = response['openconfig_lldpinterfaces']['interface'] - if neigh_list is None: - return - show_cli_output(sys.argv[2],neigh_list) - elif 'openconfig_lldpinterface' in list(response.keys()): - neigh = response['openconfig_lldpinterface']#[0]['neighbors']['neighbor'] - if neigh is None: - return - if sys.argv[3] is not None: - if neigh[0]['neighbors']['neighbor'][0]['state'] is None: - print('No LLDP neighbor interface') - else: - show_cli_output(sys.argv[2],neigh) - else: - show_cli_output(sys.argv[2],neigh) - else: - print("Failed") - except ApiException as e: - print(("Exception when calling OpenconfigLldpApi->%s : %s\n" %(func.__name__, e))) if __name__ == '__main__': pipestr().write(sys.argv) - func = eval(sys.argv[1], globals(), openconfig_lldp_client.OpenconfigLldpApi.__dict__) + func = sys.argv[1] run(func, sys.argv[2:]) diff --git a/CLI/actioner/sonic-cli-sys.py b/CLI/actioner/sonic-cli-sys.py index 6f8d3a6e2d..eaed808b69 100644 --- a/CLI/actioner/sonic-cli-sys.py +++ b/CLI/actioner/sonic-cli-sys.py @@ -18,147 +18,93 @@ ########################################################################### import sys -import time -import json -import ast -import openconfig_system_client +from cli_client import ApiClient from rpipe_utils import pipestr -from openconfig_system_client.rest import ApiException from scripts.render_cli import show_cli_output -import urllib3 -urllib3.disable_warnings() - - -plugins = dict() - def util_capitalize(value): - for key,val in list(value.items()): - temp = key.split('_') + for key, val in list(value.items()): + temp = key.replace('-', '_').split('_') alt_key = '' for i in temp: - alt_key = alt_key + i.capitalize() + ' ' - value[alt_key]=value.pop(key) + alt_key = alt_key + i.capitalize() + ' ' + value[alt_key] = value.pop(key) return value + def system_state_key_change(value): - value.pop('motd_banner') - value.pop('login_banner') + value.pop('motd-banner', None) + value.pop('login-banner', None) return util_capitalize(value) def memory_key_change(value): - value['Total']=value.pop('physical') - value['Used']=value.pop('reserved') + value['Total'] = value.pop('physical', 'Unknown') + value['Used'] = value.pop('reserved', 'Unknown') return value -def register(func): - """Register sdk client method as a plug-in""" - plugins[func.__name__] = func - return func - - -def call_method(name, args): - method = plugins[name] - return method(args) -def generate_body(func, args): - body = None - # Get the rules of all ACL table entries. - - if func.__name__ == 'get_openconfig_system_system_state': - keypath = [] - elif func.__name__ == 'get_openconfig_system_system_clock': - keypath = [] - elif func.__name__ == 'get_openconfig_system_system_memory': - keypath = [] - elif func.__name__ == 'get_openconfig_system_system_cpus': - keypath = [] - elif func.__name__ == 'get_openconfig_system_system_processes': - keypath = [] - elif func.__name__ == 'get_openconfig_system_components': - keypath = [] - - else: - body = {} - - return keypath,body +class Handlers: + @staticmethod + def get_openconfig_system_system_state(template, *args): + resp = ApiClient().get("/restconf/data/openconfig-system:system/state") + if not resp.ok(): + print(resp.error_message()) + return 1 + if resp.content: + data = resp.content.get("openconfig-system:state", {}) + show_cli_output(template, system_state_key_change(data)) + return 0 + + @staticmethod + def get_openconfig_system_system_memory(template, *args): + resp = ApiClient().get("/restconf/data/openconfig-system:system/memory/state") + if not resp.ok(): + print(resp.error_message()) + return 1 + if resp.content: + data = resp.content.get("openconfig-system:state", {}) + show_cli_output(template, memory_key_change(data)) + return 0 + + @staticmethod + def get_openconfig_system_system_cpus(template, *args): + resp = ApiClient().get("/restconf/data/openconfig-system:system/cpus/cpu") + if not resp.ok(): + print(resp.error_message()) + return 1 + if resp.content: + data = resp.content.get("openconfig-system:cpu", []) + show_cli_output(template, data) + return 0 + + @staticmethod + def get_openconfig_system_system_processes(template, pid, *args): + resp = ApiClient().get("/restconf/data/openconfig-system:system/processes/process") + if not resp.ok(): + print(resp.error_message()) + return 1 + data = resp.content.get("openconfig-system:process", []) if resp.content else [] + # Show all process info if pid is not given + if not pid.isnumeric(): + show_cli_output(template, data) + return 0 + # Lookup specific process by pid and display its state. + # Not passing pid in the GET url since server is not handling 'not found' case properly + for proc in data: + if proc["pid"] == pid: + proc_state = proc.get("state", {}) + show_cli_output(template, util_capitalize(proc_state)) + break + return 0 def run(func, args): - c = openconfig_system_client.Configuration() - c.verify_ssl = False - aa = openconfig_system_client.OpenconfigSystemApi(api_client=openconfig_system_client.ApiClient(configuration=c)) - - # create a body block - keypath, body = generate_body(func, args) - - try: - if body is not None: - api_response = getattr(aa,func.__name__)(*keypath, body=body) - - else : - api_response = getattr(aa,func.__name__)(*keypath) - if api_response is None: - print ("Success") - else: - response = api_response.to_dict() - if 'openconfig_systemstate' in list(response.keys()): - value = response['openconfig_systemstate'] - if value is None: - return - show_cli_output(sys.argv[2], system_state_key_change(value)) - - elif 'openconfig_systemmemory' in list(response.keys()): - value = response['openconfig_systemmemory'] - if value is None: - return - show_cli_output(sys.argv[2], memory_key_change(value['state'])) - elif 'openconfig_systemcpus' in list(response.keys()): - value = response['openconfig_systemcpus'] - if value is None: - return - show_cli_output(sys.argv[2], value['cpu']) - elif 'openconfig_systemprocesses' in list(response.keys()): - value = response['openconfig_systemprocesses'] - if 'pid' not in sys.argv: - if value is None: - return - show_cli_output(sys.argv[2],value['process']) - else: - for proc in value['process']: - if proc['pid'] == int(sys.argv[3]): - show_cli_output(sys.argv[2],util_capitalize(proc['state'])) - return - else: - print("Failed") - except ApiException as e: - if e.body != "": - body = json.loads(e.body) - if "ietf-restconf:errors" in body: - err = body["ietf-restconf:errors"] - if "error" in err: - errList = err["error"] - - errDict = {} - for dict in errList: - for k, v in dict.items(): - errDict[k] = v - - if "error-message" in errDict: - print("%Error: " + errDict["error-message"]) - return - print("%Error: Application Failure") - return - print("%Error: Application Failure") - else: - print("Failed") + return getattr(Handlers, func)(*args) -if __name__ == '__main__': +if __name__ == '__main__': pipestr().write(sys.argv) - #pdb.set_trace() - func = eval(sys.argv[1], globals(), openconfig_system_client.OpenconfigSystemApi.__dict__) + func = sys.argv[1] run(func, sys.argv[2:]) - diff --git a/CLI/clitree/cli-xml/interface.xml b/CLI/clitree/cli-xml/interface.xml index 84b354dd9a..647bcfe001 100644 --- a/CLI/clitree/cli-xml/interface.xml +++ b/CLI/clitree/cli-xml/interface.xml @@ -99,7 +99,7 @@ limitations under the License. help="Physical interface(Multiples of 4)" ptype="UINT" view="configure-if-view" - viewid="iface=Ethernet${phyifname}" + viewid="iface=Ethernet${phy-if-name}" /> diff --git a/CLI/renderer/scripts/rpipe_utils.py b/CLI/renderer/scripts/rpipe_utils.py index 41c265587b..77614815c3 100644 --- a/CLI/renderer/scripts/rpipe_utils.py +++ b/CLI/renderer/scripts/rpipe_utils.py @@ -2,7 +2,7 @@ import re import os -import pwd +# import pwd from time import gmtime, strftime class pipestr: @@ -11,8 +11,9 @@ class pipestr: For passing the pipestr from the actioner to the renderer """ def __init__(self): - pwrec = pwd.getpwuid(os.getuid()) - self.pipestr = '/tmp/pipestr-' + pwrec.pw_name + # pwrec = pwd.getpwuid(os.getuid()) + # self.pipestr = '/tmp/pipestr-' + pwrec.pw_name + self.pipestr = "/tmp/pipestr-{}".format(os.getuid()) def write(self, argv): pipe_str = '' diff --git a/CLI/renderer/templates/lldp_neighbor_show.j2 b/CLI/renderer/templates/lldp_neighbor_show.j2 index 16c5134783..992d27c7ab 100755 --- a/CLI/renderer/templates/lldp_neighbor_show.j2 +++ b/CLI/renderer/templates/lldp_neighbor_show.j2 @@ -5,10 +5,10 @@ {% set value = neigh['neighbors']['neighbor'][0] %} {{'Interface:'}}{{' '}}{{value['id']}}{{',via:'}}{{' LLDP'}} {{' Chassis:'}} -{{' ChassisID: '}}{{value['state']['chassis_id']}} -{{' SysName: '}}{{value['state']['system_name']}} -{% set desc = value['state']['system_description'].split('\r\n')[1:] %} -{{' SysDescr: '}}{{value['state']['system_description'].split('\r\n')[0]}} +{{' ChassisID: '}}{{value['state']['chassis-id']}} +{{' SysName: '}}{{value['state']['system-name']}} +{% set desc = value['state']['system-description'].split('\r\n')[1:] %} +{{' SysDescr: '}}{{value['state']['system-description'].split('\r\n')[0]}} {% for v in desc %} {{' '}}{{v}} {% endfor %} @@ -22,8 +22,8 @@ {{' Capability: '}}{{cap['name'].split(':')[1]}}{{', '}}{{en}} {% endfor %} {{' Port'}} -{{' PortID: '}}{{value['state']['port_id']}} -{{' PortDescr: '}}{{value['state']['port_description']}} +{{' PortID: '}}{{value['state']['port-id']}} +{{' PortDescr: '}}{{value['state']['port-description']}} {{'-----------------------------------------------------------'}} {% endfor %} diff --git a/CLI/renderer/templates/lldp_show.j2 b/CLI/renderer/templates/lldp_show.j2 index 1f8eaf71d5..33c3ffe692 100644 --- a/CLI/renderer/templates/lldp_show.j2 +++ b/CLI/renderer/templates/lldp_show.j2 @@ -13,6 +13,6 @@ {% endif %} {% endfor %} {% set value = neigh['neighbors']['neighbor'][0] %} -{{value['id'].ljust(20)}}{{value['state']['system_name'].ljust(20)}}{{value['state']['port_id'].ljust(20)}}{{(cap_list | join() | string).ljust(20)}}{{value['state']['port_description'].ljust(20)}} +{{value['id'].ljust(20)}}{{value['state']['system-name'].ljust(20)}}{{value['state']['port-id'].ljust(20)}}{{(cap_list | join() | string).ljust(20)}}{{value['state']['port-description'].ljust(20)}} {% endfor %} diff --git a/CLI/renderer/templates/show_access_group.j2 b/CLI/renderer/templates/show_access_group.j2 index a51bc8aa16..5d12b91ed6 100644 --- a/CLI/renderer/templates/show_access_group.j2 +++ b/CLI/renderer/templates/show_access_group.j2 @@ -4,18 +4,18 @@ {% if "interface" in key %} {% for interface in json_output[key] %} {% set if_id = interface["id"] %} - {% if interface["ingress_acl_sets"] %} + {% if interface["ingress-acl-sets"] %} {% set idirection = "ingress" %} {% endif %} - {% if interface["egress_acl_sets"] %} + {% if interface["egress-acl-sets"] %} {% set edirection = "egress" %} {% endif %} {% if idirection %} - {% set ing_acl_sets = idirection + "_acl_sets" %} - {% set ing_acl_set = idirection + "_acl_set" %} + {% set ing_acl_sets = idirection + "-acl-sets" %} + {% set ing_acl_set = idirection + "-acl-set" %} {% set ing_acl_set_list = interface[ing_acl_sets][ing_acl_set] %} {% for ing_acl_set in ing_acl_set_list %} - {% set i_acl_name = ing_acl_set["set_name"] %} + {% set i_acl_name = ing_acl_set["set-name"] %} {% if idirection == "ingress" %} {% set idirection = "Ingress" %} {% endif %} @@ -23,11 +23,11 @@ {% endfor %} {% endif %} {% if edirection %} - {% set eg_acl_sets = edirection + "_acl_sets" %} - {% set eg_acl_set = edirection + "_acl_set" %} + {% set eg_acl_sets = edirection + "-acl-sets" %} + {% set eg_acl_set = edirection + "-acl-set" %} {% set eg_acl_set_list = interface[eg_acl_sets][eg_acl_set] %} {% for eg_acl_set in eg_acl_set_list %} - {% set e_acl_name = eg_acl_set["set_name"] %} + {% set e_acl_name = eg_acl_set["set-name"] %} {% if edirection == "egress" %} {% set edirection = "Egress" %} {% endif %} diff --git a/CLI/renderer/templates/show_access_list.j2 b/CLI/renderer/templates/show_access_list.j2 index 46c90199a0..47a57fa7e8 100644 --- a/CLI/renderer/templates/show_access_list.j2 +++ b/CLI/renderer/templates/show_access_list.j2 @@ -2,10 +2,10 @@ {% for seq in acl_entry_list %} {% set response_list = [] %} {# Get sequence id #} - {% set seqid = seq["sequence_id"] %} + {% set seqid = seq["sequence-id"] %} {% set _list = response_list.append( seqid ) %} {# Get forwarding action #} - {% set fwd_action = seq["actions"]["config"]["forwarding_action"] %} + {% set fwd_action = seq["actions"]["config"]["forwarding-action"] %} {%- if "ACCEPT" in fwd_action %} {% set fwd_action = "permit" %} {%- endif %} @@ -17,26 +17,26 @@ {% set proto = seq["ipv4"]["state"]["protocol"].split(':')[1].split('_')[1]|lower %} {% set _list = response_list.append( proto ) %} {# Get Source IP #} - {% set src_ip = seq["ipv4"]["state"]["source_address"] %} + {% set src_ip = seq["ipv4"]["state"]["source-address"] %} {% set _list = response_list.append( src_ip ) %} {# include src port number if available #} {%- if seq["transport"] %} - {%- if seq["transport"]["config"]["source_port"] %} - {% set src_port = "eq " + seq["transport"]["config"]["source_port"] %} + {%- if seq["transport"]["config"]["source-port"] %} + {% set src_port = "eq " + seq["transport"]["config"]["source-port"]|string %} {% set _list = response_list.append( src_port ) %} {%- endif %} {%- endif %} {# Get Destination IP #} - {% set dstn_ip = seq["ipv4"]["state"]["destination_address"] %} + {% set dstn_ip = seq["ipv4"]["state"]["destination-address"] %} {% set _list = response_list.append( dstn_ip ) %} {# include dstn port number if available #} {%- if seq["transport"] %} - {%- if seq["transport"]["config"]["destination_port"] %} - {% set dstn_port = "eq " + seq["transport"]["config"]["destination_port"] %} + {%- if seq["transport"]["config"]["destination-port"] %} + {% set dstn_port = "eq " + seq["transport"]["config"]["destination-port"]|string %} {% set _list = response_list.append( dstn_port ) %} {%- endif %} - {%- if seq["transport"]["config"]["tcp_flags"] %} - {% for var in seq["transport"]["config"]["tcp_flags"] %} + {%- if seq["transport"]["config"]["tcp-flags"] %} + {% for var in seq["transport"]["config"]["tcp-flags"] %} {% set flag = var.split(':')[1].split('_')[1]|lower %} {% set _list = response_list.append( flag ) %} {% endfor %} @@ -50,18 +50,18 @@ {%- endmacro %} {% for key in json_output %} {# This condition checks if the JSON response has data from the acl-entry list #} - {% if "acl_entry" in key -%} + {% if "acl-entry" in key -%} {% set acl_entry = json_output[key] -%} {{ traverse_acl_entry(acl_entry) }} {%- endif %} {% endfor %} {% for acl_sets in json_output -%} - {% if "acl_set" in acl_sets -%} + {% if "acl-set" in acl_sets -%} {# This condition checks if the JSON response has data from the acl-sets container output -#} {% for acl_set in json_output[acl_sets] %} {% if acl_set["state"] -%} ip access-list {{ acl_set["state"]["name"] }} - {% set acl_entry_list = acl_set["acl_entries"] %} + {% set acl_entry_list = acl_set["acl-entries"] %} {% if acl_entry_list -%} {% for each in acl_entry_list -%} {% set acl_entry = acl_entry_list[each] -%} diff --git a/CLI/renderer/templates/system_processes_show.j2 b/CLI/renderer/templates/system_processes_show.j2 index c3960df150..a6ae891ef0 100755 --- a/CLI/renderer/templates/system_processes_show.j2 +++ b/CLI/renderer/templates/system_processes_show.j2 @@ -4,6 +4,6 @@ {{'--------------------------------------------------------------------------'}} {% for process in json_output %} {%set name = (process['state']['name'] | string).split(' ')%} -{{(process['pid'] | string).ljust(just_var)}} {{(process['state']['cpu_utilization'] | string).ljust(just_var)}} {#{{(process['state']['cpu_usage_user'] | string).ljust(just_var)}} {{(process['state']['cpu_usage_system'] | string).ljust(just_var)}}#} {{(process['state']['memory_utilization'] | string).ljust(just_var)}} {{(process['state']['memory_usage'] | string).ljust(just_var)}} {#{{(process['state']['start_time'] | string).ljust(10)}} {{(process['state']['uptime'] | string).ljust(just_var)}}#} {{name[0]}} +{{(process['pid'] | string).ljust(just_var)}} {{(process['state']['cpu-utilization'] | string).ljust(just_var)}} {#{{(process['state']['cpu-usage-user'] | string).ljust(just_var)}} {{(process['state']['cpu-usage-system'] | string).ljust(just_var)}}#} {{(process['state']['memory-utilization'] | string).ljust(just_var)}} {{(process['state']['memory-usage'] | string).ljust(just_var)}} {#{{(process['state']['start-time'] | string).ljust(10)}} {{(process['state']['uptime'] | string).ljust(just_var)}}#} {{name[0]}} {% endfor %} diff --git a/models/codegen.config b/models/codegen.config index 95a7510c70..ea12c4c03c 100644 --- a/models/codegen.config +++ b/models/codegen.config @@ -35,10 +35,6 @@ YANGAPI_EXCLUDES += # over this list. Note that the entry should be the yang module name # which is used for generated yaml file name. PY_YANGAPI_CLIENTS += -PY_YANGAPI_CLIENTS += openconfig-acl -PY_YANGAPI_CLIENTS += openconfig-interfaces -PY_YANGAPI_CLIENTS += openconfig-lldp -PY_YANGAPI_CLIENTS += openconfig-system ## # OPENAPI_EXCLUDES indicates the OpenAPI specs to be excluded from codegen.