CSIT python API introduction
[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             reply_value[item] = value
150     reply_dict[reply_key] = reply_value
151     return reply_dict
152
153
154 def process_reply(api_reply):
155     """Process API reply for smooth converting to JSON string.
156
157     :param api_reply: API reply.
158     :type api_reply: Vpp_serializer reply object (named tuple) or list of
159         vpp_serializer reply objects
160     :returns: Processed API reply.
161     :rtype: list or dict
162     """
163
164     if isinstance(api_reply, list):
165         converted_reply = list()
166         for r in api_reply:
167             converted_reply.append(convert_reply(r))
168     else:
169         converted_reply = convert_reply(api_reply)
170     return converted_reply
171
172
173 def main():
174     """Main function for the Python API provider.
175
176     :raises PapiCommandInputError: If invalid attribute name or invalid value is
177         used in API call.
178     :raises PapiCommandError: If PAPI command(s) execution failed.
179     """
180
181     parser = argparse.ArgumentParser()
182     parser.add_argument("-j", "--json_data",
183                         required=True,
184                         type=str,
185                         help="JSON string (list) containing API name(s) and "
186                              "its/their input argument(s).")
187     parser.add_argument("-d", "--json_dir",
188                         type=str,
189                         default='/usr/share/vpp/api/',
190                         help="Directory containing all vpp json api files.")
191     args = parser.parse_args()
192     json_string = args.json_data
193     vpp_json_dir = args.json_dir
194
195     vpp = papi_init(vpp_json_dir=vpp_json_dir)
196
197     reply = list()
198     json_data = json.loads(json_string)
199     papi_connect(vpp, CLIENT_NAME)
200     for data in json_data:
201         api_name = data['api_name']
202         api_args_unicode = data['api_args']
203         api_reply = dict(api_name=api_name)
204         api_args = dict()
205         for a_k, a_v in api_args_unicode.iteritems():
206             value = binascii.unhexlify(a_v) if isinstance(a_v, unicode) else a_v
207             api_args[str(a_k)] = value
208         try:
209             rep = papi_run(vpp, api_name, api_args)
210             api_reply['api_reply'] = process_reply(rep)
211             reply.append(api_reply)
212         except (AttributeError, ValueError) as err:
213             papi_disconnect(vpp)
214             raise PapiCommandInputError(
215                 'PAPI command {api}({args}) input error:\n{exc}'.format(
216                     api=api_name, args=api_args), exc=repr(err))
217         except Exception as err:
218             papi_disconnect(vpp)
219             raise PapiCommandError(
220                 'PAPI command {api}({args}) error:\n{exc}'.format(
221                     api=api_name, args=api_args), exc=repr(err))
222     papi_disconnect(vpp)
223
224     return json.dumps(reply)
225
226
227 if __name__ == '__main__':
228     sys.stdout.write(main())
229     sys.stdout.flush()
230     sys.exit(0)