blob: e67ee192f998e77949299d99cda8dca9584dc4ef [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 Troanbcdde1a2022-12-06 17:42:24 +0100533 def make_pack_function(self, msg, i, multipart):
534 def f(**kwargs):
535 return self._call_vpp_pack(i, msg, **kwargs)
536
537 f.msg = msg
538 return f
539
Ole Troaneabd6072018-08-09 12:50:55 +0200540 def _register_functions(self, do_async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100541 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
542 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera8aedf5e2018-07-06 11:07:21 +0200543 self._api = VppApiDynamicMethodHolder()
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500544 for name, msg in self.messages.items():
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200545 n = name + "_" + msg.crc[2:]
Ole Troandaa4bff2019-08-28 14:12:02 +0200546 i = self.transport.get_msg_index(n)
Ole Troan3cc49712017-03-08 12:02:24 +0100547 if i > 0:
Ole Troana7564e82018-06-12 21:06:44 +0200548 self.id_msgdef[i] = msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100549 self.id_names[i] = name
Ole Troandfb984d2018-12-07 14:31:16 +0100550
551 # Create function for client side messages.
552 if name in self.services:
Ole Troanf5db3712020-05-20 15:47:06 +0200553 f = self.make_function(msg, i, self.services[name], do_async)
Ole Troanbcdde1a2022-12-06 17:42:24 +0100554 f_pack = self.make_pack_function(msg, i, self.services[name])
Ole Troandfb984d2018-12-07 14:31:16 +0100555 setattr(self._api, name, FuncWrapper(f))
Ole Troanbcdde1a2022-12-06 17:42:24 +0100556 setattr(self._api, name + "_pack", FuncWrapper(f_pack))
Ole Troan3cc49712017-03-08 12:02:24 +0100557 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200558 self.logger.debug("No such message type or failed CRC checksum: %s", n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100559
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200560 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, do_async):
561 pfx = chroot_prefix.encode("utf-8") if chroot_prefix else None
Ole Troan94495f22018-08-02 11:58:12 +0200562
Ole Troanf22824b2022-12-06 17:30:49 +0100563 rv = self.transport.connect(name, pfx, msg_handler, rx_qlen, do_async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100564 if rv != 0:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200565 raise VPPIOError(2, "Connect failed")
Ole Troan94495f22018-08-02 11:58:12 +0200566 self.vpp_dictionary_maxid = self.transport.msg_table_max_index()
Ole Troaneabd6072018-08-09 12:50:55 +0200567 self._register_functions(do_async=do_async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100568
569 # Initialise control ping
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200570 crc = self.messages["control_ping"].crc
Ole Troan94495f22018-08-02 11:58:12 +0200571 self.control_ping_index = self.transport.get_msg_index(
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200572 ("control_ping" + "_" + crc[2:])
573 )
574 self.control_ping_msgdef = self.messages["control_ping"]
Klement Sekera180402d2018-02-17 10:58:37 +0100575 if self.async_thread:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200576 self.event_thread = threading.Thread(target=self.thread_msg_handler)
Klement Sekera180402d2018-02-17 10:58:37 +0100577 self.event_thread.daemon = True
578 self.event_thread.start()
Vratko Polak94e45312019-05-27 18:36:23 +0200579 else:
580 self.event_thread = None
Ole Troan4df97162017-07-07 16:06:08 +0200581 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100582
Ole Troaneabd6072018-08-09 12:50:55 +0200583 def connect(self, name, chroot_prefix=None, do_async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100584 """Attach to VPP.
585
586 name - the name of the client.
587 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
Ole Troaneabd6072018-08-09 12:50:55 +0200588 do_async - if true, messages are sent without waiting for a reply
Ole Troandfc9b7c2017-03-06 23:51:57 +0100589 rx_qlen - the length of the VPP message receive queue between
590 client and server.
591 """
Ole Troan94495f22018-08-02 11:58:12 +0200592 msg_handler = self.transport.get_callback(do_async)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200593 return self.connect_internal(
594 name, msg_handler, chroot_prefix, rx_qlen, do_async
595 )
Ole Troandfc9b7c2017-03-06 23:51:57 +0100596
Ole Troan6bf177c2017-08-17 10:34:32 +0200597 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100598 """Attach to VPP in synchronous mode. Application must poll for events.
599
600 name - the name of the client.
601 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
602 rx_qlen - the length of the VPP message receive queue between
603 client and server.
604 """
605
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200606 return self.connect_internal(name, None, chroot_prefix, rx_qlen, do_async=False)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100607
Ole Troana03f4ef2016-12-02 12:53:55 +0100608 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100609 """Detach from VPP."""
Ole Troan94495f22018-08-02 11:58:12 +0200610 rv = self.transport.disconnect()
Vratko Polak94e45312019-05-27 18:36:23 +0200611 if self.event_thread is not None:
612 self.message_queue.put("terminate event thread")
Ole Troana03f4ef2016-12-02 12:53:55 +0100613 return rv
614
Ole Troan5016f992017-01-19 09:44:44 +0100615 def msg_handler_sync(self, msg):
616 """Process an incoming message from VPP in sync mode.
617
618 The message may be a reply or it may be an async notification.
619 """
620 r = self.decode_incoming_msg(msg)
621 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100622 return
623
Ole Troan5016f992017-01-19 09:44:44 +0100624 # If we have a context, then use the context to find any
625 # request waiting for a reply
626 context = 0
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200627 if hasattr(r, "context") and r.context > 0:
Ole Troan5016f992017-01-19 09:44:44 +0100628 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200629
Ole Troan5016f992017-01-19 09:44:44 +0100630 if context == 0:
631 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100632 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100633 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200634 raise VPPIOError(2, "RPC reply message received in event handler")
Ole Troan5016f992017-01-19 09:44:44 +0100635
Ole Troan413f4a52018-11-28 11:36:05 +0100636 def has_context(self, msg):
637 if len(msg) < 10:
638 return False
639
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200640 header = VPPType(
641 "header_with_context",
642 [["u16", "msgid"], ["u32", "client_index"], ["u32", "context"]],
643 )
Ole Troan413f4a52018-11-28 11:36:05 +0100644
645 (i, ci, context), size = header.unpack(msg, 0)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200646 if self.id_names[i] == "rx_thread_exit":
Ole Troan413f4a52018-11-28 11:36:05 +0100647 return
648
649 #
650 # Decode message and returns a tuple.
651 #
652 msgobj = self.id_msgdef[i]
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200653 if "context" in msgobj.field_by_name and context >= 0:
Ole Troan413f4a52018-11-28 11:36:05 +0100654 return True
655 return False
656
Ole Troan0bcad322018-12-11 13:04:01 +0100657 def decode_incoming_msg(self, msg, no_type_conversion=False):
Ole Troan5016f992017-01-19 09:44:44 +0100658 if not msg:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200659 logger.warning("vpp_api.read failed")
Ole Troan5016f992017-01-19 09:44:44 +0100660 return
Ole Troan413f4a52018-11-28 11:36:05 +0100661
Ole Troanc84cbad2018-09-06 22:58:05 +0200662 (i, ci), size = self.header.unpack(msg, 0)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200663 if self.id_names[i] == "rx_thread_exit":
Ole Troan5016f992017-01-19 09:44:44 +0100664 return
665
666 #
667 # Decode message and returns a tuple.
668 #
Ole Troana7564e82018-06-12 21:06:44 +0200669 msgobj = self.id_msgdef[i]
670 if not msgobj:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200671 raise VPPIOError(2, "Reply message undefined")
Ole Troan5016f992017-01-19 09:44:44 +0100672
Ole Troan0bcad322018-12-11 13:04:01 +0100673 r, size = msgobj.unpack(msg, ntc=no_type_conversion)
Ole Troana03f4ef2016-12-02 12:53:55 +0100674 return r
675
Ole Troan5016f992017-01-19 09:44:44 +0100676 def msg_handler_async(self, msg):
677 """Process a message from VPP in async mode.
678
679 In async mode, all messages are returned to the callback.
680 """
681 r = self.decode_incoming_msg(msg)
682 if r is None:
683 return
684
685 msgname = type(r).__name__
686
Ole Troan4df97162017-07-07 16:06:08 +0200687 if self.event_callback:
688 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100689
690 def _control_ping(self, context):
691 """Send a ping command."""
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200692 self._call_vpp_async(
693 self.control_ping_index, self.control_ping_msgdef, context=context
694 )
Ole Troan5016f992017-01-19 09:44:44 +0100695
Ole Troana7564e82018-06-12 21:06:44 +0200696 def validate_args(self, msg, kwargs):
697 d = set(kwargs.keys()) - set(msg.field_by_name.keys())
698 if d:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200699 raise VPPValueError("Invalid argument {} to {}".format(list(d), msg.name))
Ole Troana7564e82018-06-12 21:06:44 +0200700
Ole Troanfd574082019-11-27 23:12:48 +0100701 def _add_stat(self, name, ms):
702 if not name in self.stats:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200703 self.stats[name] = {"max": ms, "count": 1, "avg": ms}
Ole Troanfd574082019-11-27 23:12:48 +0100704 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200705 if ms > self.stats[name]["max"]:
706 self.stats[name]["max"] = ms
707 self.stats[name]["count"] += 1
708 n = self.stats[name]["count"]
709 self.stats[name]["avg"] = self.stats[name]["avg"] * (n - 1) / n + ms / n
Ole Troanfd574082019-11-27 23:12:48 +0100710
711 def get_stats(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200712 s = "\n=== API PAPI STATISTICS ===\n"
713 s += "{:<30} {:>4} {:>6} {:>6}\n".format("message", "cnt", "avg", "max")
714 for n in sorted(self.stats.items(), key=lambda v: v[1]["avg"], reverse=True):
715 s += "{:<30} {:>4} {:>6.2f} {:>6.2f}\n".format(
716 n[0], n[1]["count"], n[1]["avg"], n[1]["max"]
717 )
Ole Troanfd574082019-11-27 23:12:48 +0100718 return s
719
Paul Vinciguerra65315142020-04-26 22:04:32 -0400720 def get_field_options(self, msg, fld_name):
721 # when there is an option, the msgdef has 3 elements.
722 # ['u32', 'ring_size', {'default': 1024}]
723 for _def in self.messages[msg].msgdef:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200724 if isinstance(_def, list) and len(_def) == 3 and _def[1] == fld_name:
Paul Vinciguerra65315142020-04-26 22:04:32 -0400725 return _def[2]
726
Ole Troanf5db3712020-05-20 15:47:06 +0200727 def _call_vpp(self, i, msgdef, service, **kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100728 """Given a message, send the message and await a reply.
729
730 msgdef - the message packing definition
731 i - the message type index
732 multipart - True if the message returns multiple
733 messages in return.
734 context - context number - chosen at random if not
735 supplied.
736 The remainder of the kwargs are the arguments to the API call.
737
738 The return value is the message or message array containing
739 the response. It will raise an IOError exception if there was
740 no response within the timeout window.
741 """
Ole Troanfd574082019-11-27 23:12:48 +0100742 ts = time.time()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200743 if "context" not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100744 context = self.get_context()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200745 kwargs["context"] = context
Ole Troandfc9b7c2017-03-06 23:51:57 +0100746 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200747 context = kwargs["context"]
748 kwargs["_vl_msg_id"] = i
Ole Troan5016f992017-01-19 09:44:44 +0100749
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200750 no_type_conversion = kwargs.pop("_no_type_conversion", False)
751 timeout = kwargs.pop("_timeout", None)
Ole Troan0bcad322018-12-11 13:04:01 +0100752
Ole Troan94495f22018-08-02 11:58:12 +0200753 try:
754 if self.transport.socket_index:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200755 kwargs["client_index"] = self.transport.socket_index
Ole Troan94495f22018-08-02 11:58:12 +0200756 except AttributeError:
757 pass
Ole Troan413f4a52018-11-28 11:36:05 +0100758 self.validate_args(msgdef, kwargs)
759
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200760 s = "Calling {}({})".format(
761 msgdef.name, ",".join(["{!r}:{!r}".format(k, v) for k, v in kwargs.items()])
762 )
Vratko Polakb6590202019-07-16 14:32:55 +0200763 self.logger.debug(s)
Ole Troan413f4a52018-11-28 11:36:05 +0100764
765 b = msgdef.pack(kwargs)
Ole Troan94495f22018-08-02 11:58:12 +0200766 self.transport.suspend()
767
768 self.transport.write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100769
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200770 msgreply = service["reply"]
771 stream = True if "stream" in service else False
Ole Troanf5db3712020-05-20 15:47:06 +0200772 if stream:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200773 if "stream_msg" in service:
Ole Troanf5db3712020-05-20 15:47:06 +0200774 # New service['reply'] = _reply and service['stream_message'] = _details
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200775 stream_message = service["stream_msg"]
776 modern = True
Ole Troanf5db3712020-05-20 15:47:06 +0200777 else:
778 # Old service['reply'] = _details
779 stream_message = msgreply
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200780 msgreply = "control_ping_reply"
Ole Troanf5db3712020-05-20 15:47:06 +0200781 modern = False
782 # Send a ping after the request - we use its response
783 # to detect that we have seen all results.
784 self._control_ping(context)
Ole Troan5016f992017-01-19 09:44:44 +0100785
786 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100787 rl = []
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200788 while True:
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500789 r = self.read_blocking(no_type_conversion, timeout)
Vratko Polak09385472019-09-10 13:35:11 +0200790 if r is None:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200791 raise VPPIOError(2, "VPP API client: read failed")
Ole Troandfc9b7c2017-03-06 23:51:57 +0100792 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200793 if context not in r or r.context == 0 or context != r.context:
Ole Troan94495f22018-08-02 11:58:12 +0200794 # Message being queued
Ole Troandfc9b7c2017-03-06 23:51:57 +0100795 self.message_queue.put_nowait(r)
796 continue
Ole Troanf5db3712020-05-20 15:47:06 +0200797 if msgname != msgreply and (stream and (msgname != stream_message)):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200798 print("REPLY MISMATCH", msgreply, msgname, stream_message, stream)
Ole Troanf5db3712020-05-20 15:47:06 +0200799 if not stream:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100800 rl = r
801 break
Ole Troanf5db3712020-05-20 15:47:06 +0200802 if msgname == msgreply:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200803 if modern: # Return both reply and list
Ole Troanf5db3712020-05-20 15:47:06 +0200804 rl = r, rl
Ole Troandfc9b7c2017-03-06 23:51:57 +0100805 break
806
807 rl.append(r)
808
Ole Troan94495f22018-08-02 11:58:12 +0200809 self.transport.resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100810
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200811 s = "Return value: {!r}".format(r)
Klement Sekera5e2f84d2019-09-12 09:01:06 +0000812 if len(s) > 80:
813 s = s[:80] + "..."
814 self.logger.debug(s)
Ole Troanfd574082019-11-27 23:12:48 +0100815 te = time.time()
816 self._add_stat(msgdef.name, (te - ts) * 1000)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100817 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100818
Ole Troana7564e82018-06-12 21:06:44 +0200819 def _call_vpp_async(self, i, msg, **kwargs):
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200820 """Given a message, send the message and return the context.
Ole Troan5016f992017-01-19 09:44:44 +0100821
822 msgdef - the message packing definition
823 i - the message type index
824 context - context number - chosen at random if not
825 supplied.
826 The remainder of the kwargs are the arguments to the API call.
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200827
828 The reply message(s) will be delivered later to the registered callback.
829 The returned context will help with assigning which call
830 the reply belongs to.
Ole Troan5016f992017-01-19 09:44:44 +0100831 """
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200832 if "context" not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100833 context = self.get_context()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200834 kwargs["context"] = context
Ole Troan7e3a8752016-12-05 10:27:09 +0100835 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200836 context = kwargs["context"]
Ole Troan94495f22018-08-02 11:58:12 +0200837 try:
838 if self.transport.socket_index:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200839 kwargs["client_index"] = self.transport.socket_index
Ole Troan94495f22018-08-02 11:58:12 +0200840 except AttributeError:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200841 kwargs["client_index"] = 0
842 kwargs["_vl_msg_id"] = i
Ole Troana7564e82018-06-12 21:06:44 +0200843 b = msg.pack(kwargs)
Ole Troan7e3a8752016-12-05 10:27:09 +0100844
Ole Troan94495f22018-08-02 11:58:12 +0200845 self.transport.write(b)
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200846 return context
Ole Troan7e3a8752016-12-05 10:27:09 +0100847
Ole Troanbcdde1a2022-12-06 17:42:24 +0100848 def _call_vpp_pack(self, i, msg, **kwargs):
849 """Given a message, return the binary representation."""
850 kwargs["_vl_msg_id"] = i
851 kwargs["client_index"] = 0
852 kwargs["context"] = 0
853 return msg.pack(kwargs)
854
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500855 def read_blocking(self, no_type_conversion=False, timeout=None):
Vratko Polak09385472019-09-10 13:35:11 +0200856 """Get next received message from transport within timeout, decoded.
857
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500858 Note that notifications have context zero
Vratko Polak09385472019-09-10 13:35:11 +0200859 and are not put into receive queue (at least for socket transport),
860 use async_thread with registered callback for processing them.
861
862 If no message appears in the queue within timeout, return None.
863
864 Optionally, type conversion can be skipped,
865 as some of conversions are into less precise types.
866
867 When r is the return value of this, the caller can get message name as:
868 msgname = type(r).__name__
869 and context number (type long) as:
870 context = r.context
871
872 :param no_type_conversion: If false, type conversions are applied.
873 :type no_type_conversion: bool
874 :returns: Decoded message, or None if no message (within timeout).
875 :rtype: Whatever VPPType.unpack returns, depends on no_type_conversion.
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500876 :raises VppTransportShmemIOError if timed out.
Vratko Polak09385472019-09-10 13:35:11 +0200877 """
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500878 msg = self.transport.read(timeout=timeout)
Vratko Polak09385472019-09-10 13:35:11 +0200879 if not msg:
880 return None
881 return self.decode_incoming_msg(msg, no_type_conversion)
882
Ole Troana03f4ef2016-12-02 12:53:55 +0100883 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100884 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100885
Ole Troan5016f992017-01-19 09:44:44 +0100886 This will be called for async notifications in sync mode,
887 and all messages in async mode. In sync mode, replies to
888 requests will not come here.
889
890 callback is a fn(msg_type_name, msg_type) that will be
891 called when a message comes in. While this function is
892 executing, note that (a) you are in a background thread and
893 may wish to use threading.Lock to protect your datastructures,
894 and (b) message processing from VPP will stop (so if you take
895 a long while about it you may provoke reply timeouts or cause
896 VPP to fill the RX buffer). Passing None will disable the
897 callback.
898 """
899 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100900
901 def thread_msg_handler(self):
Ole Troan94495f22018-08-02 11:58:12 +0200902 """Python thread calling the user registered message handler.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100903
904 This is to emulate the old style event callback scheme. Modern
905 clients should provide their own thread to poll the event
906 queue.
907 """
908 while True:
909 r = self.message_queue.get()
Klement Sekera180402d2018-02-17 10:58:37 +0100910 if r == "terminate event thread":
911 break
Ole Troandfc9b7c2017-03-06 23:51:57 +0100912 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200913 if self.event_callback:
914 self.event_callback(msgname, r)
Chris Luke52bf22e2017-11-03 23:32:38 -0400915
Ole Troanc046d702019-10-14 23:07:06 +0200916 def validate_message_table(self, namecrctable):
917 """Take a dictionary of name_crc message names
918 and returns an array of missing messages"""
919
920 missing_table = []
921 for name_crc in namecrctable:
922 i = self.transport.get_msg_index(name_crc)
923 if i <= 0:
924 missing_table.append(name_crc)
925 return missing_table
926
927 def dump_message_table(self):
928 """Return VPPs API message table as name_crc dictionary"""
929 return self.transport.message_table
930
931 def dump_message_table_filtered(self, msglist):
932 """Return VPPs API message table as name_crc dictionary,
933 filtered by message name list."""
934
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200935 replies = [self.services[n]["reply"] for n in msglist]
Ole Troanc046d702019-10-14 23:07:06 +0200936 message_table_filtered = {}
937 for name in msglist + replies:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200938 for k, v in self.transport.message_table.items():
Ole Troanc046d702019-10-14 23:07:06 +0200939 if k.startswith(name):
940 message_table_filtered[k] = v
941 break
942 return message_table_filtered
943
Paul Vinciguerra8a2fa3b2019-06-16 21:23:31 -0400944 def __repr__(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200945 return (
946 "<VPPApiClient apifiles=%s, testmode=%s, async_thread=%s, "
947 "logger=%s, read_timeout=%s, "
948 "server_address='%s'>"
949 % (
950 self._apifiles,
951 self.testmode,
952 self.async_thread,
953 self.logger,
954 self.read_timeout,
955 self.server_address,
956 )
957 )
Paul Vinciguerra8a2fa3b2019-06-16 21:23:31 -0400958
Ole Troanf5db3712020-05-20 15:47:06 +0200959 def details_iter(self, f, **kwargs):
960 cursor = 0
961 while True:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200962 kwargs["cursor"] = cursor
Ole Troanf5db3712020-05-20 15:47:06 +0200963 rv, details = f(**kwargs)
Ole Troanf5db3712020-05-20 15:47:06 +0200964 for d in details:
965 yield d
966 if rv.retval == 0 or rv.retval != -165:
967 break
968 cursor = rv.cursor