blob: 2e121386bb5e52cc81163df8639f212f695b10c5 [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
Paul Vinciguerra5fced042019-02-26 20:39:44 -080024import functools
Ole Troan4df97162017-07-07 16:06:08 +020025import json
26import threading
Chris Luke52bf22e2017-11-03 23:32:38 -040027import fnmatch
Klement Sekera180402d2018-02-17 10:58:37 +010028import weakref
Ole Troan4df97162017-07-07 16:06:08 +020029import atexit
Ole Troana7564e82018-06-12 21:06:44 +020030from . vpp_serializer import VPPType, VPPEnumType, VPPUnionType, BaseTypes
Ole Troan53fffa12018-11-13 12:36:56 +010031from . vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias
Ole Troan8006c6a2018-12-17 12:02:26 +010032from . macaddress import MACAddress, mac_pton, mac_ntop
Ole Troan4df97162017-07-07 16:06:08 +020033
Ole Troan413f4a52018-11-28 11:36:05 +010034logger = logging.getLogger(__name__)
35
Ole Troan4df97162017-07-07 16:06:08 +020036if sys.version[0] == '2':
37 import Queue as queue
38else:
39 import queue as queue
40
Ole Troanafddd832018-02-28 14:55:20 +010041
Paul Vinciguerra5fced042019-02-26 20:39:44 -080042def metaclass(metaclass):
43 @functools.wraps(metaclass)
44 def wrapper(cls):
45 return metaclass(cls.__name__, cls.__bases__, cls.__dict__.copy())
46
47 return wrapper
48
49
Ole Troan0685da42018-10-16 14:42:50 +020050class VppEnumType(type):
51 def __getattr__(cls, name):
52 t = vpp_get_type(name)
53 return t.enum
54
55
Paul Vinciguerra5fced042019-02-26 20:39:44 -080056@metaclass(VppEnumType)
Paul Vinciguerra7e713f12018-11-26 12:04:48 -080057class VppEnum(object):
Paul Vinciguerra5fced042019-02-26 20:39:44 -080058 pass
Ole Troan0685da42018-10-16 14:42:50 +020059
60
Klement Sekera180402d2018-02-17 10:58:37 +010061def vpp_atexit(vpp_weakref):
Ole Troan5016f992017-01-19 09:44:44 +010062 """Clean up VPP connection on shutdown."""
Klement Sekera180402d2018-02-17 10:58:37 +010063 vpp_instance = vpp_weakref()
Ole Troan94495f22018-08-02 11:58:12 +020064 if vpp_instance and vpp_instance.transport.connected:
Klement Sekera180402d2018-02-17 10:58:37 +010065 vpp_instance.logger.debug('Cleaning up VPP on exit')
66 vpp_instance.disconnect()
Ole Troan5016f992017-01-19 09:44:44 +010067
Ole Troan8006c6a2018-12-17 12:02:26 +010068
Ole Troan0bcad322018-12-11 13:04:01 +010069if sys.version[0] == '2':
70 def vpp_iterator(d):
Ole Troan4df97162017-07-07 16:06:08 +020071 return d.iteritems()
Ole Troan0bcad322018-12-11 13:04:01 +010072else:
73 def vpp_iterator(d):
Ole Troan4df97162017-07-07 16:06:08 +020074 return d.items()
75
76
Ole Troan413f4a52018-11-28 11:36:05 +010077def call_logger(msgdef, kwargs):
78 s = 'Calling {}('.format(msgdef.name)
79 for k, v in kwargs.items():
80 s += '{}:{} '.format(k, v)
81 s += ')'
82 return s
83
84
85def return_logger(r):
86 s = 'Return from {}'.format(r)
87 return s
88
89
Klement Sekera8aedf5e2018-07-06 11:07:21 +020090class VppApiDynamicMethodHolder(object):
Klement Sekera7112c542017-03-01 09:53:19 +010091 pass
92
93
94class FuncWrapper(object):
95 def __init__(self, func):
96 self._func = func
97 self.__name__ = func.__name__
98
99 def __call__(self, **kwargs):
100 return self._func(**kwargs)
101
102
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800103class VPPApiError(Exception):
104 pass
105
106
107class VPPNotImplementedError(NotImplementedError):
108 pass
109
110
111class VPPIOError(IOError):
112 pass
113
114
115class VPPRuntimeError(RuntimeError):
116 pass
117
118
119class VPPValueError(ValueError):
120 pass
121
122
Paul Vinciguerra7e713f12018-11-26 12:04:48 -0800123class VPP(object):
Ole Troan5016f992017-01-19 09:44:44 +0100124 """VPP interface.
125
126 This class provides the APIs to VPP. The APIs are loaded
127 from provided .api.json files and makes functions accordingly.
128 These functions are documented in the VPP .api files, as they
129 are dynamically created.
130
131 Additionally, VPP can send callback messages; this class
132 provides a means to register a callback function to receive
133 these messages in a background thread.
134 """
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800135 VPPApiError = VPPApiError
136 VPPRuntimeError = VPPRuntimeError
137 VPPValueError = VPPValueError
138 VPPNotImplementedError = VPPNotImplementedError
139 VPPIOError = VPPIOError
Ole Troana7564e82018-06-12 21:06:44 +0200140
141 def process_json_file(self, apidef_file):
142 api = json.load(apidef_file)
143 types = {}
144 for t in api['enums']:
145 t[0] = 'vl_api_' + t[0] + '_t'
146 types[t[0]] = {'type': 'enum', 'data': t}
147 for t in api['unions']:
148 t[0] = 'vl_api_' + t[0] + '_t'
149 types[t[0]] = {'type': 'union', 'data': t}
150 for t in api['types']:
151 t[0] = 'vl_api_' + t[0] + '_t'
152 types[t[0]] = {'type': 'type', 'data': t}
Ole Troan53fffa12018-11-13 12:36:56 +0100153 for t, v in api['aliases'].items():
154 types['vl_api_' + t + '_t'] = {'type': 'alias', 'data': v}
Ole Troandfb984d2018-12-07 14:31:16 +0100155 self.services.update(api['services'])
Ole Troana7564e82018-06-12 21:06:44 +0200156
157 i = 0
158 while True:
159 unresolved = {}
160 for k, v in types.items():
161 t = v['data']
Ole Troan53fffa12018-11-13 12:36:56 +0100162 if not vpp_get_type(k):
Ole Troan0685da42018-10-16 14:42:50 +0200163 if v['type'] == 'enum':
164 try:
165 VPPEnumType(t[0], t[1:])
166 except ValueError:
167 unresolved[k] = v
168 elif v['type'] == 'union':
169 try:
170 VPPUnionType(t[0], t[1:])
171 except ValueError:
172 unresolved[k] = v
173 elif v['type'] == 'type':
174 try:
175 VPPType(t[0], t[1:])
176 except ValueError:
177 unresolved[k] = v
Ole Troan53fffa12018-11-13 12:36:56 +0100178 elif v['type'] == 'alias':
179 try:
180 VPPTypeAlias(k, t)
181 except ValueError:
182 unresolved[k] = v
Ole Troana7564e82018-06-12 21:06:44 +0200183 if len(unresolved) == 0:
184 break
185 if i > 3:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800186 raise VPPValueError('Unresolved type definitions {}'
187 .format(unresolved))
Ole Troana7564e82018-06-12 21:06:44 +0200188 types = unresolved
189 i += 1
190
191 for m in api['messages']:
192 try:
193 self.messages[m[0]] = VPPMessage(m[0], m[1:])
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800194 except VPPNotImplementedError:
Ole Troana7564e82018-06-12 21:06:44 +0200195 self.logger.error('Not implemented error for {}'.format(m[0]))
196
Ole Troan4df97162017-07-07 16:06:08 +0200197 def __init__(self, apifiles=None, testmode=False, async_thread=True,
Igor Mikhailov (imichail)5efd14f2018-10-30 12:17:49 -0700198 logger=None, loglevel=None,
Ole Troan94495f22018-08-02 11:58:12 +0200199 read_timeout=5, use_socket=False,
200 server_address='/run/vpp-api.sock'):
Ole Troan5016f992017-01-19 09:44:44 +0100201 """Create a VPP API object.
202
203 apifiles is a list of files containing API
204 descriptions that will be loaded - methods will be
205 dynamically created reflecting these APIs. If not
206 provided this will load the API files from VPP's
207 default install location.
Ian Wellsd0e812f2018-06-06 14:12:27 +0100208
209 logger, if supplied, is the logging logger object to log to.
210 loglevel, if supplied, is the log level this logger is set
211 to report at (from the loglevels in the logging module).
Ole Troan5016f992017-01-19 09:44:44 +0100212 """
Ian Wellsd0e812f2018-06-06 14:12:27 +0100213 if logger is None:
214 logger = logging.getLogger(__name__)
215 if loglevel is not None:
216 logger.setLevel(loglevel)
Ole Troan3cc49712017-03-08 12:02:24 +0100217 self.logger = logger
Ole Troan3cc49712017-03-08 12:02:24 +0100218
Ole Troana03f4ef2016-12-02 12:53:55 +0100219 self.messages = {}
Ole Troandfb984d2018-12-07 14:31:16 +0100220 self.services = {}
Ole Troana03f4ef2016-12-02 12:53:55 +0100221 self.id_names = []
222 self.id_msgdef = []
Ole Troana7564e82018-06-12 21:06:44 +0200223 self.header = VPPType('header', [['u16', 'msgid'],
224 ['u32', 'client_index']])
Ole Troan5016f992017-01-19 09:44:44 +0100225 self.apifiles = []
Ole Troan3d31f002017-01-26 11:13:00 +0100226 self.event_callback = None
Ole Troan4df97162017-07-07 16:06:08 +0200227 self.message_queue = queue.Queue()
dongjuan84937522017-11-09 14:46:36 +0800228 self.read_timeout = read_timeout
Klement Sekera180402d2018-02-17 10:58:37 +0100229 self.async_thread = async_thread
Ole Troan5f9dcff2016-08-01 04:59:13 +0200230
Ole Troan94495f22018-08-02 11:58:12 +0200231 if use_socket:
232 from . vpp_transport_socket import VppTransport
233 else:
234 from . vpp_transport_shmem import VppTransport
235
Ole Troanf5984bd2016-12-18 13:15:08 +0100236 if not apifiles:
237 # Pick up API definitions from default directory
Chris Luke52bf22e2017-11-03 23:32:38 -0400238 try:
239 apifiles = self.find_api_files()
240 except RuntimeError:
241 # In test mode we don't care that we can't find the API files
242 if testmode:
243 apifiles = []
244 else:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800245 raise VPPRuntimeError
Ole Troanf5984bd2016-12-18 13:15:08 +0100246
Ole Troana03f4ef2016-12-02 12:53:55 +0100247 for file in apifiles:
Ole Troana03f4ef2016-12-02 12:53:55 +0100248 with open(file) as apidef_file:
Ole Troana7564e82018-06-12 21:06:44 +0200249 self.process_json_file(apidef_file)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200250
Ole Troan4df97162017-07-07 16:06:08 +0200251 self.apifiles = apifiles
Ole Troan5f9dcff2016-08-01 04:59:13 +0200252
Ole Troana03f4ef2016-12-02 12:53:55 +0100253 # Basic sanity check
Ole Troanf5984bd2016-12-18 13:15:08 +0100254 if len(self.messages) == 0 and not testmode:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800255 raise VPPValueError(1, 'Missing JSON message definitions')
Ole Troan5f9dcff2016-08-01 04:59:13 +0200256
Ole Troan94495f22018-08-02 11:58:12 +0200257 self.transport = VppTransport(self, read_timeout=read_timeout,
258 server_address=server_address)
Ole Troan5016f992017-01-19 09:44:44 +0100259 # Make sure we allow VPP to clean up the message rings.
Klement Sekera180402d2018-02-17 10:58:37 +0100260 atexit.register(vpp_atexit, weakref.ref(self))
Ole Troan5f9dcff2016-08-01 04:59:13 +0200261
Ole Troana03f4ef2016-12-02 12:53:55 +0100262 class ContextId(object):
Ole Troan5016f992017-01-19 09:44:44 +0100263 """Thread-safe provider of unique context IDs."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100264 def __init__(self):
265 self.context = 0
Ole Troan4df97162017-07-07 16:06:08 +0200266 self.lock = threading.Lock()
267
Ole Troana03f4ef2016-12-02 12:53:55 +0100268 def __call__(self):
Ole Troan5016f992017-01-19 09:44:44 +0100269 """Get a new unique (or, at least, not recently used) context."""
Ole Troan4df97162017-07-07 16:06:08 +0200270 with self.lock:
271 self.context += 1
272 return self.context
Ole Troana03f4ef2016-12-02 12:53:55 +0100273 get_context = ContextId()
Ole Troan5f9dcff2016-08-01 04:59:13 +0200274
Ole Troan0685da42018-10-16 14:42:50 +0200275 def get_type(self, name):
276 return vpp_get_type(name)
277
Chris Luke52bf22e2017-11-03 23:32:38 -0400278 @classmethod
279 def find_api_dir(cls):
280 """Attempt to find the best directory in which API definition
281 files may reside. If the value VPP_API_DIR exists in the environment
282 then it is first on the search list. If we're inside a recognized
283 location in a VPP source tree (src/scripts and src/vpp-api/python)
284 then entries from there to the likely locations in build-root are
285 added. Finally the location used by system packages is added.
286
287 :returns: A single directory name, or None if no such directory
288 could be found.
289 """
290 dirs = []
291
292 if 'VPP_API_DIR' in os.environ:
293 dirs.append(os.environ['VPP_API_DIR'])
294
295 # perhaps we're in the 'src/scripts' or 'src/vpp-api/python' dir;
296 # in which case, plot a course to likely places in the src tree
297 import __main__ as main
298 if hasattr(main, '__file__'):
299 # get the path of the calling script
300 localdir = os.path.dirname(os.path.realpath(main.__file__))
301 else:
302 # use cwd if there is no calling script
Andrey "Zed" Zaikin68e2ffb2018-04-24 14:50:02 +0300303 localdir = os.getcwd()
Chris Luke52bf22e2017-11-03 23:32:38 -0400304 localdir_s = localdir.split(os.path.sep)
305
306 def dmatch(dir):
307 """Match dir against right-hand components of the script dir"""
308 d = dir.split('/') # param 'dir' assumes a / separator
Ole Troanafddd832018-02-28 14:55:20 +0100309 length = len(d)
310 return len(localdir_s) > length and localdir_s[-length:] == d
Chris Luke52bf22e2017-11-03 23:32:38 -0400311
312 def sdir(srcdir, variant):
313 """Build a path from srcdir to the staged API files of
314 'variant' (typically '' or '_debug')"""
315 # Since 'core' and 'plugin' files are staged
316 # in separate directories, we target the parent dir.
317 return os.path.sep.join((
318 srcdir,
319 'build-root',
320 'install-vpp%s-native' % variant,
321 'vpp',
322 'share',
323 'vpp',
324 'api',
325 ))
326
327 srcdir = None
328 if dmatch('src/scripts'):
329 srcdir = os.path.sep.join(localdir_s[:-2])
330 elif dmatch('src/vpp-api/python'):
331 srcdir = os.path.sep.join(localdir_s[:-3])
332 elif dmatch('test'):
333 # we're apparently running tests
334 srcdir = os.path.sep.join(localdir_s[:-1])
335
336 if srcdir:
337 # we're in the source tree, try both the debug and release
338 # variants.
Chris Luke52bf22e2017-11-03 23:32:38 -0400339 dirs.append(sdir(srcdir, '_debug'))
340 dirs.append(sdir(srcdir, ''))
341
342 # Test for staged copies of the scripts
343 # For these, since we explicitly know if we're running a debug versus
344 # release variant, target only the relevant directory
345 if dmatch('build-root/install-vpp_debug-native/vpp/bin'):
346 srcdir = os.path.sep.join(localdir_s[:-4])
347 dirs.append(sdir(srcdir, '_debug'))
348 if dmatch('build-root/install-vpp-native/vpp/bin'):
349 srcdir = os.path.sep.join(localdir_s[:-4])
350 dirs.append(sdir(srcdir, ''))
351
352 # finally, try the location system packages typically install into
353 dirs.append(os.path.sep.join(('', 'usr', 'share', 'vpp', 'api')))
354
355 # check the directories for existance; first one wins
356 for dir in dirs:
357 if os.path.isdir(dir):
358 return dir
359
360 return None
361
362 @classmethod
363 def find_api_files(cls, api_dir=None, patterns='*'):
364 """Find API definition files from the given directory tree with the
365 given pattern. If no directory is given then find_api_dir() is used
366 to locate one. If no pattern is given then all definition files found
367 in the directory tree are used.
368
369 :param api_dir: A directory tree in which to locate API definition
370 files; subdirectories are descended into.
371 If this is None then find_api_dir() is called to discover it.
372 :param patterns: A list of patterns to use in each visited directory
373 when looking for files.
374 This can be a list/tuple object or a comma-separated string of
375 patterns. Each value in the list will have leading/trialing
376 whitespace stripped.
377 The pattern specifies the first part of the filename, '.api.json'
378 is appended.
379 The results are de-duplicated, thus overlapping patterns are fine.
380 If this is None it defaults to '*' meaning "all API files".
381 :returns: A list of file paths for the API files found.
382 """
383 if api_dir is None:
384 api_dir = cls.find_api_dir()
385 if api_dir is None:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800386 raise VPPApiError("api_dir cannot be located")
Chris Luke52bf22e2017-11-03 23:32:38 -0400387
388 if isinstance(patterns, list) or isinstance(patterns, tuple):
389 patterns = [p.strip() + '.api.json' for p in patterns]
390 else:
391 patterns = [p.strip() + '.api.json' for p in patterns.split(",")]
392
393 api_files = []
394 for root, dirnames, files in os.walk(api_dir):
395 # iterate all given patterns and de-dup the result
396 files = set(sum([fnmatch.filter(files, p) for p in patterns], []))
397 for filename in files:
398 api_files.append(os.path.join(root, filename))
399
400 return api_files
401
Klement Sekera7112c542017-03-01 09:53:19 +0100402 @property
403 def api(self):
404 if not hasattr(self, "_api"):
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800405 raise VPPApiError("Not connected, api definitions not available")
Klement Sekera7112c542017-03-01 09:53:19 +0100406 return self._api
407
Ole Troaneabd6072018-08-09 12:50:55 +0200408 def make_function(self, msg, i, multipart, do_async):
409 if (do_async):
Ole Troana7564e82018-06-12 21:06:44 +0200410 def f(**kwargs):
411 return self._call_vpp_async(i, msg, **kwargs)
412 else:
413 def f(**kwargs):
414 return self._call_vpp(i, msg, multipart, **kwargs)
415
416 f.__name__ = str(msg.name)
417 f.__doc__ = ", ".join(["%s %s" %
418 (msg.fieldtypes[j], k)
419 for j, k in enumerate(msg.fields)])
Ole Troanf159f582019-02-28 20:20:47 +0100420 f.msg = msg
421
Ole Troana7564e82018-06-12 21:06:44 +0200422 return f
423
Ole Troaneabd6072018-08-09 12:50:55 +0200424 def _register_functions(self, do_async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100425 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
426 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera8aedf5e2018-07-06 11:07:21 +0200427 self._api = VppApiDynamicMethodHolder()
Ole Troana7564e82018-06-12 21:06:44 +0200428 for name, msg in vpp_iterator(self.messages):
429 n = name + '_' + msg.crc[2:]
Ole Troan94495f22018-08-02 11:58:12 +0200430 i = self.transport.get_msg_index(n.encode())
Ole Troan3cc49712017-03-08 12:02:24 +0100431 if i > 0:
Ole Troana7564e82018-06-12 21:06:44 +0200432 self.id_msgdef[i] = msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100433 self.id_names[i] = name
Ole Troandfb984d2018-12-07 14:31:16 +0100434
435 # Create function for client side messages.
436 if name in self.services:
Ole Troan0bcad322018-12-11 13:04:01 +0100437 if 'stream' in self.services[name] and \
438 self.services[name]['stream']:
Ole Troandfb984d2018-12-07 14:31:16 +0100439 multipart = True
440 else:
441 multipart = False
442 f = self.make_function(msg, i, multipart, do_async)
443 setattr(self._api, name, FuncWrapper(f))
Ole Troan3cc49712017-03-08 12:02:24 +0100444 else:
Ole Troan4df97162017-07-07 16:06:08 +0200445 self.logger.debug(
446 'No such message type or failed CRC checksum: %s', n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100447
Ole Troan4df97162017-07-07 16:06:08 +0200448 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen,
Ole Troaneabd6072018-08-09 12:50:55 +0200449 do_async):
Ole Troan94495f22018-08-02 11:58:12 +0200450 pfx = chroot_prefix.encode() if chroot_prefix else None
451
452 rv = self.transport.connect(name.encode(), pfx, msg_handler, rx_qlen)
Ole Troana03f4ef2016-12-02 12:53:55 +0100453 if rv != 0:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800454 raise VPPIOError(2, 'Connect failed')
Ole Troan94495f22018-08-02 11:58:12 +0200455 self.vpp_dictionary_maxid = self.transport.msg_table_max_index()
Ole Troaneabd6072018-08-09 12:50:55 +0200456 self._register_functions(do_async=do_async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100457
458 # Initialise control ping
Ole Troana7564e82018-06-12 21:06:44 +0200459 crc = self.messages['control_ping'].crc
Ole Troan94495f22018-08-02 11:58:12 +0200460 self.control_ping_index = self.transport.get_msg_index(
Ole Troan4df97162017-07-07 16:06:08 +0200461 ('control_ping' + '_' + crc[2:]).encode())
Ole Troana03f4ef2016-12-02 12:53:55 +0100462 self.control_ping_msgdef = self.messages['control_ping']
Klement Sekera180402d2018-02-17 10:58:37 +0100463 if self.async_thread:
464 self.event_thread = threading.Thread(
465 target=self.thread_msg_handler)
466 self.event_thread.daemon = True
467 self.event_thread.start()
Ole Troan4df97162017-07-07 16:06:08 +0200468 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100469
Ole Troaneabd6072018-08-09 12:50:55 +0200470 def connect(self, name, chroot_prefix=None, do_async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100471 """Attach to VPP.
472
473 name - the name of the client.
474 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
Ole Troaneabd6072018-08-09 12:50:55 +0200475 do_async - if true, messages are sent without waiting for a reply
Ole Troandfc9b7c2017-03-06 23:51:57 +0100476 rx_qlen - the length of the VPP message receive queue between
477 client and server.
478 """
Ole Troan94495f22018-08-02 11:58:12 +0200479 msg_handler = self.transport.get_callback(do_async)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100480 return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen,
Ole Troaneabd6072018-08-09 12:50:55 +0200481 do_async)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100482
Ole Troan6bf177c2017-08-17 10:34:32 +0200483 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100484 """Attach to VPP in synchronous mode. Application must poll for events.
485
486 name - the name of the client.
487 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
488 rx_qlen - the length of the VPP message receive queue between
489 client and server.
490 """
491
Ole Troan94495f22018-08-02 11:58:12 +0200492 return self.connect_internal(name, None, chroot_prefix, rx_qlen,
Ole Troaneabd6072018-08-09 12:50:55 +0200493 do_async=False)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100494
Ole Troana03f4ef2016-12-02 12:53:55 +0100495 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100496 """Detach from VPP."""
Ole Troan94495f22018-08-02 11:58:12 +0200497 rv = self.transport.disconnect()
Klement Sekera180402d2018-02-17 10:58:37 +0100498 self.message_queue.put("terminate event thread")
Ole Troana03f4ef2016-12-02 12:53:55 +0100499 return rv
500
Ole Troan5016f992017-01-19 09:44:44 +0100501 def msg_handler_sync(self, msg):
502 """Process an incoming message from VPP in sync mode.
503
504 The message may be a reply or it may be an async notification.
505 """
506 r = self.decode_incoming_msg(msg)
507 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100508 return
509
Ole Troan5016f992017-01-19 09:44:44 +0100510 # If we have a context, then use the context to find any
511 # request waiting for a reply
512 context = 0
513 if hasattr(r, 'context') and r.context > 0:
514 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200515
Ole Troan5016f992017-01-19 09:44:44 +0100516 if context == 0:
517 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100518 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100519 else:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800520 raise VPPIOError(2, 'RPC reply message received in event handler')
Ole Troan5016f992017-01-19 09:44:44 +0100521
Ole Troan413f4a52018-11-28 11:36:05 +0100522 def has_context(self, msg):
523 if len(msg) < 10:
524 return False
525
526 header = VPPType('header_with_context', [['u16', 'msgid'],
527 ['u32', 'client_index'],
528 ['u32', 'context']])
529
530 (i, ci, context), size = header.unpack(msg, 0)
531 if self.id_names[i] == 'rx_thread_exit':
532 return
533
534 #
535 # Decode message and returns a tuple.
536 #
537 msgobj = self.id_msgdef[i]
538 if 'context' in msgobj.field_by_name and context >= 0:
539 return True
540 return False
541
Ole Troan0bcad322018-12-11 13:04:01 +0100542 def decode_incoming_msg(self, msg, no_type_conversion=False):
Ole Troan5016f992017-01-19 09:44:44 +0100543 if not msg:
Ole Troan3cc49712017-03-08 12:02:24 +0100544 self.logger.warning('vpp_api.read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100545 return
Ole Troan413f4a52018-11-28 11:36:05 +0100546
Ole Troanc84cbad2018-09-06 22:58:05 +0200547 (i, ci), size = self.header.unpack(msg, 0)
Ole Troan5016f992017-01-19 09:44:44 +0100548 if self.id_names[i] == 'rx_thread_exit':
549 return
550
551 #
552 # Decode message and returns a tuple.
553 #
Ole Troana7564e82018-06-12 21:06:44 +0200554 msgobj = self.id_msgdef[i]
555 if not msgobj:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800556 raise VPPIOError(2, 'Reply message undefined')
Ole Troan5016f992017-01-19 09:44:44 +0100557
Ole Troan0bcad322018-12-11 13:04:01 +0100558 r, size = msgobj.unpack(msg, ntc=no_type_conversion)
Ole Troana03f4ef2016-12-02 12:53:55 +0100559 return r
560
Ole Troan5016f992017-01-19 09:44:44 +0100561 def msg_handler_async(self, msg):
562 """Process a message from VPP in async mode.
563
564 In async mode, all messages are returned to the callback.
565 """
566 r = self.decode_incoming_msg(msg)
567 if r is None:
568 return
569
570 msgname = type(r).__name__
571
Ole Troan4df97162017-07-07 16:06:08 +0200572 if self.event_callback:
573 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100574
575 def _control_ping(self, context):
576 """Send a ping command."""
577 self._call_vpp_async(self.control_ping_index,
Ole Troan4df97162017-07-07 16:06:08 +0200578 self.control_ping_msgdef,
Ole Troan5016f992017-01-19 09:44:44 +0100579 context=context)
580
Ole Troana7564e82018-06-12 21:06:44 +0200581 def validate_args(self, msg, kwargs):
582 d = set(kwargs.keys()) - set(msg.field_by_name.keys())
583 if d:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800584 raise VPPValueError('Invalid argument {} to {}'
Ole Troan0bcad322018-12-11 13:04:01 +0100585 .format(list(d), msg.name))
Ole Troana7564e82018-06-12 21:06:44 +0200586
Ole Troan413f4a52018-11-28 11:36:05 +0100587 def _call_vpp(self, i, msgdef, multipart, **kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100588 """Given a message, send the message and await a reply.
589
590 msgdef - the message packing definition
591 i - the message type index
592 multipart - True if the message returns multiple
593 messages in return.
594 context - context number - chosen at random if not
595 supplied.
596 The remainder of the kwargs are the arguments to the API call.
597
598 The return value is the message or message array containing
599 the response. It will raise an IOError exception if there was
600 no response within the timeout window.
601 """
602
Ole Troan4df97162017-07-07 16:06:08 +0200603 if 'context' not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100604 context = self.get_context()
605 kwargs['context'] = context
606 else:
607 context = kwargs['context']
608 kwargs['_vl_msg_id'] = i
Ole Troan5016f992017-01-19 09:44:44 +0100609
Ole Troan0bcad322018-12-11 13:04:01 +0100610 no_type_conversion = kwargs.pop('_no_type_conversion', False)
611
Ole Troan94495f22018-08-02 11:58:12 +0200612 try:
613 if self.transport.socket_index:
614 kwargs['client_index'] = self.transport.socket_index
615 except AttributeError:
616 pass
Ole Troan413f4a52018-11-28 11:36:05 +0100617 self.validate_args(msgdef, kwargs)
618
619 logging.debug(call_logger(msgdef, kwargs))
620
621 b = msgdef.pack(kwargs)
Ole Troan94495f22018-08-02 11:58:12 +0200622 self.transport.suspend()
623
624 self.transport.write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100625
626 if multipart:
627 # Send a ping after the request - we use its response
628 # to detect that we have seen all results.
629 self._control_ping(context)
630
631 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100632 rl = []
633 while (True):
Ole Troan94495f22018-08-02 11:58:12 +0200634 msg = self.transport.read()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100635 if not msg:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800636 raise VPPIOError(2, 'VPP API client: read failed')
Ole Troan0bcad322018-12-11 13:04:01 +0100637 r = self.decode_incoming_msg(msg, no_type_conversion)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100638 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200639 if context not in r or r.context == 0 or context != r.context:
Ole Troan94495f22018-08-02 11:58:12 +0200640 # Message being queued
Ole Troandfc9b7c2017-03-06 23:51:57 +0100641 self.message_queue.put_nowait(r)
642 continue
643
644 if not multipart:
645 rl = r
646 break
647 if msgname == 'control_ping_reply':
648 break
649
650 rl.append(r)
651
Ole Troan94495f22018-08-02 11:58:12 +0200652 self.transport.resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100653
Ole Troan413f4a52018-11-28 11:36:05 +0100654 logger.debug(return_logger(rl))
Ole Troandfc9b7c2017-03-06 23:51:57 +0100655 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100656
Ole Troana7564e82018-06-12 21:06:44 +0200657 def _call_vpp_async(self, i, msg, **kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100658 """Given a message, send the message and await a reply.
659
660 msgdef - the message packing definition
661 i - the message type index
662 context - context number - chosen at random if not
663 supplied.
664 The remainder of the kwargs are the arguments to the API call.
665 """
Ole Troan4df97162017-07-07 16:06:08 +0200666 if 'context' not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100667 context = self.get_context()
668 kwargs['context'] = context
669 else:
670 context = kwargs['context']
Ole Troan94495f22018-08-02 11:58:12 +0200671 try:
672 if self.transport.socket_index:
673 kwargs['client_index'] = self.transport.socket_index
674 except AttributeError:
675 kwargs['client_index'] = 0
Ole Troan7e3a8752016-12-05 10:27:09 +0100676 kwargs['_vl_msg_id'] = i
Ole Troana7564e82018-06-12 21:06:44 +0200677 b = msg.pack(kwargs)
Ole Troan7e3a8752016-12-05 10:27:09 +0100678
Ole Troan94495f22018-08-02 11:58:12 +0200679 self.transport.write(b)
Ole Troan7e3a8752016-12-05 10:27:09 +0100680
Ole Troana03f4ef2016-12-02 12:53:55 +0100681 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100682 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100683
Ole Troan5016f992017-01-19 09:44:44 +0100684 This will be called for async notifications in sync mode,
685 and all messages in async mode. In sync mode, replies to
686 requests will not come here.
687
688 callback is a fn(msg_type_name, msg_type) that will be
689 called when a message comes in. While this function is
690 executing, note that (a) you are in a background thread and
691 may wish to use threading.Lock to protect your datastructures,
692 and (b) message processing from VPP will stop (so if you take
693 a long while about it you may provoke reply timeouts or cause
694 VPP to fill the RX buffer). Passing None will disable the
695 callback.
696 """
697 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100698
699 def thread_msg_handler(self):
Ole Troan94495f22018-08-02 11:58:12 +0200700 """Python thread calling the user registered message handler.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100701
702 This is to emulate the old style event callback scheme. Modern
703 clients should provide their own thread to poll the event
704 queue.
705 """
706 while True:
707 r = self.message_queue.get()
Klement Sekera180402d2018-02-17 10:58:37 +0100708 if r == "terminate event thread":
709 break
Ole Troandfc9b7c2017-03-06 23:51:57 +0100710 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200711 if self.event_callback:
712 self.event_callback(msgname, r)
Chris Luke52bf22e2017-11-03 23:32:38 -0400713
714
715# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4