PAPI: Use Python3 for PapiProvider
[csit.git] / resources / tools / papi / vpp_papi_provider.py
1 #!/usr/bin/env python3
2
3 # Copyright (c) 2019 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 """Python API provider.
17 """
18
19 import argparse
20 import binascii
21 import json
22 import os
23 import sys
24
25 # Sphinx creates auto-generated documentation by importing the python source
26 # files and collecting the docstrings from them. The NO_VPP_PAPI flag allows
27 # the vpp_papi_provider.py file to be importable without having to build
28 # the whole vpp api if the user only wishes to generate the test documentation.
29 do_import = True
30 try:
31     no_vpp_papi = os.getenv("NO_VPP_PAPI")
32     if no_vpp_papi == "1":
33         do_import = False
34 except:
35     pass
36
37 if do_import:
38     # TODO: run os.walk once per whole suite and store the path in environmental
39     # variable
40     modules_path = None
41     for root, dirs, files in os.walk('/usr/lib'):
42         for name in files:
43             if name == 'vpp_papi.py':
44                 modules_path = os.path.split(root)[0]
45                 break
46     if modules_path:
47         sys.path.append(modules_path)
48         from vpp_papi import VPP
49     else:
50         raise RuntimeError('vpp_papi module not found')
51
52 # client name
53 CLIENT_NAME = 'csit_papi'
54
55
56 def papi_init():
57     """Construct a VPP instance from VPP JSON API files.
58
59     :param vpp_json_dir: Directory containing all the JSON API files. If VPP is
60         installed in the system it will be in /usr/share/vpp/api/.
61     :type vpp_json_dir: str
62     :returns: VPP instance.
63     :rtype: VPP object
64     :raises PapiJsonFileError: If no api.json file found.
65     :raises PapiInitError: If PAPI initialization failed.
66     """
67     try:
68         vpp = VPP()
69         return vpp
70     except Exception as err:
71         raise RuntimeError('PAPI init failed:\n{err}'.format(err=repr(err)))
72
73
74 def papi_connect(vpp_client, name='vpp_api'):
75     """Attach to VPP client.
76
77     :param vpp_client: VPP instance to connect to.
78     :param name: VPP client name.
79     :type vpp_client: VPP object
80     :type name: str
81     :returns: Return code of VPP.connect() method.
82     :rtype: int
83     """
84     return vpp_client.connect(name)
85
86
87 def papi_disconnect(vpp_client):
88     """Detach from VPP client.
89
90     :param vpp_client: VPP instance to detach from.
91     :type vpp_client: VPP object
92     """
93     vpp_client.disconnect()
94
95
96 def papi_run(vpp_client, api_name, api_args):
97     """Run PAPI.
98
99     :param vpp_client: VPP instance.
100     :param api_name: VPP API name.
101     :param api_args: Input arguments of the API.
102     :type vpp_client: VPP object
103     :type api_name: str
104     :type api_args: dict
105     :returns: VPP API reply.
106     :rtype: Vpp_serializer reply object
107     """
108     papi_fn = getattr(vpp_client.api, api_name)
109     return papi_fn(**api_args)
110
111
112 def convert_reply(api_r):
113     """Process API reply / a part of API reply for smooth converting to
114     JSON string.
115
116     Apply binascii.hexlify() method for string values.
117
118     :param api_r: API reply.
119     :type api_r: Vpp_serializer reply object (named tuple)
120     :returns: Processed API reply / a part of API reply.
121     :rtype: dict
122     """
123     unwanted_fields = ['count', 'index']
124
125     reply_dict = dict()
126     reply_key = repr(api_r).split('(')[0]
127     reply_value = dict()
128     for item in dir(api_r):
129         if not item.startswith('_') and item not in unwanted_fields:
130             # attr_value = getattr(api_r, item)
131             # value = binascii.hexlify(attr_value) \
132             #     if isinstance(attr_value, str) else attr_value
133             value = getattr(api_r, item)
134             reply_value[item] = value
135     reply_dict[reply_key] = reply_value
136     return reply_dict
137
138
139 def process_reply(api_reply):
140     """Process API reply for smooth converting to JSON string.
141
142     :param api_reply: API reply.
143     :type api_reply: Vpp_serializer reply object (named tuple) or list of
144         vpp_serializer reply objects
145     :returns: Processed API reply.
146     :rtype: list or dict
147     """
148
149     if isinstance(api_reply, list):
150         converted_reply = list()
151         for r in api_reply:
152             converted_reply.append(convert_reply(r))
153     else:
154         converted_reply = convert_reply(api_reply)
155     return converted_reply
156
157
158 def main():
159     """Main function for the Python API provider.
160
161     :raises RuntimeError: If invalid attribute name or invalid value is
162         used in API call or if PAPI command(s) execution failed.
163     """
164
165     parser = argparse.ArgumentParser()
166     parser.add_argument("-j", "--json_data",
167                         required=True,
168                         type=str,
169                         help="JSON string (list) containing API name(s) and "
170                              "its/their input argument(s).")
171     parser.add_argument("-d", "--json_dir",
172                         type=str,
173                         default='/usr/share/vpp/api/',
174                         help="Directory containing all vpp json api files.")
175     args = parser.parse_args()
176     json_string = args.json_data
177
178     vpp = papi_init()
179
180     reply = list()
181     json_data = json.loads(json_string)
182     papi_connect(vpp, CLIENT_NAME)
183     for data in json_data:
184         api_name = data['api_name']
185         api_args_unicode = data['api_args']
186         api_reply = dict(api_name=api_name)
187         api_args = dict()
188         for a_k, a_v in api_args_unicode.items():
189             value = binascii.unhexlify(a_v) if isinstance(a_v, unicode) else a_v
190             api_args[str(a_k)] = value
191         try:
192             rep = papi_run(vpp, api_name, api_args)
193             api_reply['api_reply'] = process_reply(rep)
194             reply.append(api_reply)
195         except (AttributeError, ValueError) as err:
196             papi_disconnect(vpp)
197             raise RuntimeError('PAPI command {api}({args}) input error:\n{err}'.
198                                format(api=api_name,
199                                       args=api_args,
200                                       err=repr(err)))
201         except Exception as err:
202             papi_disconnect(vpp)
203             raise RuntimeError('PAPI command {api}({args}) error:\n{exc}'.
204                                format(api=api_name,
205                                       args=api_args,
206                                       exc=repr(err)))
207     papi_disconnect(vpp)
208
209     return json.dumps(reply)
210
211
212 if __name__ == '__main__':
213     sys.stdout.write(main())
214     sys.stdout.flush()
215     sys.exit(0)