blob: 4de257c6924a48953faced4d701e0af55949d67c [file] [log] [blame]
Ole Troana03f4ef2016-12-02 12:53:55 +01001#!/usr/bin/env python
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
Ole Troan4df97162017-07-07 16:06:08 +020019import sys
20import os
21import logging
22import collections
23import struct
24import json
25import threading
Chris Luke52bf22e2017-11-03 23:32:38 -040026import fnmatch
Klement Sekera180402d2018-02-17 10:58:37 +010027import weakref
Ole Troan4df97162017-07-07 16:06:08 +020028import atexit
Ole Troana7564e82018-06-12 21:06:44 +020029from . vpp_serializer import VPPType, VPPEnumType, VPPUnionType, BaseTypes
Ole Troan53fffa12018-11-13 12:36:56 +010030from . vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias
Ole Troan4df97162017-07-07 16:06:08 +020031
Ole Troan413f4a52018-11-28 11:36:05 +010032logger = logging.getLogger(__name__)
33
Ole Troan4df97162017-07-07 16:06:08 +020034if sys.version[0] == '2':
35 import Queue as queue
36else:
37 import queue as queue
38
Ole Troanafddd832018-02-28 14:55:20 +010039
Ole Troan0685da42018-10-16 14:42:50 +020040class VppEnumType(type):
41 def __getattr__(cls, name):
42 t = vpp_get_type(name)
43 return t.enum
44
45
46# Python3
47# class VppEnum(metaclass=VppEnumType):
48# pass
Paul Vinciguerra7e713f12018-11-26 12:04:48 -080049class VppEnum(object):
Ole Troan0685da42018-10-16 14:42:50 +020050 __metaclass__ = VppEnumType
51
52
Klement Sekera180402d2018-02-17 10:58:37 +010053def vpp_atexit(vpp_weakref):
Ole Troan5016f992017-01-19 09:44:44 +010054 """Clean up VPP connection on shutdown."""
Klement Sekera180402d2018-02-17 10:58:37 +010055 vpp_instance = vpp_weakref()
Ole Troan94495f22018-08-02 11:58:12 +020056 if vpp_instance and vpp_instance.transport.connected:
Klement Sekera180402d2018-02-17 10:58:37 +010057 vpp_instance.logger.debug('Cleaning up VPP on exit')
58 vpp_instance.disconnect()
Ole Troan5016f992017-01-19 09:44:44 +010059
Ole Troan0bcad322018-12-11 13:04:01 +010060if sys.version[0] == '2':
61 def vpp_iterator(d):
Ole Troan4df97162017-07-07 16:06:08 +020062 return d.iteritems()
Ole Troan0bcad322018-12-11 13:04:01 +010063else:
64 def vpp_iterator(d):
Ole Troan4df97162017-07-07 16:06:08 +020065 return d.items()
66
67
Ole Troan413f4a52018-11-28 11:36:05 +010068def call_logger(msgdef, kwargs):
69 s = 'Calling {}('.format(msgdef.name)
70 for k, v in kwargs.items():
71 s += '{}:{} '.format(k, v)
72 s += ')'
73 return s
74
75
76def return_logger(r):
77 s = 'Return from {}'.format(r)
78 return s
79
80
Klement Sekera8aedf5e2018-07-06 11:07:21 +020081class VppApiDynamicMethodHolder(object):
Klement Sekera7112c542017-03-01 09:53:19 +010082 pass
83
84
85class FuncWrapper(object):
86 def __init__(self, func):
87 self._func = func
88 self.__name__ = func.__name__
89
90 def __call__(self, **kwargs):
91 return self._func(**kwargs)
92
93
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -080094class VPPApiError(Exception):
95 pass
96
97
98class VPPNotImplementedError(NotImplementedError):
99 pass
100
101
102class VPPIOError(IOError):
103 pass
104
105
106class VPPRuntimeError(RuntimeError):
107 pass
108
109
110class VPPValueError(ValueError):
111 pass
112
113
Paul Vinciguerra7e713f12018-11-26 12:04:48 -0800114class VPP(object):
Ole Troan5016f992017-01-19 09:44:44 +0100115 """VPP interface.
116
117 This class provides the APIs to VPP. The APIs are loaded
118 from provided .api.json files and makes functions accordingly.
119 These functions are documented in the VPP .api files, as they
120 are dynamically created.
121
122 Additionally, VPP can send callback messages; this class
123 provides a means to register a callback function to receive
124 these messages in a background thread.
125 """
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800126 VPPApiError = VPPApiError
127 VPPRuntimeError = VPPRuntimeError
128 VPPValueError = VPPValueError
129 VPPNotImplementedError = VPPNotImplementedError
130 VPPIOError = VPPIOError
Ole Troana7564e82018-06-12 21:06:44 +0200131
132 def process_json_file(self, apidef_file):
133 api = json.load(apidef_file)
134 types = {}
135 for t in api['enums']:
136 t[0] = 'vl_api_' + t[0] + '_t'
137 types[t[0]] = {'type': 'enum', 'data': t}
138 for t in api['unions']:
139 t[0] = 'vl_api_' + t[0] + '_t'
140 types[t[0]] = {'type': 'union', 'data': t}
141 for t in api['types']:
142 t[0] = 'vl_api_' + t[0] + '_t'
143 types[t[0]] = {'type': 'type', 'data': t}
Ole Troan53fffa12018-11-13 12:36:56 +0100144 for t, v in api['aliases'].items():
145 types['vl_api_' + t + '_t'] = {'type': 'alias', 'data': v}
Ole Troandfb984d2018-12-07 14:31:16 +0100146 self.services.update(api['services'])
Ole Troana7564e82018-06-12 21:06:44 +0200147
148 i = 0
149 while True:
150 unresolved = {}
151 for k, v in types.items():
152 t = v['data']
Ole Troan53fffa12018-11-13 12:36:56 +0100153 if not vpp_get_type(k):
Ole Troan0685da42018-10-16 14:42:50 +0200154 if v['type'] == 'enum':
155 try:
156 VPPEnumType(t[0], t[1:])
157 except ValueError:
158 unresolved[k] = v
159 elif v['type'] == 'union':
160 try:
161 VPPUnionType(t[0], t[1:])
162 except ValueError:
163 unresolved[k] = v
164 elif v['type'] == 'type':
165 try:
166 VPPType(t[0], t[1:])
167 except ValueError:
168 unresolved[k] = v
Ole Troan53fffa12018-11-13 12:36:56 +0100169 elif v['type'] == 'alias':
170 try:
171 VPPTypeAlias(k, t)
172 except ValueError:
173 unresolved[k] = v
Ole Troana7564e82018-06-12 21:06:44 +0200174 if len(unresolved) == 0:
175 break
176 if i > 3:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800177 raise VPPValueError('Unresolved type definitions {}'
178 .format(unresolved))
Ole Troana7564e82018-06-12 21:06:44 +0200179 types = unresolved
180 i += 1
181
182 for m in api['messages']:
183 try:
184 self.messages[m[0]] = VPPMessage(m[0], m[1:])
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800185 except VPPNotImplementedError:
Ole Troana7564e82018-06-12 21:06:44 +0200186 self.logger.error('Not implemented error for {}'.format(m[0]))
187
Ole Troan4df97162017-07-07 16:06:08 +0200188 def __init__(self, apifiles=None, testmode=False, async_thread=True,
Igor Mikhailov (imichail)5efd14f2018-10-30 12:17:49 -0700189 logger=None, loglevel=None,
Ole Troan94495f22018-08-02 11:58:12 +0200190 read_timeout=5, use_socket=False,
191 server_address='/run/vpp-api.sock'):
Ole Troan5016f992017-01-19 09:44:44 +0100192 """Create a VPP API object.
193
194 apifiles is a list of files containing API
195 descriptions that will be loaded - methods will be
196 dynamically created reflecting these APIs. If not
197 provided this will load the API files from VPP's
198 default install location.
Ian Wellsd0e812f2018-06-06 14:12:27 +0100199
200 logger, if supplied, is the logging logger object to log to.
201 loglevel, if supplied, is the log level this logger is set
202 to report at (from the loglevels in the logging module).
Ole Troan5016f992017-01-19 09:44:44 +0100203 """
Ian Wellsd0e812f2018-06-06 14:12:27 +0100204 if logger is None:
205 logger = logging.getLogger(__name__)
206 if loglevel is not None:
207 logger.setLevel(loglevel)
Ole Troan3cc49712017-03-08 12:02:24 +0100208 self.logger = logger
Ole Troan3cc49712017-03-08 12:02:24 +0100209
Ole Troana03f4ef2016-12-02 12:53:55 +0100210 self.messages = {}
Ole Troandfb984d2018-12-07 14:31:16 +0100211 self.services = {}
Ole Troana03f4ef2016-12-02 12:53:55 +0100212 self.id_names = []
213 self.id_msgdef = []
Ole Troana7564e82018-06-12 21:06:44 +0200214 self.header = VPPType('header', [['u16', 'msgid'],
215 ['u32', 'client_index']])
Ole Troan5016f992017-01-19 09:44:44 +0100216 self.apifiles = []
Ole Troan3d31f002017-01-26 11:13:00 +0100217 self.event_callback = None
Ole Troan4df97162017-07-07 16:06:08 +0200218 self.message_queue = queue.Queue()
dongjuan84937522017-11-09 14:46:36 +0800219 self.read_timeout = read_timeout
Klement Sekera180402d2018-02-17 10:58:37 +0100220 self.async_thread = async_thread
Ole Troan5f9dcff2016-08-01 04:59:13 +0200221
Ole Troan94495f22018-08-02 11:58:12 +0200222 if use_socket:
223 from . vpp_transport_socket import VppTransport
224 else:
225 from . vpp_transport_shmem import VppTransport
226
Ole Troanf5984bd2016-12-18 13:15:08 +0100227 if not apifiles:
228 # Pick up API definitions from default directory
Chris Luke52bf22e2017-11-03 23:32:38 -0400229 try:
230 apifiles = self.find_api_files()
231 except RuntimeError:
232 # In test mode we don't care that we can't find the API files
233 if testmode:
234 apifiles = []
235 else:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800236 raise VPPRuntimeError
Ole Troanf5984bd2016-12-18 13:15:08 +0100237
Ole Troana03f4ef2016-12-02 12:53:55 +0100238 for file in apifiles:
Ole Troana03f4ef2016-12-02 12:53:55 +0100239 with open(file) as apidef_file:
Ole Troana7564e82018-06-12 21:06:44 +0200240 self.process_json_file(apidef_file)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200241
Ole Troan4df97162017-07-07 16:06:08 +0200242 self.apifiles = apifiles
Ole Troan5f9dcff2016-08-01 04:59:13 +0200243
Ole Troana03f4ef2016-12-02 12:53:55 +0100244 # Basic sanity check
Ole Troanf5984bd2016-12-18 13:15:08 +0100245 if len(self.messages) == 0 and not testmode:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800246 raise VPPValueError(1, 'Missing JSON message definitions')
Ole Troan5f9dcff2016-08-01 04:59:13 +0200247
Ole Troan94495f22018-08-02 11:58:12 +0200248 self.transport = VppTransport(self, read_timeout=read_timeout,
249 server_address=server_address)
Ole Troan5016f992017-01-19 09:44:44 +0100250 # Make sure we allow VPP to clean up the message rings.
Klement Sekera180402d2018-02-17 10:58:37 +0100251 atexit.register(vpp_atexit, weakref.ref(self))
Ole Troan5f9dcff2016-08-01 04:59:13 +0200252
Ole Troana03f4ef2016-12-02 12:53:55 +0100253 class ContextId(object):
Ole Troan5016f992017-01-19 09:44:44 +0100254 """Thread-safe provider of unique context IDs."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100255 def __init__(self):
256 self.context = 0
Ole Troan4df97162017-07-07 16:06:08 +0200257 self.lock = threading.Lock()
258
Ole Troana03f4ef2016-12-02 12:53:55 +0100259 def __call__(self):
Ole Troan5016f992017-01-19 09:44:44 +0100260 """Get a new unique (or, at least, not recently used) context."""
Ole Troan4df97162017-07-07 16:06:08 +0200261 with self.lock:
262 self.context += 1
263 return self.context
Ole Troana03f4ef2016-12-02 12:53:55 +0100264 get_context = ContextId()
Ole Troan5f9dcff2016-08-01 04:59:13 +0200265
Ole Troan0685da42018-10-16 14:42:50 +0200266 def get_type(self, name):
267 return vpp_get_type(name)
268
Chris Luke52bf22e2017-11-03 23:32:38 -0400269 @classmethod
270 def find_api_dir(cls):
271 """Attempt to find the best directory in which API definition
272 files may reside. If the value VPP_API_DIR exists in the environment
273 then it is first on the search list. If we're inside a recognized
274 location in a VPP source tree (src/scripts and src/vpp-api/python)
275 then entries from there to the likely locations in build-root are
276 added. Finally the location used by system packages is added.
277
278 :returns: A single directory name, or None if no such directory
279 could be found.
280 """
281 dirs = []
282
283 if 'VPP_API_DIR' in os.environ:
284 dirs.append(os.environ['VPP_API_DIR'])
285
286 # perhaps we're in the 'src/scripts' or 'src/vpp-api/python' dir;
287 # in which case, plot a course to likely places in the src tree
288 import __main__ as main
289 if hasattr(main, '__file__'):
290 # get the path of the calling script
291 localdir = os.path.dirname(os.path.realpath(main.__file__))
292 else:
293 # use cwd if there is no calling script
Andrey "Zed" Zaikin68e2ffb2018-04-24 14:50:02 +0300294 localdir = os.getcwd()
Chris Luke52bf22e2017-11-03 23:32:38 -0400295 localdir_s = localdir.split(os.path.sep)
296
297 def dmatch(dir):
298 """Match dir against right-hand components of the script dir"""
299 d = dir.split('/') # param 'dir' assumes a / separator
Ole Troanafddd832018-02-28 14:55:20 +0100300 length = len(d)
301 return len(localdir_s) > length and localdir_s[-length:] == d
Chris Luke52bf22e2017-11-03 23:32:38 -0400302
303 def sdir(srcdir, variant):
304 """Build a path from srcdir to the staged API files of
305 'variant' (typically '' or '_debug')"""
306 # Since 'core' and 'plugin' files are staged
307 # in separate directories, we target the parent dir.
308 return os.path.sep.join((
309 srcdir,
310 'build-root',
311 'install-vpp%s-native' % variant,
312 'vpp',
313 'share',
314 'vpp',
315 'api',
316 ))
317
318 srcdir = None
319 if dmatch('src/scripts'):
320 srcdir = os.path.sep.join(localdir_s[:-2])
321 elif dmatch('src/vpp-api/python'):
322 srcdir = os.path.sep.join(localdir_s[:-3])
323 elif dmatch('test'):
324 # we're apparently running tests
325 srcdir = os.path.sep.join(localdir_s[:-1])
326
327 if srcdir:
328 # we're in the source tree, try both the debug and release
329 # variants.
Chris Luke52bf22e2017-11-03 23:32:38 -0400330 dirs.append(sdir(srcdir, '_debug'))
331 dirs.append(sdir(srcdir, ''))
332
333 # Test for staged copies of the scripts
334 # For these, since we explicitly know if we're running a debug versus
335 # release variant, target only the relevant directory
336 if dmatch('build-root/install-vpp_debug-native/vpp/bin'):
337 srcdir = os.path.sep.join(localdir_s[:-4])
338 dirs.append(sdir(srcdir, '_debug'))
339 if dmatch('build-root/install-vpp-native/vpp/bin'):
340 srcdir = os.path.sep.join(localdir_s[:-4])
341 dirs.append(sdir(srcdir, ''))
342
343 # finally, try the location system packages typically install into
344 dirs.append(os.path.sep.join(('', 'usr', 'share', 'vpp', 'api')))
345
346 # check the directories for existance; first one wins
347 for dir in dirs:
348 if os.path.isdir(dir):
349 return dir
350
351 return None
352
353 @classmethod
354 def find_api_files(cls, api_dir=None, patterns='*'):
355 """Find API definition files from the given directory tree with the
356 given pattern. If no directory is given then find_api_dir() is used
357 to locate one. If no pattern is given then all definition files found
358 in the directory tree are used.
359
360 :param api_dir: A directory tree in which to locate API definition
361 files; subdirectories are descended into.
362 If this is None then find_api_dir() is called to discover it.
363 :param patterns: A list of patterns to use in each visited directory
364 when looking for files.
365 This can be a list/tuple object or a comma-separated string of
366 patterns. Each value in the list will have leading/trialing
367 whitespace stripped.
368 The pattern specifies the first part of the filename, '.api.json'
369 is appended.
370 The results are de-duplicated, thus overlapping patterns are fine.
371 If this is None it defaults to '*' meaning "all API files".
372 :returns: A list of file paths for the API files found.
373 """
374 if api_dir is None:
375 api_dir = cls.find_api_dir()
376 if api_dir is None:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800377 raise VPPApiError("api_dir cannot be located")
Chris Luke52bf22e2017-11-03 23:32:38 -0400378
379 if isinstance(patterns, list) or isinstance(patterns, tuple):
380 patterns = [p.strip() + '.api.json' for p in patterns]
381 else:
382 patterns = [p.strip() + '.api.json' for p in patterns.split(",")]
383
384 api_files = []
385 for root, dirnames, files in os.walk(api_dir):
386 # iterate all given patterns and de-dup the result
387 files = set(sum([fnmatch.filter(files, p) for p in patterns], []))
388 for filename in files:
389 api_files.append(os.path.join(root, filename))
390
391 return api_files
392
Klement Sekera7112c542017-03-01 09:53:19 +0100393 @property
394 def api(self):
395 if not hasattr(self, "_api"):
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800396 raise VPPApiError("Not connected, api definitions not available")
Klement Sekera7112c542017-03-01 09:53:19 +0100397 return self._api
398
Ole Troaneabd6072018-08-09 12:50:55 +0200399 def make_function(self, msg, i, multipart, do_async):
400 if (do_async):
Ole Troana7564e82018-06-12 21:06:44 +0200401 def f(**kwargs):
402 return self._call_vpp_async(i, msg, **kwargs)
403 else:
404 def f(**kwargs):
405 return self._call_vpp(i, msg, multipart, **kwargs)
406
407 f.__name__ = str(msg.name)
408 f.__doc__ = ", ".join(["%s %s" %
409 (msg.fieldtypes[j], k)
410 for j, k in enumerate(msg.fields)])
411 return f
412
Ole Troaneabd6072018-08-09 12:50:55 +0200413 def _register_functions(self, do_async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100414 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
415 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera8aedf5e2018-07-06 11:07:21 +0200416 self._api = VppApiDynamicMethodHolder()
Ole Troana7564e82018-06-12 21:06:44 +0200417 for name, msg in vpp_iterator(self.messages):
418 n = name + '_' + msg.crc[2:]
Ole Troan94495f22018-08-02 11:58:12 +0200419 i = self.transport.get_msg_index(n.encode())
Ole Troan3cc49712017-03-08 12:02:24 +0100420 if i > 0:
Ole Troana7564e82018-06-12 21:06:44 +0200421 self.id_msgdef[i] = msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100422 self.id_names[i] = name
Ole Troandfb984d2018-12-07 14:31:16 +0100423
424 # Create function for client side messages.
425 if name in self.services:
Ole Troan0bcad322018-12-11 13:04:01 +0100426 if 'stream' in self.services[name] and \
427 self.services[name]['stream']:
Ole Troandfb984d2018-12-07 14:31:16 +0100428 multipart = True
429 else:
430 multipart = False
431 f = self.make_function(msg, i, multipart, do_async)
432 setattr(self._api, name, FuncWrapper(f))
Ole Troan3cc49712017-03-08 12:02:24 +0100433 else:
Ole Troan4df97162017-07-07 16:06:08 +0200434 self.logger.debug(
435 'No such message type or failed CRC checksum: %s', n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100436
Ole Troan4df97162017-07-07 16:06:08 +0200437 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen,
Ole Troaneabd6072018-08-09 12:50:55 +0200438 do_async):
Ole Troan94495f22018-08-02 11:58:12 +0200439 pfx = chroot_prefix.encode() if chroot_prefix else None
440
441 rv = self.transport.connect(name.encode(), pfx, msg_handler, rx_qlen)
Ole Troana03f4ef2016-12-02 12:53:55 +0100442 if rv != 0:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800443 raise VPPIOError(2, 'Connect failed')
Ole Troan94495f22018-08-02 11:58:12 +0200444 self.vpp_dictionary_maxid = self.transport.msg_table_max_index()
Ole Troaneabd6072018-08-09 12:50:55 +0200445 self._register_functions(do_async=do_async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100446
447 # Initialise control ping
Ole Troana7564e82018-06-12 21:06:44 +0200448 crc = self.messages['control_ping'].crc
Ole Troan94495f22018-08-02 11:58:12 +0200449 self.control_ping_index = self.transport.get_msg_index(
Ole Troan4df97162017-07-07 16:06:08 +0200450 ('control_ping' + '_' + crc[2:]).encode())
Ole Troana03f4ef2016-12-02 12:53:55 +0100451 self.control_ping_msgdef = self.messages['control_ping']
Klement Sekera180402d2018-02-17 10:58:37 +0100452 if self.async_thread:
453 self.event_thread = threading.Thread(
454 target=self.thread_msg_handler)
455 self.event_thread.daemon = True
456 self.event_thread.start()
Ole Troan4df97162017-07-07 16:06:08 +0200457 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100458
Ole Troaneabd6072018-08-09 12:50:55 +0200459 def connect(self, name, chroot_prefix=None, do_async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100460 """Attach to VPP.
461
462 name - the name of the client.
463 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
Ole Troaneabd6072018-08-09 12:50:55 +0200464 do_async - if true, messages are sent without waiting for a reply
Ole Troandfc9b7c2017-03-06 23:51:57 +0100465 rx_qlen - the length of the VPP message receive queue between
466 client and server.
467 """
Ole Troan94495f22018-08-02 11:58:12 +0200468 msg_handler = self.transport.get_callback(do_async)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100469 return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen,
Ole Troaneabd6072018-08-09 12:50:55 +0200470 do_async)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100471
Ole Troan6bf177c2017-08-17 10:34:32 +0200472 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100473 """Attach to VPP in synchronous mode. Application must poll for events.
474
475 name - the name of the client.
476 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
477 rx_qlen - the length of the VPP message receive queue between
478 client and server.
479 """
480
Ole Troan94495f22018-08-02 11:58:12 +0200481 return self.connect_internal(name, None, chroot_prefix, rx_qlen,
Ole Troaneabd6072018-08-09 12:50:55 +0200482 do_async=False)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100483
Ole Troana03f4ef2016-12-02 12:53:55 +0100484 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100485 """Detach from VPP."""
Ole Troan94495f22018-08-02 11:58:12 +0200486 rv = self.transport.disconnect()
Klement Sekera180402d2018-02-17 10:58:37 +0100487 self.message_queue.put("terminate event thread")
Ole Troana03f4ef2016-12-02 12:53:55 +0100488 return rv
489
Ole Troan5016f992017-01-19 09:44:44 +0100490 def msg_handler_sync(self, msg):
491 """Process an incoming message from VPP in sync mode.
492
493 The message may be a reply or it may be an async notification.
494 """
495 r = self.decode_incoming_msg(msg)
496 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100497 return
498
Ole Troan5016f992017-01-19 09:44:44 +0100499 # If we have a context, then use the context to find any
500 # request waiting for a reply
501 context = 0
502 if hasattr(r, 'context') and r.context > 0:
503 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200504
Ole Troan5016f992017-01-19 09:44:44 +0100505 if context == 0:
506 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100507 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100508 else:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800509 raise VPPIOError(2, 'RPC reply message received in event handler')
Ole Troan5016f992017-01-19 09:44:44 +0100510
Ole Troan413f4a52018-11-28 11:36:05 +0100511 def has_context(self, msg):
512 if len(msg) < 10:
513 return False
514
515 header = VPPType('header_with_context', [['u16', 'msgid'],
516 ['u32', 'client_index'],
517 ['u32', 'context']])
518
519 (i, ci, context), size = header.unpack(msg, 0)
520 if self.id_names[i] == 'rx_thread_exit':
521 return
522
523 #
524 # Decode message and returns a tuple.
525 #
526 msgobj = self.id_msgdef[i]
527 if 'context' in msgobj.field_by_name and context >= 0:
528 return True
529 return False
530
Ole Troan0bcad322018-12-11 13:04:01 +0100531 def decode_incoming_msg(self, msg, no_type_conversion=False):
Ole Troan5016f992017-01-19 09:44:44 +0100532 if not msg:
Ole Troan3cc49712017-03-08 12:02:24 +0100533 self.logger.warning('vpp_api.read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100534 return
Ole Troan413f4a52018-11-28 11:36:05 +0100535
Ole Troanc84cbad2018-09-06 22:58:05 +0200536 (i, ci), size = self.header.unpack(msg, 0)
Ole Troan5016f992017-01-19 09:44:44 +0100537 if self.id_names[i] == 'rx_thread_exit':
538 return
539
540 #
541 # Decode message and returns a tuple.
542 #
Ole Troana7564e82018-06-12 21:06:44 +0200543 msgobj = self.id_msgdef[i]
544 if not msgobj:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800545 raise VPPIOError(2, 'Reply message undefined')
Ole Troan5016f992017-01-19 09:44:44 +0100546
Ole Troan0bcad322018-12-11 13:04:01 +0100547 r, size = msgobj.unpack(msg, ntc=no_type_conversion)
Ole Troana03f4ef2016-12-02 12:53:55 +0100548 return r
549
Ole Troan5016f992017-01-19 09:44:44 +0100550 def msg_handler_async(self, msg):
551 """Process a message from VPP in async mode.
552
553 In async mode, all messages are returned to the callback.
554 """
555 r = self.decode_incoming_msg(msg)
556 if r is None:
557 return
558
559 msgname = type(r).__name__
560
Ole Troan4df97162017-07-07 16:06:08 +0200561 if self.event_callback:
562 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100563
564 def _control_ping(self, context):
565 """Send a ping command."""
566 self._call_vpp_async(self.control_ping_index,
Ole Troan4df97162017-07-07 16:06:08 +0200567 self.control_ping_msgdef,
Ole Troan5016f992017-01-19 09:44:44 +0100568 context=context)
569
Ole Troana7564e82018-06-12 21:06:44 +0200570 def validate_args(self, msg, kwargs):
571 d = set(kwargs.keys()) - set(msg.field_by_name.keys())
572 if d:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800573 raise VPPValueError('Invalid argument {} to {}'
Ole Troan0bcad322018-12-11 13:04:01 +0100574 .format(list(d), msg.name))
Ole Troana7564e82018-06-12 21:06:44 +0200575
Ole Troan413f4a52018-11-28 11:36:05 +0100576 def _call_vpp(self, i, msgdef, multipart, **kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100577 """Given a message, send the message and await a reply.
578
579 msgdef - the message packing definition
580 i - the message type index
581 multipart - True if the message returns multiple
582 messages in return.
583 context - context number - chosen at random if not
584 supplied.
585 The remainder of the kwargs are the arguments to the API call.
586
587 The return value is the message or message array containing
588 the response. It will raise an IOError exception if there was
589 no response within the timeout window.
590 """
591
Ole Troan4df97162017-07-07 16:06:08 +0200592 if 'context' not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100593 context = self.get_context()
594 kwargs['context'] = context
595 else:
596 context = kwargs['context']
597 kwargs['_vl_msg_id'] = i
Ole Troan5016f992017-01-19 09:44:44 +0100598
Ole Troan0bcad322018-12-11 13:04:01 +0100599 no_type_conversion = kwargs.pop('_no_type_conversion', False)
600
Ole Troan94495f22018-08-02 11:58:12 +0200601 try:
602 if self.transport.socket_index:
603 kwargs['client_index'] = self.transport.socket_index
604 except AttributeError:
605 pass
Ole Troan413f4a52018-11-28 11:36:05 +0100606 self.validate_args(msgdef, kwargs)
607
608 logging.debug(call_logger(msgdef, kwargs))
609
610 b = msgdef.pack(kwargs)
Ole Troan94495f22018-08-02 11:58:12 +0200611 self.transport.suspend()
612
613 self.transport.write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100614
615 if multipart:
616 # Send a ping after the request - we use its response
617 # to detect that we have seen all results.
618 self._control_ping(context)
619
620 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100621 rl = []
622 while (True):
Ole Troan94495f22018-08-02 11:58:12 +0200623 msg = self.transport.read()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100624 if not msg:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800625 raise VPPIOError(2, 'VPP API client: read failed')
Ole Troan0bcad322018-12-11 13:04:01 +0100626 r = self.decode_incoming_msg(msg, no_type_conversion)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100627 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200628 if context not in r or r.context == 0 or context != r.context:
Ole Troan94495f22018-08-02 11:58:12 +0200629 # Message being queued
Ole Troandfc9b7c2017-03-06 23:51:57 +0100630 self.message_queue.put_nowait(r)
631 continue
632
633 if not multipart:
634 rl = r
635 break
636 if msgname == 'control_ping_reply':
637 break
638
639 rl.append(r)
640
Ole Troan94495f22018-08-02 11:58:12 +0200641 self.transport.resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100642
Ole Troan413f4a52018-11-28 11:36:05 +0100643 logger.debug(return_logger(rl))
Ole Troandfc9b7c2017-03-06 23:51:57 +0100644 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100645
Ole Troana7564e82018-06-12 21:06:44 +0200646 def _call_vpp_async(self, i, msg, **kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100647 """Given a message, send the message and await a reply.
648
649 msgdef - the message packing definition
650 i - the message type index
651 context - context number - chosen at random if not
652 supplied.
653 The remainder of the kwargs are the arguments to the API call.
654 """
Ole Troan4df97162017-07-07 16:06:08 +0200655 if 'context' not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100656 context = self.get_context()
657 kwargs['context'] = context
658 else:
659 context = kwargs['context']
Ole Troan94495f22018-08-02 11:58:12 +0200660 try:
661 if self.transport.socket_index:
662 kwargs['client_index'] = self.transport.socket_index
663 except AttributeError:
664 kwargs['client_index'] = 0
Ole Troan7e3a8752016-12-05 10:27:09 +0100665 kwargs['_vl_msg_id'] = i
Ole Troana7564e82018-06-12 21:06:44 +0200666 b = msg.pack(kwargs)
Ole Troan7e3a8752016-12-05 10:27:09 +0100667
Ole Troan94495f22018-08-02 11:58:12 +0200668 self.transport.write(b)
Ole Troan7e3a8752016-12-05 10:27:09 +0100669
Ole Troana03f4ef2016-12-02 12:53:55 +0100670 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100671 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100672
Ole Troan5016f992017-01-19 09:44:44 +0100673 This will be called for async notifications in sync mode,
674 and all messages in async mode. In sync mode, replies to
675 requests will not come here.
676
677 callback is a fn(msg_type_name, msg_type) that will be
678 called when a message comes in. While this function is
679 executing, note that (a) you are in a background thread and
680 may wish to use threading.Lock to protect your datastructures,
681 and (b) message processing from VPP will stop (so if you take
682 a long while about it you may provoke reply timeouts or cause
683 VPP to fill the RX buffer). Passing None will disable the
684 callback.
685 """
686 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100687
688 def thread_msg_handler(self):
Ole Troan94495f22018-08-02 11:58:12 +0200689 """Python thread calling the user registered message handler.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100690
691 This is to emulate the old style event callback scheme. Modern
692 clients should provide their own thread to poll the event
693 queue.
694 """
695 while True:
696 r = self.message_queue.get()
Klement Sekera180402d2018-02-17 10:58:37 +0100697 if r == "terminate event thread":
698 break
Ole Troandfc9b7c2017-03-06 23:51:57 +0100699 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200700 if self.event_callback:
701 self.event_callback(msgname, r)
Chris Luke52bf22e2017-11-03 23:32:38 -0400702
703
704# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4