X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=blobdiff_plain;f=resources%2Ftools%2Fpapi%2Fvpp_papi_provider.py;h=bd333b1e87af4373dfd55ab5bd2c905aa56ffaf5;hp=845cc932cad1b232262ecc1a8d33f4c29654e2fb;hb=8dc3a2a0568e1b8e31d02e477102471bf4bc0ee1;hpb=35ae14a1a109d357bb548e0fb8aaf941ed4a85f2 diff --git a/resources/tools/papi/vpp_papi_provider.py b/resources/tools/papi/vpp_papi_provider.py old mode 100644 new mode 100755 index 845cc932ca..bd333b1e87 --- a/resources/tools/papi/vpp_papi_provider.py +++ b/resources/tools/papi/vpp_papi_provider.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -# Copyright (c) 2018 Cisco and/or its affiliates. +# Copyright (c) 2020 Cisco and/or its affiliates. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: @@ -13,208 +13,298 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Python API provider. +r"""CSIT PAPI Provider + +TODO: Add description. + +Examples: +--------- + +Request/reply or dump: + + vpp_papi_provider.py \ + --method request \ + --data '[{"api_name": "show_version", "api_args": {}}]' + +VPP-stats: + + vpp_papi_provider.py \ + --method stats \ + --data '[["^/if", "/err/ip4-input", "/sys/node/ip4-input"], ["^/if"]]' """ import argparse -import binascii -import fnmatch import json import os import sys -sys.path.append('/tmp/openvpp-testing') -try: - from resources.libraries.python.PapiErrors import * -except: - raise + +# Client name +CLIENT_NAME = u"csit_papi" + # Sphinx creates auto-generated documentation by importing the python source # files and collecting the docstrings from them. The NO_VPP_PAPI flag allows # the vpp_papi_provider.py file to be importable without having to build # the whole vpp api if the user only wishes to generate the test documentation. -do_import = True + try: - no_vpp_papi = os.getenv("NO_VPP_PAPI") - if no_vpp_papi == "1": - do_import = False -except: - pass + do_import = bool(not os.getenv(u"NO_VPP_PAPI") == u"1") +except KeyError: + do_import = True if do_import: - # TODO: run os.walk once per whole suite and store the path in environmental - # variable + + # Find the directory where the modules are installed. The directory depends + # on the OS used. + # TODO: Find a better way to import papi modules. + modules_path = None - for root, dirs, files in os.walk('/usr/lib'): + for root, dirs, files in os.walk(u"/usr/lib"): for name in files: - if name == 'vpp_papi.py': + if name == u"vpp_papi.py": modules_path = os.path.split(root)[0] break if modules_path: sys.path.append(modules_path) - from vpp_papi import VPP + from vpp_papi import VPPApiClient + from vpp_papi.vpp_stats import VPPStats else: - raise PapiInitError('vpp_papi module not found') - -# client name -CLIENT_NAME = 'csit_papi' - - -def papi_init(): - """Construct a VPP instance from VPP JSON API files. - - :param vpp_json_dir: Directory containing all the JSON API files. If VPP is - installed in the system it will be in /usr/share/vpp/api/. - :type vpp_json_dir: str - :returns: VPP instance. - :rtype: VPP object - :raises PapiJsonFileError: If no api.json file found. - :raises PapiInitError: If PAPI initialization failed. - """ - try: - vpp = VPP() - return vpp - except Exception as err: - raise PapiInitError('PAPI init failed:\n{exc}'.format(exc=repr(err))) + raise RuntimeError(u"vpp_papi module not found") -def papi_connect(vpp_client, name='vpp_api'): - """Attach to VPP client. - - :param vpp_client: VPP instance to connect to. - :param name: VPP client name. - :type vpp_client: VPP object - :type name: str - :returns: Return code of VPP.connect() method. - :rtype: int - """ - return vpp_client.connect(name) - - -def papi_disconnect(vpp_client): - """Detach from VPP client. - - :param vpp_client: VPP instance to detach from. - :type vpp_client: VPP object - """ - vpp_client.disconnect() - +def _convert_reply(api_r): + """Process API reply / a part of API reply for smooth converting to + JSON string. -def papi_run(vpp_client, api_name, api_args): - """api_name + It is used only with 'request' and 'dump' methods. - :param vpp_client: VPP instance. - :param api_name: VPP API name. - :param api_args: Input arguments of the API. - :type vpp_client: VPP object - :type api_name: str - :type api_args: dict - :returns: VPP API reply. - :rtype: Vpp_serializer reply object - """ - papi_fn = getattr(vpp_client.api, api_name) - return papi_fn(**api_args) + Apply binascii.hexlify() method for string values. + TODO: Implement complex solution to process of replies. -def convert_reply(api_r): - """Process API reply / a part of API reply for smooth converting to - JSON string. - - # Apply binascii.hexlify() method for string values. :param api_r: API reply. :type api_r: Vpp_serializer reply object (named tuple) :returns: Processed API reply / a part of API reply. :rtype: dict """ - unwanted_fields = ['count', 'index'] + unwanted_fields = [u"count", u"index", u"context"] + + def process_value(val): + """Process value. + + :param val: Value to be processed. + :type val: object + :returns: Processed value. + :rtype: dict or str or int + """ + if isinstance(val, dict): + for val_k, val_v in val.items(): + val[str(val_k)] = process_value(val_v) + return val + elif isinstance(val, list): + for idx, val_l in enumerate(val): + val[idx] = process_value(val_l) + return val + elif isinstance(val, bytes): + val.hex() + elif hasattr(val, u"__int__"): + return int(val) + elif hasattr(val, "__str__"): + return str(val).encode(encoding=u"utf-8").hex() + # Next handles parameters not supporting preferred integer or string + # representation to get it logged + elif hasattr(val, u"__repr__"): + return repr(val) + else: + return val reply_dict = dict() - reply_key = repr(api_r).split('(')[0] + reply_key = repr(api_r).split(u"(")[0] reply_value = dict() for item in dir(api_r): - if not item.startswith('_') and item not in unwanted_fields: - # attr_value = getattr(api_r, item) - # value = binascii.hexlify(attr_value) \ - # if isinstance(attr_value, str) else attr_value - value = getattr(api_r, item) - reply_value[item] = value + if not item.startswith(u"_") and item not in unwanted_fields: + reply_value[item] = process_value(getattr(api_r, item)) reply_dict[reply_key] = reply_value return reply_dict -def process_reply(api_reply): - """Process API reply for smooth converting to JSON string. - - :param api_reply: API reply. - :type api_reply: Vpp_serializer reply object (named tuple) or list of - vpp_serializer reply objects - :returns: Processed API reply. - :rtype: list or dict - """ - - if isinstance(api_reply, list): - converted_reply = list() - for r in api_reply: - converted_reply.append(convert_reply(r)) - else: - converted_reply = convert_reply(api_reply) - return converted_reply - +def process_json_request(args): + """Process the request/reply and dump classes of VPP API methods. -def main(): - """Main function for the Python API provider. - - :raises PapiCommandInputError: If invalid attribute name or invalid value is - used in API call. - :raises PapiCommandError: If PAPI command(s) execution failed. + :param args: Command line arguments passed to VPP PAPI Provider. + :type args: ArgumentParser + :returns: JSON formatted string. + :rtype: str + :raises RuntimeError: If PAPI command error occurs. """ - parser = argparse.ArgumentParser() - parser.add_argument("-j", "--json_data", - required=True, - type=str, - help="JSON string (list) containing API name(s) and " - "its/their input argument(s).") - parser.add_argument("-d", "--json_dir", - type=str, - default='/usr/share/vpp/api/', - help="Directory containing all vpp json api files.") - args = parser.parse_args() - json_string = args.json_data - - vpp = papi_init() + try: + vpp = VPPApiClient() + except Exception as err: + raise RuntimeError(f"PAPI init failed:\n{err!r}") reply = list() - json_data = json.loads(json_string) - papi_connect(vpp, CLIENT_NAME) + + def process_value(val): + """Process value. + + :param val: Value to be processed. + :type val: object + :returns: Processed value. + :rtype: dict or str or int + """ + if isinstance(val, dict): + for val_k, val_v in val.items(): + val[str(val_k)] = process_value(val_v) + return val + elif isinstance(val, list): + for idx, val_l in enumerate(val): + val[idx] = process_value(val_l) + return val + elif isinstance(val, str): + return bytes.fromhex(val).decode(encoding=u"utf-8") + elif isinstance(val, int): + return val + else: + return str(val) + + json_data = json.loads(args.data) + vpp.connect(CLIENT_NAME) for data in json_data: - api_name = data['api_name'] - api_args_unicode = data['api_args'] + api_name = data[u"api_name"] + api_args_unicode = data[u"api_args"] api_reply = dict(api_name=api_name) api_args = dict() - for a_k, a_v in api_args_unicode.iteritems(): - value = binascii.unhexlify(a_v) if isinstance(a_v, unicode) else a_v - api_args[str(a_k)] = value + for a_k, a_v in api_args_unicode.items(): + api_args[str(a_k)] = process_value(a_v) try: - rep = papi_run(vpp, api_name, api_args) - api_reply['api_reply'] = process_reply(rep) + papi_fn = getattr(vpp.api, api_name) + rep = papi_fn(**api_args) + + if isinstance(rep, list): + converted_reply = list() + for r in rep: + converted_reply.append(_convert_reply(r)) + else: + converted_reply = _convert_reply(rep) + + api_reply[u"api_reply"] = converted_reply reply.append(api_reply) except (AttributeError, ValueError) as err: - papi_disconnect(vpp) - raise PapiCommandInputError( - 'PAPI command {api}({args}) input error:\n{exc}'.format( - api=api_name, args=api_args), exc=repr(err)) + vpp.disconnect() + raise RuntimeError( + f"PAPI command {api_name}({api_args}) input error:\n{err!r}" + ) except Exception as err: - papi_disconnect(vpp) - raise PapiCommandError( - 'PAPI command {api}({args}) error:\n{exc}'.format( - api=api_name, args=api_args), exc=repr(err)) - papi_disconnect(vpp) + vpp.disconnect() + raise RuntimeError( + f"PAPI command {api_name}({api_args}) error:\n{err!r}" + ) + vpp.disconnect() + + return json.dumps(reply) + + +def process_stats(args): + """Process the VPP Stats. + + :param args: Command line arguments passed to VPP PAPI Provider. + :type args: ArgumentParser + :returns: JSON formatted string. + :rtype: str + :raises RuntimeError: If PAPI command error occurs. + """ + + try: + stats = VPPStats(args.socket) + except Exception as err: + raise RuntimeError(f"PAPI init failed:\n{err!r}") + + json_data = json.loads(args.data) + + reply = list() + + for path in json_data: + directory = stats.ls(path) + data = stats.dump(directory) + reply.append(data) + + try: + return json.dumps(reply) + except UnicodeDecodeError as err: + raise RuntimeError(f"PAPI reply {reply} error:\n{err!r}") + + +def process_stats_request(args): + """Process the VPP Stats requests. + + :param args: Command line arguments passed to VPP PAPI Provider. + :type args: ArgumentParser + :returns: JSON formatted string. + :rtype: str + :raises RuntimeError: If PAPI command error occurs. + """ + + try: + stats = VPPStats(args.socket) + except Exception as err: + raise RuntimeError(f"PAPI init failed:\n{err!r}") + + try: + json_data = json.loads(args.data) + except ValueError as err: + raise RuntimeError(f"Input json string is invalid:\n{err!r}") + + papi_fn = getattr(stats, json_data[u"api_name"]) + reply = papi_fn(**json_data.get(u"api_args", {})) return json.dumps(reply) -if __name__ == '__main__': +def main(): + """Main function for the Python API provider. + """ + + # The functions which process different types of VPP Python API methods. + process_request = dict( + request=process_json_request, + dump=process_json_request, + stats=process_stats, + stats_request=process_stats_request + ) + + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=__doc__ + ) + parser.add_argument( + u"-m", u"--method", required=True, + choices=[str(key) for key in process_request.keys()], + help=u"Specifies the VPP API methods: " + u"1. request - simple request / reply; " + u"2. dump - dump function;" + u"3. stats - VPP statistics." + ) + parser.add_argument( + u"-d", u"--data", required=True, + help=u"If the method is 'request' or 'dump', data is a JSON string " + u"(list) containing API name(s) and its/their input argument(s). " + u"If the method is 'stats', data is a JSON string containing t" + u"he list of path(s) to the required data." + ) + parser.add_argument( + u"-s", u"--socket", default=u"/var/run/vpp/stats.sock", + help=u"A file descriptor over the VPP stats Unix domain socket. " + u"It is used only if method=='stats'." + ) + + args = parser.parse_args() + + return process_request[args.method](args) + + +if __name__ == u"__main__": sys.stdout.write(main()) sys.stdout.flush() sys.exit(0)