blob: edc0c6ab364ba429e71733dc5e46b2b110036fbe [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 Troan3cc49712017-03-08 12:02:24 +010029from cffi import FFI
Ole Troana74b7412017-11-01 10:49:03 +010030import cffi
Ole Troana7564e82018-06-12 21:06:44 +020031from . vpp_serializer import VPPType, VPPEnumType, VPPUnionType, BaseTypes
32from . vpp_serializer import VPPMessage
Ole Troan4df97162017-07-07 16:06:08 +020033
34if sys.version[0] == '2':
35 import Queue as queue
36else:
37 import queue as queue
38
Ole Troan3cc49712017-03-08 12:02:24 +010039ffi = FFI()
40ffi.cdef("""
Damjan Marion5fec1e82017-04-13 19:13:47 +020041typedef void (*vac_callback_t)(unsigned char * data, int len);
42typedef void (*vac_error_callback_t)(void *, unsigned char *, int);
43int vac_connect(char * name, char * chroot_prefix, vac_callback_t cb,
Ole Troan3cc49712017-03-08 12:02:24 +010044 int rx_qlen);
Damjan Marion5fec1e82017-04-13 19:13:47 +020045int vac_disconnect(void);
46int vac_read(char **data, int *l, unsigned short timeout);
47int vac_write(char *data, int len);
48void vac_free(void * msg);
Ole Troan5f9dcff2016-08-01 04:59:13 +020049
Damjan Marion5fec1e82017-04-13 19:13:47 +020050int vac_get_msg_index(unsigned char * name);
51int vac_msg_table_size(void);
52int vac_msg_table_max_index(void);
Ole Troan3cc49712017-03-08 12:02:24 +010053
Damjan Marion5fec1e82017-04-13 19:13:47 +020054void vac_rx_suspend (void);
55void vac_rx_resume (void);
56void vac_set_error_handler(vac_error_callback_t);
Ole Troan3cc49712017-03-08 12:02:24 +010057 """)
58
59# Barfs on failure, no need to check success.
Damjan Marion5fec1e82017-04-13 19:13:47 +020060vpp_api = ffi.dlopen('libvppapiclient.so')
Ole Troan1732fc12016-08-30 21:03:51 +020061
Ole Troanafddd832018-02-28 14:55:20 +010062
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()
66 if vpp_instance.connected:
67 vpp_instance.logger.debug('Cleaning up VPP on exit')
68 vpp_instance.disconnect()
Ole Troan5016f992017-01-19 09:44:44 +010069
Ole Troanafddd832018-02-28 14:55:20 +010070
Ole Troan3cc49712017-03-08 12:02:24 +010071vpp_object = None
72
Ole Troan4df97162017-07-07 16:06:08 +020073
74def vpp_iterator(d):
75 if sys.version[0] == '2':
76 return d.iteritems()
77 else:
78 return d.items()
79
80
Ole Troan3cc49712017-03-08 12:02:24 +010081@ffi.callback("void(unsigned char *, int)")
Damjan Marion5fec1e82017-04-13 19:13:47 +020082def vac_callback_sync(data, len):
Ole Troan3cc49712017-03-08 12:02:24 +010083 vpp_object.msg_handler_sync(ffi.buffer(data, len))
Ole Troan4df97162017-07-07 16:06:08 +020084
85
Ole Troan3cc49712017-03-08 12:02:24 +010086@ffi.callback("void(unsigned char *, int)")
Damjan Marion5fec1e82017-04-13 19:13:47 +020087def vac_callback_async(data, len):
Ole Troan3cc49712017-03-08 12:02:24 +010088 vpp_object.msg_handler_async(ffi.buffer(data, len))
Ole Troan4df97162017-07-07 16:06:08 +020089
90
Ole Troan3cc49712017-03-08 12:02:24 +010091@ffi.callback("void(void *, unsigned char *, int)")
Damjan Marion5fec1e82017-04-13 19:13:47 +020092def vac_error_handler(arg, msg, msg_len):
Ole Troan4df97162017-07-07 16:06:08 +020093 vpp_object.logger.warning("VPP API client:: %s", ffi.string(msg, msg_len))
94
Klement Sekera7112c542017-03-01 09:53:19 +010095
96class Empty(object):
97 pass
98
99
100class FuncWrapper(object):
101 def __init__(self, func):
102 self._func = func
103 self.__name__ = func.__name__
104
105 def __call__(self, **kwargs):
106 return self._func(**kwargs)
107
108
Ole Troana03f4ef2016-12-02 12:53:55 +0100109class VPP():
Ole Troan5016f992017-01-19 09:44:44 +0100110 """VPP interface.
111
112 This class provides the APIs to VPP. The APIs are loaded
113 from provided .api.json files and makes functions accordingly.
114 These functions are documented in the VPP .api files, as they
115 are dynamically created.
116
117 Additionally, VPP can send callback messages; this class
118 provides a means to register a callback function to receive
119 these messages in a background thread.
120 """
Ole Troana7564e82018-06-12 21:06:44 +0200121
122 def process_json_file(self, apidef_file):
123 api = json.load(apidef_file)
124 types = {}
125 for t in api['enums']:
126 t[0] = 'vl_api_' + t[0] + '_t'
127 types[t[0]] = {'type': 'enum', 'data': t}
128 for t in api['unions']:
129 t[0] = 'vl_api_' + t[0] + '_t'
130 types[t[0]] = {'type': 'union', 'data': t}
131 for t in api['types']:
132 t[0] = 'vl_api_' + t[0] + '_t'
133 types[t[0]] = {'type': 'type', 'data': t}
134
135 i = 0
136 while True:
137 unresolved = {}
138 for k, v in types.items():
139 t = v['data']
140 if v['type'] == 'enum':
141 try:
142 VPPEnumType(t[0], t[1:])
143 except ValueError:
144 unresolved[k] = v
145 elif v['type'] == 'union':
146 try:
147 VPPUnionType(t[0], t[1:])
148 except ValueError:
149 unresolved[k] = v
150 elif v['type'] == 'type':
151 try:
152 VPPType(t[0], t[1:])
153 except ValueError:
154 unresolved[k] = v
155 if len(unresolved) == 0:
156 break
157 if i > 3:
158 raise ValueError('Unresolved type definitions {}'
159 .format(unresolved))
160 types = unresolved
161 i += 1
162
163 for m in api['messages']:
164 try:
165 self.messages[m[0]] = VPPMessage(m[0], m[1:])
166 except NotImplementedError:
167 self.logger.error('Not implemented error for {}'.format(m[0]))
168
Ole Troan4df97162017-07-07 16:06:08 +0200169 def __init__(self, apifiles=None, testmode=False, async_thread=True,
Ole Troana7564e82018-06-12 21:06:44 +0200170 logger=logging.getLogger('vpp_papi'), loglevel='debug',
Ole Troanafddd832018-02-28 14:55:20 +0100171 read_timeout=0):
Ole Troan5016f992017-01-19 09:44:44 +0100172 """Create a VPP API object.
173
174 apifiles is a list of files containing API
175 descriptions that will be loaded - methods will be
176 dynamically created reflecting these APIs. If not
177 provided this will load the API files from VPP's
178 default install location.
Ian Wellsd0e812f2018-06-06 14:12:27 +0100179
180 logger, if supplied, is the logging logger object to log to.
181 loglevel, if supplied, is the log level this logger is set
182 to report at (from the loglevels in the logging module).
Ole Troan5016f992017-01-19 09:44:44 +0100183 """
Ole Troan3cc49712017-03-08 12:02:24 +0100184 global vpp_object
185 vpp_object = self
Ian Wellsd0e812f2018-06-06 14:12:27 +0100186
187 if logger is None:
188 logger = logging.getLogger(__name__)
189 if loglevel is not None:
190 logger.setLevel(loglevel)
Ole Troan3cc49712017-03-08 12:02:24 +0100191 self.logger = logger
Ole Troan3cc49712017-03-08 12:02:24 +0100192
Ole Troana03f4ef2016-12-02 12:53:55 +0100193 self.messages = {}
194 self.id_names = []
195 self.id_msgdef = []
Ole Troana03f4ef2016-12-02 12:53:55 +0100196 self.connected = False
Ole Troana7564e82018-06-12 21:06:44 +0200197 self.header = VPPType('header', [['u16', 'msgid'],
198 ['u32', 'client_index']])
Ole Troan5016f992017-01-19 09:44:44 +0100199 self.apifiles = []
Ole Troan3d31f002017-01-26 11:13:00 +0100200 self.event_callback = None
Ole Troan4df97162017-07-07 16:06:08 +0200201 self.message_queue = queue.Queue()
dongjuan84937522017-11-09 14:46:36 +0800202 self.read_timeout = read_timeout
Ole Troandfc9b7c2017-03-06 23:51:57 +0100203 self.vpp_api = vpp_api
Klement Sekera180402d2018-02-17 10:58:37 +0100204 self.async_thread = async_thread
Ole Troan5f9dcff2016-08-01 04:59:13 +0200205
Ole Troanf5984bd2016-12-18 13:15:08 +0100206 if not apifiles:
207 # Pick up API definitions from default directory
Chris Luke52bf22e2017-11-03 23:32:38 -0400208 try:
209 apifiles = self.find_api_files()
210 except RuntimeError:
211 # In test mode we don't care that we can't find the API files
212 if testmode:
213 apifiles = []
214 else:
215 raise
Ole Troanf5984bd2016-12-18 13:15:08 +0100216
Ole Troana03f4ef2016-12-02 12:53:55 +0100217 for file in apifiles:
Ole Troana03f4ef2016-12-02 12:53:55 +0100218 with open(file) as apidef_file:
Ole Troana7564e82018-06-12 21:06:44 +0200219 self.process_json_file(apidef_file)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200220
Ole Troan4df97162017-07-07 16:06:08 +0200221 self.apifiles = apifiles
Ole Troan5f9dcff2016-08-01 04:59:13 +0200222
Ole Troana03f4ef2016-12-02 12:53:55 +0100223 # Basic sanity check
Ole Troanf5984bd2016-12-18 13:15:08 +0100224 if len(self.messages) == 0 and not testmode:
225 raise ValueError(1, 'Missing JSON message definitions')
Ole Troan5f9dcff2016-08-01 04:59:13 +0200226
Ole Troan5016f992017-01-19 09:44:44 +0100227 # Make sure we allow VPP to clean up the message rings.
Klement Sekera180402d2018-02-17 10:58:37 +0100228 atexit.register(vpp_atexit, weakref.ref(self))
Ole Troan5f9dcff2016-08-01 04:59:13 +0200229
Ole Troan3cc49712017-03-08 12:02:24 +0100230 # Register error handler
Ole Troana7564e82018-06-12 21:06:44 +0200231 if not testmode:
232 vpp_api.vac_set_error_handler(vac_error_handler)
Ole Troan3cc49712017-03-08 12:02:24 +0100233
Ole Troana74b7412017-11-01 10:49:03 +0100234 # Support legacy CFFI
235 # from_buffer supported from 1.8.0
Ole Troanafddd832018-02-28 14:55:20 +0100236 (major, minor, patch) = [int(s) for s in
237 cffi.__version__.split('.', 3)]
Ole Troana74b7412017-11-01 10:49:03 +0100238 if major >= 1 and minor >= 8:
239 self._write = self._write_new_cffi
240 else:
241 self._write = self._write_legacy_cffi
242
Ole Troana03f4ef2016-12-02 12:53:55 +0100243 class ContextId(object):
Ole Troan5016f992017-01-19 09:44:44 +0100244 """Thread-safe provider of unique context IDs."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100245 def __init__(self):
246 self.context = 0
Ole Troan4df97162017-07-07 16:06:08 +0200247 self.lock = threading.Lock()
248
Ole Troana03f4ef2016-12-02 12:53:55 +0100249 def __call__(self):
Ole Troan5016f992017-01-19 09:44:44 +0100250 """Get a new unique (or, at least, not recently used) context."""
Ole Troan4df97162017-07-07 16:06:08 +0200251 with self.lock:
252 self.context += 1
253 return self.context
Ole Troana03f4ef2016-12-02 12:53:55 +0100254 get_context = ContextId()
Ole Troan5f9dcff2016-08-01 04:59:13 +0200255
Chris Luke52bf22e2017-11-03 23:32:38 -0400256 @classmethod
257 def find_api_dir(cls):
258 """Attempt to find the best directory in which API definition
259 files may reside. If the value VPP_API_DIR exists in the environment
260 then it is first on the search list. If we're inside a recognized
261 location in a VPP source tree (src/scripts and src/vpp-api/python)
262 then entries from there to the likely locations in build-root are
263 added. Finally the location used by system packages is added.
264
265 :returns: A single directory name, or None if no such directory
266 could be found.
267 """
268 dirs = []
269
270 if 'VPP_API_DIR' in os.environ:
271 dirs.append(os.environ['VPP_API_DIR'])
272
273 # perhaps we're in the 'src/scripts' or 'src/vpp-api/python' dir;
274 # in which case, plot a course to likely places in the src tree
275 import __main__ as main
276 if hasattr(main, '__file__'):
277 # get the path of the calling script
278 localdir = os.path.dirname(os.path.realpath(main.__file__))
279 else:
280 # use cwd if there is no calling script
Andrey "Zed" Zaikin68e2ffb2018-04-24 14:50:02 +0300281 localdir = os.getcwd()
Chris Luke52bf22e2017-11-03 23:32:38 -0400282 localdir_s = localdir.split(os.path.sep)
283
284 def dmatch(dir):
285 """Match dir against right-hand components of the script dir"""
286 d = dir.split('/') # param 'dir' assumes a / separator
Ole Troanafddd832018-02-28 14:55:20 +0100287 length = len(d)
288 return len(localdir_s) > length and localdir_s[-length:] == d
Chris Luke52bf22e2017-11-03 23:32:38 -0400289
290 def sdir(srcdir, variant):
291 """Build a path from srcdir to the staged API files of
292 'variant' (typically '' or '_debug')"""
293 # Since 'core' and 'plugin' files are staged
294 # in separate directories, we target the parent dir.
295 return os.path.sep.join((
296 srcdir,
297 'build-root',
298 'install-vpp%s-native' % variant,
299 'vpp',
300 'share',
301 'vpp',
302 'api',
303 ))
304
305 srcdir = None
306 if dmatch('src/scripts'):
307 srcdir = os.path.sep.join(localdir_s[:-2])
308 elif dmatch('src/vpp-api/python'):
309 srcdir = os.path.sep.join(localdir_s[:-3])
310 elif dmatch('test'):
311 # we're apparently running tests
312 srcdir = os.path.sep.join(localdir_s[:-1])
313
314 if srcdir:
315 # we're in the source tree, try both the debug and release
316 # variants.
Chris Luke52bf22e2017-11-03 23:32:38 -0400317 dirs.append(sdir(srcdir, '_debug'))
318 dirs.append(sdir(srcdir, ''))
319
320 # Test for staged copies of the scripts
321 # For these, since we explicitly know if we're running a debug versus
322 # release variant, target only the relevant directory
323 if dmatch('build-root/install-vpp_debug-native/vpp/bin'):
324 srcdir = os.path.sep.join(localdir_s[:-4])
325 dirs.append(sdir(srcdir, '_debug'))
326 if dmatch('build-root/install-vpp-native/vpp/bin'):
327 srcdir = os.path.sep.join(localdir_s[:-4])
328 dirs.append(sdir(srcdir, ''))
329
330 # finally, try the location system packages typically install into
331 dirs.append(os.path.sep.join(('', 'usr', 'share', 'vpp', 'api')))
332
333 # check the directories for existance; first one wins
334 for dir in dirs:
335 if os.path.isdir(dir):
336 return dir
337
338 return None
339
340 @classmethod
341 def find_api_files(cls, api_dir=None, patterns='*'):
342 """Find API definition files from the given directory tree with the
343 given pattern. If no directory is given then find_api_dir() is used
344 to locate one. If no pattern is given then all definition files found
345 in the directory tree are used.
346
347 :param api_dir: A directory tree in which to locate API definition
348 files; subdirectories are descended into.
349 If this is None then find_api_dir() is called to discover it.
350 :param patterns: A list of patterns to use in each visited directory
351 when looking for files.
352 This can be a list/tuple object or a comma-separated string of
353 patterns. Each value in the list will have leading/trialing
354 whitespace stripped.
355 The pattern specifies the first part of the filename, '.api.json'
356 is appended.
357 The results are de-duplicated, thus overlapping patterns are fine.
358 If this is None it defaults to '*' meaning "all API files".
359 :returns: A list of file paths for the API files found.
360 """
361 if api_dir is None:
362 api_dir = cls.find_api_dir()
363 if api_dir is None:
364 raise RuntimeError("api_dir cannot be located")
365
366 if isinstance(patterns, list) or isinstance(patterns, tuple):
367 patterns = [p.strip() + '.api.json' for p in patterns]
368 else:
369 patterns = [p.strip() + '.api.json' for p in patterns.split(",")]
370
371 api_files = []
372 for root, dirnames, files in os.walk(api_dir):
373 # iterate all given patterns and de-dup the result
374 files = set(sum([fnmatch.filter(files, p) for p in patterns], []))
375 for filename in files:
376 api_files.append(os.path.join(root, filename))
377
378 return api_files
379
Ole Troana03f4ef2016-12-02 12:53:55 +0100380 def status(self):
Ole Troan5016f992017-01-19 09:44:44 +0100381 """Debug function: report current VPP API status to stdout."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100382 print('Connected') if self.connected else print('Not Connected')
Ole Troan5016f992017-01-19 09:44:44 +0100383 print('Read API definitions from', ', '.join(self.apifiles))
Ole Troan5f9dcff2016-08-01 04:59:13 +0200384
Klement Sekera7112c542017-03-01 09:53:19 +0100385 @property
386 def api(self):
387 if not hasattr(self, "_api"):
388 raise Exception("Not connected, api definitions not available")
389 return self._api
390
Ole Troana7564e82018-06-12 21:06:44 +0200391 def make_function(self, msg, i, multipart, async):
392 if (async):
393 def f(**kwargs):
394 return self._call_vpp_async(i, msg, **kwargs)
395 else:
396 def f(**kwargs):
397 return self._call_vpp(i, msg, multipart, **kwargs)
398
399 f.__name__ = str(msg.name)
400 f.__doc__ = ", ".join(["%s %s" %
401 (msg.fieldtypes[j], k)
402 for j, k in enumerate(msg.fields)])
403 return f
404
Wojciech Dec64bc6122016-12-12 11:32:25 +0100405 def _register_functions(self, async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100406 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
407 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera7112c542017-03-01 09:53:19 +0100408 self._api = Empty()
Ole Troana7564e82018-06-12 21:06:44 +0200409 for name, msg in vpp_iterator(self.messages):
410 n = name + '_' + msg.crc[2:]
Ole Troan4df97162017-07-07 16:06:08 +0200411 i = vpp_api.vac_get_msg_index(n.encode())
Ole Troan3cc49712017-03-08 12:02:24 +0100412 if i > 0:
Ole Troana7564e82018-06-12 21:06:44 +0200413 self.id_msgdef[i] = msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100414 self.id_names[i] = name
Ole Troana7564e82018-06-12 21:06:44 +0200415 # TODO: Fix multipart (use services)
Ole Troana03f4ef2016-12-02 12:53:55 +0100416 multipart = True if name.find('_dump') > 0 else False
Ole Troana7564e82018-06-12 21:06:44 +0200417 f = self.make_function(msg, i, multipart, async)
Klement Sekera7112c542017-03-01 09:53:19 +0100418 setattr(self._api, name, FuncWrapper(f))
Ole Troan3cc49712017-03-08 12:02:24 +0100419 else:
Ole Troan4df97162017-07-07 16:06:08 +0200420 self.logger.debug(
421 'No such message type or failed CRC checksum: %s', n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100422
Ole Troana74b7412017-11-01 10:49:03 +0100423 def _write_new_cffi(self, buf):
Ole Troan5016f992017-01-19 09:44:44 +0100424 """Send a binary-packed message to VPP."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100425 if not self.connected:
426 raise IOError(1, 'Not connected')
Ole Troan4df97162017-07-07 16:06:08 +0200427 return vpp_api.vac_write(ffi.from_buffer(buf), len(buf))
Ole Troana03f4ef2016-12-02 12:53:55 +0100428
Ole Troana74b7412017-11-01 10:49:03 +0100429 def _write_legacy_cffi(self, buf):
430 """Send a binary-packed message to VPP."""
431 if not self.connected:
432 raise IOError(1, 'Not connected')
Andrey "Zed" Zaikin7fe930b2018-04-12 12:14:02 +0300433 return vpp_api.vac_write(bytes(buf), len(buf))
Ole Troana74b7412017-11-01 10:49:03 +0100434
Ole Troan4df97162017-07-07 16:06:08 +0200435 def _read(self):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100436 if not self.connected:
437 raise IOError(1, 'Not connected')
Ole Troan3cc49712017-03-08 12:02:24 +0100438 mem = ffi.new("char **")
439 size = ffi.new("int *")
Damjan Marion5fec1e82017-04-13 19:13:47 +0200440 rv = vpp_api.vac_read(mem, size, self.read_timeout)
Ole Troan3cc49712017-03-08 12:02:24 +0100441 if rv:
Ole Troanb0856b42017-08-17 12:48:08 +0200442 raise IOError(rv, 'vac_read failed')
Ole Troan3cc49712017-03-08 12:02:24 +0100443 msg = bytes(ffi.buffer(mem[0], size[0]))
Damjan Marion5fec1e82017-04-13 19:13:47 +0200444 vpp_api.vac_free(mem[0])
Ole Troan3cc49712017-03-08 12:02:24 +0100445 return msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100446
Ole Troan4df97162017-07-07 16:06:08 +0200447 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen,
448 async):
Ole Troan6bf177c2017-08-17 10:34:32 +0200449 pfx = chroot_prefix.encode() if chroot_prefix else ffi.NULL
450 rv = vpp_api.vac_connect(name.encode(), pfx, msg_handler, rx_qlen)
Ole Troana03f4ef2016-12-02 12:53:55 +0100451 if rv != 0:
452 raise IOError(2, 'Connect failed')
Ole Troan7e3a8752016-12-05 10:27:09 +0100453 self.connected = True
Damjan Marion5fec1e82017-04-13 19:13:47 +0200454 self.vpp_dictionary_maxid = vpp_api.vac_msg_table_max_index()
Wojciech Dec64bc6122016-12-12 11:32:25 +0100455 self._register_functions(async=async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100456
457 # Initialise control ping
Ole Troana7564e82018-06-12 21:06:44 +0200458 crc = self.messages['control_ping'].crc
Ole Troan4df97162017-07-07 16:06:08 +0200459 self.control_ping_index = vpp_api.vac_get_msg_index(
460 ('control_ping' + '_' + crc[2:]).encode())
Ole Troana03f4ef2016-12-02 12:53:55 +0100461 self.control_ping_msgdef = self.messages['control_ping']
Klement Sekera180402d2018-02-17 10:58:37 +0100462 if self.async_thread:
463 self.event_thread = threading.Thread(
464 target=self.thread_msg_handler)
465 self.event_thread.daemon = True
466 self.event_thread.start()
Ole Troan4df97162017-07-07 16:06:08 +0200467 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100468
Ole Troan6bf177c2017-08-17 10:34:32 +0200469 def connect(self, name, chroot_prefix=None, async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100470 """Attach to VPP.
471
472 name - the name of the client.
473 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
474 async - if true, messages are sent without waiting for a reply
475 rx_qlen - the length of the VPP message receive queue between
476 client and server.
477 """
Ole Troan4df97162017-07-07 16:06:08 +0200478 msg_handler = vac_callback_sync if not async else vac_callback_async
Ole Troandfc9b7c2017-03-06 23:51:57 +0100479 return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen,
480 async)
481
Ole Troan6bf177c2017-08-17 10:34:32 +0200482 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100483 """Attach to VPP in synchronous mode. Application must poll for events.
484
485 name - the name of the client.
486 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
487 rx_qlen - the length of the VPP message receive queue between
488 client and server.
489 """
490
Ole Troan3cc49712017-03-08 12:02:24 +0100491 return self.connect_internal(name, ffi.NULL, chroot_prefix, rx_qlen,
Ole Troandfc9b7c2017-03-06 23:51:57 +0100492 async=False)
493
Ole Troana03f4ef2016-12-02 12:53:55 +0100494 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100495 """Detach from VPP."""
Damjan Marion5fec1e82017-04-13 19:13:47 +0200496 rv = vpp_api.vac_disconnect()
Ole Troan5016f992017-01-19 09:44:44 +0100497 self.connected = False
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:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100520 raise IOError(2, 'RPC reply message received in event handler')
Ole Troan5016f992017-01-19 09:44:44 +0100521
522 def decode_incoming_msg(self, msg):
523 if not msg:
Ole Troan3cc49712017-03-08 12:02:24 +0100524 self.logger.warning('vpp_api.read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100525 return
526
Ole Troana7564e82018-06-12 21:06:44 +0200527 i, ci = self.header.unpack(msg, 0)
Ole Troan5016f992017-01-19 09:44:44 +0100528 if self.id_names[i] == 'rx_thread_exit':
529 return
530
531 #
532 # Decode message and returns a tuple.
533 #
Ole Troana7564e82018-06-12 21:06:44 +0200534 msgobj = self.id_msgdef[i]
535 if not msgobj:
Ole Troan5016f992017-01-19 09:44:44 +0100536 raise IOError(2, 'Reply message undefined')
537
Ole Troana7564e82018-06-12 21:06:44 +0200538 r = msgobj.unpack(msg)
Ole Troan5016f992017-01-19 09:44:44 +0100539
Ole Troana03f4ef2016-12-02 12:53:55 +0100540 return r
541
Ole Troan5016f992017-01-19 09:44:44 +0100542 def msg_handler_async(self, msg):
543 """Process a message from VPP in async mode.
544
545 In async mode, all messages are returned to the callback.
546 """
547 r = self.decode_incoming_msg(msg)
548 if r is None:
549 return
550
551 msgname = type(r).__name__
552
Ole Troan4df97162017-07-07 16:06:08 +0200553 if self.event_callback:
554 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100555
556 def _control_ping(self, context):
557 """Send a ping command."""
558 self._call_vpp_async(self.control_ping_index,
Ole Troan4df97162017-07-07 16:06:08 +0200559 self.control_ping_msgdef,
Ole Troan5016f992017-01-19 09:44:44 +0100560 context=context)
561
Ole Troana7564e82018-06-12 21:06:44 +0200562 def validate_args(self, msg, kwargs):
563 d = set(kwargs.keys()) - set(msg.field_by_name.keys())
564 if d:
565 raise ValueError('Invalid argument {} to {}'
566 .format(list(d), msg.name))
567
568 def _call_vpp(self, i, msg, multipart, **kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100569 """Given a message, send the message and await a reply.
570
571 msgdef - the message packing definition
572 i - the message type index
573 multipart - True if the message returns multiple
574 messages in return.
575 context - context number - chosen at random if not
576 supplied.
577 The remainder of the kwargs are the arguments to the API call.
578
579 The return value is the message or message array containing
580 the response. It will raise an IOError exception if there was
581 no response within the timeout window.
582 """
583
Ole Troan4df97162017-07-07 16:06:08 +0200584 if 'context' not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100585 context = self.get_context()
586 kwargs['context'] = context
587 else:
588 context = kwargs['context']
589 kwargs['_vl_msg_id'] = i
Ole Troan5016f992017-01-19 09:44:44 +0100590
Ole Troana7564e82018-06-12 21:06:44 +0200591 self.validate_args(msg, kwargs)
592 b = msg.pack(kwargs)
Damjan Marion5fec1e82017-04-13 19:13:47 +0200593 vpp_api.vac_rx_suspend()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100594 self._write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100595
596 if multipart:
597 # Send a ping after the request - we use its response
598 # to detect that we have seen all results.
599 self._control_ping(context)
600
601 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100602 rl = []
603 while (True):
604 msg = self._read()
605 if not msg:
Ole Troan4df97162017-07-07 16:06:08 +0200606 raise IOError(2, 'VPP API client: read failed')
Ole Troandfc9b7c2017-03-06 23:51:57 +0100607 r = self.decode_incoming_msg(msg)
608 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200609 if context not in r or r.context == 0 or context != r.context:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100610 self.message_queue.put_nowait(r)
611 continue
612
613 if not multipart:
614 rl = r
615 break
616 if msgname == 'control_ping_reply':
617 break
618
619 rl.append(r)
620
Damjan Marion5fec1e82017-04-13 19:13:47 +0200621 vpp_api.vac_rx_resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100622
623 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100624
Ole Troana7564e82018-06-12 21:06:44 +0200625 def _call_vpp_async(self, i, msg, **kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100626 """Given a message, send the message and await a reply.
627
628 msgdef - the message packing definition
629 i - the message type index
630 context - context number - chosen at random if not
631 supplied.
632 The remainder of the kwargs are the arguments to the API call.
633 """
Ole Troan4df97162017-07-07 16:06:08 +0200634 if 'context' not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100635 context = self.get_context()
636 kwargs['context'] = context
637 else:
638 context = kwargs['context']
Ole Troana7564e82018-06-12 21:06:44 +0200639 kwargs['client_index'] = 0
Ole Troan7e3a8752016-12-05 10:27:09 +0100640 kwargs['_vl_msg_id'] = i
Ole Troana7564e82018-06-12 21:06:44 +0200641 b = msg.pack(kwargs)
Ole Troan7e3a8752016-12-05 10:27:09 +0100642
643 self._write(b)
644
Ole Troana03f4ef2016-12-02 12:53:55 +0100645 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100646 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100647
Ole Troan5016f992017-01-19 09:44:44 +0100648 This will be called for async notifications in sync mode,
649 and all messages in async mode. In sync mode, replies to
650 requests will not come here.
651
652 callback is a fn(msg_type_name, msg_type) that will be
653 called when a message comes in. While this function is
654 executing, note that (a) you are in a background thread and
655 may wish to use threading.Lock to protect your datastructures,
656 and (b) message processing from VPP will stop (so if you take
657 a long while about it you may provoke reply timeouts or cause
658 VPP to fill the RX buffer). Passing None will disable the
659 callback.
660 """
661 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100662
663 def thread_msg_handler(self):
664 """Python thread calling the user registerd message handler.
665
666 This is to emulate the old style event callback scheme. Modern
667 clients should provide their own thread to poll the event
668 queue.
669 """
670 while True:
671 r = self.message_queue.get()
Klement Sekera180402d2018-02-17 10:58:37 +0100672 if r == "terminate event thread":
673 break
Ole Troandfc9b7c2017-03-06 23:51:57 +0100674 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200675 if self.event_callback:
676 self.event_callback(msgname, r)
Chris Luke52bf22e2017-11-03 23:32:38 -0400677
678
679# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4