FIX: Update PAPI to work with string changes in Python API
[csit.git] / resources / tools / papi / vpp_papi_provider.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2018 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 fnmatch
22 import json
23 import os
24 import sys
25
26 sys.path.append('/tmp/openvpp-testing')
27 try:
28     from resources.libraries.python.PapiErrors import *
29 except:
30     raise
31
32 # Sphinx creates auto-generated documentation by importing the python source
33 # files and collecting the docstrings from them. The NO_VPP_PAPI flag allows
34 # the vpp_papi_provider.py file to be importable without having to build
35 # the whole vpp api if the user only wishes to generate the test documentation.
36 do_import = True
37 try:
38     no_vpp_papi = os.getenv("NO_VPP_PAPI")
39     if no_vpp_papi == "1":
40         do_import = False
41 except:
42     pass
43
44 if do_import:
45     # TODO: run os.walk once per whole suite and store the path in environmental
46     # variable
47     modules_path = None
48     for root, dirs, files in os.walk('/usr/lib'):
49         for name in files:
50             if name == 'vpp_papi.py':
51                 modules_path = os.path.split(root)[0]
52                 break
53     if modules_path:
54         sys.path.append(modules_path)
55         from vpp_papi import VPP
56     else:
57         raise PapiInitError('vpp_papi module not found')
58
59 # client name
60 CLIENT_NAME = 'csit_papi'
61
62
63 def papi_init(vpp_json_dir='/usr/share/vpp/api/'):
64     """Construct a VPP instance from VPP JSON API files.
65
66     :param vpp_json_dir: Directory containing all the JSON API files. If VPP is
67         installed in the system it will be in /usr/share/vpp/api/.
68     :type vpp_json_dir: str
69     :returns: VPP instance.
70     :rtype: VPP object
71     :raises PapiJsonFileError: If no api.json file found.
72     :raises PapiInitError: If PAPI initialization failed.
73     """
74     # construct a list of all the json api files
75     jsonfiles = []
76     for root, dirnames, filenames in os.walk(vpp_json_dir):
77         for filename in fnmatch.filter(filenames, '*.api.json'):
78             jsonfiles.append(os.path.join(vpp_json_dir, filename))
79     if not jsonfiles:
80         raise PapiJsonFileError(
81             'No json api files found in location {dir}'.format(
82                 dir=vpp_json_dir))
83
84     try:
85         vpp = VPP(jsonfiles)
86         return vpp
87     except Exception as err:
88         raise PapiInitError('PAPI init failed:\n{exc}'.format(exc=repr(err)))
89
90
91 def papi_connect(vpp_client, name='vpp_api'):
92     """Attach to VPP client.
93
94     :param vpp_client: VPP instance to connect to.
95     :param name: VPP client name.
96     :type vpp_client: VPP object
97     :type name: str
98     :returns: Return code of VPP.connect() method.
99     :rtype: int
100     """
101     return vpp_client.connect(name)
102
103
104 def papi_disconnect(vpp_client):
105     """Detach from VPP client.
106
107     :param vpp_client: VPP instance to detach from.
108     :type vpp_client: VPP object
109     """
110     vpp_client.disconnect()
111
112
113 def papi_run(vpp_client, api_name, api_args):
114     """api_name
115
116     :param vpp_client: VPP instance.
117     :param api_name: VPP API name.
118     :param api_args: Input arguments of the API.
119     :type vpp_client: VPP object
120     :type api_name: str
121     :type api_args: dict
122     :returns: VPP API reply.
123     :rtype: Vpp_serializer reply object
124     """
125     papi_fn = getattr(vpp_client.api, api_name)
126     return papi_fn(**api_args)
127
128
129 def convert_reply(api_r):
130     """Process API reply / a part of API reply for smooth converting to
131     JSON string.
132
133     # Apply binascii.hexlify() method for string values.
134     :param api_r: API reply.
135     :type api_r: Vpp_serializer reply object (named tuple)
136     :returns: Processed API reply / a part of API reply.
137     :rtype: dict
138     """
139     unwanted_fields = ['count', 'index']
140
141     reply_dict = dict()
142     reply_key = repr(api_r).split('(')[0]
143     reply_value = dict()
144     for item in dir(api_r):
145         if not item.startswith('_') and item not in unwanted_fields:
146             # attr_value = getattr(api_r, item)
147             # value = binascii.hexlify(attr_value) \
148             #     if isinstance(attr_value, str) else attr_value
149             value = getattr(api_r, item)
150             reply_value[item] = value
151     reply_dict[reply_key] = reply_value
152     return reply_dict
153
154
155 def process_reply(api_reply):
156     """Process API reply for smooth converting to JSON string.
157
158     :param api_reply: API reply.
159     :type api_reply: Vpp_serializer reply object (named tuple) or list of
160         vpp_serializer reply objects
161     :returns: Processed API reply.
162     :rtype: list or dict
163     """
164
165     if isinstance(api_reply, list):
166         converted_reply = list()
167         for r in api_reply:
168             converted_reply.append(convert_reply(r))
169     else:
170         converted_reply = convert_reply(api_reply)
171     return converted_reply
172
173
174 def main():
175     """Main function for the Python API provider.
176
177     :raises PapiCommandInputError: If invalid attribute name or invalid value is
178         used in API call.
179     :raises PapiCommandError: If PAPI command(s) execution failed.
180     """
181
182     parser = argparse.ArgumentParser()
183     parser.add_argument("-j", "--json_data",
184                         required=True,
185                         type=str,
186                         help="JSON string (list) containing API name(s) and "
187                              "its/their input argument(s).")
188     parser.add_argument("-d", "--json_dir",
189                         type=str,
190                         default='/usr/share/vpp/api/',
191                         help="Directory containing all vpp json api files.")
192     args = parser.parse_args()
193     json_string = args.json_data
194     vpp_json_dir = args.json_dir
195
196     vpp = papi_init(vpp_json_dir=vpp_json_dir)
197
198     reply = list()
199     json_data = json.loads(json_string)
200     papi_connect(vpp, CLIENT_NAME)
201     for data in json_data:
202         api_name = data['api_name']
203         api_args_unicode = data['api_args']
204         api_reply = dict(api_name=api_name)
205         api_args = dict()
206         for a_k, a_v in api_args_unicode.iteritems():
207             value = binascii.unhexlify(a_v) if isinstance(a_v, unicode) else a_v
208             api_args[str(a_k)] = value
209         try:
210             rep = papi_run(vpp, api_name, api_args)
211             api_reply['api_reply'] = process_reply(rep)
212             reply.append(api_reply)
213         except (AttributeError, ValueError) as err:
214             papi_disconnect(vpp)
215             raise PapiCommandInputError(
216                 'PAPI command {api}({args}) input error:\n{exc}'.format(
217                     api=api_name, args=api_args), exc=repr(err))
218         except Exception as err:
219             papi_disconnect(vpp)
220             raise PapiCommandError(
221                 'PAPI command {api}({args}) error:\n{exc}'.format(
222                     api=api_name, args=api_args), exc=repr(err))
223     papi_disconnect(vpp)
224
225     return json.dumps(reply)
226
227
228 if __name__ == '__main__':
229     sys.stdout.write(main())
230     sys.stdout.flush()
231     sys.exit(0)