blob: 1f5cce23a79a9948ff292e971887bceedb610108 [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
Paul Vinciguerra2af6e922019-06-06 07:06:09 -040019import ctypes
Ole Troan4df97162017-07-07 16:06:08 +020020import sys
Paul Vinciguerra2af6e922019-06-06 07:06:09 -040021import multiprocessing as mp
Ole Troan4df97162017-07-07 16:06:08 +020022import os
23import logging
24import collections
25import struct
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 Troana7564e82018-06-12 21:06:44 +020032from . vpp_serializer import VPPType, VPPEnumType, VPPUnionType, BaseTypes
Ole Troan53fffa12018-11-13 12:36:56 +010033from . vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias
Ole Troan8006c6a2018-12-17 12:02:26 +010034from . macaddress import MACAddress, mac_pton, mac_ntop
Ole Troan4df97162017-07-07 16:06:08 +020035
Ole Troan413f4a52018-11-28 11:36:05 +010036logger = logging.getLogger(__name__)
37
Ole Troan4df97162017-07-07 16:06:08 +020038if sys.version[0] == '2':
39 import Queue as queue
40else:
41 import queue as queue
42
Ole Troanafddd832018-02-28 14:55:20 +010043
Paul Vinciguerra5fced042019-02-26 20:39:44 -080044def metaclass(metaclass):
45 @functools.wraps(metaclass)
46 def wrapper(cls):
47 return metaclass(cls.__name__, cls.__bases__, cls.__dict__.copy())
48
49 return wrapper
50
51
Ole Troan0685da42018-10-16 14:42:50 +020052class VppEnumType(type):
53 def __getattr__(cls, name):
54 t = vpp_get_type(name)
55 return t.enum
56
57
Paul Vinciguerra5fced042019-02-26 20:39:44 -080058@metaclass(VppEnumType)
Paul Vinciguerra7e713f12018-11-26 12:04:48 -080059class VppEnum(object):
Paul Vinciguerra5fced042019-02-26 20:39:44 -080060 pass
Ole Troan0685da42018-10-16 14:42:50 +020061
62
Klement Sekera180402d2018-02-17 10:58:37 +010063def vpp_atexit(vpp_weakref):
Ole Troan5016f992017-01-19 09:44:44 +010064 """Clean up VPP connection on shutdown."""
Klement Sekera180402d2018-02-17 10:58:37 +010065 vpp_instance = vpp_weakref()
Ole Troan94495f22018-08-02 11:58:12 +020066 if vpp_instance and vpp_instance.transport.connected:
Klement Sekera180402d2018-02-17 10:58:37 +010067 vpp_instance.logger.debug('Cleaning up VPP on exit')
68 vpp_instance.disconnect()
Ole Troan5016f992017-01-19 09:44:44 +010069
Ole Troan8006c6a2018-12-17 12:02:26 +010070
Ole Troan0bcad322018-12-11 13:04:01 +010071if sys.version[0] == '2':
72 def vpp_iterator(d):
Ole Troan4df97162017-07-07 16:06:08 +020073 return d.iteritems()
Ole Troan0bcad322018-12-11 13:04:01 +010074else:
75 def vpp_iterator(d):
Ole Troan4df97162017-07-07 16:06:08 +020076 return d.items()
77
78
Ole Troan413f4a52018-11-28 11:36:05 +010079def call_logger(msgdef, kwargs):
80 s = 'Calling {}('.format(msgdef.name)
81 for k, v in kwargs.items():
82 s += '{}:{} '.format(k, v)
83 s += ')'
84 return s
85
86
87def return_logger(r):
88 s = 'Return from {}'.format(r)
89 return s
90
91
Klement Sekera8aedf5e2018-07-06 11:07:21 +020092class VppApiDynamicMethodHolder(object):
Klement Sekera7112c542017-03-01 09:53:19 +010093 pass
94
95
96class FuncWrapper(object):
97 def __init__(self, func):
98 self._func = func
99 self.__name__ = func.__name__
Paul Vinciguerrab8daa252019-03-19 15:04:17 -0700100 self.__doc__ = func.__doc__
Klement Sekera7112c542017-03-01 09:53:19 +0100101
102 def __call__(self, **kwargs):
103 return self._func(**kwargs)
104
105
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800106class VPPApiError(Exception):
107 pass
108
109
110class VPPNotImplementedError(NotImplementedError):
111 pass
112
113
114class VPPIOError(IOError):
115 pass
116
117
118class VPPRuntimeError(RuntimeError):
119 pass
120
121
122class VPPValueError(ValueError):
123 pass
124
125
Paul Vinciguerra19542292019-03-17 17:34:46 -0700126class VPPApiClient(object):
Ole Troan5016f992017-01-19 09:44:44 +0100127 """VPP interface.
128
129 This class provides the APIs to VPP. The APIs are loaded
130 from provided .api.json files and makes functions accordingly.
131 These functions are documented in the VPP .api files, as they
132 are dynamically created.
133
134 Additionally, VPP can send callback messages; this class
135 provides a means to register a callback function to receive
136 these messages in a background thread.
137 """
Paul Vinciguerra19542292019-03-17 17:34:46 -0700138 apidir = None
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800139 VPPApiError = VPPApiError
140 VPPRuntimeError = VPPRuntimeError
141 VPPValueError = VPPValueError
142 VPPNotImplementedError = VPPNotImplementedError
143 VPPIOError = VPPIOError
Ole Troana7564e82018-06-12 21:06:44 +0200144
145 def process_json_file(self, apidef_file):
146 api = json.load(apidef_file)
147 types = {}
148 for t in api['enums']:
149 t[0] = 'vl_api_' + t[0] + '_t'
150 types[t[0]] = {'type': 'enum', 'data': t}
151 for t in api['unions']:
152 t[0] = 'vl_api_' + t[0] + '_t'
153 types[t[0]] = {'type': 'union', 'data': t}
154 for t in api['types']:
155 t[0] = 'vl_api_' + t[0] + '_t'
156 types[t[0]] = {'type': 'type', 'data': t}
Ole Troan53fffa12018-11-13 12:36:56 +0100157 for t, v in api['aliases'].items():
158 types['vl_api_' + t + '_t'] = {'type': 'alias', 'data': v}
Ole Troandfb984d2018-12-07 14:31:16 +0100159 self.services.update(api['services'])
Ole Troana7564e82018-06-12 21:06:44 +0200160
161 i = 0
162 while True:
163 unresolved = {}
164 for k, v in types.items():
165 t = v['data']
Ole Troan53fffa12018-11-13 12:36:56 +0100166 if not vpp_get_type(k):
Ole Troan0685da42018-10-16 14:42:50 +0200167 if v['type'] == 'enum':
168 try:
169 VPPEnumType(t[0], t[1:])
170 except ValueError:
171 unresolved[k] = v
172 elif v['type'] == 'union':
173 try:
174 VPPUnionType(t[0], t[1:])
175 except ValueError:
176 unresolved[k] = v
177 elif v['type'] == 'type':
178 try:
179 VPPType(t[0], t[1:])
180 except ValueError:
181 unresolved[k] = v
Ole Troan53fffa12018-11-13 12:36:56 +0100182 elif v['type'] == 'alias':
183 try:
184 VPPTypeAlias(k, t)
185 except ValueError:
186 unresolved[k] = v
Ole Troana7564e82018-06-12 21:06:44 +0200187 if len(unresolved) == 0:
188 break
189 if i > 3:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800190 raise VPPValueError('Unresolved type definitions {}'
191 .format(unresolved))
Ole Troana7564e82018-06-12 21:06:44 +0200192 types = unresolved
193 i += 1
194
195 for m in api['messages']:
196 try:
197 self.messages[m[0]] = VPPMessage(m[0], m[1:])
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800198 except VPPNotImplementedError:
Ole Troana7564e82018-06-12 21:06:44 +0200199 self.logger.error('Not implemented error for {}'.format(m[0]))
200
Ole Troan4df97162017-07-07 16:06:08 +0200201 def __init__(self, apifiles=None, testmode=False, async_thread=True,
Igor Mikhailov (imichail)5efd14f2018-10-30 12:17:49 -0700202 logger=None, loglevel=None,
Ole Troan94495f22018-08-02 11:58:12 +0200203 read_timeout=5, use_socket=False,
204 server_address='/run/vpp-api.sock'):
Ole Troan5016f992017-01-19 09:44:44 +0100205 """Create a VPP API object.
206
207 apifiles is a list of files containing API
208 descriptions that will be loaded - methods will be
209 dynamically created reflecting these APIs. If not
210 provided this will load the API files from VPP's
211 default install location.
Ian Wellsd0e812f2018-06-06 14:12:27 +0100212
213 logger, if supplied, is the logging logger object to log to.
214 loglevel, if supplied, is the log level this logger is set
215 to report at (from the loglevels in the logging module).
Ole Troan5016f992017-01-19 09:44:44 +0100216 """
Ian Wellsd0e812f2018-06-06 14:12:27 +0100217 if logger is None:
218 logger = logging.getLogger(__name__)
219 if loglevel is not None:
220 logger.setLevel(loglevel)
Ole Troan3cc49712017-03-08 12:02:24 +0100221 self.logger = logger
Ole Troan3cc49712017-03-08 12:02:24 +0100222
Ole Troana03f4ef2016-12-02 12:53:55 +0100223 self.messages = {}
Ole Troandfb984d2018-12-07 14:31:16 +0100224 self.services = {}
Ole Troana03f4ef2016-12-02 12:53:55 +0100225 self.id_names = []
226 self.id_msgdef = []
Ole Troana7564e82018-06-12 21:06:44 +0200227 self.header = VPPType('header', [['u16', 'msgid'],
228 ['u32', 'client_index']])
Ole Troan5016f992017-01-19 09:44:44 +0100229 self.apifiles = []
Ole Troan3d31f002017-01-26 11:13:00 +0100230 self.event_callback = None
Ole Troan4df97162017-07-07 16:06:08 +0200231 self.message_queue = queue.Queue()
dongjuan84937522017-11-09 14:46:36 +0800232 self.read_timeout = read_timeout
Klement Sekera180402d2018-02-17 10:58:37 +0100233 self.async_thread = async_thread
Paul Vinciguerra34ce5742019-06-06 17:14:37 -0400234 self.event_thread = None
Ole Troan5f9dcff2016-08-01 04:59:13 +0200235
Ole Troan94495f22018-08-02 11:58:12 +0200236 if use_socket:
237 from . vpp_transport_socket import VppTransport
238 else:
239 from . vpp_transport_shmem import VppTransport
240
Ole Troanf5984bd2016-12-18 13:15:08 +0100241 if not apifiles:
242 # Pick up API definitions from default directory
Chris Luke52bf22e2017-11-03 23:32:38 -0400243 try:
244 apifiles = self.find_api_files()
245 except RuntimeError:
246 # In test mode we don't care that we can't find the API files
247 if testmode:
248 apifiles = []
249 else:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800250 raise VPPRuntimeError
Ole Troanf5984bd2016-12-18 13:15:08 +0100251
Ole Troana03f4ef2016-12-02 12:53:55 +0100252 for file in apifiles:
Ole Troana03f4ef2016-12-02 12:53:55 +0100253 with open(file) as apidef_file:
Ole Troana7564e82018-06-12 21:06:44 +0200254 self.process_json_file(apidef_file)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200255
Ole Troan4df97162017-07-07 16:06:08 +0200256 self.apifiles = apifiles
Ole Troan5f9dcff2016-08-01 04:59:13 +0200257
Ole Troana03f4ef2016-12-02 12:53:55 +0100258 # Basic sanity check
Ole Troanf5984bd2016-12-18 13:15:08 +0100259 if len(self.messages) == 0 and not testmode:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800260 raise VPPValueError(1, 'Missing JSON message definitions')
Ole Troan5f9dcff2016-08-01 04:59:13 +0200261
Ole Troan94495f22018-08-02 11:58:12 +0200262 self.transport = VppTransport(self, read_timeout=read_timeout,
263 server_address=server_address)
Ole Troan5016f992017-01-19 09:44:44 +0100264 # Make sure we allow VPP to clean up the message rings.
Klement Sekera180402d2018-02-17 10:58:37 +0100265 atexit.register(vpp_atexit, weakref.ref(self))
Ole Troan5f9dcff2016-08-01 04:59:13 +0200266
Ole Troana03f4ef2016-12-02 12:53:55 +0100267 class ContextId(object):
Paul Vinciguerra2af6e922019-06-06 07:06:09 -0400268 """Multiprocessing-safe provider of unique context IDs."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100269 def __init__(self):
Paul Vinciguerra2af6e922019-06-06 07:06:09 -0400270 self.context = mp.Value(ctypes.c_uint, 0)
271 self.lock = mp.Lock()
Ole Troan4df97162017-07-07 16:06:08 +0200272
Ole Troana03f4ef2016-12-02 12:53:55 +0100273 def __call__(self):
Ole Troan5016f992017-01-19 09:44:44 +0100274 """Get a new unique (or, at least, not recently used) context."""
Ole Troan4df97162017-07-07 16:06:08 +0200275 with self.lock:
Paul Vinciguerra2af6e922019-06-06 07:06:09 -0400276 self.context.value += 1
277 return self.context.value
Ole Troana03f4ef2016-12-02 12:53:55 +0100278 get_context = ContextId()
Ole Troan5f9dcff2016-08-01 04:59:13 +0200279
Ole Troan0685da42018-10-16 14:42:50 +0200280 def get_type(self, name):
281 return vpp_get_type(name)
282
Chris Luke52bf22e2017-11-03 23:32:38 -0400283 @classmethod
284 def find_api_dir(cls):
285 """Attempt to find the best directory in which API definition
286 files may reside. If the value VPP_API_DIR exists in the environment
287 then it is first on the search list. If we're inside a recognized
288 location in a VPP source tree (src/scripts and src/vpp-api/python)
289 then entries from there to the likely locations in build-root are
290 added. Finally the location used by system packages is added.
291
292 :returns: A single directory name, or None if no such directory
293 could be found.
294 """
Vratko Polakcbf1fd22019-05-23 19:01:29 +0200295 dirs = [cls.apidir] if cls.apidir else []
Chris Luke52bf22e2017-11-03 23:32:38 -0400296
297 # perhaps we're in the 'src/scripts' or 'src/vpp-api/python' dir;
298 # in which case, plot a course to likely places in the src tree
299 import __main__ as main
300 if hasattr(main, '__file__'):
301 # get the path of the calling script
302 localdir = os.path.dirname(os.path.realpath(main.__file__))
303 else:
304 # use cwd if there is no calling script
Andrey "Zed" Zaikin68e2ffb2018-04-24 14:50:02 +0300305 localdir = os.getcwd()
Chris Luke52bf22e2017-11-03 23:32:38 -0400306 localdir_s = localdir.split(os.path.sep)
307
308 def dmatch(dir):
309 """Match dir against right-hand components of the script dir"""
310 d = dir.split('/') # param 'dir' assumes a / separator
Ole Troanafddd832018-02-28 14:55:20 +0100311 length = len(d)
312 return len(localdir_s) > length and localdir_s[-length:] == d
Chris Luke52bf22e2017-11-03 23:32:38 -0400313
314 def sdir(srcdir, variant):
315 """Build a path from srcdir to the staged API files of
316 'variant' (typically '' or '_debug')"""
317 # Since 'core' and 'plugin' files are staged
318 # in separate directories, we target the parent dir.
319 return os.path.sep.join((
320 srcdir,
321 'build-root',
322 'install-vpp%s-native' % variant,
323 'vpp',
324 'share',
325 'vpp',
326 'api',
327 ))
328
329 srcdir = None
330 if dmatch('src/scripts'):
331 srcdir = os.path.sep.join(localdir_s[:-2])
332 elif dmatch('src/vpp-api/python'):
333 srcdir = os.path.sep.join(localdir_s[:-3])
334 elif dmatch('test'):
335 # we're apparently running tests
336 srcdir = os.path.sep.join(localdir_s[:-1])
337
338 if srcdir:
339 # we're in the source tree, try both the debug and release
340 # variants.
Chris Luke52bf22e2017-11-03 23:32:38 -0400341 dirs.append(sdir(srcdir, '_debug'))
342 dirs.append(sdir(srcdir, ''))
343
344 # Test for staged copies of the scripts
345 # For these, since we explicitly know if we're running a debug versus
346 # release variant, target only the relevant directory
347 if dmatch('build-root/install-vpp_debug-native/vpp/bin'):
348 srcdir = os.path.sep.join(localdir_s[:-4])
349 dirs.append(sdir(srcdir, '_debug'))
350 if dmatch('build-root/install-vpp-native/vpp/bin'):
351 srcdir = os.path.sep.join(localdir_s[:-4])
352 dirs.append(sdir(srcdir, ''))
353
354 # finally, try the location system packages typically install into
355 dirs.append(os.path.sep.join(('', 'usr', 'share', 'vpp', 'api')))
356
Paul Vinciguerra19542292019-03-17 17:34:46 -0700357 # check the directories for existence; first one wins
Chris Luke52bf22e2017-11-03 23:32:38 -0400358 for dir in dirs:
359 if os.path.isdir(dir):
360 return dir
361
362 return None
363
364 @classmethod
365 def find_api_files(cls, api_dir=None, patterns='*'):
366 """Find API definition files from the given directory tree with the
367 given pattern. If no directory is given then find_api_dir() is used
368 to locate one. If no pattern is given then all definition files found
369 in the directory tree are used.
370
371 :param api_dir: A directory tree in which to locate API definition
372 files; subdirectories are descended into.
373 If this is None then find_api_dir() is called to discover it.
374 :param patterns: A list of patterns to use in each visited directory
375 when looking for files.
376 This can be a list/tuple object or a comma-separated string of
377 patterns. Each value in the list will have leading/trialing
378 whitespace stripped.
379 The pattern specifies the first part of the filename, '.api.json'
380 is appended.
381 The results are de-duplicated, thus overlapping patterns are fine.
382 If this is None it defaults to '*' meaning "all API files".
383 :returns: A list of file paths for the API files found.
384 """
385 if api_dir is None:
386 api_dir = cls.find_api_dir()
387 if api_dir is None:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800388 raise VPPApiError("api_dir cannot be located")
Chris Luke52bf22e2017-11-03 23:32:38 -0400389
390 if isinstance(patterns, list) or isinstance(patterns, tuple):
391 patterns = [p.strip() + '.api.json' for p in patterns]
392 else:
393 patterns = [p.strip() + '.api.json' for p in patterns.split(",")]
394
395 api_files = []
396 for root, dirnames, files in os.walk(api_dir):
397 # iterate all given patterns and de-dup the result
398 files = set(sum([fnmatch.filter(files, p) for p in patterns], []))
399 for filename in files:
400 api_files.append(os.path.join(root, filename))
401
402 return api_files
403
Klement Sekera7112c542017-03-01 09:53:19 +0100404 @property
405 def api(self):
406 if not hasattr(self, "_api"):
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800407 raise VPPApiError("Not connected, api definitions not available")
Klement Sekera7112c542017-03-01 09:53:19 +0100408 return self._api
409
Ole Troaneabd6072018-08-09 12:50:55 +0200410 def make_function(self, msg, i, multipart, do_async):
411 if (do_async):
Ole Troana7564e82018-06-12 21:06:44 +0200412 def f(**kwargs):
413 return self._call_vpp_async(i, msg, **kwargs)
414 else:
415 def f(**kwargs):
416 return self._call_vpp(i, msg, multipart, **kwargs)
417
418 f.__name__ = str(msg.name)
419 f.__doc__ = ", ".join(["%s %s" %
420 (msg.fieldtypes[j], k)
421 for j, k in enumerate(msg.fields)])
Ole Troanf159f582019-02-28 20:20:47 +0100422 f.msg = msg
423
Ole Troana7564e82018-06-12 21:06:44 +0200424 return f
425
Ole Troaneabd6072018-08-09 12:50:55 +0200426 def _register_functions(self, do_async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100427 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
428 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera8aedf5e2018-07-06 11:07:21 +0200429 self._api = VppApiDynamicMethodHolder()
Ole Troana7564e82018-06-12 21:06:44 +0200430 for name, msg in vpp_iterator(self.messages):
431 n = name + '_' + msg.crc[2:]
Paul Vinciguerra3d04e332019-03-19 17:32:39 -0700432 i = self.transport.get_msg_index(n.encode('utf-8'))
Ole Troan3cc49712017-03-08 12:02:24 +0100433 if i > 0:
Ole Troana7564e82018-06-12 21:06:44 +0200434 self.id_msgdef[i] = msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100435 self.id_names[i] = name
Ole Troandfb984d2018-12-07 14:31:16 +0100436
437 # Create function for client side messages.
438 if name in self.services:
Ole Troan0bcad322018-12-11 13:04:01 +0100439 if 'stream' in self.services[name] and \
440 self.services[name]['stream']:
Ole Troandfb984d2018-12-07 14:31:16 +0100441 multipart = True
442 else:
443 multipart = False
444 f = self.make_function(msg, i, multipart, do_async)
445 setattr(self._api, name, FuncWrapper(f))
Ole Troan3cc49712017-03-08 12:02:24 +0100446 else:
Ole Troan4df97162017-07-07 16:06:08 +0200447 self.logger.debug(
448 'No such message type or failed CRC checksum: %s', n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100449
Ole Troan4df97162017-07-07 16:06:08 +0200450 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen,
Ole Troaneabd6072018-08-09 12:50:55 +0200451 do_async):
Paul Vinciguerra3d04e332019-03-19 17:32:39 -0700452 pfx = chroot_prefix.encode('utf-8') if chroot_prefix else None
Ole Troan94495f22018-08-02 11:58:12 +0200453
Paul Vinciguerra3d04e332019-03-19 17:32:39 -0700454 rv = self.transport.connect(name.encode('utf-8'), pfx,
455 msg_handler, rx_qlen)
Ole Troana03f4ef2016-12-02 12:53:55 +0100456 if rv != 0:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800457 raise VPPIOError(2, 'Connect failed')
Ole Troan94495f22018-08-02 11:58:12 +0200458 self.vpp_dictionary_maxid = self.transport.msg_table_max_index()
Ole Troaneabd6072018-08-09 12:50:55 +0200459 self._register_functions(do_async=do_async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100460
461 # Initialise control ping
Ole Troana7564e82018-06-12 21:06:44 +0200462 crc = self.messages['control_ping'].crc
Ole Troan94495f22018-08-02 11:58:12 +0200463 self.control_ping_index = self.transport.get_msg_index(
Paul Vinciguerra3d04e332019-03-19 17:32:39 -0700464 ('control_ping' + '_' + crc[2:]).encode('utf-8'))
Ole Troana03f4ef2016-12-02 12:53:55 +0100465 self.control_ping_msgdef = self.messages['control_ping']
Klement Sekera180402d2018-02-17 10:58:37 +0100466 if self.async_thread:
467 self.event_thread = threading.Thread(
468 target=self.thread_msg_handler)
469 self.event_thread.daemon = True
470 self.event_thread.start()
Vratko Polak94e45312019-05-27 18:36:23 +0200471 else:
472 self.event_thread = None
Ole Troan4df97162017-07-07 16:06:08 +0200473 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100474
Ole Troaneabd6072018-08-09 12:50:55 +0200475 def connect(self, name, chroot_prefix=None, do_async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100476 """Attach to VPP.
477
478 name - the name of the client.
479 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
Ole Troaneabd6072018-08-09 12:50:55 +0200480 do_async - if true, messages are sent without waiting for a reply
Ole Troandfc9b7c2017-03-06 23:51:57 +0100481 rx_qlen - the length of the VPP message receive queue between
482 client and server.
483 """
Ole Troan94495f22018-08-02 11:58:12 +0200484 msg_handler = self.transport.get_callback(do_async)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100485 return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen,
Ole Troaneabd6072018-08-09 12:50:55 +0200486 do_async)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100487
Ole Troan6bf177c2017-08-17 10:34:32 +0200488 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100489 """Attach to VPP in synchronous mode. Application must poll for events.
490
491 name - the name of the client.
492 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
493 rx_qlen - the length of the VPP message receive queue between
494 client and server.
495 """
496
Ole Troan94495f22018-08-02 11:58:12 +0200497 return self.connect_internal(name, None, chroot_prefix, rx_qlen,
Ole Troaneabd6072018-08-09 12:50:55 +0200498 do_async=False)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100499
Ole Troana03f4ef2016-12-02 12:53:55 +0100500 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100501 """Detach from VPP."""
Ole Troan94495f22018-08-02 11:58:12 +0200502 rv = self.transport.disconnect()
Vratko Polak94e45312019-05-27 18:36:23 +0200503 if self.event_thread is not None:
504 self.message_queue.put("terminate event thread")
Ole Troana03f4ef2016-12-02 12:53:55 +0100505 return rv
506
Ole Troan5016f992017-01-19 09:44:44 +0100507 def msg_handler_sync(self, msg):
508 """Process an incoming message from VPP in sync mode.
509
510 The message may be a reply or it may be an async notification.
511 """
512 r = self.decode_incoming_msg(msg)
513 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100514 return
515
Ole Troan5016f992017-01-19 09:44:44 +0100516 # If we have a context, then use the context to find any
517 # request waiting for a reply
518 context = 0
519 if hasattr(r, 'context') and r.context > 0:
520 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200521
Ole Troan5016f992017-01-19 09:44:44 +0100522 if context == 0:
523 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100524 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100525 else:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800526 raise VPPIOError(2, 'RPC reply message received in event handler')
Ole Troan5016f992017-01-19 09:44:44 +0100527
Ole Troan413f4a52018-11-28 11:36:05 +0100528 def has_context(self, msg):
529 if len(msg) < 10:
530 return False
531
532 header = VPPType('header_with_context', [['u16', 'msgid'],
533 ['u32', 'client_index'],
534 ['u32', 'context']])
535
536 (i, ci, context), size = header.unpack(msg, 0)
537 if self.id_names[i] == 'rx_thread_exit':
538 return
539
540 #
541 # Decode message and returns a tuple.
542 #
543 msgobj = self.id_msgdef[i]
544 if 'context' in msgobj.field_by_name and context >= 0:
545 return True
546 return False
547
Ole Troan0bcad322018-12-11 13:04:01 +0100548 def decode_incoming_msg(self, msg, no_type_conversion=False):
Ole Troan5016f992017-01-19 09:44:44 +0100549 if not msg:
Ole Troan3cc49712017-03-08 12:02:24 +0100550 self.logger.warning('vpp_api.read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100551 return
Ole Troan413f4a52018-11-28 11:36:05 +0100552
Ole Troanc84cbad2018-09-06 22:58:05 +0200553 (i, ci), size = self.header.unpack(msg, 0)
Ole Troan5016f992017-01-19 09:44:44 +0100554 if self.id_names[i] == 'rx_thread_exit':
555 return
556
557 #
558 # Decode message and returns a tuple.
559 #
Ole Troana7564e82018-06-12 21:06:44 +0200560 msgobj = self.id_msgdef[i]
561 if not msgobj:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800562 raise VPPIOError(2, 'Reply message undefined')
Ole Troan5016f992017-01-19 09:44:44 +0100563
Ole Troan0bcad322018-12-11 13:04:01 +0100564 r, size = msgobj.unpack(msg, ntc=no_type_conversion)
Ole Troana03f4ef2016-12-02 12:53:55 +0100565 return r
566
Ole Troan5016f992017-01-19 09:44:44 +0100567 def msg_handler_async(self, msg):
568 """Process a message from VPP in async mode.
569
570 In async mode, all messages are returned to the callback.
571 """
572 r = self.decode_incoming_msg(msg)
573 if r is None:
574 return
575
576 msgname = type(r).__name__
577
Ole Troan4df97162017-07-07 16:06:08 +0200578 if self.event_callback:
579 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100580
581 def _control_ping(self, context):
582 """Send a ping command."""
583 self._call_vpp_async(self.control_ping_index,
Ole Troan4df97162017-07-07 16:06:08 +0200584 self.control_ping_msgdef,
Ole Troan5016f992017-01-19 09:44:44 +0100585 context=context)
586
Ole Troana7564e82018-06-12 21:06:44 +0200587 def validate_args(self, msg, kwargs):
588 d = set(kwargs.keys()) - set(msg.field_by_name.keys())
589 if d:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800590 raise VPPValueError('Invalid argument {} to {}'
Ole Troan0bcad322018-12-11 13:04:01 +0100591 .format(list(d), msg.name))
Ole Troana7564e82018-06-12 21:06:44 +0200592
Ole Troan413f4a52018-11-28 11:36:05 +0100593 def _call_vpp(self, i, msgdef, multipart, **kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100594 """Given a message, send the message and await a reply.
595
596 msgdef - the message packing definition
597 i - the message type index
598 multipart - True if the message returns multiple
599 messages in return.
600 context - context number - chosen at random if not
601 supplied.
602 The remainder of the kwargs are the arguments to the API call.
603
604 The return value is the message or message array containing
605 the response. It will raise an IOError exception if there was
606 no response within the timeout window.
607 """
608
Ole Troan4df97162017-07-07 16:06:08 +0200609 if 'context' not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100610 context = self.get_context()
611 kwargs['context'] = context
612 else:
613 context = kwargs['context']
614 kwargs['_vl_msg_id'] = i
Ole Troan5016f992017-01-19 09:44:44 +0100615
Ole Troan0bcad322018-12-11 13:04:01 +0100616 no_type_conversion = kwargs.pop('_no_type_conversion', False)
617
Ole Troan94495f22018-08-02 11:58:12 +0200618 try:
619 if self.transport.socket_index:
620 kwargs['client_index'] = self.transport.socket_index
621 except AttributeError:
622 pass
Ole Troan413f4a52018-11-28 11:36:05 +0100623 self.validate_args(msgdef, kwargs)
624
625 logging.debug(call_logger(msgdef, kwargs))
626
627 b = msgdef.pack(kwargs)
Ole Troan94495f22018-08-02 11:58:12 +0200628 self.transport.suspend()
629
630 self.transport.write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100631
632 if multipart:
633 # Send a ping after the request - we use its response
634 # to detect that we have seen all results.
635 self._control_ping(context)
636
637 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100638 rl = []
639 while (True):
Ole Troan94495f22018-08-02 11:58:12 +0200640 msg = self.transport.read()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100641 if not msg:
Paul Vinciguerra6ccc6e92018-11-27 08:15:22 -0800642 raise VPPIOError(2, 'VPP API client: read failed')
Ole Troan0bcad322018-12-11 13:04:01 +0100643 r = self.decode_incoming_msg(msg, no_type_conversion)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100644 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200645 if context not in r or r.context == 0 or context != r.context:
Ole Troan94495f22018-08-02 11:58:12 +0200646 # Message being queued
Ole Troandfc9b7c2017-03-06 23:51:57 +0100647 self.message_queue.put_nowait(r)
648 continue
649
650 if not multipart:
651 rl = r
652 break
653 if msgname == 'control_ping_reply':
654 break
655
656 rl.append(r)
657
Ole Troan94495f22018-08-02 11:58:12 +0200658 self.transport.resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100659
Ole Troan413f4a52018-11-28 11:36:05 +0100660 logger.debug(return_logger(rl))
Ole Troandfc9b7c2017-03-06 23:51:57 +0100661 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100662
Ole Troana7564e82018-06-12 21:06:44 +0200663 def _call_vpp_async(self, i, msg, **kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100664 """Given a message, send the message and await a reply.
665
666 msgdef - the message packing definition
667 i - the message type index
668 context - context number - chosen at random if not
669 supplied.
670 The remainder of the kwargs are the arguments to the API call.
671 """
Ole Troan4df97162017-07-07 16:06:08 +0200672 if 'context' not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100673 context = self.get_context()
674 kwargs['context'] = context
675 else:
676 context = kwargs['context']
Ole Troan94495f22018-08-02 11:58:12 +0200677 try:
678 if self.transport.socket_index:
679 kwargs['client_index'] = self.transport.socket_index
680 except AttributeError:
681 kwargs['client_index'] = 0
Ole Troan7e3a8752016-12-05 10:27:09 +0100682 kwargs['_vl_msg_id'] = i
Ole Troana7564e82018-06-12 21:06:44 +0200683 b = msg.pack(kwargs)
Ole Troan7e3a8752016-12-05 10:27:09 +0100684
Ole Troan94495f22018-08-02 11:58:12 +0200685 self.transport.write(b)
Ole Troan7e3a8752016-12-05 10:27:09 +0100686
Ole Troana03f4ef2016-12-02 12:53:55 +0100687 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100688 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100689
Ole Troan5016f992017-01-19 09:44:44 +0100690 This will be called for async notifications in sync mode,
691 and all messages in async mode. In sync mode, replies to
692 requests will not come here.
693
694 callback is a fn(msg_type_name, msg_type) that will be
695 called when a message comes in. While this function is
696 executing, note that (a) you are in a background thread and
697 may wish to use threading.Lock to protect your datastructures,
698 and (b) message processing from VPP will stop (so if you take
699 a long while about it you may provoke reply timeouts or cause
700 VPP to fill the RX buffer). Passing None will disable the
701 callback.
702 """
703 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100704
705 def thread_msg_handler(self):
Ole Troan94495f22018-08-02 11:58:12 +0200706 """Python thread calling the user registered message handler.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100707
708 This is to emulate the old style event callback scheme. Modern
709 clients should provide their own thread to poll the event
710 queue.
711 """
712 while True:
713 r = self.message_queue.get()
Klement Sekera180402d2018-02-17 10:58:37 +0100714 if r == "terminate event thread":
715 break
Ole Troandfc9b7c2017-03-06 23:51:57 +0100716 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200717 if self.event_callback:
718 self.event_callback(msgname, r)
Chris Luke52bf22e2017-11-03 23:32:38 -0400719
Paul Vinciguerra19542292019-03-17 17:34:46 -0700720# Provide the old name for backward compatibility.
721VPP = VPPApiClient
Chris Luke52bf22e2017-11-03 23:32:38 -0400722
723# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4