FIX: IPUtil after vpp api changes
[csit.git] / resources / tools / papi / vpp_papi_provider.py
1 #!/usr/bin/env python2
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 r"""CSIT PAPI Provider
17
18 TODO: Add description.
19
20 Examples:
21 ---------
22
23 Request/reply or dump:
24
25     vpp_papi_provider.py \
26         --method request \
27         --data '[{"api_name": "show_version", "api_args": {}}]'
28
29 VPP-stats:
30
31     vpp_papi_provider.py \
32         --method stats \
33         --data '[["^/if", "/err/ip4-input", "/sys/node/ip4-input"], ["^/if"]]'
34 """
35
36 import argparse
37 import binascii
38 import json
39 import os
40 import sys
41
42
43 # Client name
44 CLIENT_NAME = 'csit_papi'
45
46
47 # Sphinx creates auto-generated documentation by importing the python source
48 # files and collecting the docstrings from them. The NO_VPP_PAPI flag allows
49 # the vpp_papi_provider.py file to be importable without having to build
50 # the whole vpp api if the user only wishes to generate the test documentation.
51
52 try:
53     do_import = False if os.getenv("NO_VPP_PAPI") == "1" else True
54 except KeyError:
55     do_import = True
56
57 if do_import:
58
59     # Find the directory where the modules are installed. The directory depends
60     # on the OS used.
61     # TODO: Find a better way to import papi modules.
62
63     modules_path = None
64     for root, dirs, files in os.walk('/usr/lib'):
65         for name in files:
66             if name == 'vpp_papi.py':
67                 modules_path = os.path.split(root)[0]
68                 break
69     if modules_path:
70         sys.path.append(modules_path)
71         from vpp_papi import VPP
72         from vpp_papi.vpp_stats import VPPStats
73     else:
74         raise RuntimeError('vpp_papi module not found')
75
76
77 def _convert_reply(api_r):
78     """Process API reply / a part of API reply for smooth converting to
79     JSON string.
80
81     It is used only with 'request' and 'dump' methods.
82
83     Apply binascii.hexlify() method for string values.
84
85     TODO: Implement complex solution to process of replies.
86
87     :param api_r: API reply.
88     :type api_r: Vpp_serializer reply object (named tuple)
89     :returns: Processed API reply / a part of API reply.
90     :rtype: dict
91     """
92     unwanted_fields = ['count', 'index', 'context']
93
94     def process_value(val):
95         """Process value.
96
97         :param val: Value to be processed.
98         :type val: object
99         :returns: Processed value.
100         :rtype: dict or str or int
101         """
102         if isinstance(val, dict):
103             for val_k, val_v in val.iteritems():
104                 val[str(val_k)] = process_value(val_v)
105             return val
106         elif isinstance(val, list):
107             for idx, val_l in enumerate(val):
108                 val[idx] = process_value(val_l)
109             return val
110         elif hasattr(val, '__int__'):
111             return int(val)
112         elif hasattr(val, '__str__'):
113             return binascii.hexlify(str(val))
114         # Next handles parameters not supporting preferred integer or string
115         # representation to get it logged
116         elif hasattr(val, '__repr__'):
117             return repr(val)
118         else:
119             return val
120
121     reply_dict = dict()
122     reply_key = repr(api_r).split('(')[0]
123     reply_value = dict()
124     for item in dir(api_r):
125         if not item.startswith('_') and item not in unwanted_fields:
126             reply_value[item] = process_value(getattr(api_r, item))
127     reply_dict[reply_key] = reply_value
128     return reply_dict
129
130
131 def process_json_request(args):
132     """Process the request/reply and dump classes of VPP API methods.
133
134     :param args: Command line arguments passed to VPP PAPI Provider.
135     :type args: ArgumentParser
136     :returns: JSON formatted string.
137     :rtype: str
138     :raises RuntimeError: If PAPI command error occurs.
139     """
140
141     try:
142         vpp = VPP()
143     except Exception as err:
144         raise RuntimeError('PAPI init failed:\n{err}'.format(err=repr(err)))
145
146     reply = list()
147
148     def process_value(val):
149         """Process value.
150
151         :param val: Value to be processed.
152         :type val: object
153         :returns: Processed value.
154         :rtype: dict or str or int
155         """
156         if isinstance(val, dict):
157             for val_k, val_v in val.iteritems():
158                 val[str(val_k)] = process_value(val_v)
159             return val
160         elif isinstance(val, list):
161             for idx, val_l in enumerate(val):
162                 val[idx] = process_value(val_l)
163             return val
164         elif isinstance(val, unicode):
165             return binascii.unhexlify(val)
166         elif isinstance(val, int):
167             return val
168         else:
169             return str(val)
170
171     json_data = json.loads(args.data)
172     vpp.connect(CLIENT_NAME)
173     for data in json_data:
174         api_name = data['api_name']
175         api_args_unicode = data['api_args']
176         api_reply = dict(api_name=api_name)
177         api_args = dict()
178         for a_k, a_v in api_args_unicode.items():
179             api_args[str(a_k)] = process_value(a_v)
180         try:
181             papi_fn = getattr(vpp.api, api_name)
182             rep = papi_fn(**api_args)
183
184             if isinstance(rep, list):
185                 converted_reply = list()
186                 for r in rep:
187                     converted_reply.append(_convert_reply(r))
188             else:
189                 converted_reply = _convert_reply(rep)
190
191             api_reply['api_reply'] = converted_reply
192             reply.append(api_reply)
193         except (AttributeError, ValueError) as err:
194             vpp.disconnect()
195             raise RuntimeError('PAPI command {api}({args}) input error:\n{err}'.
196                                format(api=api_name,
197                                       args=api_args,
198                                       err=repr(err)))
199         except Exception as err:
200             vpp.disconnect()
201             raise RuntimeError('PAPI command {api}({args}) error:\n{exc}'.
202                                format(api=api_name,
203                                       args=api_args,
204                                       exc=repr(err)))
205     vpp.disconnect()
206
207     return json.dumps(reply)
208
209
210 def process_stats(args):
211     """Process the VPP Stats.
212
213     :param args: Command line arguments passed to VPP PAPI Provider.
214     :type args: ArgumentParser
215     :returns: JSON formatted string.
216     :rtype: str
217     :raises RuntimeError: If PAPI command error occurs.
218     """
219
220     try:
221         stats = VPPStats(args.socket)
222     except Exception as err:
223         raise RuntimeError('PAPI init failed:\n{err}'.format(err=repr(err)))
224
225     json_data = json.loads(args.data)
226
227     reply = list()
228
229     for path in json_data:
230         directory = stats.ls(path)
231         data = stats.dump(directory)
232         reply.append(data)
233
234     try:
235         return json.dumps(reply)
236     except UnicodeDecodeError as err:
237         raise RuntimeError('PAPI reply {reply} error:\n{exc}'.format(
238             reply=reply, exc=repr(err)))
239
240
241 def process_stats_request(args):
242     """Process the VPP Stats requests.
243
244     :param args: Command line arguments passed to VPP PAPI Provider.
245     :type args: ArgumentParser
246     :returns: JSON formatted string.
247     :rtype: str
248     :raises RuntimeError: If PAPI command error occurs.
249     """
250
251     try:
252         stats = VPPStats(args.socket)
253     except Exception as err:
254         raise RuntimeError('PAPI init failed:\n{err}'.format(err=repr(err)))
255
256     try:
257         json_data = json.loads(args.data)
258     except ValueError as err:
259         raise RuntimeError('Input json string is invalid:\n{err}'.
260                            format(err=repr(err)))
261
262     papi_fn = getattr(stats, json_data["api_name"])
263     reply = papi_fn(**json_data.get("api_args", {}))
264
265     return json.dumps(reply)
266
267
268 def main():
269     """Main function for the Python API provider.
270     """
271
272     # The functions which process different types of VPP Python API methods.
273     process_request = dict(
274         request=process_json_request,
275         dump=process_json_request,
276         stats=process_stats,
277         stats_request=process_stats_request
278     )
279
280     parser = argparse.ArgumentParser(
281         formatter_class=argparse.RawDescriptionHelpFormatter,
282         description=__doc__)
283     parser.add_argument("-m", "--method",
284                         required=True,
285                         choices=[str(key) for key in process_request.keys()],
286                         help="Specifies the VPP API methods: 1. request - "
287                              "simple request / reply; 2. dump - dump function;"
288                              "3. stats - VPP statistics.")
289     parser.add_argument("-d", "--data",
290                         required=True,
291                         help="If the method is 'request' or 'dump', data is a "
292                              "JSON string (list) containing API name(s) and "
293                              "its/their input argument(s). "
294                              "If the method is 'stats', data is a JSON string "
295                              "containing the list of path(s) to the required "
296                              "data.")
297     parser.add_argument("-s", "--socket",
298                         default="/var/run/vpp/stats.sock",
299                         help="A file descriptor over the VPP stats Unix domain "
300                              "socket. It is used only if method=='stats'.")
301
302     args = parser.parse_args()
303
304     return process_request[args.method](args)
305
306
307 if __name__ == '__main__':
308     sys.stdout.write(main())
309     sys.stdout.flush()
310     sys.exit(0)