3 # Copyright (c) 2021 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"]]'
43 CLIENT_NAME = u"csit_papi"
46 # Sphinx creates auto-generated documentation by importing the python source
47 # files and collecting the docstrings from them. The NO_VPP_PAPI flag allows
48 # the vpp_papi_provider.py file to be importable without having to build
49 # the whole vpp api if the user only wishes to generate the test documentation.
52 do_import = bool(not os.getenv(u"NO_VPP_PAPI") == u"1")
58 # Find the directory where the modules are installed. The directory depends
60 # TODO: Find a better way to import papi modules.
63 for root, dirs, files in os.walk(u"/usr/lib"):
65 if name == u"vpp_papi.py":
66 modules_path = os.path.split(root)[0]
69 sys.path.append(modules_path)
70 from vpp_papi import VPPApiClient
71 from vpp_papi.vpp_stats import VPPStats
73 raise RuntimeError(u"vpp_papi module not found")
76 def _convert_reply(api_r):
77 """Process API reply / a part of API reply for smooth converting to
80 It is used only with 'request' and 'dump' methods.
82 Apply binascii.hexlify() method for string values.
84 TODO: Implement complex solution to process of replies.
86 :param api_r: API reply.
87 :type api_r: Vpp_serializer reply object (named tuple)
88 :returns: Processed API reply / a part of API reply.
91 unwanted_fields = [u"count", u"index", u"context"]
93 def process_value(val):
96 :param val: Value to be processed.
98 :returns: Processed value.
99 :rtype: dict or str or int
101 if isinstance(val, dict):
102 for val_k, val_v in val.items():
103 val[str(val_k)] = process_value(val_v)
105 elif isinstance(val, list):
106 for idx, val_l in enumerate(val):
107 val[idx] = process_value(val_l)
109 elif isinstance(val, bytes):
111 elif hasattr(val, u"__int__"):
113 elif hasattr(val, "__str__"):
114 return str(val).encode(encoding=u"utf-8").hex()
115 # Next handles parameters not supporting preferred integer or string
116 # representation to get it logged
117 elif hasattr(val, u"__repr__"):
123 reply_key = repr(api_r).split(u"(")[0]
125 for item in dir(api_r):
126 if not item.startswith(u"_") and item not in unwanted_fields:
127 reply_value[item] = process_value(getattr(api_r, item))
128 reply_dict[reply_key] = reply_value
132 def process_json_request(args):
133 """Process the request/reply and dump classes of VPP API methods.
135 :param args: Command line arguments passed to VPP PAPI Provider.
136 :type args: ArgumentParser
137 :returns: JSON formatted string.
139 :raises RuntimeError: If PAPI command error occurs.
144 except Exception as err:
145 raise RuntimeError(f"PAPI init failed:\n{err!r}")
149 def process_value(val):
152 :param val: Value to be processed.
154 :returns: Processed value.
155 :rtype: dict or str or int
157 if isinstance(val, dict):
158 for val_k, val_v in val.items():
159 val[str(val_k)] = process_value(val_v)
161 elif isinstance(val, list):
162 for idx, val_l in enumerate(val):
163 val[idx] = process_value(val_l)
165 elif isinstance(val, str):
166 return bytes.fromhex(val).decode(encoding=u"utf-8")
167 elif isinstance(val, int):
172 json_data = json.loads(args.data)
173 vpp.connect(CLIENT_NAME)
174 for data in json_data:
175 api_name = data[u"api_name"]
176 api_args_unicode = data[u"api_args"]
177 api_reply = dict(api_name=api_name)
179 for a_k, a_v in api_args_unicode.items():
180 api_args[str(a_k)] = process_value(a_v)
182 papi_fn = getattr(vpp.api, api_name)
183 rep = papi_fn(**api_args)
185 if isinstance(rep, list):
186 converted_reply = list()
188 converted_reply.append(_convert_reply(r))
190 converted_reply = _convert_reply(rep)
192 api_reply[u"api_reply"] = converted_reply
193 reply.append(api_reply)
194 except (AttributeError, ValueError) as err:
197 f"PAPI command {api_name}({api_args}) input error:\n{err!r}"
199 except Exception as err:
202 f"PAPI command {api_name}({api_args}) error:\n{err!r}"
206 return json.dumps(reply)
209 def process_stats(args):
210 """Process the VPP Stats.
212 The reply contains single item covering all paths.
214 :param args: Command line arguments passed to VPP PAPI Provider.
215 :type args: ArgumentParser
216 :returns: JSON formatted string.
218 :raises RuntimeError: If PAPI command error occurs.
222 stats = VPPStats(args.socket)
223 except Exception as err:
224 raise RuntimeError(f"PAPI init failed:\n{err!r}")
226 paths = json.loads(args.data)
227 directory = stats.ls(paths)
228 reply = [stats.dump(directory)]
231 return json.dumps(reply)
232 except UnicodeDecodeError as err:
233 raise RuntimeError(f"PAPI reply {reply} error:\n{err!r}")
236 def process_stats_request(args):
237 """Process the VPP Stats requests.
239 :param args: Command line arguments passed to VPP PAPI Provider.
240 :type args: ArgumentParser
241 :returns: JSON formatted string.
243 :raises RuntimeError: If PAPI command error occurs.
247 stats = VPPStats(args.socket)
248 except Exception as err:
249 raise RuntimeError(f"PAPI init failed:\n{err!r}")
252 json_data = json.loads(args.data)
253 except ValueError as err:
254 raise RuntimeError(f"Input json string is invalid:\n{err!r}")
256 papi_fn = getattr(stats, json_data[u"api_name"])
257 reply = papi_fn(**json_data.get(u"api_args", {}))
259 return json.dumps(reply)
263 """Main function for the Python API provider.
266 # The functions which process different types of VPP Python API methods.
267 process_request = dict(
268 request=process_json_request,
269 dump=process_json_request,
271 stats_request=process_stats_request
274 parser = argparse.ArgumentParser(
275 formatter_class=argparse.RawDescriptionHelpFormatter,
279 u"-m", u"--method", required=True,
280 choices=[str(key) for key in process_request.keys()],
281 help=u"Specifies the VPP API methods: "
282 u"1. request - simple request / reply; "
283 u"2. dump - dump function;"
284 u"3. stats - VPP statistics."
287 u"-d", u"--data", required=True,
288 help=u"If the method is 'request' or 'dump', data is a JSON string "
289 u"(list) containing API name(s) and its/their input argument(s). "
290 u"If the method is 'stats', data is a JSON string containing t"
291 u"he list of path(s) to the required data."
294 u"-s", u"--socket", default=u"/var/run/vpp/stats.sock",
295 help=u"A file descriptor over the VPP stats Unix domain socket. "
296 u"It is used only if method=='stats'."
299 args = parser.parse_args()
301 return process_request[args.method](args)
304 if __name__ == u"__main__":
305 sys.stdout.write(main())