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:
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
16 r"""CSIT PAPI Provider
18 TODO: Add description.
23 Request/reply or dump:
25 vpp_papi_provider.py \
27 --data '[{"api_name": "show_version", "api_args": {}}]'
31 vpp_papi_provider.py \
33 --data '[["^/if", "/err/ip4-input", "/sys/node/ip4-input"], ["^/if"]]'
44 CLIENT_NAME = 'csit_papi'
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.
53 do_import = False if os.getenv("NO_VPP_PAPI") == "1" else True
59 # Find the directory where the modules are installed. The directory depends
61 # TODO: Find a better way to import papi modules.
64 for root, dirs, files in os.walk('/usr/lib'):
66 if name == 'vpp_papi.py':
67 modules_path = os.path.split(root)[0]
70 sys.path.append(modules_path)
71 from vpp_papi import VPP
72 from vpp_papi.vpp_stats import VPPStats
74 raise RuntimeError('vpp_papi module not found')
77 def _convert_reply(api_r):
78 """Process API reply / a part of API reply for smooth converting to
81 It is used only with 'request' and 'dump' methods.
83 Apply binascii.hexlify() method for string values.
85 TODO: Implement complex solution to process of replies.
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.
92 unwanted_fields = ['count', 'index', 'context']
94 def process_value(val):
97 :param val: Value to be processed.
99 :returns: Processed value.
100 :rtype: dict or str or int
102 if isinstance(val, dict):
103 for val_k, val_v in val.iteritems():
104 val[str(val_k)] = process_value(val_v)
106 elif isinstance(val, list):
107 for idx, val_l in enumerate(val):
108 val[idx] = process_value(val_l)
110 elif hasattr(val, '__int__'):
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__'):
122 reply_key = repr(api_r).split('(')[0]
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
131 def process_json_request(args):
132 """Process the request/reply and dump classes of VPP API methods.
134 :param args: Command line arguments passed to VPP PAPI Provider.
135 :type args: ArgumentParser
136 :returns: JSON formatted string.
138 :raises RuntimeError: If PAPI command error occurs.
143 except Exception as err:
144 raise RuntimeError('PAPI init failed:\n{err}'.format(err=repr(err)))
148 def process_value(val):
151 :param val: Value to be processed.
153 :returns: Processed value.
154 :rtype: dict or str or int
156 if isinstance(val, dict):
157 for val_k, val_v in val.iteritems():
158 val[str(val_k)] = process_value(val_v)
160 elif isinstance(val, list):
161 for idx, val_l in enumerate(val):
162 val[idx] = process_value(val_l)
164 elif isinstance(val, unicode):
165 return binascii.unhexlify(val)
166 elif isinstance(val, int):
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)
178 for a_k, a_v in api_args_unicode.items():
179 api_args[str(a_k)] = process_value(a_v)
181 papi_fn = getattr(vpp.api, api_name)
182 rep = papi_fn(**api_args)
184 if isinstance(rep, list):
185 converted_reply = list()
187 converted_reply.append(_convert_reply(r))
189 converted_reply = _convert_reply(rep)
191 api_reply['api_reply'] = converted_reply
192 reply.append(api_reply)
193 except (AttributeError, ValueError) as err:
195 raise RuntimeError('PAPI command {api}({args}) input error:\n{err}'.
199 except Exception as err:
201 raise RuntimeError('PAPI command {api}({args}) error:\n{exc}'.
207 return json.dumps(reply)
210 def process_stats(args):
211 """Process the VPP Stats.
213 :param args: Command line arguments passed to VPP PAPI Provider.
214 :type args: ArgumentParser
215 :returns: JSON formatted string.
217 :raises RuntimeError: If PAPI command error occurs.
221 stats = VPPStats(args.socket)
222 except Exception as err:
223 raise RuntimeError('PAPI init failed:\n{err}'.format(err=repr(err)))
225 json_data = json.loads(args.data)
229 for path in json_data:
230 directory = stats.ls(path)
231 data = stats.dump(directory)
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)))
241 def process_stats_request(args):
242 """Process the VPP Stats requests.
244 :param args: Command line arguments passed to VPP PAPI Provider.
245 :type args: ArgumentParser
246 :returns: JSON formatted string.
248 :raises RuntimeError: If PAPI command error occurs.
252 stats = VPPStats(args.socket)
253 except Exception as err:
254 raise RuntimeError('PAPI init failed:\n{err}'.format(err=repr(err)))
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)))
262 papi_fn = getattr(stats, json_data["api_name"])
263 reply = papi_fn(**json_data.get("api_args", {}))
265 return json.dumps(reply)
269 """Main function for the Python API provider.
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,
277 stats_request=process_stats_request
280 parser = argparse.ArgumentParser(
281 formatter_class=argparse.RawDescriptionHelpFormatter,
283 parser.add_argument("-m", "--method",
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",
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 "
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'.")
302 args = parser.parse_args()
304 return process_request[args.method](args)
307 if __name__ == '__main__':
308 sys.stdout.write(main())