blob: 30c00cd8dd318425957a204b0ecb147261723e8e [file] [log] [blame]
Renato Botelho do Coutoead1e532019-10-31 13:31:07 -05001#!/usr/bin/env python3
Ole Troan5f9dcff2016-08-01 04:59:13 +02002#
3# Copyright (c) 2016 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.
Ole Troana03f4ef2016-12-02 12:53:55 +010015#
Ole Troan5f9dcff2016-08-01 04:59:13 +020016
Ole Troan5f9dcff2016-08-01 04:59:13 +020017from __future__ import print_function
Ole Troana7564e82018-06-12 21:06:44 +020018from __future__ import absolute_import
Paul Vinciguerra2af6e922019-06-06 07:06:09 -040019import ctypes
Paul Vinciguerrae64e5ff2020-04-28 00:27:38 -040020import ipaddress
Paul Vinciguerra2af6e922019-06-06 07:06:09 -040021import multiprocessing as mp
Ole Troan4df97162017-07-07 16:06:08 +020022import os
Paul Vinciguerrad7adc292020-12-02 14:34:27 -050023import queue
Ole Troan4df97162017-07-07 16:06:08 +020024import logging
Paul Vinciguerra5fced042019-02-26 20:39:44 -080025import functools
Ole Troan4df97162017-07-07 16:06:08 +020026import json
27import threading
Chris Luke52bf22e2017-11-03 23:32:38 -040028import fnmatch
Klement Sekera180402d2018-02-17 10:58:37 +010029import weakref
Ole Troan4df97162017-07-07 16:06:08 +020030import atexit
Ole Troanfd574082019-11-27 23:12:48 +010031import time
Ole Troanac0babd2024-01-23 18:56:23 +010032import pkg_resources
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020033from .vpp_format import verify_enum_hint
34from .vpp_serializer import VPPType, VPPEnumType, VPPEnumFlagType, VPPUnionType
35from .vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias
Ole Troan4df97162017-07-07 16:06:08 +020036
Paul Vinciguerra46d68642020-12-01 02:00:35 -050037try:
38 import VppTransport
39except ModuleNotFoundError:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020040
Paul Vinciguerra46d68642020-12-01 02:00:35 -050041 class V:
42 """placeholder for VppTransport as the implementation is dependent on
43 VPPAPIClient's initialization values
44 """
45
46 VppTransport = V
47
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020048from .vpp_transport_socket import VppTransport
Ole Troan041372b2021-05-04 12:45:57 +020049
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020050logger = logging.getLogger("vpp_papi")
Paul Vinciguerra46d68642020-12-01 02:00:35 -050051logger.addHandler(logging.NullHandler())
52
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020053__all__ = (
54 "FuncWrapper",
55 "VppApiDynamicMethodHolder",
56 "VppEnum",
57 "VppEnumType",
58 "VppEnumFlag",
59 "VPPIOError",
60 "VPPRuntimeError",
61 "VPPValueError",
62 "VPPApiClient",
63)
Paul Vinciguerraae8819f2019-06-07 13:35:37 -040064
Ole Troanafddd832018-02-28 14:55:20 +010065
Paul Vinciguerra5fced042019-02-26 20:39:44 -080066def metaclass(metaclass):
67 @functools.wraps(metaclass)
68 def wrapper(cls):
69 return metaclass(cls.__name__, cls.__bases__, cls.__dict__.copy())
70
71 return wrapper
72
73
Ole Troan0685da42018-10-16 14:42:50 +020074class VppEnumType(type):
75 def __getattr__(cls, name):
76 t = vpp_get_type(name)
77 return t.enum
78
79
Paul Vinciguerra5fced042019-02-26 20:39:44 -080080@metaclass(VppEnumType)
Paul Vinciguerrad7adc292020-12-02 14:34:27 -050081class VppEnum:
Paul Vinciguerra5fced042019-02-26 20:39:44 -080082 pass
Ole Troan0685da42018-10-16 14:42:50 +020083
84
Paul Vinciguerra3825d932020-12-03 21:06:28 -050085@metaclass(VppEnumType)
86class VppEnumFlag:
87 pass
88
89
Klement Sekera180402d2018-02-17 10:58:37 +010090def vpp_atexit(vpp_weakref):
Ole Troan5016f992017-01-19 09:44:44 +010091 """Clean up VPP connection on shutdown."""
Klement Sekera180402d2018-02-17 10:58:37 +010092 vpp_instance = vpp_weakref()
Ole Troan94495f22018-08-02 11:58:12 +020093 if vpp_instance and vpp_instance.transport.connected:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020094 logger.debug("Cleaning up VPP on exit")
Klement Sekera180402d2018-02-17 10:58:37 +010095 vpp_instance.disconnect()
Ole Troan5016f992017-01-19 09:44:44 +010096
Ole Troan8006c6a2018-12-17 12:02:26 +010097
Paul Vinciguerrae64e5ff2020-04-28 00:27:38 -040098def add_convenience_methods():
99 # provide convenience methods to IP[46]Address.vapi_af
100 def _vapi_af(self):
101 if 6 == self._version:
102 return VppEnum.vl_api_address_family_t.ADDRESS_IP6.value
103 if 4 == self._version:
104 return VppEnum.vl_api_address_family_t.ADDRESS_IP4.value
105 raise ValueError("Invalid _version.")
106
107 def _vapi_af_name(self):
108 if 6 == self._version:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200109 return "ip6"
Paul Vinciguerrae64e5ff2020-04-28 00:27:38 -0400110 if 4 == self._version:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200111 return "ip4"
Paul Vinciguerrae64e5ff2020-04-28 00:27:38 -0400112 raise ValueError("Invalid _version.")
113
114 ipaddress._IPAddressBase.vapi_af = property(_vapi_af)
115 ipaddress._IPAddressBase.vapi_af_name = property(_vapi_af_name)
116
117
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500118class VppApiDynamicMethodHolder:
Klement Sekera7112c542017-03-01 09:53:19 +0100119 pass
120
121
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500122class FuncWrapper:
Klement Sekera7112c542017-03-01 09:53:19 +0100123 def __init__(self, func):
124 self._func = func
125 self.__name__ = func.__name__
Paul Vinciguerrab8daa252019-03-19 15:04:17 -0700126 self.__doc__ = func.__doc__
Klement Sekera7112c542017-03-01 09:53:19 +0100127
128 def __call__(self, **kwargs):
129 return self._func(**kwargs)
130
Paul Vinciguerra48664592019-06-19 22:19:02 -0400131 def __repr__(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200132 return "<FuncWrapper(func=<%s(%s)>)>" % (self.__name__, self.__doc__)
Paul Vinciguerra48664592019-06-19 22:19:02 -0400133
Klement Sekera7112c542017-03-01 09:53:19 +0100134
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800135class VPPApiError(Exception):
136 pass
137
138
139class VPPNotImplementedError(NotImplementedError):
140 pass
141
142
143class VPPIOError(IOError):
144 pass
145
146
147class VPPRuntimeError(RuntimeError):
148 pass
149
150
151class VPPValueError(ValueError):
152 pass
153
Paul Vinciguerrae64e5ff2020-04-28 00:27:38 -0400154
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500155class VPPApiJSONFiles:
Chris Luke52bf22e2017-11-03 23:32:38 -0400156 @classmethod
Pim van Pelt15882222022-12-12 00:00:16 +0000157 def find_api_dir(cls, dirs=[]):
Chris Luke52bf22e2017-11-03 23:32:38 -0400158 """Attempt to find the best directory in which API definition
159 files may reside. If the value VPP_API_DIR exists in the environment
160 then it is first on the search list. If we're inside a recognized
161 location in a VPP source tree (src/scripts and src/vpp-api/python)
162 then entries from there to the likely locations in build-root are
163 added. Finally the location used by system packages is added.
164
165 :returns: A single directory name, or None if no such directory
166 could be found.
167 """
Chris Luke52bf22e2017-11-03 23:32:38 -0400168
169 # perhaps we're in the 'src/scripts' or 'src/vpp-api/python' dir;
170 # in which case, plot a course to likely places in the src tree
171 import __main__ as main
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200172
Pim van Pelt15882222022-12-12 00:00:16 +0000173 if os.getenv("VPP_API_DIR"):
174 dirs.append(os.getenv("VPP_API_DIR"))
175
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200176 if hasattr(main, "__file__"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400177 # get the path of the calling script
178 localdir = os.path.dirname(os.path.realpath(main.__file__))
179 else:
180 # use cwd if there is no calling script
Andrey "Zed" Zaikin68e2ffb2018-04-24 14:50:02 +0300181 localdir = os.getcwd()
Chris Luke52bf22e2017-11-03 23:32:38 -0400182 localdir_s = localdir.split(os.path.sep)
183
184 def dmatch(dir):
185 """Match dir against right-hand components of the script dir"""
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200186 d = dir.split("/") # param 'dir' assumes a / separator
Ole Troanafddd832018-02-28 14:55:20 +0100187 length = len(d)
188 return len(localdir_s) > length and localdir_s[-length:] == d
Chris Luke52bf22e2017-11-03 23:32:38 -0400189
190 def sdir(srcdir, variant):
191 """Build a path from srcdir to the staged API files of
192 'variant' (typically '' or '_debug')"""
193 # Since 'core' and 'plugin' files are staged
194 # in separate directories, we target the parent dir.
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200195 return os.path.sep.join(
196 (
197 srcdir,
198 "build-root",
199 "install-vpp%s-native" % variant,
200 "vpp",
201 "share",
202 "vpp",
203 "api",
204 )
205 )
Chris Luke52bf22e2017-11-03 23:32:38 -0400206
207 srcdir = None
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200208 if dmatch("src/scripts"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400209 srcdir = os.path.sep.join(localdir_s[:-2])
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200210 elif dmatch("src/vpp-api/python"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400211 srcdir = os.path.sep.join(localdir_s[:-3])
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200212 elif dmatch("test"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400213 # we're apparently running tests
214 srcdir = os.path.sep.join(localdir_s[:-1])
215
216 if srcdir:
217 # we're in the source tree, try both the debug and release
218 # variants.
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200219 dirs.append(sdir(srcdir, "_debug"))
220 dirs.append(sdir(srcdir, ""))
Chris Luke52bf22e2017-11-03 23:32:38 -0400221
222 # Test for staged copies of the scripts
223 # For these, since we explicitly know if we're running a debug versus
224 # release variant, target only the relevant directory
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200225 if dmatch("build-root/install-vpp_debug-native/vpp/bin"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400226 srcdir = os.path.sep.join(localdir_s[:-4])
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200227 dirs.append(sdir(srcdir, "_debug"))
228 if dmatch("build-root/install-vpp-native/vpp/bin"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400229 srcdir = os.path.sep.join(localdir_s[:-4])
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200230 dirs.append(sdir(srcdir, ""))
Chris Luke52bf22e2017-11-03 23:32:38 -0400231
232 # finally, try the location system packages typically install into
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200233 dirs.append(os.path.sep.join(("", "usr", "share", "vpp", "api")))
Chris Luke52bf22e2017-11-03 23:32:38 -0400234
Paul Vinciguerra19542292019-03-17 17:34:46 -0700235 # check the directories for existence; first one wins
Chris Luke52bf22e2017-11-03 23:32:38 -0400236 for dir in dirs:
237 if os.path.isdir(dir):
238 return dir
239
240 return None
241
242 @classmethod
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200243 def find_api_files(cls, api_dir=None, patterns="*"): # -> list
Chris Luke52bf22e2017-11-03 23:32:38 -0400244 """Find API definition files from the given directory tree with the
245 given pattern. If no directory is given then find_api_dir() is used
246 to locate one. If no pattern is given then all definition files found
247 in the directory tree are used.
248
249 :param api_dir: A directory tree in which to locate API definition
250 files; subdirectories are descended into.
251 If this is None then find_api_dir() is called to discover it.
252 :param patterns: A list of patterns to use in each visited directory
253 when looking for files.
254 This can be a list/tuple object or a comma-separated string of
255 patterns. Each value in the list will have leading/trialing
256 whitespace stripped.
257 The pattern specifies the first part of the filename, '.api.json'
258 is appended.
259 The results are de-duplicated, thus overlapping patterns are fine.
260 If this is None it defaults to '*' meaning "all API files".
261 :returns: A list of file paths for the API files found.
262 """
263 if api_dir is None:
Ole Troanedfe2c02019-07-30 15:38:13 +0200264 api_dir = cls.find_api_dir([])
Chris Luke52bf22e2017-11-03 23:32:38 -0400265 if api_dir is None:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800266 raise VPPApiError("api_dir cannot be located")
Chris Luke52bf22e2017-11-03 23:32:38 -0400267
268 if isinstance(patterns, list) or isinstance(patterns, tuple):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200269 patterns = [p.strip() + ".api.json" for p in patterns]
Chris Luke52bf22e2017-11-03 23:32:38 -0400270 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200271 patterns = [p.strip() + ".api.json" for p in patterns.split(",")]
Chris Luke52bf22e2017-11-03 23:32:38 -0400272
273 api_files = []
274 for root, dirnames, files in os.walk(api_dir):
275 # iterate all given patterns and de-dup the result
276 files = set(sum([fnmatch.filter(files, p) for p in patterns], []))
277 for filename in files:
278 api_files.append(os.path.join(root, filename))
279
280 return api_files
281
Ole Troanedfe2c02019-07-30 15:38:13 +0200282 @classmethod
283 def process_json_file(self, apidef_file):
Ole Troanac0babd2024-01-23 18:56:23 +0100284 api = json.load(apidef_file)
285 return self._process_json(api)
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500286
287 @classmethod
288 def process_json_str(self, json_str):
Ole Troanac0babd2024-01-23 18:56:23 +0100289 api = json.loads(json_str)
290 return self._process_json(api)
291
292 @classmethod
293 def process_json_array_str(self, json_str):
294 services = {}
295 messages = {}
296
297 apis = json.loads(json_str)
298 for a in apis:
299 m, s = self._process_json(a)
300 messages.update(m)
301 services.update(s)
302 return messages, services
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500303
304 @staticmethod
Ole Troanac0babd2024-01-23 18:56:23 +0100305 def _process_json(api): # -> Tuple[Dict, Dict]
Ole Troanedfe2c02019-07-30 15:38:13 +0200306 types = {}
307 services = {}
308 messages = {}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500309 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200310 for t in api["enums"]:
311 t[0] = "vl_api_" + t[0] + "_t"
312 types[t[0]] = {"type": "enum", "data": t}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500313 except KeyError:
314 pass
Paul Vinciguerra3825d932020-12-03 21:06:28 -0500315 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200316 for t in api["enumflags"]:
317 t[0] = "vl_api_" + t[0] + "_t"
318 types[t[0]] = {"type": "enum", "data": t}
Paul Vinciguerra3825d932020-12-03 21:06:28 -0500319 except KeyError:
320 pass
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500321 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200322 for t in api["unions"]:
323 t[0] = "vl_api_" + t[0] + "_t"
324 types[t[0]] = {"type": "union", "data": t}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500325 except KeyError:
326 pass
327
328 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200329 for t in api["types"]:
330 t[0] = "vl_api_" + t[0] + "_t"
331 types[t[0]] = {"type": "type", "data": t}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500332 except KeyError:
333 pass
334
335 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200336 for t, v in api["aliases"].items():
337 types["vl_api_" + t + "_t"] = {"type": "alias", "data": v}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500338 except KeyError:
339 pass
340
341 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200342 services.update(api["services"])
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500343 except KeyError:
344 pass
Ole Troanedfe2c02019-07-30 15:38:13 +0200345
346 i = 0
347 while True:
348 unresolved = {}
349 for k, v in types.items():
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200350 t = v["data"]
Ole Troanedfe2c02019-07-30 15:38:13 +0200351 if not vpp_get_type(k):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200352 if v["type"] == "enum":
Ole Troanedfe2c02019-07-30 15:38:13 +0200353 try:
354 VPPEnumType(t[0], t[1:])
355 except ValueError:
356 unresolved[k] = v
Paul Vinciguerra3825d932020-12-03 21:06:28 -0500357 if not vpp_get_type(k):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200358 if v["type"] == "enumflag":
Paul Vinciguerra3825d932020-12-03 21:06:28 -0500359 try:
360 VPPEnumFlagType(t[0], t[1:])
361 except ValueError:
362 unresolved[k] = v
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200363 elif v["type"] == "union":
Ole Troanedfe2c02019-07-30 15:38:13 +0200364 try:
365 VPPUnionType(t[0], t[1:])
366 except ValueError:
367 unresolved[k] = v
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200368 elif v["type"] == "type":
Ole Troanedfe2c02019-07-30 15:38:13 +0200369 try:
370 VPPType(t[0], t[1:])
371 except ValueError:
372 unresolved[k] = v
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200373 elif v["type"] == "alias":
Ole Troanedfe2c02019-07-30 15:38:13 +0200374 try:
375 VPPTypeAlias(k, t)
376 except ValueError:
377 unresolved[k] = v
378 if len(unresolved) == 0:
379 break
380 if i > 3:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200381 raise VPPValueError("Unresolved type definitions {}".format(unresolved))
Ole Troanedfe2c02019-07-30 15:38:13 +0200382 types = unresolved
383 i += 1
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500384 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200385 for m in api["messages"]:
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500386 try:
387 messages[m[0]] = VPPMessage(m[0], m[1:])
388 except VPPNotImplementedError:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200389 logger.error("Not implemented error for {}".format(m[0]))
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500390 except KeyError:
391 pass
Ole Troanedfe2c02019-07-30 15:38:13 +0200392 return messages, services
393
Maxime Peim77caeb12023-11-14 15:26:41 +0100394 @staticmethod
395 def load_api(apifiles=None, apidir=None):
396 messages = {}
397 services = {}
398 if not apifiles:
399 # Pick up API definitions from default directory
400 try:
401 if isinstance(apidir, list):
402 apifiles = []
403 for d in apidir:
404 apifiles += VPPApiJSONFiles.find_api_files(d)
405 else:
406 apifiles = VPPApiJSONFiles.find_api_files(apidir)
407 except (RuntimeError, VPPApiError):
408 raise VPPRuntimeError
409
410 for file in apifiles:
411 with open(file) as apidef_file:
412 m, s = VPPApiJSONFiles.process_json_file(apidef_file)
413 messages.update(m)
414 services.update(s)
415
416 return apifiles, messages, services
417
Paul Vinciguerrae64e5ff2020-04-28 00:27:38 -0400418
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500419class VPPApiClient:
Ole Troanedfe2c02019-07-30 15:38:13 +0200420 """VPP interface.
421
422 This class provides the APIs to VPP. The APIs are loaded
423 from provided .api.json files and makes functions accordingly.
424 These functions are documented in the VPP .api files, as they
425 are dynamically created.
426
427 Additionally, VPP can send callback messages; this class
428 provides a means to register a callback function to receive
429 these messages in a background thread.
430 """
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200431
Ole Troanedfe2c02019-07-30 15:38:13 +0200432 VPPApiError = VPPApiError
433 VPPRuntimeError = VPPRuntimeError
434 VPPValueError = VPPValueError
435 VPPNotImplementedError = VPPNotImplementedError
436 VPPIOError = VPPIOError
437
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200438 def __init__(
439 self,
440 *,
441 apifiles=None,
Maxime Peim77caeb12023-11-14 15:26:41 +0100442 apidir=None,
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200443 testmode=False,
444 async_thread=True,
445 logger=None,
446 loglevel=None,
447 read_timeout=5,
448 use_socket=True,
449 server_address="/run/vpp/api.sock",
Ole Troanac0babd2024-01-23 18:56:23 +0100450 bootstrapapi=False,
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200451 ):
Ole Troanedfe2c02019-07-30 15:38:13 +0200452 """Create a VPP API object.
453
454 apifiles is a list of files containing API
455 descriptions that will be loaded - methods will be
456 dynamically created reflecting these APIs. If not
457 provided this will load the API files from VPP's
458 default install location.
459
460 logger, if supplied, is the logging logger object to log to.
461 loglevel, if supplied, is the log level this logger is set
462 to report at (from the loglevels in the logging module).
463 """
464 if logger is None:
465 logger = logging.getLogger(
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200466 "{}.{}".format(__name__, self.__class__.__name__)
467 )
Ole Troanedfe2c02019-07-30 15:38:13 +0200468 if loglevel is not None:
469 logger.setLevel(loglevel)
470 self.logger = logger
471
472 self.messages = {}
473 self.services = {}
474 self.id_names = []
475 self.id_msgdef = []
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200476 self.header = VPPType("header", [["u16", "msgid"], ["u32", "client_index"]])
Ole Troanedfe2c02019-07-30 15:38:13 +0200477 self.apifiles = []
Maxime Peim77caeb12023-11-14 15:26:41 +0100478 self.apidir = apidir
Ole Troanedfe2c02019-07-30 15:38:13 +0200479 self.event_callback = None
480 self.message_queue = queue.Queue()
481 self.read_timeout = read_timeout
482 self.async_thread = async_thread
483 self.event_thread = None
484 self.testmode = testmode
Ole Troanedfe2c02019-07-30 15:38:13 +0200485 self.server_address = server_address
486 self._apifiles = apifiles
Ole Troanfd574082019-11-27 23:12:48 +0100487 self.stats = {}
Ole Troanac0babd2024-01-23 18:56:23 +0100488 self.bootstrapapi = bootstrapapi
Ole Troanedfe2c02019-07-30 15:38:13 +0200489
Ole Troanac0babd2024-01-23 18:56:23 +0100490 if not bootstrapapi:
491 if self.apidir is None and hasattr(self.__class__, "apidir"):
492 # Keep supporting the old style of providing apidir.
493 self.apidir = self.__class__.apidir
494 try:
495 self.apifiles, self.messages, self.services = VPPApiJSONFiles.load_api(
496 apifiles, self.apidir
497 )
498 except VPPRuntimeError as e:
499 if testmode:
500 self.apifiles = []
501 else:
502 raise e
503 else:
504 # Bootstrap the API (memclnt.api bundled with VPP PAPI)
505 resource_path = "/".join(("data", "memclnt.api.json"))
506 file_content = pkg_resources.resource_string(__name__, resource_path)
507 self.messages, self.services = VPPApiJSONFiles.process_json_str(
508 file_content
Maxime Peim77caeb12023-11-14 15:26:41 +0100509 )
Ole Troanedfe2c02019-07-30 15:38:13 +0200510
511 # Basic sanity check
512 if len(self.messages) == 0 and not testmode:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200513 raise VPPValueError(1, "Missing JSON message definitions")
Ole Troanac0babd2024-01-23 18:56:23 +0100514 if not bootstrapapi:
515 if not (verify_enum_hint(VppEnum.vl_api_address_family_t)):
516 raise VPPRuntimeError(
517 "Invalid address family hints. " "Cannot continue."
518 )
Ole Troanedfe2c02019-07-30 15:38:13 +0200519
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200520 self.transport = VppTransport(
521 self, read_timeout=read_timeout, server_address=server_address
522 )
Ole Troanedfe2c02019-07-30 15:38:13 +0200523 # Make sure we allow VPP to clean up the message rings.
524 atexit.register(vpp_atexit, weakref.ref(self))
525
Paul Vinciguerrae64e5ff2020-04-28 00:27:38 -0400526 add_convenience_methods()
527
Ole Troanedfe2c02019-07-30 15:38:13 +0200528 def get_function(self, name):
529 return getattr(self._api, name)
530
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500531 class ContextId:
Ole Troanedfe2c02019-07-30 15:38:13 +0200532 """Multiprocessing-safe provider of unique context IDs."""
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200533
Ole Troanedfe2c02019-07-30 15:38:13 +0200534 def __init__(self):
535 self.context = mp.Value(ctypes.c_uint, 0)
536 self.lock = mp.Lock()
537
538 def __call__(self):
539 """Get a new unique (or, at least, not recently used) context."""
540 with self.lock:
541 self.context.value += 1
542 return self.context.value
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200543
Ole Troanedfe2c02019-07-30 15:38:13 +0200544 get_context = ContextId()
545
546 def get_type(self, name):
547 return vpp_get_type(name)
548
Klement Sekera7112c542017-03-01 09:53:19 +0100549 @property
550 def api(self):
551 if not hasattr(self, "_api"):
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800552 raise VPPApiError("Not connected, api definitions not available")
Klement Sekera7112c542017-03-01 09:53:19 +0100553 return self._api
554
Ole Troaneabd6072018-08-09 12:50:55 +0200555 def make_function(self, msg, i, multipart, do_async):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200556 if do_async:
557
Ole Troana7564e82018-06-12 21:06:44 +0200558 def f(**kwargs):
559 return self._call_vpp_async(i, msg, **kwargs)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200560
Ole Troana7564e82018-06-12 21:06:44 +0200561 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200562
Ole Troana7564e82018-06-12 21:06:44 +0200563 def f(**kwargs):
564 return self._call_vpp(i, msg, multipart, **kwargs)
565
566 f.__name__ = str(msg.name)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200567 f.__doc__ = ", ".join(
568 ["%s %s" % (msg.fieldtypes[j], k) for j, k in enumerate(msg.fields)]
569 )
Ole Troanf159f582019-02-28 20:20:47 +0100570 f.msg = msg
571
Ole Troana7564e82018-06-12 21:06:44 +0200572 return f
573
Ole Troanbcdde1a2022-12-06 17:42:24 +0100574 def make_pack_function(self, msg, i, multipart):
575 def f(**kwargs):
576 return self._call_vpp_pack(i, msg, **kwargs)
577
578 f.msg = msg
579 return f
580
Ole Troaneabd6072018-08-09 12:50:55 +0200581 def _register_functions(self, do_async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100582 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
583 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera8aedf5e2018-07-06 11:07:21 +0200584 self._api = VppApiDynamicMethodHolder()
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500585 for name, msg in self.messages.items():
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200586 n = name + "_" + msg.crc[2:]
Ole Troandaa4bff2019-08-28 14:12:02 +0200587 i = self.transport.get_msg_index(n)
Ole Troan3cc49712017-03-08 12:02:24 +0100588 if i > 0:
Ole Troana7564e82018-06-12 21:06:44 +0200589 self.id_msgdef[i] = msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100590 self.id_names[i] = name
Ole Troandfb984d2018-12-07 14:31:16 +0100591
592 # Create function for client side messages.
593 if name in self.services:
Ole Troanf5db3712020-05-20 15:47:06 +0200594 f = self.make_function(msg, i, self.services[name], do_async)
Ole Troanbcdde1a2022-12-06 17:42:24 +0100595 f_pack = self.make_pack_function(msg, i, self.services[name])
Ole Troandfb984d2018-12-07 14:31:16 +0100596 setattr(self._api, name, FuncWrapper(f))
Ole Troanbcdde1a2022-12-06 17:42:24 +0100597 setattr(self._api, name + "_pack", FuncWrapper(f_pack))
Ole Troan3cc49712017-03-08 12:02:24 +0100598 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200599 self.logger.debug("No such message type or failed CRC checksum: %s", n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100600
Ole Troanac0babd2024-01-23 18:56:23 +0100601 def get_api_definitions(self):
602 """get_api_definition. Bootstrap from the embedded memclnt.api.json file."""
603
604 # Bootstrap so we can call the get_api_json function
605 self._register_functions(do_async=False)
606
607 r = self.api.get_api_json()
608 if r.retval != 0:
609 raise VPPApiError("Failed to load API definitions from VPP")
610
611 # Process JSON
612 m, s = VPPApiJSONFiles.process_json_array_str(r.json)
613 self.messages.update(m)
614 self.services.update(s)
615
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200616 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, do_async):
617 pfx = chroot_prefix.encode("utf-8") if chroot_prefix else None
Ole Troan94495f22018-08-02 11:58:12 +0200618
Ole Troanf22824b2022-12-06 17:30:49 +0100619 rv = self.transport.connect(name, pfx, msg_handler, rx_qlen, do_async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100620 if rv != 0:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200621 raise VPPIOError(2, "Connect failed")
Ole Troan94495f22018-08-02 11:58:12 +0200622 self.vpp_dictionary_maxid = self.transport.msg_table_max_index()
Ole Troanac0babd2024-01-23 18:56:23 +0100623
624 # Register functions
625 if self.bootstrapapi:
626 self.get_api_definitions()
Ole Troaneabd6072018-08-09 12:50:55 +0200627 self._register_functions(do_async=do_async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100628
629 # Initialise control ping
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200630 crc = self.messages["control_ping"].crc
Ole Troan94495f22018-08-02 11:58:12 +0200631 self.control_ping_index = self.transport.get_msg_index(
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200632 ("control_ping" + "_" + crc[2:])
633 )
634 self.control_ping_msgdef = self.messages["control_ping"]
Ole Troanac0babd2024-01-23 18:56:23 +0100635
Klement Sekera180402d2018-02-17 10:58:37 +0100636 if self.async_thread:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200637 self.event_thread = threading.Thread(target=self.thread_msg_handler)
Klement Sekera180402d2018-02-17 10:58:37 +0100638 self.event_thread.daemon = True
639 self.event_thread.start()
Vratko Polak94e45312019-05-27 18:36:23 +0200640 else:
641 self.event_thread = None
Ole Troan4df97162017-07-07 16:06:08 +0200642 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100643
Ole Troaneabd6072018-08-09 12:50:55 +0200644 def connect(self, name, chroot_prefix=None, do_async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100645 """Attach to VPP.
646
647 name - the name of the client.
648 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
Ole Troaneabd6072018-08-09 12:50:55 +0200649 do_async - if true, messages are sent without waiting for a reply
Ole Troandfc9b7c2017-03-06 23:51:57 +0100650 rx_qlen - the length of the VPP message receive queue between
651 client and server.
652 """
Ole Troan94495f22018-08-02 11:58:12 +0200653 msg_handler = self.transport.get_callback(do_async)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200654 return self.connect_internal(
655 name, msg_handler, chroot_prefix, rx_qlen, do_async
656 )
Ole Troandfc9b7c2017-03-06 23:51:57 +0100657
Ole Troan6bf177c2017-08-17 10:34:32 +0200658 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100659 """Attach to VPP in synchronous mode. Application must poll for events.
660
661 name - the name of the client.
662 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
663 rx_qlen - the length of the VPP message receive queue between
664 client and server.
665 """
666
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200667 return self.connect_internal(name, None, chroot_prefix, rx_qlen, do_async=False)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100668
Ole Troana03f4ef2016-12-02 12:53:55 +0100669 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100670 """Detach from VPP."""
Ole Troan94495f22018-08-02 11:58:12 +0200671 rv = self.transport.disconnect()
Vratko Polak94e45312019-05-27 18:36:23 +0200672 if self.event_thread is not None:
673 self.message_queue.put("terminate event thread")
Ole Troana03f4ef2016-12-02 12:53:55 +0100674 return rv
675
Ole Troan5016f992017-01-19 09:44:44 +0100676 def msg_handler_sync(self, msg):
677 """Process an incoming message from VPP in sync mode.
678
679 The message may be a reply or it may be an async notification.
680 """
681 r = self.decode_incoming_msg(msg)
682 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100683 return
684
Ole Troan5016f992017-01-19 09:44:44 +0100685 # If we have a context, then use the context to find any
686 # request waiting for a reply
687 context = 0
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200688 if hasattr(r, "context") and r.context > 0:
Ole Troan5016f992017-01-19 09:44:44 +0100689 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200690
Ole Troan5016f992017-01-19 09:44:44 +0100691 if context == 0:
692 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100693 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100694 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200695 raise VPPIOError(2, "RPC reply message received in event handler")
Ole Troan5016f992017-01-19 09:44:44 +0100696
Ole Troan413f4a52018-11-28 11:36:05 +0100697 def has_context(self, msg):
698 if len(msg) < 10:
699 return False
700
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200701 header = VPPType(
702 "header_with_context",
703 [["u16", "msgid"], ["u32", "client_index"], ["u32", "context"]],
704 )
Ole Troan413f4a52018-11-28 11:36:05 +0100705
706 (i, ci, context), size = header.unpack(msg, 0)
Ole Troanac0babd2024-01-23 18:56:23 +0100707
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200708 if self.id_names[i] == "rx_thread_exit":
Ole Troan413f4a52018-11-28 11:36:05 +0100709 return
710
711 #
712 # Decode message and returns a tuple.
713 #
714 msgobj = self.id_msgdef[i]
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200715 if "context" in msgobj.field_by_name and context >= 0:
Ole Troan413f4a52018-11-28 11:36:05 +0100716 return True
717 return False
718
Ole Troan0bcad322018-12-11 13:04:01 +0100719 def decode_incoming_msg(self, msg, no_type_conversion=False):
Ole Troan5016f992017-01-19 09:44:44 +0100720 if not msg:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200721 logger.warning("vpp_api.read failed")
Ole Troan5016f992017-01-19 09:44:44 +0100722 return
Ole Troan413f4a52018-11-28 11:36:05 +0100723
Ole Troanc84cbad2018-09-06 22:58:05 +0200724 (i, ci), size = self.header.unpack(msg, 0)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200725 if self.id_names[i] == "rx_thread_exit":
Ole Troan5016f992017-01-19 09:44:44 +0100726 return
727
728 #
729 # Decode message and returns a tuple.
730 #
Ole Troana7564e82018-06-12 21:06:44 +0200731 msgobj = self.id_msgdef[i]
732 if not msgobj:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200733 raise VPPIOError(2, "Reply message undefined")
Ole Troan5016f992017-01-19 09:44:44 +0100734
Ole Troan0bcad322018-12-11 13:04:01 +0100735 r, size = msgobj.unpack(msg, ntc=no_type_conversion)
Ole Troana03f4ef2016-12-02 12:53:55 +0100736 return r
737
Ole Troan5016f992017-01-19 09:44:44 +0100738 def msg_handler_async(self, msg):
739 """Process a message from VPP in async mode.
740
741 In async mode, all messages are returned to the callback.
742 """
743 r = self.decode_incoming_msg(msg)
744 if r is None:
745 return
746
747 msgname = type(r).__name__
748
Ole Troan4df97162017-07-07 16:06:08 +0200749 if self.event_callback:
750 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100751
752 def _control_ping(self, context):
753 """Send a ping command."""
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200754 self._call_vpp_async(
755 self.control_ping_index, self.control_ping_msgdef, context=context
756 )
Ole Troan5016f992017-01-19 09:44:44 +0100757
Ole Troana7564e82018-06-12 21:06:44 +0200758 def validate_args(self, msg, kwargs):
759 d = set(kwargs.keys()) - set(msg.field_by_name.keys())
760 if d:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200761 raise VPPValueError("Invalid argument {} to {}".format(list(d), msg.name))
Ole Troana7564e82018-06-12 21:06:44 +0200762
Ole Troanfd574082019-11-27 23:12:48 +0100763 def _add_stat(self, name, ms):
764 if not name in self.stats:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200765 self.stats[name] = {"max": ms, "count": 1, "avg": ms}
Ole Troanfd574082019-11-27 23:12:48 +0100766 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200767 if ms > self.stats[name]["max"]:
768 self.stats[name]["max"] = ms
769 self.stats[name]["count"] += 1
770 n = self.stats[name]["count"]
771 self.stats[name]["avg"] = self.stats[name]["avg"] * (n - 1) / n + ms / n
Ole Troanfd574082019-11-27 23:12:48 +0100772
773 def get_stats(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200774 s = "\n=== API PAPI STATISTICS ===\n"
775 s += "{:<30} {:>4} {:>6} {:>6}\n".format("message", "cnt", "avg", "max")
776 for n in sorted(self.stats.items(), key=lambda v: v[1]["avg"], reverse=True):
777 s += "{:<30} {:>4} {:>6.2f} {:>6.2f}\n".format(
778 n[0], n[1]["count"], n[1]["avg"], n[1]["max"]
779 )
Ole Troanfd574082019-11-27 23:12:48 +0100780 return s
781
Paul Vinciguerra65315142020-04-26 22:04:32 -0400782 def get_field_options(self, msg, fld_name):
783 # when there is an option, the msgdef has 3 elements.
784 # ['u32', 'ring_size', {'default': 1024}]
785 for _def in self.messages[msg].msgdef:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200786 if isinstance(_def, list) and len(_def) == 3 and _def[1] == fld_name:
Paul Vinciguerra65315142020-04-26 22:04:32 -0400787 return _def[2]
788
Ole Troanf5db3712020-05-20 15:47:06 +0200789 def _call_vpp(self, i, msgdef, service, **kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100790 """Given a message, send the message and await a reply.
791
792 msgdef - the message packing definition
793 i - the message type index
794 multipart - True if the message returns multiple
795 messages in return.
796 context - context number - chosen at random if not
797 supplied.
798 The remainder of the kwargs are the arguments to the API call.
799
800 The return value is the message or message array containing
801 the response. It will raise an IOError exception if there was
802 no response within the timeout window.
803 """
Ole Troanfd574082019-11-27 23:12:48 +0100804 ts = time.time()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200805 if "context" not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100806 context = self.get_context()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200807 kwargs["context"] = context
Ole Troandfc9b7c2017-03-06 23:51:57 +0100808 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200809 context = kwargs["context"]
810 kwargs["_vl_msg_id"] = i
Ole Troan5016f992017-01-19 09:44:44 +0100811
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200812 no_type_conversion = kwargs.pop("_no_type_conversion", False)
813 timeout = kwargs.pop("_timeout", None)
Ole Troan0bcad322018-12-11 13:04:01 +0100814
Ole Troan94495f22018-08-02 11:58:12 +0200815 try:
816 if self.transport.socket_index:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200817 kwargs["client_index"] = self.transport.socket_index
Ole Troan94495f22018-08-02 11:58:12 +0200818 except AttributeError:
819 pass
Ole Troan413f4a52018-11-28 11:36:05 +0100820 self.validate_args(msgdef, kwargs)
821
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200822 s = "Calling {}({})".format(
823 msgdef.name, ",".join(["{!r}:{!r}".format(k, v) for k, v in kwargs.items()])
824 )
Vratko Polakb6590202019-07-16 14:32:55 +0200825 self.logger.debug(s)
Ole Troan413f4a52018-11-28 11:36:05 +0100826
827 b = msgdef.pack(kwargs)
Ole Troan94495f22018-08-02 11:58:12 +0200828 self.transport.suspend()
829
830 self.transport.write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100831
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200832 msgreply = service["reply"]
833 stream = True if "stream" in service else False
Ole Troanf5db3712020-05-20 15:47:06 +0200834 if stream:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200835 if "stream_msg" in service:
Ole Troanf5db3712020-05-20 15:47:06 +0200836 # New service['reply'] = _reply and service['stream_message'] = _details
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200837 stream_message = service["stream_msg"]
838 modern = True
Ole Troanf5db3712020-05-20 15:47:06 +0200839 else:
840 # Old service['reply'] = _details
841 stream_message = msgreply
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200842 msgreply = "control_ping_reply"
Ole Troanf5db3712020-05-20 15:47:06 +0200843 modern = False
844 # Send a ping after the request - we use its response
845 # to detect that we have seen all results.
846 self._control_ping(context)
Ole Troan5016f992017-01-19 09:44:44 +0100847
848 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100849 rl = []
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200850 while True:
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500851 r = self.read_blocking(no_type_conversion, timeout)
Vratko Polak09385472019-09-10 13:35:11 +0200852 if r is None:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200853 raise VPPIOError(2, "VPP API client: read failed")
Ole Troandfc9b7c2017-03-06 23:51:57 +0100854 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200855 if context not in r or r.context == 0 or context != r.context:
Ole Troan94495f22018-08-02 11:58:12 +0200856 # Message being queued
Ole Troandfc9b7c2017-03-06 23:51:57 +0100857 self.message_queue.put_nowait(r)
858 continue
Ole Troanf5db3712020-05-20 15:47:06 +0200859 if msgname != msgreply and (stream and (msgname != stream_message)):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200860 print("REPLY MISMATCH", msgreply, msgname, stream_message, stream)
Ole Troanf5db3712020-05-20 15:47:06 +0200861 if not stream:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100862 rl = r
863 break
Ole Troanf5db3712020-05-20 15:47:06 +0200864 if msgname == msgreply:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200865 if modern: # Return both reply and list
Ole Troanf5db3712020-05-20 15:47:06 +0200866 rl = r, rl
Ole Troandfc9b7c2017-03-06 23:51:57 +0100867 break
868
869 rl.append(r)
870
Ole Troan94495f22018-08-02 11:58:12 +0200871 self.transport.resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100872
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200873 s = "Return value: {!r}".format(r)
Klement Sekera5e2f84d2019-09-12 09:01:06 +0000874 if len(s) > 80:
875 s = s[:80] + "..."
876 self.logger.debug(s)
Ole Troanfd574082019-11-27 23:12:48 +0100877 te = time.time()
878 self._add_stat(msgdef.name, (te - ts) * 1000)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100879 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100880
Ole Troana7564e82018-06-12 21:06:44 +0200881 def _call_vpp_async(self, i, msg, **kwargs):
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200882 """Given a message, send the message and return the context.
Ole Troan5016f992017-01-19 09:44:44 +0100883
884 msgdef - the message packing definition
885 i - the message type index
886 context - context number - chosen at random if not
887 supplied.
888 The remainder of the kwargs are the arguments to the API call.
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200889
890 The reply message(s) will be delivered later to the registered callback.
891 The returned context will help with assigning which call
892 the reply belongs to.
Ole Troan5016f992017-01-19 09:44:44 +0100893 """
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200894 if "context" not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100895 context = self.get_context()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200896 kwargs["context"] = context
Ole Troan7e3a8752016-12-05 10:27:09 +0100897 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200898 context = kwargs["context"]
Ole Troan94495f22018-08-02 11:58:12 +0200899 try:
900 if self.transport.socket_index:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200901 kwargs["client_index"] = self.transport.socket_index
Ole Troan94495f22018-08-02 11:58:12 +0200902 except AttributeError:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200903 kwargs["client_index"] = 0
904 kwargs["_vl_msg_id"] = i
Ole Troana7564e82018-06-12 21:06:44 +0200905 b = msg.pack(kwargs)
Ole Troan7e3a8752016-12-05 10:27:09 +0100906
Ole Troan94495f22018-08-02 11:58:12 +0200907 self.transport.write(b)
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200908 return context
Ole Troan7e3a8752016-12-05 10:27:09 +0100909
Ole Troanbcdde1a2022-12-06 17:42:24 +0100910 def _call_vpp_pack(self, i, msg, **kwargs):
911 """Given a message, return the binary representation."""
912 kwargs["_vl_msg_id"] = i
913 kwargs["client_index"] = 0
914 kwargs["context"] = 0
915 return msg.pack(kwargs)
916
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500917 def read_blocking(self, no_type_conversion=False, timeout=None):
Vratko Polak09385472019-09-10 13:35:11 +0200918 """Get next received message from transport within timeout, decoded.
919
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500920 Note that notifications have context zero
Vratko Polak09385472019-09-10 13:35:11 +0200921 and are not put into receive queue (at least for socket transport),
922 use async_thread with registered callback for processing them.
923
924 If no message appears in the queue within timeout, return None.
925
926 Optionally, type conversion can be skipped,
927 as some of conversions are into less precise types.
928
929 When r is the return value of this, the caller can get message name as:
930 msgname = type(r).__name__
931 and context number (type long) as:
932 context = r.context
933
934 :param no_type_conversion: If false, type conversions are applied.
935 :type no_type_conversion: bool
936 :returns: Decoded message, or None if no message (within timeout).
937 :rtype: Whatever VPPType.unpack returns, depends on no_type_conversion.
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500938 :raises VppTransportShmemIOError if timed out.
Vratko Polak09385472019-09-10 13:35:11 +0200939 """
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500940 msg = self.transport.read(timeout=timeout)
Vratko Polak09385472019-09-10 13:35:11 +0200941 if not msg:
942 return None
943 return self.decode_incoming_msg(msg, no_type_conversion)
944
Ole Troana03f4ef2016-12-02 12:53:55 +0100945 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100946 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100947
Ole Troan5016f992017-01-19 09:44:44 +0100948 This will be called for async notifications in sync mode,
949 and all messages in async mode. In sync mode, replies to
950 requests will not come here.
951
952 callback is a fn(msg_type_name, msg_type) that will be
953 called when a message comes in. While this function is
954 executing, note that (a) you are in a background thread and
955 may wish to use threading.Lock to protect your datastructures,
956 and (b) message processing from VPP will stop (so if you take
957 a long while about it you may provoke reply timeouts or cause
958 VPP to fill the RX buffer). Passing None will disable the
959 callback.
960 """
961 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100962
963 def thread_msg_handler(self):
Ole Troan94495f22018-08-02 11:58:12 +0200964 """Python thread calling the user registered message handler.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100965
966 This is to emulate the old style event callback scheme. Modern
967 clients should provide their own thread to poll the event
968 queue.
969 """
970 while True:
971 r = self.message_queue.get()
Klement Sekera180402d2018-02-17 10:58:37 +0100972 if r == "terminate event thread":
973 break
Ole Troandfc9b7c2017-03-06 23:51:57 +0100974 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200975 if self.event_callback:
976 self.event_callback(msgname, r)
Chris Luke52bf22e2017-11-03 23:32:38 -0400977
Ole Troanc046d702019-10-14 23:07:06 +0200978 def validate_message_table(self, namecrctable):
979 """Take a dictionary of name_crc message names
980 and returns an array of missing messages"""
981
982 missing_table = []
983 for name_crc in namecrctable:
984 i = self.transport.get_msg_index(name_crc)
985 if i <= 0:
986 missing_table.append(name_crc)
987 return missing_table
988
989 def dump_message_table(self):
990 """Return VPPs API message table as name_crc dictionary"""
991 return self.transport.message_table
992
993 def dump_message_table_filtered(self, msglist):
994 """Return VPPs API message table as name_crc dictionary,
995 filtered by message name list."""
996
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200997 replies = [self.services[n]["reply"] for n in msglist]
Ole Troanc046d702019-10-14 23:07:06 +0200998 message_table_filtered = {}
999 for name in msglist + replies:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +02001000 for k, v in self.transport.message_table.items():
Ole Troanc046d702019-10-14 23:07:06 +02001001 if k.startswith(name):
1002 message_table_filtered[k] = v
1003 break
1004 return message_table_filtered
1005
Paul Vinciguerra8a2fa3b2019-06-16 21:23:31 -04001006 def __repr__(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +02001007 return (
1008 "<VPPApiClient apifiles=%s, testmode=%s, async_thread=%s, "
1009 "logger=%s, read_timeout=%s, "
1010 "server_address='%s'>"
1011 % (
1012 self._apifiles,
1013 self.testmode,
1014 self.async_thread,
1015 self.logger,
1016 self.read_timeout,
1017 self.server_address,
1018 )
1019 )
Paul Vinciguerra8a2fa3b2019-06-16 21:23:31 -04001020
Ole Troanf5db3712020-05-20 15:47:06 +02001021 def details_iter(self, f, **kwargs):
1022 cursor = 0
1023 while True:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +02001024 kwargs["cursor"] = cursor
Ole Troanf5db3712020-05-20 15:47:06 +02001025 rv, details = f(**kwargs)
Ole Troanf5db3712020-05-20 15:47:06 +02001026 for d in details:
1027 yield d
1028 if rv.retval == 0 or rv.retval != -165:
1029 break
1030 cursor = rv.cursor