CSIT-1468: InterfaceUtil migration from VAT to PAPI
[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     reply_dict = dict()
95     reply_key = repr(api_r).split('(')[0]
96     reply_value = dict()
97     for item in dir(api_r):
98         if not item.startswith('_') and item not in unwanted_fields:
99             attr_value = getattr(api_r, item)
100             if isinstance(attr_value, list) or isinstance(attr_value, dict):
101                 value = attr_value
102             elif hasattr(attr_value, '__int__'):
103                 value = int(attr_value)
104             elif hasattr(attr_value, '__str__'):
105                 value = binascii.hexlify(str(attr_value))
106             # Next handles parameters not supporting preferred integer or string
107             # representation to get it logged
108             elif hasattr(attr_value, '__repr__'):
109                 value = repr(attr_value)
110             else:
111                 value = attr_value
112             reply_value[item] = value
113     reply_dict[reply_key] = reply_value
114     return reply_dict
115
116
117 def process_json_request(args):
118     """Process the request/reply and dump classes of VPP API methods.
119
120     :param args: Command line arguments passed to VPP PAPI Provider.
121     :type args: ArgumentParser
122     :returns: JSON formatted string.
123     :rtype: str
124     :raises RuntimeError: If PAPI command error occurs.
125     """
126
127     try:
128         vpp = VPP()
129     except Exception as err:
130         raise RuntimeError('PAPI init failed:\n{err}'.format(err=repr(err)))
131
132     reply = list()
133
134     def process_value(val):
135         if isinstance(val, dict):
136             val_dict = dict()
137             for val_k, val_v in val.iteritems():
138                 val_dict[str(val_k)] = process_value(val_v)
139             return val_dict
140         elif isinstance(val, unicode):
141             return binascii.unhexlify(val)
142         elif isinstance(val, int):
143             return val
144         else:
145             return str(val)
146
147     json_data = json.loads(args.data)
148     vpp.connect(CLIENT_NAME)
149     for data in json_data:
150         api_name = data['api_name']
151         api_args_unicode = data['api_args']
152         api_reply = dict(api_name=api_name)
153         api_args = dict()
154         for a_k, a_v in api_args_unicode.items():
155             api_args[str(a_k)] = process_value(a_v)
156         try:
157             papi_fn = getattr(vpp.api, api_name)
158             rep = papi_fn(**api_args)
159
160             if isinstance(rep, list):
161                 converted_reply = list()
162                 for r in rep:
163                     converted_reply.append(_convert_reply(r))
164             else:
165                 converted_reply = _convert_reply(rep)
166
167             api_reply['api_reply'] = converted_reply
168             reply.append(api_reply)
169         except (AttributeError, ValueError) as err:
170             vpp.disconnect()
171             raise RuntimeError('PAPI command {api}({args}) input error:\n{err}'.
172                                format(api=api_name,
173                                       args=api_args,
174                                       err=repr(err)))
175         except Exception as err:
176             vpp.disconnect()
177             raise RuntimeError('PAPI command {api}({args}) error:\n{exc}'.
178                                format(api=api_name,
179                                       args=api_args,
180                                       exc=repr(err)))
181     vpp.disconnect()
182
183     return json.dumps(reply)
184
185
186 def process_stats(args):
187     """Process the VPP Stats.
188
189     :param args: Command line arguments passed to VPP PAPI Provider.
190     :type args: ArgumentParser
191     :returns: JSON formatted string.
192     :rtype: str
193     :raises RuntimeError: If PAPI command error occurs.
194     """
195
196     try:
197         stats = VPPStats(args.socket)
198     except Exception as err:
199         raise RuntimeError('PAPI init failed:\n{err}'.format(err=repr(err)))
200
201     json_data = json.loads(args.data)
202
203     reply = list()
204
205     for path in json_data:
206         directory = stats.ls(path)
207         data = stats.dump(directory)
208         reply.append(data)
209
210     try:
211         return json.dumps(reply)
212     except UnicodeDecodeError as err:
213         raise RuntimeError('PAPI reply {reply} error:\n{exc}'.format(
214             reply=reply, exc=repr(err)))
215
216
217 def main():
218     """Main function for the Python API provider.
219     """
220
221     # The functions which process different types of VPP Python API methods.
222     process_request = dict(
223         request=process_json_request,
224         dump=process_json_request,
225         stats=process_stats
226     )
227
228     parser = argparse.ArgumentParser(
229         formatter_class=argparse.RawDescriptionHelpFormatter,
230         description=__doc__)
231     parser.add_argument("-m", "--method",
232                         required=True,
233                         choices=[str(key) for key in process_request.keys()],
234                         help="Specifies the VPP API methods: 1. request - "
235                              "simple request / reply; 2. dump - dump function;"
236                              "3. stats - VPP statistics.")
237     parser.add_argument("-d", "--data",
238                         required=True,
239                         help="If the method is 'request' or 'dump', data is a "
240                              "JSON string (list) containing API name(s) and "
241                              "its/their input argument(s). "
242                              "If the method is 'stats', data is a JSON string "
243                              "containing the list of path(s) to the required "
244                              "data.")
245     parser.add_argument("-s", "--socket",
246                         default="/var/run/vpp/stats.sock",
247                         help="A file descriptor over the VPP stats Unix domain "
248                              "socket. It is used only if method=='stats'.")
249
250     args = parser.parse_args()
251
252     return process_request[args.method](args)
253
254
255 if __name__ == '__main__':
256     sys.stdout.write(main())
257     sys.stdout.flush()
258     sys.exit(0)