CSIT-1337: Migrate L2Util library 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     json_data = json.loads(args.data)
135     vpp.connect(CLIENT_NAME)
136     for data in json_data:
137         api_name = data['api_name']
138         api_args_unicode = data['api_args']
139         api_reply = dict(api_name=api_name)
140         api_args = dict()
141         for a_k, a_v in api_args_unicode.items():
142             value = binascii.unhexlify(a_v) if isinstance(a_v, unicode) else a_v
143             api_args[str(a_k)] = value if isinstance(value, int) else str(value)
144         try:
145             papi_fn = getattr(vpp.api, api_name)
146             rep = papi_fn(**api_args)
147
148             if isinstance(rep, list):
149                 converted_reply = list()
150                 for r in rep:
151                     converted_reply.append(_convert_reply(r))
152             else:
153                 converted_reply = _convert_reply(rep)
154
155             api_reply['api_reply'] = converted_reply
156             reply.append(api_reply)
157         except (AttributeError, ValueError) as err:
158             vpp.disconnect()
159             raise RuntimeError('PAPI command {api}({args}) input error:\n{err}'.
160                                format(api=api_name,
161                                       args=api_args,
162                                       err=repr(err)))
163         except Exception as err:
164             vpp.disconnect()
165             raise RuntimeError('PAPI command {api}({args}) error:\n{exc}'.
166                                format(api=api_name,
167                                       args=api_args,
168                                       exc=repr(err)))
169     vpp.disconnect()
170
171     return json.dumps(reply)
172
173
174 def process_stats(args):
175     """Process the VPP Stats.
176
177     :param args: Command line arguments passed to VPP PAPI Provider.
178     :type args: ArgumentParser
179     :returns: JSON formatted string.
180     :rtype: str
181     :raises RuntimeError: If PAPI command error occurs.
182     """
183
184     try:
185         stats = VPPStats(args.socket)
186     except Exception as err:
187         raise RuntimeError('PAPI init failed:\n{err}'.format(err=repr(err)))
188
189     json_data = json.loads(args.data)
190
191     reply = list()
192
193     for path in json_data:
194         directory = stats.ls(path)
195         data = stats.dump(directory)
196         reply.append(data)
197
198     try:
199         return json.dumps(reply)
200     except UnicodeDecodeError as err:
201         raise RuntimeError('PAPI reply {reply} error:\n{exc}'.format(
202             reply=reply, exc=repr(err)))
203
204
205 def main():
206     """Main function for the Python API provider.
207     """
208
209     # The functions which process different types of VPP Python API methods.
210     process_request = dict(
211         request=process_json_request,
212         dump=process_json_request,
213         stats=process_stats
214     )
215
216     parser = argparse.ArgumentParser(
217         formatter_class=argparse.RawDescriptionHelpFormatter,
218         description=__doc__)
219     parser.add_argument("-m", "--method",
220                         required=True,
221                         choices=[str(key) for key in process_request.keys()],
222                         help="Specifies the VPP API methods: 1. request - "
223                              "simple request / reply; 2. dump - dump function;"
224                              "3. stats - VPP statistics.")
225     parser.add_argument("-d", "--data",
226                         required=True,
227                         help="If the method is 'request' or 'dump', data is a "
228                              "JSON string (list) containing API name(s) and "
229                              "its/their input argument(s). "
230                              "If the method is 'stats', data is a JSON string "
231                              "containing the list of path(s) to the required "
232                              "data.")
233     parser.add_argument("-s", "--socket",
234                         default="/var/run/vpp/stats.sock",
235                         help="A file descriptor over the VPP stats Unix domain "
236                              "socket. It is used only if method=='stats'.")
237
238     args = parser.parse_args()
239
240     return process_request[args.method](args)
241
242
243 if __name__ == '__main__':
244     sys.stdout.write(main())
245     sys.stdout.flush()
246     sys.exit(0)