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