blob: a9edfed81bef6f4f3fdefd190dda0e6d5e7f3061 [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
Pim van Pelt15882222022-12-12 00:00:16 +0000157 def find_api_dir(cls, dirs=[]):
Chris Luke52bf22e2017-11-03 23:32:38 -0400158 """Attempt to find the best directory in which API definition
159 files may reside. If the value VPP_API_DIR exists in the environment
160 then it is first on the search list. If we're inside a recognized
161 location in a VPP source tree (src/scripts and src/vpp-api/python)
162 then entries from there to the likely locations in build-root are
163 added. Finally the location used by system packages is added.
164
165 :returns: A single directory name, or None if no such directory
166 could be found.
167 """
Chris Luke52bf22e2017-11-03 23:32:38 -0400168
169 # perhaps we're in the 'src/scripts' or 'src/vpp-api/python' dir;
170 # in which case, plot a course to likely places in the src tree
171 import __main__ as main
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200172
Pim van Pelt15882222022-12-12 00:00:16 +0000173 if os.getenv("VPP_API_DIR"):
174 dirs.append(os.getenv("VPP_API_DIR"))
175
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200176 if hasattr(main, "__file__"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400177 # get the path of the calling script
178 localdir = os.path.dirname(os.path.realpath(main.__file__))
179 else:
180 # use cwd if there is no calling script
Andrey "Zed" Zaikin68e2ffb2018-04-24 14:50:02 +0300181 localdir = os.getcwd()
Chris Luke52bf22e2017-11-03 23:32:38 -0400182 localdir_s = localdir.split(os.path.sep)
183
184 def dmatch(dir):
185 """Match dir against right-hand components of the script dir"""
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200186 d = dir.split("/") # param 'dir' assumes a / separator
Ole Troanafddd832018-02-28 14:55:20 +0100187 length = len(d)
188 return len(localdir_s) > length and localdir_s[-length:] == d
Chris Luke52bf22e2017-11-03 23:32:38 -0400189
190 def sdir(srcdir, variant):
191 """Build a path from srcdir to the staged API files of
192 'variant' (typically '' or '_debug')"""
193 # Since 'core' and 'plugin' files are staged
194 # in separate directories, we target the parent dir.
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200195 return os.path.sep.join(
196 (
197 srcdir,
198 "build-root",
199 "install-vpp%s-native" % variant,
200 "vpp",
201 "share",
202 "vpp",
203 "api",
204 )
205 )
Chris Luke52bf22e2017-11-03 23:32:38 -0400206
207 srcdir = None
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200208 if dmatch("src/scripts"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400209 srcdir = os.path.sep.join(localdir_s[:-2])
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200210 elif dmatch("src/vpp-api/python"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400211 srcdir = os.path.sep.join(localdir_s[:-3])
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200212 elif dmatch("test"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400213 # we're apparently running tests
214 srcdir = os.path.sep.join(localdir_s[:-1])
215
216 if srcdir:
217 # we're in the source tree, try both the debug and release
218 # variants.
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200219 dirs.append(sdir(srcdir, "_debug"))
220 dirs.append(sdir(srcdir, ""))
Chris Luke52bf22e2017-11-03 23:32:38 -0400221
222 # Test for staged copies of the scripts
223 # For these, since we explicitly know if we're running a debug versus
224 # release variant, target only the relevant directory
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200225 if dmatch("build-root/install-vpp_debug-native/vpp/bin"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400226 srcdir = os.path.sep.join(localdir_s[:-4])
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200227 dirs.append(sdir(srcdir, "_debug"))
228 if dmatch("build-root/install-vpp-native/vpp/bin"):
Chris Luke52bf22e2017-11-03 23:32:38 -0400229 srcdir = os.path.sep.join(localdir_s[:-4])
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200230 dirs.append(sdir(srcdir, ""))
Chris Luke52bf22e2017-11-03 23:32:38 -0400231
232 # finally, try the location system packages typically install into
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200233 dirs.append(os.path.sep.join(("", "usr", "share", "vpp", "api")))
Chris Luke52bf22e2017-11-03 23:32:38 -0400234
Paul Vinciguerra19542292019-03-17 17:34:46 -0700235 # check the directories for existence; first one wins
Chris Luke52bf22e2017-11-03 23:32:38 -0400236 for dir in dirs:
237 if os.path.isdir(dir):
238 return dir
239
240 return None
241
242 @classmethod
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200243 def find_api_files(cls, api_dir=None, patterns="*"): # -> list
Chris Luke52bf22e2017-11-03 23:32:38 -0400244 """Find API definition files from the given directory tree with the
245 given pattern. If no directory is given then find_api_dir() is used
246 to locate one. If no pattern is given then all definition files found
247 in the directory tree are used.
248
249 :param api_dir: A directory tree in which to locate API definition
250 files; subdirectories are descended into.
251 If this is None then find_api_dir() is called to discover it.
252 :param patterns: A list of patterns to use in each visited directory
253 when looking for files.
254 This can be a list/tuple object or a comma-separated string of
255 patterns. Each value in the list will have leading/trialing
256 whitespace stripped.
257 The pattern specifies the first part of the filename, '.api.json'
258 is appended.
259 The results are de-duplicated, thus overlapping patterns are fine.
260 If this is None it defaults to '*' meaning "all API files".
261 :returns: A list of file paths for the API files found.
262 """
263 if api_dir is None:
Ole Troanedfe2c02019-07-30 15:38:13 +0200264 api_dir = cls.find_api_dir([])
Chris Luke52bf22e2017-11-03 23:32:38 -0400265 if api_dir is None:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800266 raise VPPApiError("api_dir cannot be located")
Chris Luke52bf22e2017-11-03 23:32:38 -0400267
268 if isinstance(patterns, list) or isinstance(patterns, tuple):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200269 patterns = [p.strip() + ".api.json" for p in patterns]
Chris Luke52bf22e2017-11-03 23:32:38 -0400270 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200271 patterns = [p.strip() + ".api.json" for p in patterns.split(",")]
Chris Luke52bf22e2017-11-03 23:32:38 -0400272
273 api_files = []
274 for root, dirnames, files in os.walk(api_dir):
275 # iterate all given patterns and de-dup the result
276 files = set(sum([fnmatch.filter(files, p) for p in patterns], []))
277 for filename in files:
278 api_files.append(os.path.join(root, filename))
279
280 return api_files
281
Ole Troanedfe2c02019-07-30 15:38:13 +0200282 @classmethod
283 def process_json_file(self, apidef_file):
284 api = json.load(apidef_file)
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500285 return self._process_json(api)
286
287 @classmethod
288 def process_json_str(self, json_str):
289 api = json.loads(json_str)
290 return self._process_json(api)
291
292 @staticmethod
293 def _process_json(api): # -> Tuple[Dict, Dict]
Ole Troanedfe2c02019-07-30 15:38:13 +0200294 types = {}
295 services = {}
296 messages = {}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500297 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200298 for t in api["enums"]:
299 t[0] = "vl_api_" + t[0] + "_t"
300 types[t[0]] = {"type": "enum", "data": t}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500301 except KeyError:
302 pass
Paul Vinciguerra3825d932020-12-03 21:06:28 -0500303 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200304 for t in api["enumflags"]:
305 t[0] = "vl_api_" + t[0] + "_t"
306 types[t[0]] = {"type": "enum", "data": t}
Paul Vinciguerra3825d932020-12-03 21:06:28 -0500307 except KeyError:
308 pass
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500309 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200310 for t in api["unions"]:
311 t[0] = "vl_api_" + t[0] + "_t"
312 types[t[0]] = {"type": "union", "data": t}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500313 except KeyError:
314 pass
315
316 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200317 for t in api["types"]:
318 t[0] = "vl_api_" + t[0] + "_t"
319 types[t[0]] = {"type": "type", "data": t}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500320 except KeyError:
321 pass
322
323 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200324 for t, v in api["aliases"].items():
325 types["vl_api_" + t + "_t"] = {"type": "alias", "data": v}
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500326 except KeyError:
327 pass
328
329 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200330 services.update(api["services"])
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500331 except KeyError:
332 pass
Ole Troanedfe2c02019-07-30 15:38:13 +0200333
334 i = 0
335 while True:
336 unresolved = {}
337 for k, v in types.items():
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200338 t = v["data"]
Ole Troanedfe2c02019-07-30 15:38:13 +0200339 if not vpp_get_type(k):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200340 if v["type"] == "enum":
Ole Troanedfe2c02019-07-30 15:38:13 +0200341 try:
342 VPPEnumType(t[0], t[1:])
343 except ValueError:
344 unresolved[k] = v
Paul Vinciguerra3825d932020-12-03 21:06:28 -0500345 if not vpp_get_type(k):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200346 if v["type"] == "enumflag":
Paul Vinciguerra3825d932020-12-03 21:06:28 -0500347 try:
348 VPPEnumFlagType(t[0], t[1:])
349 except ValueError:
350 unresolved[k] = v
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200351 elif v["type"] == "union":
Ole Troanedfe2c02019-07-30 15:38:13 +0200352 try:
353 VPPUnionType(t[0], t[1:])
354 except ValueError:
355 unresolved[k] = v
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200356 elif v["type"] == "type":
Ole Troanedfe2c02019-07-30 15:38:13 +0200357 try:
358 VPPType(t[0], t[1:])
359 except ValueError:
360 unresolved[k] = v
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200361 elif v["type"] == "alias":
Ole Troanedfe2c02019-07-30 15:38:13 +0200362 try:
363 VPPTypeAlias(k, t)
364 except ValueError:
365 unresolved[k] = v
366 if len(unresolved) == 0:
367 break
368 if i > 3:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200369 raise VPPValueError("Unresolved type definitions {}".format(unresolved))
Ole Troanedfe2c02019-07-30 15:38:13 +0200370 types = unresolved
371 i += 1
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500372 try:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200373 for m in api["messages"]:
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500374 try:
375 messages[m[0]] = VPPMessage(m[0], m[1:])
376 except VPPNotImplementedError:
377 ### OLE FIXME
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200378 logger.error("Not implemented error for {}".format(m[0]))
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500379 except KeyError:
380 pass
Ole Troanedfe2c02019-07-30 15:38:13 +0200381 return messages, services
382
Paul Vinciguerrae64e5ff2020-04-28 00:27:38 -0400383
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500384class VPPApiClient:
Ole Troanedfe2c02019-07-30 15:38:13 +0200385 """VPP interface.
386
387 This class provides the APIs to VPP. The APIs are loaded
388 from provided .api.json files and makes functions accordingly.
389 These functions are documented in the VPP .api files, as they
390 are dynamically created.
391
392 Additionally, VPP can send callback messages; this class
393 provides a means to register a callback function to receive
394 these messages in a background thread.
395 """
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200396
Ole Troanedfe2c02019-07-30 15:38:13 +0200397 apidir = None
398 VPPApiError = VPPApiError
399 VPPRuntimeError = VPPRuntimeError
400 VPPValueError = VPPValueError
401 VPPNotImplementedError = VPPNotImplementedError
402 VPPIOError = VPPIOError
403
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200404 def __init__(
405 self,
406 *,
407 apifiles=None,
408 testmode=False,
409 async_thread=True,
410 logger=None,
411 loglevel=None,
412 read_timeout=5,
413 use_socket=True,
414 server_address="/run/vpp/api.sock",
415 ):
Ole Troanedfe2c02019-07-30 15:38:13 +0200416 """Create a VPP API object.
417
418 apifiles is a list of files containing API
419 descriptions that will be loaded - methods will be
420 dynamically created reflecting these APIs. If not
421 provided this will load the API files from VPP's
422 default install location.
423
424 logger, if supplied, is the logging logger object to log to.
425 loglevel, if supplied, is the log level this logger is set
426 to report at (from the loglevels in the logging module).
427 """
428 if logger is None:
429 logger = logging.getLogger(
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200430 "{}.{}".format(__name__, self.__class__.__name__)
431 )
Ole Troanedfe2c02019-07-30 15:38:13 +0200432 if loglevel is not None:
433 logger.setLevel(loglevel)
434 self.logger = logger
435
436 self.messages = {}
437 self.services = {}
438 self.id_names = []
439 self.id_msgdef = []
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200440 self.header = VPPType("header", [["u16", "msgid"], ["u32", "client_index"]])
Ole Troanedfe2c02019-07-30 15:38:13 +0200441 self.apifiles = []
442 self.event_callback = None
443 self.message_queue = queue.Queue()
444 self.read_timeout = read_timeout
445 self.async_thread = async_thread
446 self.event_thread = None
447 self.testmode = testmode
Ole Troanedfe2c02019-07-30 15:38:13 +0200448 self.server_address = server_address
449 self._apifiles = apifiles
Ole Troanfd574082019-11-27 23:12:48 +0100450 self.stats = {}
Ole Troanedfe2c02019-07-30 15:38:13 +0200451
Ole Troanedfe2c02019-07-30 15:38:13 +0200452 if not apifiles:
453 # Pick up API definitions from default directory
454 try:
Ole Troan37157da2022-12-01 11:22:06 +0100455 if isinstance(self.apidir, list):
456 apifiles = []
457 for d in self.apidir:
458 apifiles += VPPApiJSONFiles.find_api_files(d)
459 else:
460 apifiles = VPPApiJSONFiles.find_api_files(self.apidir)
Paul Vinciguerra46d68642020-12-01 02:00:35 -0500461 except (RuntimeError, VPPApiError):
Ole Troanedfe2c02019-07-30 15:38:13 +0200462 # In test mode we don't care that we can't find the API files
463 if testmode:
464 apifiles = []
465 else:
466 raise VPPRuntimeError
467
468 for file in apifiles:
469 with open(file) as apidef_file:
470 m, s = VPPApiJSONFiles.process_json_file(apidef_file)
471 self.messages.update(m)
472 self.services.update(s)
473
474 self.apifiles = apifiles
475
476 # Basic sanity check
477 if len(self.messages) == 0 and not testmode:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200478 raise VPPValueError(1, "Missing JSON message definitions")
479 if not (verify_enum_hint(VppEnum.vl_api_address_family_t)):
480 raise VPPRuntimeError("Invalid address family hints. " "Cannot continue.")
Ole Troanedfe2c02019-07-30 15:38:13 +0200481
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200482 self.transport = VppTransport(
483 self, read_timeout=read_timeout, server_address=server_address
484 )
Ole Troanedfe2c02019-07-30 15:38:13 +0200485 # Make sure we allow VPP to clean up the message rings.
486 atexit.register(vpp_atexit, weakref.ref(self))
487
Paul Vinciguerrae64e5ff2020-04-28 00:27:38 -0400488 add_convenience_methods()
489
Ole Troanedfe2c02019-07-30 15:38:13 +0200490 def get_function(self, name):
491 return getattr(self._api, name)
492
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500493 class ContextId:
Ole Troanedfe2c02019-07-30 15:38:13 +0200494 """Multiprocessing-safe provider of unique context IDs."""
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200495
Ole Troanedfe2c02019-07-30 15:38:13 +0200496 def __init__(self):
497 self.context = mp.Value(ctypes.c_uint, 0)
498 self.lock = mp.Lock()
499
500 def __call__(self):
501 """Get a new unique (or, at least, not recently used) context."""
502 with self.lock:
503 self.context.value += 1
504 return self.context.value
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200505
Ole Troanedfe2c02019-07-30 15:38:13 +0200506 get_context = ContextId()
507
508 def get_type(self, name):
509 return vpp_get_type(name)
510
Klement Sekera7112c542017-03-01 09:53:19 +0100511 @property
512 def api(self):
513 if not hasattr(self, "_api"):
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800514 raise VPPApiError("Not connected, api definitions not available")
Klement Sekera7112c542017-03-01 09:53:19 +0100515 return self._api
516
Ole Troaneabd6072018-08-09 12:50:55 +0200517 def make_function(self, msg, i, multipart, do_async):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200518 if do_async:
519
Ole Troana7564e82018-06-12 21:06:44 +0200520 def f(**kwargs):
521 return self._call_vpp_async(i, msg, **kwargs)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200522
Ole Troana7564e82018-06-12 21:06:44 +0200523 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200524
Ole Troana7564e82018-06-12 21:06:44 +0200525 def f(**kwargs):
526 return self._call_vpp(i, msg, multipart, **kwargs)
527
528 f.__name__ = str(msg.name)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200529 f.__doc__ = ", ".join(
530 ["%s %s" % (msg.fieldtypes[j], k) for j, k in enumerate(msg.fields)]
531 )
Ole Troanf159f582019-02-28 20:20:47 +0100532 f.msg = msg
533
Ole Troana7564e82018-06-12 21:06:44 +0200534 return f
535
Ole Troanbcdde1a2022-12-06 17:42:24 +0100536 def make_pack_function(self, msg, i, multipart):
537 def f(**kwargs):
538 return self._call_vpp_pack(i, msg, **kwargs)
539
540 f.msg = msg
541 return f
542
Ole Troaneabd6072018-08-09 12:50:55 +0200543 def _register_functions(self, do_async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100544 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
545 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera8aedf5e2018-07-06 11:07:21 +0200546 self._api = VppApiDynamicMethodHolder()
Paul Vinciguerrad7adc292020-12-02 14:34:27 -0500547 for name, msg in self.messages.items():
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200548 n = name + "_" + msg.crc[2:]
Ole Troandaa4bff2019-08-28 14:12:02 +0200549 i = self.transport.get_msg_index(n)
Ole Troan3cc49712017-03-08 12:02:24 +0100550 if i > 0:
Ole Troana7564e82018-06-12 21:06:44 +0200551 self.id_msgdef[i] = msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100552 self.id_names[i] = name
Ole Troandfb984d2018-12-07 14:31:16 +0100553
554 # Create function for client side messages.
555 if name in self.services:
Ole Troanf5db3712020-05-20 15:47:06 +0200556 f = self.make_function(msg, i, self.services[name], do_async)
Ole Troanbcdde1a2022-12-06 17:42:24 +0100557 f_pack = self.make_pack_function(msg, i, self.services[name])
Ole Troandfb984d2018-12-07 14:31:16 +0100558 setattr(self._api, name, FuncWrapper(f))
Ole Troanbcdde1a2022-12-06 17:42:24 +0100559 setattr(self._api, name + "_pack", FuncWrapper(f_pack))
Ole Troan3cc49712017-03-08 12:02:24 +0100560 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200561 self.logger.debug("No such message type or failed CRC checksum: %s", n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100562
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200563 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, do_async):
564 pfx = chroot_prefix.encode("utf-8") if chroot_prefix else None
Ole Troan94495f22018-08-02 11:58:12 +0200565
Ole Troanf22824b2022-12-06 17:30:49 +0100566 rv = self.transport.connect(name, pfx, msg_handler, rx_qlen, do_async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100567 if rv != 0:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200568 raise VPPIOError(2, "Connect failed")
Ole Troan94495f22018-08-02 11:58:12 +0200569 self.vpp_dictionary_maxid = self.transport.msg_table_max_index()
Ole Troaneabd6072018-08-09 12:50:55 +0200570 self._register_functions(do_async=do_async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100571
572 # Initialise control ping
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200573 crc = self.messages["control_ping"].crc
Ole Troan94495f22018-08-02 11:58:12 +0200574 self.control_ping_index = self.transport.get_msg_index(
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200575 ("control_ping" + "_" + crc[2:])
576 )
577 self.control_ping_msgdef = self.messages["control_ping"]
Klement Sekera180402d2018-02-17 10:58:37 +0100578 if self.async_thread:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200579 self.event_thread = threading.Thread(target=self.thread_msg_handler)
Klement Sekera180402d2018-02-17 10:58:37 +0100580 self.event_thread.daemon = True
581 self.event_thread.start()
Vratko Polak94e45312019-05-27 18:36:23 +0200582 else:
583 self.event_thread = None
Ole Troan4df97162017-07-07 16:06:08 +0200584 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100585
Ole Troaneabd6072018-08-09 12:50:55 +0200586 def connect(self, name, chroot_prefix=None, do_async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100587 """Attach to VPP.
588
589 name - the name of the client.
590 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
Ole Troaneabd6072018-08-09 12:50:55 +0200591 do_async - if true, messages are sent without waiting for a reply
Ole Troandfc9b7c2017-03-06 23:51:57 +0100592 rx_qlen - the length of the VPP message receive queue between
593 client and server.
594 """
Ole Troan94495f22018-08-02 11:58:12 +0200595 msg_handler = self.transport.get_callback(do_async)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200596 return self.connect_internal(
597 name, msg_handler, chroot_prefix, rx_qlen, do_async
598 )
Ole Troandfc9b7c2017-03-06 23:51:57 +0100599
Ole Troan6bf177c2017-08-17 10:34:32 +0200600 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100601 """Attach to VPP in synchronous mode. Application must poll for events.
602
603 name - the name of the client.
604 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
605 rx_qlen - the length of the VPP message receive queue between
606 client and server.
607 """
608
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200609 return self.connect_internal(name, None, chroot_prefix, rx_qlen, do_async=False)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100610
Ole Troana03f4ef2016-12-02 12:53:55 +0100611 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100612 """Detach from VPP."""
Ole Troan94495f22018-08-02 11:58:12 +0200613 rv = self.transport.disconnect()
Vratko Polak94e45312019-05-27 18:36:23 +0200614 if self.event_thread is not None:
615 self.message_queue.put("terminate event thread")
Ole Troana03f4ef2016-12-02 12:53:55 +0100616 return rv
617
Ole Troan5016f992017-01-19 09:44:44 +0100618 def msg_handler_sync(self, msg):
619 """Process an incoming message from VPP in sync mode.
620
621 The message may be a reply or it may be an async notification.
622 """
623 r = self.decode_incoming_msg(msg)
624 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100625 return
626
Ole Troan5016f992017-01-19 09:44:44 +0100627 # If we have a context, then use the context to find any
628 # request waiting for a reply
629 context = 0
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200630 if hasattr(r, "context") and r.context > 0:
Ole Troan5016f992017-01-19 09:44:44 +0100631 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200632
Ole Troan5016f992017-01-19 09:44:44 +0100633 if context == 0:
634 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100635 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100636 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200637 raise VPPIOError(2, "RPC reply message received in event handler")
Ole Troan5016f992017-01-19 09:44:44 +0100638
Ole Troan413f4a52018-11-28 11:36:05 +0100639 def has_context(self, msg):
640 if len(msg) < 10:
641 return False
642
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200643 header = VPPType(
644 "header_with_context",
645 [["u16", "msgid"], ["u32", "client_index"], ["u32", "context"]],
646 )
Ole Troan413f4a52018-11-28 11:36:05 +0100647
648 (i, ci, context), size = header.unpack(msg, 0)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200649 if self.id_names[i] == "rx_thread_exit":
Ole Troan413f4a52018-11-28 11:36:05 +0100650 return
651
652 #
653 # Decode message and returns a tuple.
654 #
655 msgobj = self.id_msgdef[i]
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200656 if "context" in msgobj.field_by_name and context >= 0:
Ole Troan413f4a52018-11-28 11:36:05 +0100657 return True
658 return False
659
Ole Troan0bcad322018-12-11 13:04:01 +0100660 def decode_incoming_msg(self, msg, no_type_conversion=False):
Ole Troan5016f992017-01-19 09:44:44 +0100661 if not msg:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200662 logger.warning("vpp_api.read failed")
Ole Troan5016f992017-01-19 09:44:44 +0100663 return
Ole Troan413f4a52018-11-28 11:36:05 +0100664
Ole Troanc84cbad2018-09-06 22:58:05 +0200665 (i, ci), size = self.header.unpack(msg, 0)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200666 if self.id_names[i] == "rx_thread_exit":
Ole Troan5016f992017-01-19 09:44:44 +0100667 return
668
669 #
670 # Decode message and returns a tuple.
671 #
Ole Troana7564e82018-06-12 21:06:44 +0200672 msgobj = self.id_msgdef[i]
673 if not msgobj:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200674 raise VPPIOError(2, "Reply message undefined")
Ole Troan5016f992017-01-19 09:44:44 +0100675
Ole Troan0bcad322018-12-11 13:04:01 +0100676 r, size = msgobj.unpack(msg, ntc=no_type_conversion)
Ole Troana03f4ef2016-12-02 12:53:55 +0100677 return r
678
Ole Troan5016f992017-01-19 09:44:44 +0100679 def msg_handler_async(self, msg):
680 """Process a message from VPP in async mode.
681
682 In async mode, all messages are returned to the callback.
683 """
684 r = self.decode_incoming_msg(msg)
685 if r is None:
686 return
687
688 msgname = type(r).__name__
689
Ole Troan4df97162017-07-07 16:06:08 +0200690 if self.event_callback:
691 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100692
693 def _control_ping(self, context):
694 """Send a ping command."""
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200695 self._call_vpp_async(
696 self.control_ping_index, self.control_ping_msgdef, context=context
697 )
Ole Troan5016f992017-01-19 09:44:44 +0100698
Ole Troana7564e82018-06-12 21:06:44 +0200699 def validate_args(self, msg, kwargs):
700 d = set(kwargs.keys()) - set(msg.field_by_name.keys())
701 if d:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200702 raise VPPValueError("Invalid argument {} to {}".format(list(d), msg.name))
Ole Troana7564e82018-06-12 21:06:44 +0200703
Ole Troanfd574082019-11-27 23:12:48 +0100704 def _add_stat(self, name, ms):
705 if not name in self.stats:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200706 self.stats[name] = {"max": ms, "count": 1, "avg": ms}
Ole Troanfd574082019-11-27 23:12:48 +0100707 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200708 if ms > self.stats[name]["max"]:
709 self.stats[name]["max"] = ms
710 self.stats[name]["count"] += 1
711 n = self.stats[name]["count"]
712 self.stats[name]["avg"] = self.stats[name]["avg"] * (n - 1) / n + ms / n
Ole Troanfd574082019-11-27 23:12:48 +0100713
714 def get_stats(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200715 s = "\n=== API PAPI STATISTICS ===\n"
716 s += "{:<30} {:>4} {:>6} {:>6}\n".format("message", "cnt", "avg", "max")
717 for n in sorted(self.stats.items(), key=lambda v: v[1]["avg"], reverse=True):
718 s += "{:<30} {:>4} {:>6.2f} {:>6.2f}\n".format(
719 n[0], n[1]["count"], n[1]["avg"], n[1]["max"]
720 )
Ole Troanfd574082019-11-27 23:12:48 +0100721 return s
722
Paul Vinciguerra65315142020-04-26 22:04:32 -0400723 def get_field_options(self, msg, fld_name):
724 # when there is an option, the msgdef has 3 elements.
725 # ['u32', 'ring_size', {'default': 1024}]
726 for _def in self.messages[msg].msgdef:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200727 if isinstance(_def, list) and len(_def) == 3 and _def[1] == fld_name:
Paul Vinciguerra65315142020-04-26 22:04:32 -0400728 return _def[2]
729
Ole Troanf5db3712020-05-20 15:47:06 +0200730 def _call_vpp(self, i, msgdef, service, **kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100731 """Given a message, send the message and await a reply.
732
733 msgdef - the message packing definition
734 i - the message type index
735 multipart - True if the message returns multiple
736 messages in return.
737 context - context number - chosen at random if not
738 supplied.
739 The remainder of the kwargs are the arguments to the API call.
740
741 The return value is the message or message array containing
742 the response. It will raise an IOError exception if there was
743 no response within the timeout window.
744 """
Ole Troanfd574082019-11-27 23:12:48 +0100745 ts = time.time()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200746 if "context" not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100747 context = self.get_context()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200748 kwargs["context"] = context
Ole Troandfc9b7c2017-03-06 23:51:57 +0100749 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200750 context = kwargs["context"]
751 kwargs["_vl_msg_id"] = i
Ole Troan5016f992017-01-19 09:44:44 +0100752
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200753 no_type_conversion = kwargs.pop("_no_type_conversion", False)
754 timeout = kwargs.pop("_timeout", None)
Ole Troan0bcad322018-12-11 13:04:01 +0100755
Ole Troan94495f22018-08-02 11:58:12 +0200756 try:
757 if self.transport.socket_index:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200758 kwargs["client_index"] = self.transport.socket_index
Ole Troan94495f22018-08-02 11:58:12 +0200759 except AttributeError:
760 pass
Ole Troan413f4a52018-11-28 11:36:05 +0100761 self.validate_args(msgdef, kwargs)
762
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200763 s = "Calling {}({})".format(
764 msgdef.name, ",".join(["{!r}:{!r}".format(k, v) for k, v in kwargs.items()])
765 )
Vratko Polakb6590202019-07-16 14:32:55 +0200766 self.logger.debug(s)
Ole Troan413f4a52018-11-28 11:36:05 +0100767
768 b = msgdef.pack(kwargs)
Ole Troan94495f22018-08-02 11:58:12 +0200769 self.transport.suspend()
770
771 self.transport.write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100772
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200773 msgreply = service["reply"]
774 stream = True if "stream" in service else False
Ole Troanf5db3712020-05-20 15:47:06 +0200775 if stream:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200776 if "stream_msg" in service:
Ole Troanf5db3712020-05-20 15:47:06 +0200777 # New service['reply'] = _reply and service['stream_message'] = _details
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200778 stream_message = service["stream_msg"]
779 modern = True
Ole Troanf5db3712020-05-20 15:47:06 +0200780 else:
781 # Old service['reply'] = _details
782 stream_message = msgreply
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200783 msgreply = "control_ping_reply"
Ole Troanf5db3712020-05-20 15:47:06 +0200784 modern = False
785 # Send a ping after the request - we use its response
786 # to detect that we have seen all results.
787 self._control_ping(context)
Ole Troan5016f992017-01-19 09:44:44 +0100788
789 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100790 rl = []
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200791 while True:
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500792 r = self.read_blocking(no_type_conversion, timeout)
Vratko Polak09385472019-09-10 13:35:11 +0200793 if r is None:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200794 raise VPPIOError(2, "VPP API client: read failed")
Ole Troandfc9b7c2017-03-06 23:51:57 +0100795 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200796 if context not in r or r.context == 0 or context != r.context:
Ole Troan94495f22018-08-02 11:58:12 +0200797 # Message being queued
Ole Troandfc9b7c2017-03-06 23:51:57 +0100798 self.message_queue.put_nowait(r)
799 continue
Ole Troanf5db3712020-05-20 15:47:06 +0200800 if msgname != msgreply and (stream and (msgname != stream_message)):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200801 print("REPLY MISMATCH", msgreply, msgname, stream_message, stream)
Ole Troanf5db3712020-05-20 15:47:06 +0200802 if not stream:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100803 rl = r
804 break
Ole Troanf5db3712020-05-20 15:47:06 +0200805 if msgname == msgreply:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200806 if modern: # Return both reply and list
Ole Troanf5db3712020-05-20 15:47:06 +0200807 rl = r, rl
Ole Troandfc9b7c2017-03-06 23:51:57 +0100808 break
809
810 rl.append(r)
811
Ole Troan94495f22018-08-02 11:58:12 +0200812 self.transport.resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100813
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200814 s = "Return value: {!r}".format(r)
Klement Sekera5e2f84d2019-09-12 09:01:06 +0000815 if len(s) > 80:
816 s = s[:80] + "..."
817 self.logger.debug(s)
Ole Troanfd574082019-11-27 23:12:48 +0100818 te = time.time()
819 self._add_stat(msgdef.name, (te - ts) * 1000)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100820 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100821
Ole Troana7564e82018-06-12 21:06:44 +0200822 def _call_vpp_async(self, i, msg, **kwargs):
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200823 """Given a message, send the message and return the context.
Ole Troan5016f992017-01-19 09:44:44 +0100824
825 msgdef - the message packing definition
826 i - the message type index
827 context - context number - chosen at random if not
828 supplied.
829 The remainder of the kwargs are the arguments to the API call.
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200830
831 The reply message(s) will be delivered later to the registered callback.
832 The returned context will help with assigning which call
833 the reply belongs to.
Ole Troan5016f992017-01-19 09:44:44 +0100834 """
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200835 if "context" not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100836 context = self.get_context()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200837 kwargs["context"] = context
Ole Troan7e3a8752016-12-05 10:27:09 +0100838 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200839 context = kwargs["context"]
Ole Troan94495f22018-08-02 11:58:12 +0200840 try:
841 if self.transport.socket_index:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200842 kwargs["client_index"] = self.transport.socket_index
Ole Troan94495f22018-08-02 11:58:12 +0200843 except AttributeError:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200844 kwargs["client_index"] = 0
845 kwargs["_vl_msg_id"] = i
Ole Troana7564e82018-06-12 21:06:44 +0200846 b = msg.pack(kwargs)
Ole Troan7e3a8752016-12-05 10:27:09 +0100847
Ole Troan94495f22018-08-02 11:58:12 +0200848 self.transport.write(b)
Vratko Polak2f6e0c62019-09-06 15:20:07 +0200849 return context
Ole Troan7e3a8752016-12-05 10:27:09 +0100850
Ole Troanbcdde1a2022-12-06 17:42:24 +0100851 def _call_vpp_pack(self, i, msg, **kwargs):
852 """Given a message, return the binary representation."""
853 kwargs["_vl_msg_id"] = i
854 kwargs["client_index"] = 0
855 kwargs["context"] = 0
856 return msg.pack(kwargs)
857
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500858 def read_blocking(self, no_type_conversion=False, timeout=None):
Vratko Polak09385472019-09-10 13:35:11 +0200859 """Get next received message from transport within timeout, decoded.
860
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500861 Note that notifications have context zero
Vratko Polak09385472019-09-10 13:35:11 +0200862 and are not put into receive queue (at least for socket transport),
863 use async_thread with registered callback for processing them.
864
865 If no message appears in the queue within timeout, return None.
866
867 Optionally, type conversion can be skipped,
868 as some of conversions are into less precise types.
869
870 When r is the return value of this, the caller can get message name as:
871 msgname = type(r).__name__
872 and context number (type long) as:
873 context = r.context
874
875 :param no_type_conversion: If false, type conversions are applied.
876 :type no_type_conversion: bool
877 :returns: Decoded message, or None if no message (within timeout).
878 :rtype: Whatever VPPType.unpack returns, depends on no_type_conversion.
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500879 :raises VppTransportShmemIOError if timed out.
Vratko Polak09385472019-09-10 13:35:11 +0200880 """
Paul Vinciguerrae2ccdf02019-12-02 13:40:33 -0500881 msg = self.transport.read(timeout=timeout)
Vratko Polak09385472019-09-10 13:35:11 +0200882 if not msg:
883 return None
884 return self.decode_incoming_msg(msg, no_type_conversion)
885
Ole Troana03f4ef2016-12-02 12:53:55 +0100886 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100887 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100888
Ole Troan5016f992017-01-19 09:44:44 +0100889 This will be called for async notifications in sync mode,
890 and all messages in async mode. In sync mode, replies to
891 requests will not come here.
892
893 callback is a fn(msg_type_name, msg_type) that will be
894 called when a message comes in. While this function is
895 executing, note that (a) you are in a background thread and
896 may wish to use threading.Lock to protect your datastructures,
897 and (b) message processing from VPP will stop (so if you take
898 a long while about it you may provoke reply timeouts or cause
899 VPP to fill the RX buffer). Passing None will disable the
900 callback.
901 """
902 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100903
904 def thread_msg_handler(self):
Ole Troan94495f22018-08-02 11:58:12 +0200905 """Python thread calling the user registered message handler.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100906
907 This is to emulate the old style event callback scheme. Modern
908 clients should provide their own thread to poll the event
909 queue.
910 """
911 while True:
912 r = self.message_queue.get()
Klement Sekera180402d2018-02-17 10:58:37 +0100913 if r == "terminate event thread":
914 break
Ole Troandfc9b7c2017-03-06 23:51:57 +0100915 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200916 if self.event_callback:
917 self.event_callback(msgname, r)
Chris Luke52bf22e2017-11-03 23:32:38 -0400918
Ole Troanc046d702019-10-14 23:07:06 +0200919 def validate_message_table(self, namecrctable):
920 """Take a dictionary of name_crc message names
921 and returns an array of missing messages"""
922
923 missing_table = []
924 for name_crc in namecrctable:
925 i = self.transport.get_msg_index(name_crc)
926 if i <= 0:
927 missing_table.append(name_crc)
928 return missing_table
929
930 def dump_message_table(self):
931 """Return VPPs API message table as name_crc dictionary"""
932 return self.transport.message_table
933
934 def dump_message_table_filtered(self, msglist):
935 """Return VPPs API message table as name_crc dictionary,
936 filtered by message name list."""
937
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200938 replies = [self.services[n]["reply"] for n in msglist]
Ole Troanc046d702019-10-14 23:07:06 +0200939 message_table_filtered = {}
940 for name in msglist + replies:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200941 for k, v in self.transport.message_table.items():
Ole Troanc046d702019-10-14 23:07:06 +0200942 if k.startswith(name):
943 message_table_filtered[k] = v
944 break
945 return message_table_filtered
946
Paul Vinciguerra8a2fa3b2019-06-16 21:23:31 -0400947 def __repr__(self):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200948 return (
949 "<VPPApiClient apifiles=%s, testmode=%s, async_thread=%s, "
950 "logger=%s, read_timeout=%s, "
951 "server_address='%s'>"
952 % (
953 self._apifiles,
954 self.testmode,
955 self.async_thread,
956 self.logger,
957 self.read_timeout,
958 self.server_address,
959 )
960 )
Paul Vinciguerra8a2fa3b2019-06-16 21:23:31 -0400961
Ole Troanf5db3712020-05-20 15:47:06 +0200962 def details_iter(self, f, **kwargs):
963 cursor = 0
964 while True:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200965 kwargs["cursor"] = cursor
Ole Troanf5db3712020-05-20 15:47:06 +0200966 rv, details = f(**kwargs)
Ole Troanf5db3712020-05-20 15:47:06 +0200967 for d in details:
968 yield d
969 if rv.retval == 0 or rv.retval != -165:
970 break
971 cursor = rv.cursor