blob: 1e5d23e59b724676129b14da8444fe5bfed32e82 [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
Ole Troan4df97162017-07-07 16:06:08 +020021import sys
Paul Vinciguerra2af6e922019-06-06 07:06:09 -040022import multiprocessing as mp
Ole Troan4df97162017-07-07 16:06:08 +020023import os
Paul Vinciguerrad7adc292020-12-02 14:34:27 -050024import queue
Ole Troan4df97162017-07-07 16:06:08 +020025import logging
Paul Vinciguerra5fced042019-02-26 20:39:44 -080026import functools
Ole Troan4df97162017-07-07 16:06:08 +020027import json
28import threading
Chris Luke52bf22e2017-11-03 23:32:38 -040029import fnmatch
Klement Sekera180402d2018-02-17 10:58:37 +010030import weakref
Ole Troan4df97162017-07-07 16:06:08 +020031import atexit
Ole Troanfd574082019-11-27 23:12:48 +010032import time
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
Ole Troanedfe2c02019-07-30 15:38:13 +0200157 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
173 if hasattr(main, "__file__"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400174 # get the path of the calling script
175 localdir = os.path.dirname(os.path.realpath(main.__file__))
176 else:
177 # use cwd if there is no calling script
Andrey "Zed" Zaikin68e2ffb2018-04-24 14:50:02 +0300178 localdir = os.getcwd()
Chris Luke52bf22e2017-11-03 23:32:38 -0400179 localdir_s = localdir.split(os.path.sep)
180
181 def dmatch(dir):
182 """Match dir against right-hand components of the script dir"""
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200183 d = dir.split("/") # param 'dir' assumes a / separator
Ole Troanafddd832018-02-28 14:55:20 +0100184 length = len(d)
185 return len(localdir_s) > length and localdir_s[-length:] == d
Chris Luke52bf22e2017-11-03 23:32:38 -0400186
187 def sdir(srcdir, variant):
188 """Build a path from srcdir to the staged API files of
189 'variant' (typically '' or '_debug')"""
190 # Since 'core' and 'plugin' files are staged
191 # in separate directories, we target the parent dir.
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200192 return os.path.sep.join(
193 (
194 srcdir,
195 "build-root",
196 "install-vpp%s-native" % variant,
197 "vpp",
198 "share",
199 "vpp",
200 "api",
201 )
202 )
Chris Luke52bf22e2017-11-03 23:32:38 -0400203
204 srcdir = None
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200205 if dmatch("src/scripts"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400206 srcdir = os.path.sep.join(localdir_s[:-2])
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200207 elif dmatch("src/vpp-api/python"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400208 srcdir = os.path.sep.join(localdir_s[:-3])
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200209 elif dmatch("test"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400210 # we're apparently running tests
211 srcdir = os.path.sep.join(localdir_s[:-1])
212
213 if srcdir:
214 # we're in the source tree, try both the debug and release
215 # variants.
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200216 dirs.append(sdir(srcdir, "_debug"))
217 dirs.append(sdir(srcdir, ""))
Chris Luke52bf22e2017-11-03 23:32:38 -0400218
219 # Test for staged copies of the scripts
220 # For these, since we explicitly know if we're running a debug versus
221 # release variant, target only the relevant directory
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200222 if dmatch("build-root/install-vpp_debug-native/vpp/bin"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400223 srcdir = os.path.sep.join(localdir_s[:-4])
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200224 dirs.append(sdir(srcdir, "_debug"))
225 if dmatch("build-root/install-vpp-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, ""))
Chris Luke52bf22e2017-11-03 23:32:38 -0400228
229 # finally, try the location system packages typically install into
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200230 dirs.append(os.path.sep.join(("", "usr", "share", "vpp", "api")))
Chris Luke52bf22e2017-11-03 23:32:38 -0400231
Paul Vinciguerra19542292019-03-17 17:34:46 -0700232 # check the directories for existence; first one wins
Chris Luke52bf22e2017-11-03 23:32:38 -0400233 for dir in dirs:
234 if os.path.isdir(dir):
235 return dir
236
237 return None
238
239 @classmethod
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200240 def find_api_files(cls, api_dir=None, patterns="*"): # -> list
Chris Luke52bf22e2017-11-03 23:32:38 -0400241 """Find API definition files from the given directory tree with the
242 given pattern. If no directory is given then find_api_dir() is used
243 to locate one. If no pattern is given then all definition files found
244 in the directory tree are used.
245
246 :param api_dir: A directory tree in which to locate API definition
247 files; subdirectories are descended into.
248 If this is None then find_api_dir() is called to discover it.
249 :param patterns: A list of patterns to use in each visited directory
250 when looking for files.
251 This can be a list/tuple object or a comma-separated string of
252 patterns. Each value in the list will have leading/trialing
253 whitespace stripped.
254 The pattern specifies the first part of the filename, '.api.json'
255 is appended.
256 The results are de-duplicated, thus overlapping patterns are fine.
257 If this is None it defaults to '*' meaning "all API files".
258 :returns: A list of file paths for the API files found.
259 """
260 if api_dir is None:
Ole Troanedfe2c02019-07-30 15:38:13 +0200261 api_dir = cls.find_api_dir([])
Chris Luke52bf22e2017-11-03 23:32:38 -0400262 if api_dir is None:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800263 raise VPPApiError("api_dir cannot be located")
Chris Luke52bf22e2017-11-03 23:32:38 -0400264
265 if isinstance(patterns, list) or isinstance(patterns, tuple):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200266 patterns = [p.strip() + ".api.json" for p in patterns]
Chris Luke52bf22e2017-11-03 23:32:38 -0400267 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200268 patterns = [p.strip() + ".api.json" for p in patterns.split(",")]
Chris Luke52bf22e2017-11-03 23:32:38 -0400269
270 api_files = []
271 for root, dirnames, files in os.walk(api_dir):
272 # iterate all given patterns and de-dup the result
273 files = set(sum([fnmatch.filter(files, p) for p in patterns], []))
274 for filename in files:
275 api_files.append(os.path.join(root, filename))
276
277 return api_files
278
Ole Troanedfe2c02019-07-30 15:38:13 +0200279 @classmethod
280 def process_json_file(self, apidef_file):
281 api = json.load(apidef_file)
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500282 return self._process_json(api)
283
284 @classmethod
285 def process_json_str(self, json_str):
286 api = json.loads(json_str)
287 return self._process_json(api)
288
289 @staticmethod
290 def _process_json(api): # -> Tuple[Dict, Dict]
Ole Troanedfe2c02019-07-30 15:38:13 +0200291 types = {}
292 services = {}
293 messages = {}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500294 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200295 for t in api["enums"]:
296 t[0] = "vl_api_" + t[0] + "_t"
297 types[t[0]] = {"type": "enum", "data": t}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500298 except KeyError:
299 pass
Paul Vinciguerra3825d932020-12-03 21:06:28 -0500300 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200301 for t in api["enumflags"]:
302 t[0] = "vl_api_" + t[0] + "_t"
303 types[t[0]] = {"type": "enum", "data": t}
Paul Vinciguerra3825d932020-12-03 21:06:28 -0500304 except KeyError:
305 pass
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500306 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200307 for t in api["unions"]:
308 t[0] = "vl_api_" + t[0] + "_t"
309 types[t[0]] = {"type": "union", "data": t}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500310 except KeyError:
311 pass
312
313 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200314 for t in api["types"]:
315 t[0] = "vl_api_" + t[0] + "_t"
316 types[t[0]] = {"type": "type", "data": t}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500317 except KeyError:
318 pass
319
320 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200321 for t, v in api["aliases"].items():
322 types["vl_api_" + t + "_t"] = {"type": "alias", "data": v}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500323 except KeyError:
324 pass
325
326 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200327 services.update(api["services"])
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500328 except KeyError:
329 pass
Ole Troanedfe2c02019-07-30 15:38:13 +0200330
331 i = 0
332 while True:
333 unresolved = {}
334 for k, v in types.items():
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200335 t = v["data"]
Ole Troanedfe2c02019-07-30 15:38:13 +0200336 if not vpp_get_type(k):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200337 if v["type"] == "enum":
Ole Troanedfe2c02019-07-30 15:38:13 +0200338 try:
339 VPPEnumType(t[0], t[1:])
340 except ValueError:
341 unresolved[k] = v
Paul Vinciguerra3825d932020-12-03 21:06:28 -0500342 if not vpp_get_type(k):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200343 if v["type"] == "enumflag":
Paul Vinciguerra3825d932020-12-03 21:06:28 -0500344 try:
345 VPPEnumFlagType(t[0], t[1:])
346 except ValueError:
347 unresolved[k] = v
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200348 elif v["type"] == "union":
Ole Troanedfe2c02019-07-30 15:38:13 +0200349 try:
350 VPPUnionType(t[0], t[1:])
351 except ValueError:
352 unresolved[k] = v
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200353 elif v["type"] == "type":
Ole Troanedfe2c02019-07-30 15:38:13 +0200354 try:
355 VPPType(t[0], t[1:])
356 except ValueError:
357 unresolved[k] = v
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200358 elif v["type"] == "alias":
Ole Troanedfe2c02019-07-30 15:38:13 +0200359 try:
360 VPPTypeAlias(k, t)
361 except ValueError:
362 unresolved[k] = v
363 if len(unresolved) == 0:
364 break
365 if i > 3:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200366 raise VPPValueError("Unresolved type definitions {}".format(unresolved))
Ole Troanedfe2c02019-07-30 15:38:13 +0200367 types = unresolved
368 i += 1
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500369 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200370 for m in api["messages"]:
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500371 try:
372 messages[m[0]] = VPPMessage(m[0], m[1:])
373 except VPPNotImplementedError:
374 ### OLE FIXME
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200375 logger.error("Not implemented error for {}".format(m[0]))
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500376 except KeyError:
377 pass
Ole Troanedfe2c02019-07-30 15:38:13 +0200378 return messages, services
379
Paul Vinciguerrae64e5ff2020-04-28 00:27:38 -0400380
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500381class VPPApiClient:
Ole Troanedfe2c02019-07-30 15:38:13 +0200382 """VPP interface.
383
384 This class provides the APIs to VPP. The APIs are loaded
385 from provided .api.json files and makes functions accordingly.
386 These functions are documented in the VPP .api files, as they
387 are dynamically created.
388
389 Additionally, VPP can send callback messages; this class
390 provides a means to register a callback function to receive
391 these messages in a background thread.
392 """
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200393
Ole Troanedfe2c02019-07-30 15:38:13 +0200394 apidir = None
395 VPPApiError = VPPApiError
396 VPPRuntimeError = VPPRuntimeError
397 VPPValueError = VPPValueError
398 VPPNotImplementedError = VPPNotImplementedError
399 VPPIOError = VPPIOError
400
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200401 def __init__(
402 self,
403 *,
404 apifiles=None,
405 testmode=False,
406 async_thread=True,
407 logger=None,
408 loglevel=None,
409 read_timeout=5,
410 use_socket=True,
411 server_address="/run/vpp/api.sock",
412 ):
Ole Troanedfe2c02019-07-30 15:38:13 +0200413 """Create a VPP API object.
414
415 apifiles is a list of files containing API
416 descriptions that will be loaded - methods will be
417 dynamically created reflecting these APIs. If not
418 provided this will load the API files from VPP's
419 default install location.
420
421 logger, if supplied, is the logging logger object to log to.
422 loglevel, if supplied, is the log level this logger is set
423 to report at (from the loglevels in the logging module).
424 """
425 if logger is None:
426 logger = logging.getLogger(
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200427 "{}.{}".format(__name__, self.__class__.__name__)
428 )
Ole Troanedfe2c02019-07-30 15:38:13 +0200429 if loglevel is not None:
430 logger.setLevel(loglevel)
431 self.logger = logger
432
433 self.messages = {}
434 self.services = {}
435 self.id_names = []
436 self.id_msgdef = []
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200437 self.header = VPPType("header", [["u16", "msgid"], ["u32", "client_index"]])
Ole Troanedfe2c02019-07-30 15:38:13 +0200438 self.apifiles = []
439 self.event_callback = None
440 self.message_queue = queue.Queue()
441 self.read_timeout = read_timeout
442 self.async_thread = async_thread
443 self.event_thread = None
444 self.testmode = testmode
Ole Troanedfe2c02019-07-30 15:38:13 +0200445 self.server_address = server_address
446 self._apifiles = apifiles
Ole Troanfd574082019-11-27 23:12:48 +0100447 self.stats = {}
Ole Troanedfe2c02019-07-30 15:38:13 +0200448
Ole Troanedfe2c02019-07-30 15:38:13 +0200449 if not apifiles:
450 # Pick up API definitions from default directory
451 try:
452 apifiles = VPPApiJSONFiles.find_api_files(self.apidir)
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500453 except (RuntimeError, VPPApiError):
Ole Troanedfe2c02019-07-30 15:38:13 +0200454 # In test mode we don't care that we can't find the API files
455 if testmode:
456 apifiles = []
457 else:
458 raise VPPRuntimeError
459
460 for file in apifiles:
461 with open(file) as apidef_file:
462 m, s = VPPApiJSONFiles.process_json_file(apidef_file)
463 self.messages.update(m)
464 self.services.update(s)
465
466 self.apifiles = apifiles
467
468 # Basic sanity check
469 if len(self.messages) == 0 and not testmode:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200470 raise VPPValueError(1, "Missing JSON message definitions")
471 if not (verify_enum_hint(VppEnum.vl_api_address_family_t)):
472 raise VPPRuntimeError("Invalid address family hints. " "Cannot continue.")
Ole Troanedfe2c02019-07-30 15:38:13 +0200473
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200474 self.transport = VppTransport(
475 self, read_timeout=read_timeout, server_address=server_address
476 )
Ole Troanedfe2c02019-07-30 15:38:13 +0200477 # Make sure we allow VPP to clean up the message rings.
478 atexit.register(vpp_atexit, weakref.ref(self))
479
Paul Vinciguerrae64e5ff2020-04-28 00:27:38 -0400480 add_convenience_methods()
481
Ole Troanedfe2c02019-07-30 15:38:13 +0200482 def get_function(self, name):
483 return getattr(self._api, name)
484
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500485 class ContextId:
Ole Troanedfe2c02019-07-30 15:38:13 +0200486 """Multiprocessing-safe provider of unique context IDs."""
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200487
Ole Troanedfe2c02019-07-30 15:38:13 +0200488 def __init__(self):
489 self.context = mp.Value(ctypes.c_uint, 0)
490 self.lock = mp.Lock()
491
492 def __call__(self):
493 """Get a new unique (or, at least, not recently used) context."""
494 with self.lock:
495 self.context.value += 1
496 return self.context.value
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200497
Ole Troanedfe2c02019-07-30 15:38:13 +0200498 get_context = ContextId()
499
500 def get_type(self, name):
501 return vpp_get_type(name)
502
Klement Sekera7112c542017-03-01 09:53:19 +0100503 @property
504 def api(self):
505 if not hasattr(self, "_api"):
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800506 raise VPPApiError("Not connected, api definitions not available")
Klement Sekera7112c542017-03-01 09:53:19 +0100507 return self._api
508
Ole Troaneabd6072018-08-09 12:50:55 +0200509 def make_function(self, msg, i, multipart, do_async):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200510 if do_async:
511
Ole Troana7564e82018-06-12 21:06:44 +0200512 def f(**kwargs):
513 return self._call_vpp_async(i, msg, **kwargs)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200514
Ole Troana7564e82018-06-12 21:06:44 +0200515 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200516
Ole Troana7564e82018-06-12 21:06:44 +0200517 def f(**kwargs):
518 return self._call_vpp(i, msg, multipart, **kwargs)
519
520 f.__name__ = str(msg.name)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200521 f.__doc__ = ", ".join(
522 ["%s %s" % (msg.fieldtypes[j], k) for j, k in enumerate(msg.fields)]
523 )
Ole Troanf159f582019-02-28 20:20:47 +0100524 f.msg = msg
525
Ole Troana7564e82018-06-12 21:06:44 +0200526 return f
527
Ole Troaneabd6072018-08-09 12:50:55 +0200528 def _register_functions(self, do_async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100529 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
530 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera8aedf5e2018-07-06 11:07:21 +0200531 self._api = VppApiDynamicMethodHolder()
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500532 for name, msg in self.messages.items():
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200533 n = name + "_" + msg.crc[2:]
Ole Troandaa4bff2019-08-28 14:12:02 +0200534 i = self.transport.get_msg_index(n)
Ole Troan3cc49712017-03-08 12:02:24 +0100535 if i > 0:
Ole Troana7564e82018-06-12 21:06:44 +0200536 self.id_msgdef[i] = msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100537 self.id_names[i] = name
Ole Troandfb984d2018-12-07 14:31:16 +0100538
539 # Create function for client side messages.
540 if name in self.services:
Ole Troanf5db3712020-05-20 15:47:06 +0200541 f = self.make_function(msg, i, self.services[name], do_async)
Ole Troandfb984d2018-12-07 14:31:16 +0100542 setattr(self._api, name, FuncWrapper(f))
Ole Troan3cc49712017-03-08 12:02:24 +0100543 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200544 self.logger.debug("No such message type or failed CRC checksum: %s", n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100545
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200546 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, do_async):
547 pfx = chroot_prefix.encode("utf-8") if chroot_prefix else None
Ole Troan94495f22018-08-02 11:58:12 +0200548
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200549 rv = self.transport.connect(name, pfx, msg_handler, rx_qlen)
Ole Troana03f4ef2016-12-02 12:53:55 +0100550 if rv != 0:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200551 raise VPPIOError(2, "Connect failed")
Ole Troan94495f22018-08-02 11:58:12 +0200552 self.vpp_dictionary_maxid = self.transport.msg_table_max_index()
Ole Troaneabd6072018-08-09 12:50:55 +0200553 self._register_functions(do_async=do_async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100554
555 # Initialise control ping
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200556 crc = self.messages["control_ping"].crc
Ole Troan94495f22018-08-02 11:58:12 +0200557 self.control_ping_index = self.transport.get_msg_index(
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200558 ("control_ping" + "_" + crc[2:])
559 )
560 self.control_ping_msgdef = self.messages["control_ping"]
Klement Sekera180402d2018-02-17 10:58:37 +0100561 if self.async_thread:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200562 self.event_thread = threading.Thread(target=self.thread_msg_handler)
Klement Sekera180402d2018-02-17 10:58:37 +0100563 self.event_thread.daemon = True
564 self.event_thread.start()
Vratko Polak94e45312019-05-27 18:36:23 +0200565 else:
566 self.event_thread = None
Ole Troan4df97162017-07-07 16:06:08 +0200567 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100568
Ole Troaneabd6072018-08-09 12:50:55 +0200569 def connect(self, name, chroot_prefix=None, do_async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100570 """Attach to VPP.
571
572 name - the name of the client.
573 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
Ole Troaneabd6072018-08-09 12:50:55 +0200574 do_async - if true, messages are sent without waiting for a reply
Ole Troandfc9b7c2017-03-06 23:51:57 +0100575 rx_qlen - the length of the VPP message receive queue between
576 client and server.
577 """
Ole Troan94495f22018-08-02 11:58:12 +0200578 msg_handler = self.transport.get_callback(do_async)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200579 return self.connect_internal(
580 name, msg_handler, chroot_prefix, rx_qlen, do_async
581 )
Ole Troandfc9b7c2017-03-06 23:51:57 +0100582
Ole Troan6bf177c2017-08-17 10:34:32 +0200583 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100584 """Attach to VPP in synchronous mode. Application must poll for events.
585
586 name - the name of the client.
587 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
588 rx_qlen - the length of the VPP message receive queue between
589 client and server.
590 """
591
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200592 return self.connect_internal(name, None, chroot_prefix, rx_qlen, do_async=False)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100593
Ole Troana03f4ef2016-12-02 12:53:55 +0100594 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100595 """Detach from VPP."""
Ole Troan94495f22018-08-02 11:58:12 +0200596 rv = self.transport.disconnect()
Vratko Polak94e45312019-05-27 18:36:23 +0200597 if self.event_thread is not None:
598 self.message_queue.put("terminate event thread")
Ole Troana03f4ef2016-12-02 12:53:55 +0100599 return rv
600
Ole Troan5016f992017-01-19 09:44:44 +0100601 def msg_handler_sync(self, msg):
602 """Process an incoming message from VPP in sync mode.
603
604 The message may be a reply or it may be an async notification.
605 """
606 r = self.decode_incoming_msg(msg)
607 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100608 return
609
Ole Troan5016f992017-01-19 09:44:44 +0100610 # If we have a context, then use the context to find any
611 # request waiting for a reply
612 context = 0
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200613 if hasattr(r, "context") and r.context > 0:
Ole Troan5016f992017-01-19 09:44:44 +0100614 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200615
Ole Troan5016f992017-01-19 09:44:44 +0100616 if context == 0:
617 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100618 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100619 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200620 raise VPPIOError(2, "RPC reply message received in event handler")
Ole Troan5016f992017-01-19 09:44:44 +0100621
Ole Troan413f4a52018-11-28 11:36:05 +0100622 def has_context(self, msg):
623 if len(msg) < 10:
624 return False
625
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200626 header = VPPType(
627 "header_with_context",
628 [["u16", "msgid"], ["u32", "client_index"], ["u32", "context"]],
629 )
Ole Troan413f4a52018-11-28 11:36:05 +0100630
631 (i, ci, context), size = header.unpack(msg, 0)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200632 if self.id_names[i] == "rx_thread_exit":
Ole Troan413f4a52018-11-28 11:36:05 +0100633 return
634
635 #
636 # Decode message and returns a tuple.
637 #
638 msgobj = self.id_msgdef[i]
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200639 if "context" in msgobj.field_by_name and context >= 0:
Ole Troan413f4a52018-11-28 11:36:05 +0100640 return True
641 return False
642
Ole Troan0bcad322018-12-11 13:04:01 +0100643 def decode_incoming_msg(self, msg, no_type_conversion=False):
Ole Troan5016f992017-01-19 09:44:44 +0100644 if not msg:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200645 logger.warning("vpp_api.read failed")
Ole Troan5016f992017-01-19 09:44:44 +0100646 return
Ole Troan413f4a52018-11-28 11:36:05 +0100647
Ole Troanc84cbad2018-09-06 22:58:05 +0200648 (i, ci), size = self.header.unpack(msg, 0)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200649 if self.id_names[i] == "rx_thread_exit":
Ole Troan5016f992017-01-19 09:44:44 +0100650 return
651
652 #
653 # Decode message and returns a tuple.
654 #
Ole Troana7564e82018-06-12 21:06:44 +0200655 msgobj = self.id_msgdef[i]
656 if not msgobj:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200657 raise VPPIOError(2, "Reply message undefined")
Ole Troan5016f992017-01-19 09:44:44 +0100658
Ole Troan0bcad322018-12-11 13:04:01 +0100659 r, size = msgobj.unpack(msg, ntc=no_type_conversion)
Ole Troana03f4ef2016-12-02 12:53:55 +0100660 return r
661
Ole Troan5016f992017-01-19 09:44:44 +0100662 def msg_handler_async(self, msg):
663 """Process a message from VPP in async mode.
664
665 In async mode, all messages are returned to the callback.
666 """
667 r = self.decode_incoming_msg(msg)
668 if r is None:
669 return
670
671 msgname = type(r).__name__
672
Ole Troan4df97162017-07-07 16:06:08 +0200673 if self.event_callback:
674 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100675
676 def _control_ping(self, context):
677 """Send a ping command."""
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200678 self._call_vpp_async(
679 self.control_ping_index, self.control_ping_msgdef, context=context
680 )
Ole Troan5016f992017-01-19 09:44:44 +0100681
Ole Troana7564e82018-06-12 21:06:44 +0200682 def validate_args(self, msg, kwargs):
683 d = set(kwargs.keys()) - set(msg.field_by_name.keys())
684 if d:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200685 raise VPPValueError("Invalid argument {} to {}".format(list(d), msg.name))
Ole Troana7564e82018-06-12 21:06:44 +0200686
Ole Troanfd574082019-11-27 23:12:48 +0100687 def _add_stat(self, name, ms):
688 if not name in self.stats:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200689 self.stats[name] = {"max": ms, "count": 1, "avg": ms}
Ole Troanfd574082019-11-27 23:12:48 +0100690 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200691 if ms > self.stats[name]["max"]:
692 self.stats[name]["max"] = ms
693 self.stats[name]["count"] += 1
694 n = self.stats[name]["count"]
695 self.stats[name]["avg"] = self.stats[name]["avg"] * (n - 1) / n + ms / n
Ole Troanfd574082019-11-27 23:12:48 +0100696
697 def get_stats(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200698 s = "\n=== API PAPI STATISTICS ===\n"
699 s += "{:<30} {:>4} {:>6} {:>6}\n".format("message", "cnt", "avg", "max")
700 for n in sorted(self.stats.items(), key=lambda v: v[1]["avg"], reverse=True):
701 s += "{:<30} {:>4} {:>6.2f} {:>6.2f}\n".format(
702 n[0], n[1]["count"], n[1]["avg"], n[1]["max"]
703 )
Ole Troanfd574082019-11-27 23:12:48 +0100704 return s
705
Paul Vinciguerra65315142020-04-26 22:04:32 -0400706 def get_field_options(self, msg, fld_name):
707 # when there is an option, the msgdef has 3 elements.
708 # ['u32', 'ring_size', {'default': 1024}]
709 for _def in self.messages[msg].msgdef:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200710 if isinstance(_def, list) and len(_def) == 3 and _def[1] == fld_name:
Paul Vinciguerra65315142020-04-26 22:04:32 -0400711 return _def[2]
712
Ole Troanf5db3712020-05-20 15:47:06 +0200713 def _call_vpp(self, i, msgdef, service, **kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100714 """Given a message, send the message and await a reply.
715
716 msgdef - the message packing definition
717 i - the message type index
718 multipart - True if the message returns multiple
719 messages in return.
720 context - context number - chosen at random if not
721 supplied.
722 The remainder of the kwargs are the arguments to the API call.
723
724 The return value is the message or message array containing
725 the response. It will raise an IOError exception if there was
726 no response within the timeout window.
727 """
Ole Troanfd574082019-11-27 23:12:48 +0100728 ts = time.time()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200729 if "context" not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100730 context = self.get_context()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200731 kwargs["context"] = context
Ole Troandfc9b7c2017-03-06 23:51:57 +0100732 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200733 context = kwargs["context"]
734 kwargs["_vl_msg_id"] = i
Ole Troan5016f992017-01-19 09:44:44 +0100735
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200736 no_type_conversion = kwargs.pop("_no_type_conversion", False)
737 timeout = kwargs.pop("_timeout", None)
Ole Troan0bcad322018-12-11 13:04:01 +0100738
Ole Troan94495f22018-08-02 11:58:12 +0200739 try:
740 if self.transport.socket_index:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200741 kwargs["client_index"] = self.transport.socket_index
Ole Troan94495f22018-08-02 11:58:12 +0200742 except AttributeError:
743 pass
Ole Troan413f4a52018-11-28 11:36:05 +0100744 self.validate_args(msgdef, kwargs)
745
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200746 s = "Calling {}({})".format(
747 msgdef.name, ",".join(["{!r}:{!r}".format(k, v) for k, v in kwargs.items()])
748 )
Vratko Polakb6590202019-07-16 14:32:55 +0200749 self.logger.debug(s)
Ole Troan413f4a52018-11-28 11:36:05 +0100750
751 b = msgdef.pack(kwargs)
Ole Troan94495f22018-08-02 11:58:12 +0200752 self.transport.suspend()
753
754 self.transport.write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100755
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200756 msgreply = service["reply"]
757 stream = True if "stream" in service else False
Ole Troanf5db3712020-05-20 15:47:06 +0200758 if stream:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200759 if "stream_msg" in service:
Ole Troanf5db3712020-05-20 15:47:06 +0200760 # New service['reply'] = _reply and service['stream_message'] = _details
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200761 stream_message = service["stream_msg"]
762 modern = True
Ole Troanf5db3712020-05-20 15:47:06 +0200763 else:
764 # Old service['reply'] = _details
765 stream_message = msgreply
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200766 msgreply = "control_ping_reply"
Ole Troanf5db3712020-05-20 15:47:06 +0200767 modern = False
768 # Send a ping after the request - we use its response
769 # to detect that we have seen all results.
770 self._control_ping(context)
Ole Troan5016f992017-01-19 09:44:44 +0100771
772 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100773 rl = []
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200774 while True:
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500775 r = self.read_blocking(no_type_conversion, timeout)
Vratko Polak09385472019-09-10 13:35:11 +0200776 if r is None:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200777 raise VPPIOError(2, "VPP API client: read failed")
Ole Troandfc9b7c2017-03-06 23:51:57 +0100778 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200779 if context not in r or r.context == 0 or context != r.context:
Ole Troan94495f22018-08-02 11:58:12 +0200780 # Message being queued
Ole Troandfc9b7c2017-03-06 23:51:57 +0100781 self.message_queue.put_nowait(r)
782 continue
Ole Troanf5db3712020-05-20 15:47:06 +0200783 if msgname != msgreply and (stream and (msgname != stream_message)):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200784 print("REPLY MISMATCH", msgreply, msgname, stream_message, stream)
Ole Troanf5db3712020-05-20 15:47:06 +0200785 if not stream:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100786 rl = r
787 break
Ole Troanf5db3712020-05-20 15:47:06 +0200788 if msgname == msgreply:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200789 if modern: # Return both reply and list
Ole Troanf5db3712020-05-20 15:47:06 +0200790 rl = r, rl
Ole Troandfc9b7c2017-03-06 23:51:57 +0100791 break
792
793 rl.append(r)
794
Ole Troan94495f22018-08-02 11:58:12 +0200795 self.transport.resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100796
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200797 s = "Return value: {!r}".format(r)
Klement Sekera5e2f84d2019-09-12 09:01:06 +0000798 if len(s) > 80:
799 s = s[:80] + "..."
800 self.logger.debug(s)
Ole Troanfd574082019-11-27 23:12:48 +0100801 te = time.time()
802 self._add_stat(msgdef.name, (te - ts) * 1000)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100803 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100804
Ole Troana7564e82018-06-12 21:06:44 +0200805 def _call_vpp_async(self, i, msg, **kwargs):
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200806 """Given a message, send the message and return the context.
Ole Troan5016f992017-01-19 09:44:44 +0100807
808 msgdef - the message packing definition
809 i - the message type index
810 context - context number - chosen at random if not
811 supplied.
812 The remainder of the kwargs are the arguments to the API call.
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200813
814 The reply message(s) will be delivered later to the registered callback.
815 The returned context will help with assigning which call
816 the reply belongs to.
Ole Troan5016f992017-01-19 09:44:44 +0100817 """
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200818 if "context" not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100819 context = self.get_context()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200820 kwargs["context"] = context
Ole Troan7e3a8752016-12-05 10:27:09 +0100821 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200822 context = kwargs["context"]
Ole Troan94495f22018-08-02 11:58:12 +0200823 try:
824 if self.transport.socket_index:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200825 kwargs["client_index"] = self.transport.socket_index
Ole Troan94495f22018-08-02 11:58:12 +0200826 except AttributeError:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200827 kwargs["client_index"] = 0
828 kwargs["_vl_msg_id"] = i
Ole Troana7564e82018-06-12 21:06:44 +0200829 b = msg.pack(kwargs)
Ole Troan7e3a8752016-12-05 10:27:09 +0100830
Ole Troan94495f22018-08-02 11:58:12 +0200831 self.transport.write(b)
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200832 return context
Ole Troan7e3a8752016-12-05 10:27:09 +0100833
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500834 def read_blocking(self, no_type_conversion=False, timeout=None):
Vratko Polak09385472019-09-10 13:35:11 +0200835 """Get next received message from transport within timeout, decoded.
836
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500837 Note that notifications have context zero
Vratko Polak09385472019-09-10 13:35:11 +0200838 and are not put into receive queue (at least for socket transport),
839 use async_thread with registered callback for processing them.
840
841 If no message appears in the queue within timeout, return None.
842
843 Optionally, type conversion can be skipped,
844 as some of conversions are into less precise types.
845
846 When r is the return value of this, the caller can get message name as:
847 msgname = type(r).__name__
848 and context number (type long) as:
849 context = r.context
850
851 :param no_type_conversion: If false, type conversions are applied.
852 :type no_type_conversion: bool
853 :returns: Decoded message, or None if no message (within timeout).
854 :rtype: Whatever VPPType.unpack returns, depends on no_type_conversion.
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500855 :raises VppTransportShmemIOError if timed out.
Vratko Polak09385472019-09-10 13:35:11 +0200856 """
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500857 msg = self.transport.read(timeout=timeout)
Vratko Polak09385472019-09-10 13:35:11 +0200858 if not msg:
859 return None
860 return self.decode_incoming_msg(msg, no_type_conversion)
861
Ole Troana03f4ef2016-12-02 12:53:55 +0100862 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100863 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100864
Ole Troan5016f992017-01-19 09:44:44 +0100865 This will be called for async notifications in sync mode,
866 and all messages in async mode. In sync mode, replies to
867 requests will not come here.
868
869 callback is a fn(msg_type_name, msg_type) that will be
870 called when a message comes in. While this function is
871 executing, note that (a) you are in a background thread and
872 may wish to use threading.Lock to protect your datastructures,
873 and (b) message processing from VPP will stop (so if you take
874 a long while about it you may provoke reply timeouts or cause
875 VPP to fill the RX buffer). Passing None will disable the
876 callback.
877 """
878 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100879
880 def thread_msg_handler(self):
Ole Troan94495f22018-08-02 11:58:12 +0200881 """Python thread calling the user registered message handler.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100882
883 This is to emulate the old style event callback scheme. Modern
884 clients should provide their own thread to poll the event
885 queue.
886 """
887 while True:
888 r = self.message_queue.get()
Klement Sekera180402d2018-02-17 10:58:37 +0100889 if r == "terminate event thread":
890 break
Ole Troandfc9b7c2017-03-06 23:51:57 +0100891 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200892 if self.event_callback:
893 self.event_callback(msgname, r)
Chris Luke52bf22e2017-11-03 23:32:38 -0400894
Ole Troanc046d702019-10-14 23:07:06 +0200895 def validate_message_table(self, namecrctable):
896 """Take a dictionary of name_crc message names
897 and returns an array of missing messages"""
898
899 missing_table = []
900 for name_crc in namecrctable:
901 i = self.transport.get_msg_index(name_crc)
902 if i <= 0:
903 missing_table.append(name_crc)
904 return missing_table
905
906 def dump_message_table(self):
907 """Return VPPs API message table as name_crc dictionary"""
908 return self.transport.message_table
909
910 def dump_message_table_filtered(self, msglist):
911 """Return VPPs API message table as name_crc dictionary,
912 filtered by message name list."""
913
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200914 replies = [self.services[n]["reply"] for n in msglist]
Ole Troanc046d702019-10-14 23:07:06 +0200915 message_table_filtered = {}
916 for name in msglist + replies:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200917 for k, v in self.transport.message_table.items():
Ole Troanc046d702019-10-14 23:07:06 +0200918 if k.startswith(name):
919 message_table_filtered[k] = v
920 break
921 return message_table_filtered
922
Paul Vinciguerra8a2fa3b2019-06-16 21:23:31 -0400923 def __repr__(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200924 return (
925 "<VPPApiClient apifiles=%s, testmode=%s, async_thread=%s, "
926 "logger=%s, read_timeout=%s, "
927 "server_address='%s'>"
928 % (
929 self._apifiles,
930 self.testmode,
931 self.async_thread,
932 self.logger,
933 self.read_timeout,
934 self.server_address,
935 )
936 )
Paul Vinciguerra8a2fa3b2019-06-16 21:23:31 -0400937
Ole Troanf5db3712020-05-20 15:47:06 +0200938 def details_iter(self, f, **kwargs):
939 cursor = 0
940 while True:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200941 kwargs["cursor"] = cursor
Ole Troanf5db3712020-05-20 15:47:06 +0200942 rv, details = f(**kwargs)
Ole Troanf5db3712020-05-20 15:47:06 +0200943 for d in details:
944 yield d
945 if rv.retval == 0 or rv.retval != -165:
946 break
947 cursor = rv.cursor