blob: 34d57232865b821d4db243615dec201f9ac3bea1 [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:
Ole Troan37157da2022-12-01 11:22:06 +0100452 if isinstance(self.apidir, list):
453 apifiles = []
454 for d in self.apidir:
455 apifiles += VPPApiJSONFiles.find_api_files(d)
456 else:
457 apifiles = VPPApiJSONFiles.find_api_files(self.apidir)
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500458 except (RuntimeError, VPPApiError):
Ole Troanedfe2c02019-07-30 15:38:13 +0200459 # In test mode we don't care that we can't find the API files
460 if testmode:
461 apifiles = []
462 else:
463 raise VPPRuntimeError
464
465 for file in apifiles:
466 with open(file) as apidef_file:
467 m, s = VPPApiJSONFiles.process_json_file(apidef_file)
468 self.messages.update(m)
469 self.services.update(s)
470
471 self.apifiles = apifiles
472
473 # Basic sanity check
474 if len(self.messages) == 0 and not testmode:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200475 raise VPPValueError(1, "Missing JSON message definitions")
476 if not (verify_enum_hint(VppEnum.vl_api_address_family_t)):
477 raise VPPRuntimeError("Invalid address family hints. " "Cannot continue.")
Ole Troanedfe2c02019-07-30 15:38:13 +0200478
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200479 self.transport = VppTransport(
480 self, read_timeout=read_timeout, server_address=server_address
481 )
Ole Troanedfe2c02019-07-30 15:38:13 +0200482 # Make sure we allow VPP to clean up the message rings.
483 atexit.register(vpp_atexit, weakref.ref(self))
484
Paul Vinciguerrae64e5ff2020-04-28 00:27:38 -0400485 add_convenience_methods()
486
Ole Troanedfe2c02019-07-30 15:38:13 +0200487 def get_function(self, name):
488 return getattr(self._api, name)
489
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500490 class ContextId:
Ole Troanedfe2c02019-07-30 15:38:13 +0200491 """Multiprocessing-safe provider of unique context IDs."""
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200492
Ole Troanedfe2c02019-07-30 15:38:13 +0200493 def __init__(self):
494 self.context = mp.Value(ctypes.c_uint, 0)
495 self.lock = mp.Lock()
496
497 def __call__(self):
498 """Get a new unique (or, at least, not recently used) context."""
499 with self.lock:
500 self.context.value += 1
501 return self.context.value
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200502
Ole Troanedfe2c02019-07-30 15:38:13 +0200503 get_context = ContextId()
504
505 def get_type(self, name):
506 return vpp_get_type(name)
507
Klement Sekera7112c542017-03-01 09:53:19 +0100508 @property
509 def api(self):
510 if not hasattr(self, "_api"):
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800511 raise VPPApiError("Not connected, api definitions not available")
Klement Sekera7112c542017-03-01 09:53:19 +0100512 return self._api
513
Ole Troaneabd6072018-08-09 12:50:55 +0200514 def make_function(self, msg, i, multipart, do_async):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200515 if do_async:
516
Ole Troana7564e82018-06-12 21:06:44 +0200517 def f(**kwargs):
518 return self._call_vpp_async(i, msg, **kwargs)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200519
Ole Troana7564e82018-06-12 21:06:44 +0200520 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200521
Ole Troana7564e82018-06-12 21:06:44 +0200522 def f(**kwargs):
523 return self._call_vpp(i, msg, multipart, **kwargs)
524
525 f.__name__ = str(msg.name)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200526 f.__doc__ = ", ".join(
527 ["%s %s" % (msg.fieldtypes[j], k) for j, k in enumerate(msg.fields)]
528 )
Ole Troanf159f582019-02-28 20:20:47 +0100529 f.msg = msg
530
Ole Troana7564e82018-06-12 21:06:44 +0200531 return f
532
Ole Troaneabd6072018-08-09 12:50:55 +0200533 def _register_functions(self, do_async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100534 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
535 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera8aedf5e2018-07-06 11:07:21 +0200536 self._api = VppApiDynamicMethodHolder()
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500537 for name, msg in self.messages.items():
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200538 n = name + "_" + msg.crc[2:]
Ole Troandaa4bff2019-08-28 14:12:02 +0200539 i = self.transport.get_msg_index(n)
Ole Troan3cc49712017-03-08 12:02:24 +0100540 if i > 0:
Ole Troana7564e82018-06-12 21:06:44 +0200541 self.id_msgdef[i] = msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100542 self.id_names[i] = name
Ole Troandfb984d2018-12-07 14:31:16 +0100543
544 # Create function for client side messages.
545 if name in self.services:
Ole Troanf5db3712020-05-20 15:47:06 +0200546 f = self.make_function(msg, i, self.services[name], do_async)
Ole Troandfb984d2018-12-07 14:31:16 +0100547 setattr(self._api, name, FuncWrapper(f))
Ole Troan3cc49712017-03-08 12:02:24 +0100548 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200549 self.logger.debug("No such message type or failed CRC checksum: %s", n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100550
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200551 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, do_async):
552 pfx = chroot_prefix.encode("utf-8") if chroot_prefix else None
Ole Troan94495f22018-08-02 11:58:12 +0200553
Ole Troanf22824b2022-12-06 17:30:49 +0100554 rv = self.transport.connect(name, pfx, msg_handler, rx_qlen, do_async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100555 if rv != 0:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200556 raise VPPIOError(2, "Connect failed")
Ole Troan94495f22018-08-02 11:58:12 +0200557 self.vpp_dictionary_maxid = self.transport.msg_table_max_index()
Ole Troaneabd6072018-08-09 12:50:55 +0200558 self._register_functions(do_async=do_async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100559
560 # Initialise control ping
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200561 crc = self.messages["control_ping"].crc
Ole Troan94495f22018-08-02 11:58:12 +0200562 self.control_ping_index = self.transport.get_msg_index(
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200563 ("control_ping" + "_" + crc[2:])
564 )
565 self.control_ping_msgdef = self.messages["control_ping"]
Klement Sekera180402d2018-02-17 10:58:37 +0100566 if self.async_thread:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200567 self.event_thread = threading.Thread(target=self.thread_msg_handler)
Klement Sekera180402d2018-02-17 10:58:37 +0100568 self.event_thread.daemon = True
569 self.event_thread.start()
Vratko Polak94e45312019-05-27 18:36:23 +0200570 else:
571 self.event_thread = None
Ole Troan4df97162017-07-07 16:06:08 +0200572 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100573
Ole Troaneabd6072018-08-09 12:50:55 +0200574 def connect(self, name, chroot_prefix=None, do_async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100575 """Attach to VPP.
576
577 name - the name of the client.
578 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
Ole Troaneabd6072018-08-09 12:50:55 +0200579 do_async - if true, messages are sent without waiting for a reply
Ole Troandfc9b7c2017-03-06 23:51:57 +0100580 rx_qlen - the length of the VPP message receive queue between
581 client and server.
582 """
Ole Troan94495f22018-08-02 11:58:12 +0200583 msg_handler = self.transport.get_callback(do_async)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200584 return self.connect_internal(
585 name, msg_handler, chroot_prefix, rx_qlen, do_async
586 )
Ole Troandfc9b7c2017-03-06 23:51:57 +0100587
Ole Troan6bf177c2017-08-17 10:34:32 +0200588 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100589 """Attach to VPP in synchronous mode. Application must poll for events.
590
591 name - the name of the client.
592 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
593 rx_qlen - the length of the VPP message receive queue between
594 client and server.
595 """
596
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200597 return self.connect_internal(name, None, chroot_prefix, rx_qlen, do_async=False)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100598
Ole Troana03f4ef2016-12-02 12:53:55 +0100599 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100600 """Detach from VPP."""
Ole Troan94495f22018-08-02 11:58:12 +0200601 rv = self.transport.disconnect()
Vratko Polak94e45312019-05-27 18:36:23 +0200602 if self.event_thread is not None:
603 self.message_queue.put("terminate event thread")
Ole Troana03f4ef2016-12-02 12:53:55 +0100604 return rv
605
Ole Troan5016f992017-01-19 09:44:44 +0100606 def msg_handler_sync(self, msg):
607 """Process an incoming message from VPP in sync mode.
608
609 The message may be a reply or it may be an async notification.
610 """
611 r = self.decode_incoming_msg(msg)
612 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100613 return
614
Ole Troan5016f992017-01-19 09:44:44 +0100615 # If we have a context, then use the context to find any
616 # request waiting for a reply
617 context = 0
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200618 if hasattr(r, "context") and r.context > 0:
Ole Troan5016f992017-01-19 09:44:44 +0100619 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200620
Ole Troan5016f992017-01-19 09:44:44 +0100621 if context == 0:
622 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100623 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100624 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200625 raise VPPIOError(2, "RPC reply message received in event handler")
Ole Troan5016f992017-01-19 09:44:44 +0100626
Ole Troan413f4a52018-11-28 11:36:05 +0100627 def has_context(self, msg):
628 if len(msg) < 10:
629 return False
630
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200631 header = VPPType(
632 "header_with_context",
633 [["u16", "msgid"], ["u32", "client_index"], ["u32", "context"]],
634 )
Ole Troan413f4a52018-11-28 11:36:05 +0100635
636 (i, ci, context), size = header.unpack(msg, 0)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200637 if self.id_names[i] == "rx_thread_exit":
Ole Troan413f4a52018-11-28 11:36:05 +0100638 return
639
640 #
641 # Decode message and returns a tuple.
642 #
643 msgobj = self.id_msgdef[i]
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200644 if "context" in msgobj.field_by_name and context >= 0:
Ole Troan413f4a52018-11-28 11:36:05 +0100645 return True
646 return False
647
Ole Troan0bcad322018-12-11 13:04:01 +0100648 def decode_incoming_msg(self, msg, no_type_conversion=False):
Ole Troan5016f992017-01-19 09:44:44 +0100649 if not msg:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200650 logger.warning("vpp_api.read failed")
Ole Troan5016f992017-01-19 09:44:44 +0100651 return
Ole Troan413f4a52018-11-28 11:36:05 +0100652
Ole Troanc84cbad2018-09-06 22:58:05 +0200653 (i, ci), size = self.header.unpack(msg, 0)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200654 if self.id_names[i] == "rx_thread_exit":
Ole Troan5016f992017-01-19 09:44:44 +0100655 return
656
657 #
658 # Decode message and returns a tuple.
659 #
Ole Troana7564e82018-06-12 21:06:44 +0200660 msgobj = self.id_msgdef[i]
661 if not msgobj:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200662 raise VPPIOError(2, "Reply message undefined")
Ole Troan5016f992017-01-19 09:44:44 +0100663
Ole Troan0bcad322018-12-11 13:04:01 +0100664 r, size = msgobj.unpack(msg, ntc=no_type_conversion)
Ole Troana03f4ef2016-12-02 12:53:55 +0100665 return r
666
Ole Troan5016f992017-01-19 09:44:44 +0100667 def msg_handler_async(self, msg):
668 """Process a message from VPP in async mode.
669
670 In async mode, all messages are returned to the callback.
671 """
672 r = self.decode_incoming_msg(msg)
673 if r is None:
674 return
675
676 msgname = type(r).__name__
677
Ole Troan4df97162017-07-07 16:06:08 +0200678 if self.event_callback:
679 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100680
681 def _control_ping(self, context):
682 """Send a ping command."""
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200683 self._call_vpp_async(
684 self.control_ping_index, self.control_ping_msgdef, context=context
685 )
Ole Troan5016f992017-01-19 09:44:44 +0100686
Ole Troana7564e82018-06-12 21:06:44 +0200687 def validate_args(self, msg, kwargs):
688 d = set(kwargs.keys()) - set(msg.field_by_name.keys())
689 if d:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200690 raise VPPValueError("Invalid argument {} to {}".format(list(d), msg.name))
Ole Troana7564e82018-06-12 21:06:44 +0200691
Ole Troanfd574082019-11-27 23:12:48 +0100692 def _add_stat(self, name, ms):
693 if not name in self.stats:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200694 self.stats[name] = {"max": ms, "count": 1, "avg": ms}
Ole Troanfd574082019-11-27 23:12:48 +0100695 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200696 if ms > self.stats[name]["max"]:
697 self.stats[name]["max"] = ms
698 self.stats[name]["count"] += 1
699 n = self.stats[name]["count"]
700 self.stats[name]["avg"] = self.stats[name]["avg"] * (n - 1) / n + ms / n
Ole Troanfd574082019-11-27 23:12:48 +0100701
702 def get_stats(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200703 s = "\n=== API PAPI STATISTICS ===\n"
704 s += "{:<30} {:>4} {:>6} {:>6}\n".format("message", "cnt", "avg", "max")
705 for n in sorted(self.stats.items(), key=lambda v: v[1]["avg"], reverse=True):
706 s += "{:<30} {:>4} {:>6.2f} {:>6.2f}\n".format(
707 n[0], n[1]["count"], n[1]["avg"], n[1]["max"]
708 )
Ole Troanfd574082019-11-27 23:12:48 +0100709 return s
710
Paul Vinciguerra65315142020-04-26 22:04:32 -0400711 def get_field_options(self, msg, fld_name):
712 # when there is an option, the msgdef has 3 elements.
713 # ['u32', 'ring_size', {'default': 1024}]
714 for _def in self.messages[msg].msgdef:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200715 if isinstance(_def, list) and len(_def) == 3 and _def[1] == fld_name:
Paul Vinciguerra65315142020-04-26 22:04:32 -0400716 return _def[2]
717
Ole Troanf5db3712020-05-20 15:47:06 +0200718 def _call_vpp(self, i, msgdef, service, **kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100719 """Given a message, send the message and await a reply.
720
721 msgdef - the message packing definition
722 i - the message type index
723 multipart - True if the message returns multiple
724 messages in return.
725 context - context number - chosen at random if not
726 supplied.
727 The remainder of the kwargs are the arguments to the API call.
728
729 The return value is the message or message array containing
730 the response. It will raise an IOError exception if there was
731 no response within the timeout window.
732 """
Ole Troanfd574082019-11-27 23:12:48 +0100733 ts = time.time()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200734 if "context" not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100735 context = self.get_context()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200736 kwargs["context"] = context
Ole Troandfc9b7c2017-03-06 23:51:57 +0100737 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200738 context = kwargs["context"]
739 kwargs["_vl_msg_id"] = i
Ole Troan5016f992017-01-19 09:44:44 +0100740
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200741 no_type_conversion = kwargs.pop("_no_type_conversion", False)
742 timeout = kwargs.pop("_timeout", None)
Ole Troan0bcad322018-12-11 13:04:01 +0100743
Ole Troan94495f22018-08-02 11:58:12 +0200744 try:
745 if self.transport.socket_index:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200746 kwargs["client_index"] = self.transport.socket_index
Ole Troan94495f22018-08-02 11:58:12 +0200747 except AttributeError:
748 pass
Ole Troan413f4a52018-11-28 11:36:05 +0100749 self.validate_args(msgdef, kwargs)
750
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200751 s = "Calling {}({})".format(
752 msgdef.name, ",".join(["{!r}:{!r}".format(k, v) for k, v in kwargs.items()])
753 )
Vratko Polakb6590202019-07-16 14:32:55 +0200754 self.logger.debug(s)
Ole Troan413f4a52018-11-28 11:36:05 +0100755
756 b = msgdef.pack(kwargs)
Ole Troan94495f22018-08-02 11:58:12 +0200757 self.transport.suspend()
758
759 self.transport.write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100760
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200761 msgreply = service["reply"]
762 stream = True if "stream" in service else False
Ole Troanf5db3712020-05-20 15:47:06 +0200763 if stream:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200764 if "stream_msg" in service:
Ole Troanf5db3712020-05-20 15:47:06 +0200765 # New service['reply'] = _reply and service['stream_message'] = _details
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200766 stream_message = service["stream_msg"]
767 modern = True
Ole Troanf5db3712020-05-20 15:47:06 +0200768 else:
769 # Old service['reply'] = _details
770 stream_message = msgreply
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200771 msgreply = "control_ping_reply"
Ole Troanf5db3712020-05-20 15:47:06 +0200772 modern = False
773 # Send a ping after the request - we use its response
774 # to detect that we have seen all results.
775 self._control_ping(context)
Ole Troan5016f992017-01-19 09:44:44 +0100776
777 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100778 rl = []
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200779 while True:
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500780 r = self.read_blocking(no_type_conversion, timeout)
Vratko Polak09385472019-09-10 13:35:11 +0200781 if r is None:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200782 raise VPPIOError(2, "VPP API client: read failed")
Ole Troandfc9b7c2017-03-06 23:51:57 +0100783 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200784 if context not in r or r.context == 0 or context != r.context:
Ole Troan94495f22018-08-02 11:58:12 +0200785 # Message being queued
Ole Troandfc9b7c2017-03-06 23:51:57 +0100786 self.message_queue.put_nowait(r)
787 continue
Ole Troanf5db3712020-05-20 15:47:06 +0200788 if msgname != msgreply and (stream and (msgname != stream_message)):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200789 print("REPLY MISMATCH", msgreply, msgname, stream_message, stream)
Ole Troanf5db3712020-05-20 15:47:06 +0200790 if not stream:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100791 rl = r
792 break
Ole Troanf5db3712020-05-20 15:47:06 +0200793 if msgname == msgreply:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200794 if modern: # Return both reply and list
Ole Troanf5db3712020-05-20 15:47:06 +0200795 rl = r, rl
Ole Troandfc9b7c2017-03-06 23:51:57 +0100796 break
797
798 rl.append(r)
799
Ole Troan94495f22018-08-02 11:58:12 +0200800 self.transport.resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100801
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200802 s = "Return value: {!r}".format(r)
Klement Sekera5e2f84d2019-09-12 09:01:06 +0000803 if len(s) > 80:
804 s = s[:80] + "..."
805 self.logger.debug(s)
Ole Troanfd574082019-11-27 23:12:48 +0100806 te = time.time()
807 self._add_stat(msgdef.name, (te - ts) * 1000)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100808 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100809
Ole Troana7564e82018-06-12 21:06:44 +0200810 def _call_vpp_async(self, i, msg, **kwargs):
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200811 """Given a message, send the message and return the context.
Ole Troan5016f992017-01-19 09:44:44 +0100812
813 msgdef - the message packing definition
814 i - the message type index
815 context - context number - chosen at random if not
816 supplied.
817 The remainder of the kwargs are the arguments to the API call.
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200818
819 The reply message(s) will be delivered later to the registered callback.
820 The returned context will help with assigning which call
821 the reply belongs to.
Ole Troan5016f992017-01-19 09:44:44 +0100822 """
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200823 if "context" not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100824 context = self.get_context()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200825 kwargs["context"] = context
Ole Troan7e3a8752016-12-05 10:27:09 +0100826 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200827 context = kwargs["context"]
Ole Troan94495f22018-08-02 11:58:12 +0200828 try:
829 if self.transport.socket_index:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200830 kwargs["client_index"] = self.transport.socket_index
Ole Troan94495f22018-08-02 11:58:12 +0200831 except AttributeError:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200832 kwargs["client_index"] = 0
833 kwargs["_vl_msg_id"] = i
Ole Troana7564e82018-06-12 21:06:44 +0200834 b = msg.pack(kwargs)
Ole Troan7e3a8752016-12-05 10:27:09 +0100835
Ole Troan94495f22018-08-02 11:58:12 +0200836 self.transport.write(b)
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200837 return context
Ole Troan7e3a8752016-12-05 10:27:09 +0100838
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500839 def read_blocking(self, no_type_conversion=False, timeout=None):
Vratko Polak09385472019-09-10 13:35:11 +0200840 """Get next received message from transport within timeout, decoded.
841
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500842 Note that notifications have context zero
Vratko Polak09385472019-09-10 13:35:11 +0200843 and are not put into receive queue (at least for socket transport),
844 use async_thread with registered callback for processing them.
845
846 If no message appears in the queue within timeout, return None.
847
848 Optionally, type conversion can be skipped,
849 as some of conversions are into less precise types.
850
851 When r is the return value of this, the caller can get message name as:
852 msgname = type(r).__name__
853 and context number (type long) as:
854 context = r.context
855
856 :param no_type_conversion: If false, type conversions are applied.
857 :type no_type_conversion: bool
858 :returns: Decoded message, or None if no message (within timeout).
859 :rtype: Whatever VPPType.unpack returns, depends on no_type_conversion.
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500860 :raises VppTransportShmemIOError if timed out.
Vratko Polak09385472019-09-10 13:35:11 +0200861 """
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500862 msg = self.transport.read(timeout=timeout)
Vratko Polak09385472019-09-10 13:35:11 +0200863 if not msg:
864 return None
865 return self.decode_incoming_msg(msg, no_type_conversion)
866
Ole Troana03f4ef2016-12-02 12:53:55 +0100867 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100868 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100869
Ole Troan5016f992017-01-19 09:44:44 +0100870 This will be called for async notifications in sync mode,
871 and all messages in async mode. In sync mode, replies to
872 requests will not come here.
873
874 callback is a fn(msg_type_name, msg_type) that will be
875 called when a message comes in. While this function is
876 executing, note that (a) you are in a background thread and
877 may wish to use threading.Lock to protect your datastructures,
878 and (b) message processing from VPP will stop (so if you take
879 a long while about it you may provoke reply timeouts or cause
880 VPP to fill the RX buffer). Passing None will disable the
881 callback.
882 """
883 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100884
885 def thread_msg_handler(self):
Ole Troan94495f22018-08-02 11:58:12 +0200886 """Python thread calling the user registered message handler.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100887
888 This is to emulate the old style event callback scheme. Modern
889 clients should provide their own thread to poll the event
890 queue.
891 """
892 while True:
893 r = self.message_queue.get()
Klement Sekera180402d2018-02-17 10:58:37 +0100894 if r == "terminate event thread":
895 break
Ole Troandfc9b7c2017-03-06 23:51:57 +0100896 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200897 if self.event_callback:
898 self.event_callback(msgname, r)
Chris Luke52bf22e2017-11-03 23:32:38 -0400899
Ole Troanc046d702019-10-14 23:07:06 +0200900 def validate_message_table(self, namecrctable):
901 """Take a dictionary of name_crc message names
902 and returns an array of missing messages"""
903
904 missing_table = []
905 for name_crc in namecrctable:
906 i = self.transport.get_msg_index(name_crc)
907 if i <= 0:
908 missing_table.append(name_crc)
909 return missing_table
910
911 def dump_message_table(self):
912 """Return VPPs API message table as name_crc dictionary"""
913 return self.transport.message_table
914
915 def dump_message_table_filtered(self, msglist):
916 """Return VPPs API message table as name_crc dictionary,
917 filtered by message name list."""
918
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200919 replies = [self.services[n]["reply"] for n in msglist]
Ole Troanc046d702019-10-14 23:07:06 +0200920 message_table_filtered = {}
921 for name in msglist + replies:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200922 for k, v in self.transport.message_table.items():
Ole Troanc046d702019-10-14 23:07:06 +0200923 if k.startswith(name):
924 message_table_filtered[k] = v
925 break
926 return message_table_filtered
927
Paul Vinciguerra8a2fa3b2019-06-16 21:23:31 -0400928 def __repr__(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200929 return (
930 "<VPPApiClient apifiles=%s, testmode=%s, async_thread=%s, "
931 "logger=%s, read_timeout=%s, "
932 "server_address='%s'>"
933 % (
934 self._apifiles,
935 self.testmode,
936 self.async_thread,
937 self.logger,
938 self.read_timeout,
939 self.server_address,
940 )
941 )
Paul Vinciguerra8a2fa3b2019-06-16 21:23:31 -0400942
Ole Troanf5db3712020-05-20 15:47:06 +0200943 def details_iter(self, f, **kwargs):
944 cursor = 0
945 while True:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200946 kwargs["cursor"] = cursor
Ole Troanf5db3712020-05-20 15:47:06 +0200947 rv, details = f(**kwargs)
Ole Troanf5db3712020-05-20 15:47:06 +0200948 for d in details:
949 yield d
950 if rv.retval == 0 or rv.retval != -165:
951 break
952 cursor = rv.cursor