From 3cc4971882235a539bc6177e8e4b4d92129b3a12 Mon Sep 17 00:00:00 2001 From: Ole Troan Date: Wed, 8 Mar 2017 12:02:24 +0100 Subject: Python API: Change from cPython to CFFI. Change-Id: I03e52466fb3f909ae52b8fba601168f3eadbd972 Signed-off-by: Ole Troan --- src/vpp-api/python/vpp_papi.py | 635 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 635 insertions(+) create mode 100644 src/vpp-api/python/vpp_papi.py (limited to 'src/vpp-api/python/vpp_papi.py') diff --git a/src/vpp-api/python/vpp_papi.py b/src/vpp-api/python/vpp_papi.py new file mode 100644 index 00000000..81f6903b --- /dev/null +++ b/src/vpp-api/python/vpp_papi.py @@ -0,0 +1,635 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import print_function +import sys, os, logging, collections, struct, json, threading, glob +import atexit, Queue + +from cffi import FFI +ffi = FFI() +ffi.cdef(""" +typedef void (*pneum_callback_t)(unsigned char * data, int len); +typedef void (*pneum_error_callback_t)(void *, unsigned char *, int); +int pneum_connect(char * name, char * chroot_prefix, pneum_callback_t cb, + int rx_qlen); +int pneum_disconnect(void); +int pneum_read(char **data, int *l, unsigned short timeout); +int pneum_write(char *data, int len); +void pneum_free(void * msg); + +int pneum_get_msg_index(unsigned char * name); +int pneum_msg_table_size(void); +int pneum_msg_table_max_index(void); + +void pneum_rx_suspend (void); +void pneum_rx_resume (void); +void pneum_set_error_handler(pneum_error_callback_t); + """) + +# Barfs on failure, no need to check success. +vpp_api = ffi.dlopen('libpneum.so') + +def vpp_atexit(self): + """Clean up VPP connection on shutdown.""" + if self.connected: + self.logger.debug('Cleaning up VPP on exit') + self.disconnect() + +vpp_object = None + +@ffi.callback("void(unsigned char *, int)") +def pneum_callback_sync(data, len): + vpp_object.msg_handler_sync(ffi.buffer(data, len)) +@ffi.callback("void(unsigned char *, int)") +def pneum_callback_async(data, len): + vpp_object.msg_handler_async(ffi.buffer(data, len)) +@ffi.callback("void(void *, unsigned char *, int)") +def pneum_error_handler(arg, msg, msg_len): + vpp_object.logger.warning("PNEUM: %s", ffi.string(msg, msg_len)) + +class Empty(object): + pass + + +class FuncWrapper(object): + def __init__(self, func): + self._func = func + self.__name__ = func.__name__ + + def __call__(self, **kwargs): + return self._func(**kwargs) + + +class VPP(): + """VPP interface. + + This class provides the APIs to VPP. The APIs are loaded + from provided .api.json files and makes functions accordingly. + These functions are documented in the VPP .api files, as they + are dynamically created. + + Additionally, VPP can send callback messages; this class + provides a means to register a callback function to receive + these messages in a background thread. + """ + def __init__(self, apifiles = None, testmode = False, async_thread = True, + logger = logging.getLogger('vpp_papi'), loglevel = 'debug'): + """Create a VPP API object. + + apifiles is a list of files containing API + descriptions that will be loaded - methods will be + dynamically created reflecting these APIs. If not + provided this will load the API files from VPP's + default install location. + """ + global vpp_object + vpp_object = self + self.logger = logger + logging.basicConfig(level=getattr(logging, loglevel.upper())) + + self.messages = {} + self.id_names = [] + self.id_msgdef = [] + self.buffersize = 10000 + self.connected = False + self.header = struct.Struct('>HI') + self.apifiles = [] + self.event_callback = None + self.message_queue = Queue.Queue() + self.read_timeout = 0 + self.vpp_api = vpp_api + if async_thread: + self.event_thread = threading.Thread(target=self.thread_msg_handler) + self.event_thread.daemon = True + self.event_thread.start() + + if not apifiles: + # Pick up API definitions from default directory + apifiles = glob.glob('/usr/share/vpp/api/*.api.json') + + for file in apifiles: + with open(file) as apidef_file: + api = json.load(apidef_file) + for t in api['types']: + self.add_type(t[0], t[1:]) + + for m in api['messages']: + self.add_message(m[0], m[1:]) + self.apifiles = apifiles + + # Basic sanity check + if len(self.messages) == 0 and not testmode: + raise ValueError(1, 'Missing JSON message definitions') + + # Make sure we allow VPP to clean up the message rings. + atexit.register(vpp_atexit, self) + + # Register error handler + vpp_api.pneum_set_error_handler(pneum_error_handler) + + class ContextId(object): + """Thread-safe provider of unique context IDs.""" + def __init__(self): + self.context = 0 + self.lock = threading.Lock() + def __call__(self): + """Get a new unique (or, at least, not recently used) context.""" + with self.lock: + self.context += 1 + return self.context + get_context = ContextId() + + def status(self): + """Debug function: report current VPP API status to stdout.""" + print('Connected') if self.connected else print('Not Connected') + print('Read API definitions from', ', '.join(self.apifiles)) + + def __struct (self, t, n = None, e = -1, vl = None): + """Create a packing structure for a message.""" + base_types = { 'u8' : 'B', + 'u16' : 'H', + 'u32' : 'I', + 'i32' : 'i', + 'u64' : 'Q', + 'f64' : 'd', + } + pack = None + if t in base_types: + pack = base_types[t] + if not vl: + if e > 0 and t == 'u8': + # Fixed byte array + return struct.Struct('>' + str(e) + 's') + if e > 0: + # Fixed array of base type + return [e, struct.Struct('>' + base_types[t])] + elif e == 0: + # Old style variable array + return [-1, struct.Struct('>' + base_types[t])] + else: + # Variable length array + return [vl, struct.Struct('>s')] if t == 'u8' else \ + [vl, struct.Struct('>' + base_types[t])] + + return struct.Struct('>' + base_types[t]) + + if t in self.messages: + ### Return a list in case of array ### + if e > 0 and not vl: + return [e, lambda self, encode, buf, offset, args: ( + self.__struct_type(encode, self.messages[t], buf, offset, + args))] + if vl: + return [vl, lambda self, encode, buf, offset, args: ( + self.__struct_type(encode, self.messages[t], buf, offset, + args))] + elif e == 0: + # Old style VLA + raise NotImplementedError(1, 'No support for compound types ' + t) + return lambda self, encode, buf, offset, args: ( + self.__struct_type(encode, self.messages[t], buf, offset, args) + ) + + raise ValueError(1, 'Invalid message type: ' + t) + + def __struct_type(self, encode, msgdef, buf, offset, kwargs): + """Get a message packer or unpacker.""" + if encode: + return self.__struct_type_encode(msgdef, buf, offset, kwargs) + else: + return self.__struct_type_decode(msgdef, buf, offset) + + def __struct_type_encode(self, msgdef, buf, offset, kwargs): + off = offset + size = 0 + + for k in kwargs: + if k not in msgdef['args']: + raise ValueError(1, 'Invalid field-name in message call ' + k) + + for k,v in msgdef['args'].iteritems(): + off += size + if k in kwargs: + if type(v) is list: + if callable(v[1]): + e = kwargs[v[0]] if v[0] in kwargs else v[0] + size = 0 + for i in range(e): + size += v[1](self, True, buf, off + size, + kwargs[k][i]) + else: + if v[0] in kwargs: + l = kwargs[v[0]] + else: + l = len(kwargs[k]) + if v[1].size == 1: + buf[off:off + l] = bytearray(kwargs[k]) + size = l + else: + size = 0 + for i in kwargs[k]: + v[1].pack_into(buf, off + size, i) + size += v[1].size + else: + if callable(v): + size = v(self, True, buf, off, kwargs[k]) + else: + v.pack_into(buf, off, kwargs[k]) + size = v.size + else: + size = v.size if not type(v) is list else 0 + + return off + size - offset + + + def __getitem__(self, name): + if name in self.messages: + return self.messages[name] + return None + + def encode(self, msgdef, kwargs): + # Make suitably large buffer + buf = bytearray(self.buffersize) + offset = 0 + size = self.__struct_type(True, msgdef, buf, offset, kwargs) + return buf[:offset + size] + + def decode(self, msgdef, buf): + return self.__struct_type(False, msgdef, buf, 0, None)[1] + + def __struct_type_decode(self, msgdef, buf, offset): + res = [] + off = offset + size = 0 + for k,v in msgdef['args'].iteritems(): + off += size + if type(v) is list: + lst = [] + if callable(v[1]): # compound type + size = 0 + if v[0] in msgdef['args']: # vla + e = res[v[2]] + else: # fixed array + e = v[0] + res.append(lst) + for i in range(e): + (s,l) = v[1](self, False, buf, off + size, None) + lst.append(l) + size += s + continue + if v[1].size == 1: + if type(v[0]) is int: + size = len(buf) - off + else: + size = res[v[2]] + res.append(buf[off:off + size]) + else: + e = v[0] if type(v[0]) is int else res[v[2]] + if e == -1: + e = (len(buf) - off) / v[1].size + lst = [] + res.append(lst) + size = 0 + for i in range(e): + lst.append(v[1].unpack_from(buf, off + size)[0]) + size += v[1].size + else: + if callable(v): + (s,l) = v(self, False, buf, off, None) + res.append(l) + size += s + else: + res.append(v.unpack_from(buf, off)[0]) + size = v.size + + return off + size - offset, msgdef['return_tuple']._make(res) + + def ret_tup(self, name): + if name in self.messages and 'return_tuple' in self.messages[name]: + return self.messages[name]['return_tuple'] + return None + + def add_message(self, name, msgdef, typeonly = False): + if name in self.messages: + raise ValueError('Duplicate message name: ' + name) + + args = collections.OrderedDict() + argtypes = collections.OrderedDict() + fields = [] + msg = {} + for i, f in enumerate(msgdef): + if type(f) is dict and 'crc' in f: + msg['crc'] = f['crc'] + continue + field_type = f[0] + field_name = f[1] + if len(f) == 3 and f[2] == 0 and i != len(msgdef) - 2: + raise ValueError('Variable Length Array must be last: ' + name) + args[field_name] = self.__struct(*f) + argtypes[field_name] = field_type + if len(f) == 4: # Find offset to # elements field + args[field_name].append(args.keys().index(f[3]) - i) + fields.append(field_name) + msg['return_tuple'] = collections.namedtuple(name, fields, + rename = True) + self.messages[name] = msg + self.messages[name]['args'] = args + self.messages[name]['argtypes'] = argtypes + self.messages[name]['typeonly'] = typeonly + return self.messages[name] + + def add_type(self, name, typedef): + return self.add_message('vl_api_' + name + '_t', typedef, typeonly=True) + + def make_function(self, name, i, msgdef, multipart, async): + if (async): + f = lambda **kwargs: (self._call_vpp_async(i, msgdef, **kwargs)) + else: + f = lambda **kwargs: (self._call_vpp(i, msgdef, multipart, **kwargs)) + args = self.messages[name]['args'] + argtypes = self.messages[name]['argtypes'] + f.__name__ = str(name) + f.__doc__ = ", ".join(["%s %s" % (argtypes[k], k) for k in args.keys()]) + return f + + @property + def api(self): + if not hasattr(self, "_api"): + raise Exception("Not connected, api definitions not available") + return self._api + + def _register_functions(self, async=False): + self.id_names = [None] * (self.vpp_dictionary_maxid + 1) + self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1) + self._api = Empty() + for name, msgdef in self.messages.iteritems(): + if self.messages[name]['typeonly']: continue + crc = self.messages[name]['crc'] + n = name + '_' + crc[2:] + i = vpp_api.pneum_get_msg_index(bytes(n)) + if i > 0: + self.id_msgdef[i] = msgdef + self.id_names[i] = name + multipart = True if name.find('_dump') > 0 else False + f = self.make_function(name, i, msgdef, multipart, async) + setattr(self._api, name, FuncWrapper(f)) + + # old API stuff starts here - will be removed in 17.07 + if hasattr(self, name): + raise NameError( + 3, "Conflicting name in JSON definition: `%s'" % name) + setattr(self, name, f) + # old API stuff ends here + else: + self.logger.debug('No such message type or failed CRC checksum: %s', n) + + def _write (self, buf): + """Send a binary-packed message to VPP.""" + if not self.connected: + raise IOError(1, 'Not connected') + return vpp_api.pneum_write(str(buf), len(buf)) + + def _read (self): + if not self.connected: + raise IOError(1, 'Not connected') + mem = ffi.new("char **") + size = ffi.new("int *") + rv = vpp_api.pneum_read(mem, size, self.read_timeout) + if rv: + raise IOError(rv, 'pneum_read filed') + msg = bytes(ffi.buffer(mem[0], size[0])) + vpp_api.pneum_free(mem[0]) + return msg + + def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, async): + rv = vpp_api.pneum_connect(name, chroot_prefix, msg_handler, rx_qlen) + if rv != 0: + raise IOError(2, 'Connect failed') + self.connected = True + + self.vpp_dictionary_maxid = vpp_api.pneum_msg_table_max_index() + self._register_functions(async=async) + + # Initialise control ping + crc = self.messages['control_ping']['crc'] + self.control_ping_index = \ + vpp_api.pneum_get_msg_index( + bytes('control_ping' + '_' + crc[2:])) + self.control_ping_msgdef = self.messages['control_ping'] + + def connect(self, name, chroot_prefix = ffi.NULL, + async = False, rx_qlen = 32): + """Attach to VPP. + + name - the name of the client. + chroot_prefix - if VPP is chroot'ed, the prefix of the jail + async - if true, messages are sent without waiting for a reply + rx_qlen - the length of the VPP message receive queue between + client and server. + """ + msg_handler = pneum_callback_sync if not async \ + else pneum_callback_async + return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen, + async) + + def connect_sync (self, name, chroot_prefix = ffi.NULL, rx_qlen = 32): + """Attach to VPP in synchronous mode. Application must poll for events. + + name - the name of the client. + chroot_prefix - if VPP is chroot'ed, the prefix of the jail + rx_qlen - the length of the VPP message receive queue between + client and server. + """ + + return self.connect_internal(name, ffi.NULL, chroot_prefix, rx_qlen, + async=False) + + def disconnect(self): + """Detach from VPP.""" + rv = vpp_api.pneum_disconnect() + self.connected = False + return rv + + def msg_handler_sync(self, msg): + """Process an incoming message from VPP in sync mode. + + The message may be a reply or it may be an async notification. + """ + r = self.decode_incoming_msg(msg) + if r is None: + return + + # If we have a context, then use the context to find any + # request waiting for a reply + context = 0 + if hasattr(r, 'context') and r.context > 0: + context = r.context + + msgname = type(r).__name__ + + if context == 0: + # No context -> async notification that we feed to the callback + self.message_queue.put_nowait(r) + else: + raise IOError(2, 'RPC reply message received in event handler') + + def decode_incoming_msg(self, msg): + if not msg: + self.logger.warning('vpp_api.read failed') + return + + i, ci = self.header.unpack_from(msg, 0) + if self.id_names[i] == 'rx_thread_exit': + return + + # + # Decode message and returns a tuple. + # + msgdef = self.id_msgdef[i] + if not msgdef: + raise IOError(2, 'Reply message undefined') + + r = self.decode(msgdef, msg) + + return r + + def msg_handler_async(self, msg): + """Process a message from VPP in async mode. + + In async mode, all messages are returned to the callback. + """ + r = self.decode_incoming_msg(msg) + if r is None: + return + + msgname = type(r).__name__ + + if self.event_callback: + self.event_callback(msgname, r) + + def _control_ping(self, context): + """Send a ping command.""" + self._call_vpp_async(self.control_ping_index, + self.control_ping_msgdef, + context=context) + + def _call_vpp(self, i, msgdef, multipart, **kwargs): + """Given a message, send the message and await a reply. + + msgdef - the message packing definition + i - the message type index + multipart - True if the message returns multiple + messages in return. + context - context number - chosen at random if not + supplied. + The remainder of the kwargs are the arguments to the API call. + + The return value is the message or message array containing + the response. It will raise an IOError exception if there was + no response within the timeout window. + """ + + if not 'context' in kwargs: + context = self.get_context() + kwargs['context'] = context + else: + context = kwargs['context'] + kwargs['_vl_msg_id'] = i + b = self.encode(msgdef, kwargs) + + vpp_api.pneum_rx_suspend() + self._write(b) + + if multipart: + # Send a ping after the request - we use its response + # to detect that we have seen all results. + self._control_ping(context) + + # Block until we get a reply. + rl = [] + while (True): + msg = self._read() + if not msg: + print('PNEUM ERROR: OH MY GOD') + raise IOError(2, 'PNEUM read failed') + + r = self.decode_incoming_msg(msg) + msgname = type(r).__name__ + if not context in r or r.context == 0 or context != r.context: + self.message_queue.put_nowait(r) + continue + + if not multipart: + rl = r + break + if msgname == 'control_ping_reply': + break + + rl.append(r) + + vpp_api.pneum_rx_resume() + + return rl + + def _call_vpp_async(self, i, msgdef, **kwargs): + """Given a message, send the message and await a reply. + + msgdef - the message packing definition + i - the message type index + context - context number - chosen at random if not + supplied. + The remainder of the kwargs are the arguments to the API call. + """ + if not 'context' in kwargs: + context = self.get_context() + kwargs['context'] = context + else: + context = kwargs['context'] + kwargs['_vl_msg_id'] = i + b = self.encode(msgdef, kwargs) + + self._write(b) + + def register_event_callback(self, callback): + """Register a callback for async messages. + + This will be called for async notifications in sync mode, + and all messages in async mode. In sync mode, replies to + requests will not come here. + + callback is a fn(msg_type_name, msg_type) that will be + called when a message comes in. While this function is + executing, note that (a) you are in a background thread and + may wish to use threading.Lock to protect your datastructures, + and (b) message processing from VPP will stop (so if you take + a long while about it you may provoke reply timeouts or cause + VPP to fill the RX buffer). Passing None will disable the + callback. + """ + self.event_callback = callback + + def thread_msg_handler(self): + """Python thread calling the user registerd message handler. + + This is to emulate the old style event callback scheme. Modern + clients should provide their own thread to poll the event + queue. + """ + while True: + r = self.message_queue.get() + msgname = type(r).__name__ + if self.event_callback: + self.event_callback(msgname, r) -- cgit 1.2.3-korg From 5fec1e8b2282f4d3d1d02556020254a84c3b6e3d Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Thu, 13 Apr 2017 19:13:47 +0200 Subject: vpp-api: rename libpneum to libvppapiclient Change-Id: Ie6d2c769b316b43c40632aa9009c4ff6442cf658 Signed-off-by: Damjan Marion --- src/vpp-api.am | 33 ++- src/vpp-api/client/client.c | 489 +++++++++++++++++++++++++++++++++ src/vpp-api/client/libvppapiclient.map | 19 ++ src/vpp-api/client/test.c | 140 ++++++++++ src/vpp-api/client/vppapiclient.h | 36 +++ src/vpp-api/lua/vpp-lapi.lua | 34 +-- src/vpp-api/pneum/pneum.c | 489 --------------------------------- src/vpp-api/pneum/pneum.h | 37 --- src/vpp-api/pneum/test_pneum.c | 143 ---------- src/vpp-api/python/vpp_papi.py | 66 ++--- 10 files changed, 752 insertions(+), 734 deletions(-) create mode 100644 src/vpp-api/client/client.c create mode 100644 src/vpp-api/client/libvppapiclient.map create mode 100644 src/vpp-api/client/test.c create mode 100644 src/vpp-api/client/vppapiclient.h delete mode 100644 src/vpp-api/pneum/pneum.c delete mode 100644 src/vpp-api/pneum/pneum.h delete mode 100644 src/vpp-api/pneum/test_pneum.c (limited to 'src/vpp-api/python/vpp_papi.py') diff --git a/src/vpp-api.am b/src/vpp-api.am index 49e26da1..553eafa8 100644 --- a/src/vpp-api.am +++ b/src/vpp-api.am @@ -14,29 +14,32 @@ # # VPP API C wrapper extension # -lib_LTLIBRARIES += libpneum.la -libpneum_la_SOURCES = vpp-api/pneum/pneum.c -libpneum_la_LIBADD = \ - $(top_builddir)/libvppinfra.la \ - $(top_builddir)/libvlibmemoryclient.la \ - $(top_builddir)/libsvm.la \ +lib_LTLIBRARIES += libvppapiclient.la +libvppapiclient_la_SOURCES = \ + vpp-api/client/client.c \ + vpp-api/client/libvppapiclient.map + +libvppapiclient_la_LIBADD = \ -lpthread -lm -lrt -libpneum_la_LDFLAGS = -module -libpneum_la_CPPFLAGS = +libvppapiclient_la_LDFLAGS = \ + -Wl,-L$(top_builddir)/.libs,--whole-archive,-l:libsvm.a,-l:libvppinfra.a,-l:libvlibmemoryclient.a,--no-whole-archive \ + -Wl,--version-script=$(srcdir)/vpp-api/client/libvppapiclient.map,-lrt + +libvppapiclient_la_DEPENDENCIES = libvppinfra.la libvlibmemoryclient.la libsvm.la + +libvppapiclient_la_CPPFLAGS = -nobase_include_HEADERS += vpp-api/pneum/pneum.h +nobase_include_HEADERS += vpp-api/client/vppapiclient.h # # Test client # if ENABLE_TESTS -noinst_PROGRAMS += test_pneum -test_pneum_SOURCES = vpp-api/pneum/pneum.c vpp-api/pneum/test_pneum.c -test_pneum_LDADD = \ - $(top_builddir)/libvppinfra.la \ - $(top_builddir)/libvlibmemoryclient.la \ - $(top_builddir)/libsvm.la \ +noinst_PROGRAMS += vac_test +vac_test_SOURCES = vpp-api/client/test.c +vac_test_LDADD = \ + $(builddir)/libvppapiclient.la \ -lpthread -lm -lrt endif diff --git a/src/vpp-api/client/client.c b/src/vpp-api/client/client.c new file mode 100644 index 00000000..8bdcda01 --- /dev/null +++ b/src/vpp-api/client/client.c @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "vppapiclient.h" + +/* + * Asynchronous mode: + * Client registers a callback. All messages are sent to the callback. + * Synchronous mode: + * Client calls blocking read(). + * Clients are expected to collate events on a queue. + * vac_write() -> suspends RX thread + * vac_read() -> resumes RX thread + */ + +#define vl_typedefs /* define message structures */ +#include +#undef vl_typedefs + +#define vl_endianfun /* define message structures */ +#include +#undef vl_endianfun + +vlib_main_t vlib_global_main; +vlib_main_t **vlib_mains; + +typedef struct { + u8 connected_to_vlib; + pthread_t rx_thread_handle; + pthread_t timeout_thread_handle; + pthread_mutex_t queue_lock; + pthread_cond_t suspend_cv; + pthread_cond_t resume_cv; + pthread_mutex_t timeout_lock; + pthread_cond_t timeout_cv; + pthread_cond_t timeout_cancel_cv; + pthread_cond_t terminate_cv; +} vac_main_t; + +vac_main_t vac_main; +vac_callback_t vac_callback; +u16 read_timeout = 0; +bool rx_is_running = false; + +static void +init (void) +{ + vac_main_t *pm = &vac_main; + memset(pm, 0, sizeof(*pm)); + pthread_mutex_init(&pm->queue_lock, NULL); + pthread_cond_init(&pm->suspend_cv, NULL); + pthread_cond_init(&pm->resume_cv, NULL); + pthread_mutex_init(&pm->timeout_lock, NULL); + pthread_cond_init(&pm->timeout_cv, NULL); + pthread_cond_init(&pm->timeout_cancel_cv, NULL); + pthread_cond_init(&pm->terminate_cv, NULL); +} + +static void +cleanup (void) +{ + vac_main_t *pm = &vac_main; + pthread_cond_destroy(&pm->suspend_cv); + pthread_cond_destroy(&pm->resume_cv); + pthread_cond_destroy(&pm->timeout_cv); + pthread_cond_destroy(&pm->timeout_cancel_cv); + pthread_cond_destroy(&pm->terminate_cv); + pthread_mutex_destroy(&pm->queue_lock); + pthread_mutex_destroy(&pm->timeout_lock); + memset (pm, 0, sizeof (*pm)); +} + +/* + * Satisfy external references when -lvlib is not available. + */ +void vlib_cli_output (struct vlib_main_t * vm, char * fmt, ...) +{ + clib_warning ("vlib_cli_output called..."); +} + +void +vac_free (void * msg) +{ + vl_msg_api_free (msg); +} + +static void +vac_api_handler (void *msg) +{ + u16 id = ntohs(*((u16 *)msg)); + msgbuf_t *msgbuf = (msgbuf_t *)(((u8 *)msg) - offsetof(msgbuf_t, data)); + int l = ntohl(msgbuf->data_len); + if (l == 0) + clib_warning("Message ID %d has wrong length: %d\n", id, l); + + /* Call Python callback */ + ASSERT(vac_callback); + (vac_callback)(msg, l); + vac_free(msg); +} + +static void * +vac_rx_thread_fn (void *arg) +{ + unix_shared_memory_queue_t *q; + vac_main_t *pm = &vac_main; + api_main_t *am = &api_main; + uword msg; + + q = am->vl_input_queue; + + while (1) + while (!unix_shared_memory_queue_sub(q, (u8 *)&msg, 0)) + { + u16 id = ntohs(*((u16 *)msg)); + switch (id) { + case VL_API_RX_THREAD_EXIT: + vl_msg_api_free((void *) msg); + /* signal waiting threads that this thread is about to terminate */ + pthread_mutex_lock(&pm->queue_lock); + pthread_cond_signal(&pm->terminate_cv); + pthread_mutex_unlock(&pm->queue_lock); + pthread_exit(0); + return 0; + break; + + case VL_API_MEMCLNT_RX_THREAD_SUSPEND: + vl_msg_api_free((void * )msg); + /* Suspend thread and signal reader */ + pthread_mutex_lock(&pm->queue_lock); + pthread_cond_signal(&pm->suspend_cv); + /* Wait for the resume signal */ + pthread_cond_wait (&pm->resume_cv, &pm->queue_lock); + pthread_mutex_unlock(&pm->queue_lock); + break; + + case VL_API_MEMCLNT_READ_TIMEOUT: + clib_warning("Received read timeout in async thread\n"); + vl_msg_api_free((void *) msg); + break; + + default: + vac_api_handler((void *)msg); + } + } +} + +static void * +vac_timeout_thread_fn (void *arg) +{ + vl_api_memclnt_read_timeout_t *ep; + vac_main_t *pm = &vac_main; + api_main_t *am = &api_main; + struct timespec ts; + struct timeval tv; + u16 timeout; + int rv; + + while (1) + { + /* Wait for poke */ + pthread_mutex_lock(&pm->timeout_lock); + pthread_cond_wait (&pm->timeout_cv, &pm->timeout_lock); + timeout = read_timeout; + gettimeofday(&tv, NULL); + ts.tv_sec = tv.tv_sec + timeout; + ts.tv_nsec = 0; + rv = pthread_cond_timedwait (&pm->timeout_cancel_cv, + &pm->timeout_lock, &ts); + pthread_mutex_unlock(&pm->timeout_lock); + if (rv == ETIMEDOUT) + { + ep = vl_msg_api_alloc (sizeof (*ep)); + ep->_vl_msg_id = ntohs(VL_API_MEMCLNT_READ_TIMEOUT); + vl_msg_api_send_shmem(am->vl_input_queue, (u8 *)&ep); + } + } + pthread_exit(0); +} + +void +vac_rx_suspend (void) +{ + api_main_t *am = &api_main; + vac_main_t *pm = &vac_main; + vl_api_memclnt_rx_thread_suspend_t *ep; + + if (!pm->rx_thread_handle) return; + pthread_mutex_lock(&pm->queue_lock); + if (rx_is_running) + { + ep = vl_msg_api_alloc (sizeof (*ep)); + ep->_vl_msg_id = ntohs(VL_API_MEMCLNT_RX_THREAD_SUSPEND); + vl_msg_api_send_shmem(am->vl_input_queue, (u8 *)&ep); + /* Wait for RX thread to tell us it has suspendend */ + pthread_cond_wait(&pm->suspend_cv, &pm->queue_lock); + rx_is_running = false; + } + pthread_mutex_unlock(&pm->queue_lock); +} + +void +vac_rx_resume (void) +{ + vac_main_t *pm = &vac_main; + if (!pm->rx_thread_handle) return; + pthread_mutex_lock(&pm->queue_lock); + if (rx_is_running) goto unlock; + pthread_cond_signal(&pm->resume_cv); + rx_is_running = true; + unlock: + pthread_mutex_unlock(&pm->queue_lock); +} + +static uword * +vac_msg_table_get_hash (void) +{ + api_main_t *am = &api_main; + return (am->msg_index_by_name_and_crc); +} + +int +vac_msg_table_size(void) +{ + api_main_t *am = &api_main; + return hash_elts(am->msg_index_by_name_and_crc); +} + +int +vac_connect (char * name, char * chroot_prefix, vac_callback_t cb, + int rx_qlen) +{ + int rv = 0; + vac_main_t *pm = &vac_main; + + init(); + if (chroot_prefix != NULL) + vl_set_memory_root_path (chroot_prefix); + + if ((rv = vl_client_api_map("/vpe-api"))) { + clib_warning ("vl_client_api map rv %d", rv); + return rv; + } + + if (vl_client_connect(name, 0, rx_qlen) < 0) { + vl_client_api_unmap(); + return (-1); + } + + if (cb) { + /* Start the rx queue thread */ + rv = pthread_create(&pm->rx_thread_handle, NULL, vac_rx_thread_fn, 0); + if (rv) { + clib_warning("pthread_create returned %d", rv); + vl_client_api_unmap(); + return (-1); + } + vac_callback = cb; + rx_is_running = true; + } + + /* Start read timeout thread */ + rv = pthread_create(&pm->timeout_thread_handle, NULL, + vac_timeout_thread_fn, 0); + if (rv) { + clib_warning("pthread_create returned %d", rv); + vl_client_api_unmap(); + return (-1); + } + + pm->connected_to_vlib = 1; + + return (0); +} + +int +vac_disconnect (void) +{ + api_main_t *am = &api_main; + vac_main_t *pm = &vac_main; + + if (!pm->connected_to_vlib) return 0; + + if (pm->rx_thread_handle) { + vl_api_rx_thread_exit_t *ep; + uword junk; + ep = vl_msg_api_alloc (sizeof (*ep)); + ep->_vl_msg_id = ntohs(VL_API_RX_THREAD_EXIT); + vl_msg_api_send_shmem(am->vl_input_queue, (u8 *)&ep); + + /* wait (with timeout) until RX thread has finished */ + struct timespec ts; + struct timeval tv; + gettimeofday(&tv, NULL); + ts.tv_sec = tv.tv_sec + 5; + ts.tv_nsec = 0; + pthread_mutex_lock(&pm->queue_lock); + int rv = pthread_cond_timedwait(&pm->terminate_cv, &pm->queue_lock, &ts); + pthread_mutex_unlock(&pm->queue_lock); + /* now join so we wait until thread has -really- finished */ + if (rv == ETIMEDOUT) + pthread_cancel(pm->rx_thread_handle); + else + pthread_join(pm->rx_thread_handle, (void **) &junk); + } + if (pm->timeout_thread_handle) + pthread_cancel(pm->timeout_thread_handle); + + vl_client_disconnect(); + vl_client_api_unmap(); + vac_callback = 0; + + cleanup(); + + return (0); +} + +static void +set_timeout (unsigned short timeout) +{ + vac_main_t *pm = &vac_main; + pthread_mutex_lock(&pm->timeout_lock); + read_timeout = timeout; + pthread_cond_signal(&pm->timeout_cv); + pthread_mutex_unlock(&pm->timeout_lock); +} + +static void +unset_timeout (void) +{ + vac_main_t *pm = &vac_main; + pthread_mutex_lock(&pm->timeout_lock); + pthread_cond_signal(&pm->timeout_cancel_cv); + pthread_mutex_unlock(&pm->timeout_lock); +} + +int +vac_read (char **p, int *l, u16 timeout) +{ + unix_shared_memory_queue_t *q; + api_main_t *am = &api_main; + vac_main_t *pm = &vac_main; + uword msg; + msgbuf_t *msgbuf; + + if (!pm->connected_to_vlib) return -1; + + *l = 0; + + if (am->our_pid == 0) return (-1); + + /* Poke timeout thread */ + if (timeout) + set_timeout(timeout); + + q = am->vl_input_queue; + int rv = unix_shared_memory_queue_sub(q, (u8 *)&msg, 0); + if (rv == 0) { + u16 msg_id = ntohs(*((u16 *)msg)); + switch (msg_id) { + case VL_API_RX_THREAD_EXIT: + printf("Received thread exit\n"); + return -1; + case VL_API_MEMCLNT_RX_THREAD_SUSPEND: + printf("Received thread suspend\n"); + goto error; + case VL_API_MEMCLNT_READ_TIMEOUT: + printf("Received read timeout %ds\n", timeout); + goto error; + + default: + msgbuf = (msgbuf_t *)(((u8 *)msg) - offsetof(msgbuf_t, data)); + *l = ntohl(msgbuf->data_len); + if (*l == 0) { + printf("Unregistered API message: %d\n", msg_id); + goto error; + } + } + *p = (char *)msg; + + /* Let timeout notification thread know we're done */ + unset_timeout(); + + } else { + printf("Read failed with %d\n", rv); + } + return (rv); + + error: + vl_msg_api_free((void *) msg); + /* Client might forget to resume RX thread on failure */ + vac_rx_resume (); + return -1; +} + +/* + * XXX: Makes the assumption that client_index is the first member + */ +typedef VL_API_PACKED(struct _vl_api_header { + u16 _vl_msg_id; + u32 client_index; +}) vl_api_header_t; + +static unsigned int +vac_client_index (void) +{ + return (api_main.my_client_index); +} + +int +vac_write (char *p, int l) +{ + int rv = -1; + api_main_t *am = &api_main; + vl_api_header_t *mp = vl_msg_api_alloc(l); + unix_shared_memory_queue_t *q; + vac_main_t *pm = &vac_main; + + if (!pm->connected_to_vlib) return -1; + if (!mp) return (-1); + + memcpy(mp, p, l); + mp->client_index = vac_client_index(); + q = am->shmem_hdr->vl_input_queue; + rv = unix_shared_memory_queue_add(q, (u8 *)&mp, 0); + if (rv != 0) { + clib_warning("vpe_api_write fails: %d\n", rv); + /* Clear message */ + vac_free(mp); + } + return (rv); +} + +int +vac_get_msg_index (unsigned char * name) +{ + return vl_api_get_msg_index (name); +} + +int +vac_msg_table_max_index(void) +{ + int max = 0; + hash_pair_t *hp; + uword *h = vac_msg_table_get_hash(); + hash_foreach_pair (hp, h, + ({ + if (hp->value[0] > max) + max = hp->value[0]; + })); + + return max; +} + +void +vac_set_error_handler (vac_error_callback_t cb) +{ + if (cb) clib_error_register_handler (cb, 0); +} diff --git a/src/vpp-api/client/libvppapiclient.map b/src/vpp-api/client/libvppapiclient.map new file mode 100644 index 00000000..a9d8f7dd --- /dev/null +++ b/src/vpp-api/client/libvppapiclient.map @@ -0,0 +1,19 @@ + +VPPAPICLIENT_17.07 { + global: + vac_read; + vac_write; + vac_connect; + vac_disconnect; + vac_set_error_handler; + vac_msg_table_max_index; + vac_get_msg_index; + vac_rx_suspend; + vac_rx_resume; + vac_free; + vac_msg_table_size; + + api_main; + + local: *; +}; diff --git a/src/vpp-api/client/test.c b/src/vpp-api/client/test.c new file mode 100644 index 00000000..020115d9 --- /dev/null +++ b/src/vpp-api/client/test.c @@ -0,0 +1,140 @@ +/* + *------------------------------------------------------------------ + * test.c + * + * Copyright (c) 2016 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* time_t, time (for timestamp in second) */ +#include /* ftime, timeb (for timestamp in millisecond) */ +#include /* gettimeofday, timeval (for timestamp in microsecond) */ + +#include +#include +#include +#include + +#include +#include +#include "vppapiclient.h" + +#define vl_typedefs /* define message structures */ +#include +#undef vl_typedefs + +/* we are not linking with vlib */ +vlib_main_t vlib_global_main; +vlib_main_t **vlib_mains; + +volatile int sigterm_received = 0; +volatile u32 result_ready; +volatile u16 result_msg_id; + +/* M_NOALLOC: construct, but don't yet send a message */ + +#define M_NOALLOC(T,t) \ + do { \ + result_ready = 0; \ + memset (mp, 0, sizeof (*mp)); \ + mp->_vl_msg_id = ntohs (VL_API_##T); \ + mp->client_index = am->my_client_index; \ + } while(0); + + + +int +wrap_vac_callback (char *data, int len) +{ + //printf("Callback %d\n", len); + result_ready = 1; + result_msg_id = ntohs(*((u16 *)data)); + return (0); +} + +int main (int argc, char ** argv) +{ + api_main_t * am = &api_main; + vl_api_show_version_t message; + vl_api_show_version_t *mp; + int async = 1; + int rv = vac_connect("vac_client", NULL, NULL, 32 /* rx queue-length*/); + + if (rv != 0) { + printf("Connect failed: %d\n", rv); + exit(rv); + } + + struct timeb timer_msec; + long long int timestamp_msec_start; /* timestamp in millisecond. */ + if (!ftime(&timer_msec)) { + timestamp_msec_start = ((long long int) timer_msec.time) * 1000ll + + (long long int) timer_msec.millitm; + } + else { + timestamp_msec_start = -1; + } + + + /* + * Test vpe_api_write and vpe_api_read to send and recv message for an + * API + */ + int i; + long int no_msgs = 10000; + mp = &message; + + for (i = 0; i < no_msgs; i++) { + /* Construct the API message */ + M_NOALLOC(SHOW_VERSION, show_version); + vac_write((char *)mp, sizeof(*mp)); +#ifndef __COVERITY__ + /* As given, async is always 1. Shut up Coverity about it */ + if (!async) + while (result_ready == 0); +#endif + } + if (async) { + vl_api_control_ping_t control; + vl_api_control_ping_t *mp; + mp = &control; + M_NOALLOC(CONTROL_PING, control_ping); + vac_write((char *)mp, sizeof(*mp)); + + while (result_msg_id != VL_API_CONTROL_PING_REPLY); + } + + long long int timestamp_msec_end; /* timestamp in millisecond. */ + if (!ftime(&timer_msec)) { + timestamp_msec_end = ((long long int) timer_msec.time) * 1000ll + + (long long int) timer_msec.millitm; + } + else { + timestamp_msec_end = -1; + } + + printf("Took %lld msec, %lld msgs/msec \n", (timestamp_msec_end - timestamp_msec_start), + no_msgs/(timestamp_msec_end - timestamp_msec_start)); + printf("Exiting...\n"); + vac_disconnect(); + exit (0); +} diff --git a/src/vpp-api/client/vppapiclient.h b/src/vpp-api/client/vppapiclient.h new file mode 100644 index 00000000..839ec1f8 --- /dev/null +++ b/src/vpp-api/client/vppapiclient.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef included_vppapiclient_h +#define included_vppapiclient_h + +#include + +typedef void (*vac_callback_t)(unsigned char * data, int len); +typedef void (*vac_error_callback_t)(void *, unsigned char *, int); +int vac_connect(char * name, char * chroot_prefix, vac_callback_t cb, + int rx_qlen); +int vac_disconnect(void); +int vac_read(char **data, int *l, unsigned short timeout); +int vac_write(char *data, int len); +void vac_free(void * msg); + +int vac_get_msg_index(unsigned char * name); +int vac_msg_table_size(void); +int vac_msg_table_max_index(void); + +void vac_rx_suspend (void); +void vac_rx_resume (void); +void vac_set_error_handler(vac_error_callback_t); +#endif diff --git a/src/vpp-api/lua/vpp-lapi.lua b/src/vpp-api/lua/vpp-lapi.lua index ebfd032b..587eb110 100644 --- a/src/vpp-api/lua/vpp-lapi.lua +++ b/src/vpp-api/lua/vpp-lapi.lua @@ -420,20 +420,20 @@ end function vpp.init(vpp, args) - local pneum_api = args.pneum_api or [[ - int cough_pneum_attach(char *pneum_path, char *cough_path); - int pneum_connect(char *name, char *chroot_prefix, void *cb); - int pneum_disconnect(void); - int pneum_read(char **data, int *l); - int pneum_write(char *data, int len); - void pneum_free(char *data); - uint32_t pneum_get_msg_index(unsigned char * name); + local vac_api = args.vac_api or [[ + int cough_vac_attach(char *vac_path, char *cough_path); + int vac_connect(char *name, char *chroot_prefix, void *cb); + int vac_disconnect(void); + int vac_read(char **data, int *l); + int vac_write(char *data, int len); + void vac_free(char *data); + uint32_t vac_get_msg_index(unsigned char * name); ]] - vpp.pneum_path = args.pneum_path - ffi.cdef(pneum_api) + vpp.vac_path = args.vac_path + ffi.cdef(vac_api) local init_res = 0 - vpp.pneum = ffi.load(vpp.pneum_path) + vpp.vac = ffi.load(vpp.vac_path) if (init_res < 0) then return nil end @@ -676,7 +676,7 @@ end function vpp.resolve_message_number(msgname) local name = msgname .. "_" .. vpp.msg_name_to_crc[msgname] - local idx = vpp.pneum.pneum_get_msg_index(vpp.c_str(name)) + local idx = vpp.vac.vac_get_msg_index(vpp.c_str(name)) if vpp.debug_dump then print("Index for " .. tostring(name) .. " is " .. tostring(idx)) end @@ -692,7 +692,7 @@ function vpp.connect(vpp, client_name) if client_name then name = client_name end - local ret = vpp.pneum.pneum_connect(vpp.c_str(client_name), nil, nil) + local ret = vpp.vac.vac_connect(vpp.c_str(client_name), nil, nil) if tonumber(ret) == 0 then vpp.is_connected = true end @@ -702,7 +702,7 @@ function vpp.connect(vpp, client_name) end function vpp.disconnect(vpp) - vpp.pneum.pneum_disconnect() + vpp.vac.vac_disconnect() end function vpp.json_api(vpp, path, plugin_name) @@ -921,7 +921,7 @@ function vpp.api_write(vpp, api_name, req_table) print("Write Message length: " .. tostring(packed_len) .. "\n" .. vpp.hex_dump(ffi.string(ffi.cast('void *', req_store_cache), packed_len))) end - res = vpp.pneum.pneum_write(ffi.cast('void *', req_store_cache), packed_len) + res = vpp.vac.vac_write(ffi.cast('void *', req_store_cache), packed_len) return res end @@ -932,7 +932,7 @@ function vpp.api_read(vpp) local rep_type = "vl_api_opaque_message_t" local rep = rep_store_cache local replen = rep_len_cache - res = vpp.pneum.pneum_read(ffi.cast("void *", rep), replen) + res = vpp.vac.vac_read(ffi.cast("void *", rep), replen) if vpp.debug_dump then print("Read Message length: " .. tostring(replen[0]) .. "\n" .. vpp.hex_dump(ffi.string(ffi.cast('void *', rep[0]), replen[0]))) end @@ -946,7 +946,7 @@ function vpp.api_read(vpp) out["luaapi_message_name"] = reply_msg_name end - vpp.pneum.pneum_free(ffi.cast('void *',rep[0])) + vpp.vac.vac_free(ffi.cast('void *',rep[0])) return reply_msg_name, out end diff --git a/src/vpp-api/pneum/pneum.c b/src/vpp-api/pneum/pneum.c deleted file mode 100644 index 8b34d3e4..00000000 --- a/src/vpp-api/pneum/pneum.c +++ /dev/null @@ -1,489 +0,0 @@ -/* - * Copyright (c) 2016 Cisco and/or its affiliates. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "pneum.h" - -/* - * Asynchronous mode: - * Client registers a callback. All messages are sent to the callback. - * Synchronous mode: - * Client calls blocking read(). - * Clients are expected to collate events on a queue. - * pneum_write() -> suspends RX thread - * pneum_read() -> resumes RX thread - */ - -#define vl_typedefs /* define message structures */ -#include -#undef vl_typedefs - -#define vl_endianfun /* define message structures */ -#include -#undef vl_endianfun - -vlib_main_t vlib_global_main; -vlib_main_t **vlib_mains; - -typedef struct { - u8 connected_to_vlib; - pthread_t rx_thread_handle; - pthread_t timeout_thread_handle; - pthread_mutex_t queue_lock; - pthread_cond_t suspend_cv; - pthread_cond_t resume_cv; - pthread_mutex_t timeout_lock; - pthread_cond_t timeout_cv; - pthread_cond_t timeout_cancel_cv; - pthread_cond_t terminate_cv; -} pneum_main_t; - -pneum_main_t pneum_main; -pneum_callback_t pneum_callback; -u16 read_timeout = 0; -bool rx_is_running = false; - -static void -init (void) -{ - pneum_main_t *pm = &pneum_main; - memset(pm, 0, sizeof(*pm)); - pthread_mutex_init(&pm->queue_lock, NULL); - pthread_cond_init(&pm->suspend_cv, NULL); - pthread_cond_init(&pm->resume_cv, NULL); - pthread_mutex_init(&pm->timeout_lock, NULL); - pthread_cond_init(&pm->timeout_cv, NULL); - pthread_cond_init(&pm->timeout_cancel_cv, NULL); - pthread_cond_init(&pm->terminate_cv, NULL); -} - -static void -cleanup (void) -{ - pneum_main_t *pm = &pneum_main; - pthread_cond_destroy(&pm->suspend_cv); - pthread_cond_destroy(&pm->resume_cv); - pthread_cond_destroy(&pm->timeout_cv); - pthread_cond_destroy(&pm->timeout_cancel_cv); - pthread_cond_destroy(&pm->terminate_cv); - pthread_mutex_destroy(&pm->queue_lock); - pthread_mutex_destroy(&pm->timeout_lock); - memset (pm, 0, sizeof (*pm)); -} - -/* - * Satisfy external references when -lvlib is not available. - */ -void vlib_cli_output (struct vlib_main_t * vm, char * fmt, ...) -{ - clib_warning ("vlib_cli_output called..."); -} - -void -pneum_free (void * msg) -{ - vl_msg_api_free (msg); -} - -static void -pneum_api_handler (void *msg) -{ - u16 id = ntohs(*((u16 *)msg)); - msgbuf_t *msgbuf = (msgbuf_t *)(((u8 *)msg) - offsetof(msgbuf_t, data)); - int l = ntohl(msgbuf->data_len); - if (l == 0) - clib_warning("Message ID %d has wrong length: %d\n", id, l); - - /* Call Python callback */ - ASSERT(pneum_callback); - (pneum_callback)(msg, l); - pneum_free(msg); -} - -static void * -pneum_rx_thread_fn (void *arg) -{ - unix_shared_memory_queue_t *q; - pneum_main_t *pm = &pneum_main; - api_main_t *am = &api_main; - uword msg; - - q = am->vl_input_queue; - - while (1) - while (!unix_shared_memory_queue_sub(q, (u8 *)&msg, 0)) - { - u16 id = ntohs(*((u16 *)msg)); - switch (id) { - case VL_API_RX_THREAD_EXIT: - vl_msg_api_free((void *) msg); - /* signal waiting threads that this thread is about to terminate */ - pthread_mutex_lock(&pm->queue_lock); - pthread_cond_signal(&pm->terminate_cv); - pthread_mutex_unlock(&pm->queue_lock); - pthread_exit(0); - return 0; - break; - - case VL_API_MEMCLNT_RX_THREAD_SUSPEND: - vl_msg_api_free((void * )msg); - /* Suspend thread and signal reader */ - pthread_mutex_lock(&pm->queue_lock); - pthread_cond_signal(&pm->suspend_cv); - /* Wait for the resume signal */ - pthread_cond_wait (&pm->resume_cv, &pm->queue_lock); - pthread_mutex_unlock(&pm->queue_lock); - break; - - case VL_API_MEMCLNT_READ_TIMEOUT: - clib_warning("Received read timeout in async thread\n"); - vl_msg_api_free((void *) msg); - break; - - default: - pneum_api_handler((void *)msg); - } - } -} - -static void * -pneum_timeout_thread_fn (void *arg) -{ - vl_api_memclnt_read_timeout_t *ep; - pneum_main_t *pm = &pneum_main; - api_main_t *am = &api_main; - struct timespec ts; - struct timeval tv; - u16 timeout; - int rv; - - while (1) - { - /* Wait for poke */ - pthread_mutex_lock(&pm->timeout_lock); - pthread_cond_wait (&pm->timeout_cv, &pm->timeout_lock); - timeout = read_timeout; - gettimeofday(&tv, NULL); - ts.tv_sec = tv.tv_sec + timeout; - ts.tv_nsec = 0; - rv = pthread_cond_timedwait (&pm->timeout_cancel_cv, - &pm->timeout_lock, &ts); - pthread_mutex_unlock(&pm->timeout_lock); - if (rv == ETIMEDOUT) - { - ep = vl_msg_api_alloc (sizeof (*ep)); - ep->_vl_msg_id = ntohs(VL_API_MEMCLNT_READ_TIMEOUT); - vl_msg_api_send_shmem(am->vl_input_queue, (u8 *)&ep); - } - } - pthread_exit(0); -} - -void -pneum_rx_suspend (void) -{ - api_main_t *am = &api_main; - pneum_main_t *pm = &pneum_main; - vl_api_memclnt_rx_thread_suspend_t *ep; - - if (!pm->rx_thread_handle) return; - pthread_mutex_lock(&pm->queue_lock); - if (rx_is_running) - { - ep = vl_msg_api_alloc (sizeof (*ep)); - ep->_vl_msg_id = ntohs(VL_API_MEMCLNT_RX_THREAD_SUSPEND); - vl_msg_api_send_shmem(am->vl_input_queue, (u8 *)&ep); - /* Wait for RX thread to tell us it has suspendend */ - pthread_cond_wait(&pm->suspend_cv, &pm->queue_lock); - rx_is_running = false; - } - pthread_mutex_unlock(&pm->queue_lock); -} - -void -pneum_rx_resume (void) -{ - pneum_main_t *pm = &pneum_main; - if (!pm->rx_thread_handle) return; - pthread_mutex_lock(&pm->queue_lock); - if (rx_is_running) goto unlock; - pthread_cond_signal(&pm->resume_cv); - rx_is_running = true; - unlock: - pthread_mutex_unlock(&pm->queue_lock); -} - -static uword * -pneum_msg_table_get_hash (void) -{ - api_main_t *am = &api_main; - return (am->msg_index_by_name_and_crc); -} - -int -pneum_msg_table_size(void) -{ - api_main_t *am = &api_main; - return hash_elts(am->msg_index_by_name_and_crc); -} - -int -pneum_connect (char * name, char * chroot_prefix, pneum_callback_t cb, - int rx_qlen) -{ - int rv = 0; - pneum_main_t *pm = &pneum_main; - - init(); - if (chroot_prefix != NULL) - vl_set_memory_root_path (chroot_prefix); - - if ((rv = vl_client_api_map("/vpe-api"))) { - clib_warning ("vl_client_api map rv %d", rv); - return rv; - } - - if (vl_client_connect(name, 0, rx_qlen) < 0) { - vl_client_api_unmap(); - return (-1); - } - - if (cb) { - /* Start the rx queue thread */ - rv = pthread_create(&pm->rx_thread_handle, NULL, pneum_rx_thread_fn, 0); - if (rv) { - clib_warning("pthread_create returned %d", rv); - vl_client_api_unmap(); - return (-1); - } - pneum_callback = cb; - rx_is_running = true; - } - - /* Start read timeout thread */ - rv = pthread_create(&pm->timeout_thread_handle, NULL, - pneum_timeout_thread_fn, 0); - if (rv) { - clib_warning("pthread_create returned %d", rv); - vl_client_api_unmap(); - return (-1); - } - - pm->connected_to_vlib = 1; - - return (0); -} - -int -pneum_disconnect (void) -{ - api_main_t *am = &api_main; - pneum_main_t *pm = &pneum_main; - - if (!pm->connected_to_vlib) return 0; - - if (pm->rx_thread_handle) { - vl_api_rx_thread_exit_t *ep; - uword junk; - ep = vl_msg_api_alloc (sizeof (*ep)); - ep->_vl_msg_id = ntohs(VL_API_RX_THREAD_EXIT); - vl_msg_api_send_shmem(am->vl_input_queue, (u8 *)&ep); - - /* wait (with timeout) until RX thread has finished */ - struct timespec ts; - struct timeval tv; - gettimeofday(&tv, NULL); - ts.tv_sec = tv.tv_sec + 5; - ts.tv_nsec = 0; - pthread_mutex_lock(&pm->queue_lock); - int rv = pthread_cond_timedwait(&pm->terminate_cv, &pm->queue_lock, &ts); - pthread_mutex_unlock(&pm->queue_lock); - /* now join so we wait until thread has -really- finished */ - if (rv == ETIMEDOUT) - pthread_cancel(pm->rx_thread_handle); - else - pthread_join(pm->rx_thread_handle, (void **) &junk); - } - if (pm->timeout_thread_handle) - pthread_cancel(pm->timeout_thread_handle); - - vl_client_disconnect(); - vl_client_api_unmap(); - pneum_callback = 0; - - cleanup(); - - return (0); -} - -static void -set_timeout (unsigned short timeout) -{ - pneum_main_t *pm = &pneum_main; - pthread_mutex_lock(&pm->timeout_lock); - read_timeout = timeout; - pthread_cond_signal(&pm->timeout_cv); - pthread_mutex_unlock(&pm->timeout_lock); -} - -static void -unset_timeout (void) -{ - pneum_main_t *pm = &pneum_main; - pthread_mutex_lock(&pm->timeout_lock); - pthread_cond_signal(&pm->timeout_cancel_cv); - pthread_mutex_unlock(&pm->timeout_lock); -} - -int -pneum_read (char **p, int *l, u16 timeout) -{ - unix_shared_memory_queue_t *q; - api_main_t *am = &api_main; - pneum_main_t *pm = &pneum_main; - uword msg; - msgbuf_t *msgbuf; - - if (!pm->connected_to_vlib) return -1; - - *l = 0; - - if (am->our_pid == 0) return (-1); - - /* Poke timeout thread */ - if (timeout) - set_timeout(timeout); - - q = am->vl_input_queue; - int rv = unix_shared_memory_queue_sub(q, (u8 *)&msg, 0); - if (rv == 0) { - u16 msg_id = ntohs(*((u16 *)msg)); - switch (msg_id) { - case VL_API_RX_THREAD_EXIT: - printf("Received thread exit\n"); - return -1; - case VL_API_MEMCLNT_RX_THREAD_SUSPEND: - printf("Received thread suspend\n"); - goto error; - case VL_API_MEMCLNT_READ_TIMEOUT: - printf("Received read timeout %ds\n", timeout); - goto error; - - default: - msgbuf = (msgbuf_t *)(((u8 *)msg) - offsetof(msgbuf_t, data)); - *l = ntohl(msgbuf->data_len); - if (*l == 0) { - printf("Unregistered API message: %d\n", msg_id); - goto error; - } - } - *p = (char *)msg; - - /* Let timeout notification thread know we're done */ - unset_timeout(); - - } else { - printf("Read failed with %d\n", rv); - } - return (rv); - - error: - vl_msg_api_free((void *) msg); - /* Client might forget to resume RX thread on failure */ - pneum_rx_resume (); - return -1; -} - -/* - * XXX: Makes the assumption that client_index is the first member - */ -typedef VL_API_PACKED(struct _vl_api_header { - u16 _vl_msg_id; - u32 client_index; -}) vl_api_header_t; - -static unsigned int -pneum_client_index (void) -{ - return (api_main.my_client_index); -} - -int -pneum_write (char *p, int l) -{ - int rv = -1; - api_main_t *am = &api_main; - vl_api_header_t *mp = vl_msg_api_alloc(l); - unix_shared_memory_queue_t *q; - pneum_main_t *pm = &pneum_main; - - if (!pm->connected_to_vlib) return -1; - if (!mp) return (-1); - - memcpy(mp, p, l); - mp->client_index = pneum_client_index(); - q = am->shmem_hdr->vl_input_queue; - rv = unix_shared_memory_queue_add(q, (u8 *)&mp, 0); - if (rv != 0) { - clib_warning("vpe_api_write fails: %d\n", rv); - /* Clear message */ - pneum_free(mp); - } - return (rv); -} - -int -pneum_get_msg_index (unsigned char * name) -{ - return vl_api_get_msg_index (name); -} - -int -pneum_msg_table_max_index(void) -{ - int max = 0; - hash_pair_t *hp; - uword *h = pneum_msg_table_get_hash(); - hash_foreach_pair (hp, h, - ({ - if (hp->value[0] > max) - max = hp->value[0]; - })); - - return max; -} - -void -pneum_set_error_handler (pneum_error_callback_t cb) -{ - if (cb) clib_error_register_handler (cb, 0); -} diff --git a/src/vpp-api/pneum/pneum.h b/src/vpp-api/pneum/pneum.h deleted file mode 100644 index 669298df..00000000 --- a/src/vpp-api/pneum/pneum.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2016 Cisco and/or its affiliates. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef included_pneum_h -#define included_pneum_h - -#include -#include - -typedef void (*pneum_callback_t)(unsigned char * data, int len); -typedef void (*pneum_error_callback_t)(void *, unsigned char *, int); -int pneum_connect(char * name, char * chroot_prefix, pneum_callback_t cb, - int rx_qlen); -int pneum_disconnect(void); -int pneum_read(char **data, int *l, unsigned short timeout); -int pneum_write(char *data, int len); -void pneum_free(void * msg); - -int pneum_get_msg_index(unsigned char * name); -int pneum_msg_table_size(void); -int pneum_msg_table_max_index(void); - -void pneum_rx_suspend (void); -void pneum_rx_resume (void); -void pneum_set_error_handler(pneum_error_callback_t); -#endif diff --git a/src/vpp-api/pneum/test_pneum.c b/src/vpp-api/pneum/test_pneum.c deleted file mode 100644 index 334e58e9..00000000 --- a/src/vpp-api/pneum/test_pneum.c +++ /dev/null @@ -1,143 +0,0 @@ -/* - *------------------------------------------------------------------ - * test_pneum.c - * - * Copyright (c) 2016 Cisco and/or its affiliates. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *------------------------------------------------------------------ - */ -#include -#include -#include -#include -#include -#include -#include -#include - -#include /* time_t, time (for timestamp in second) */ -#include /* ftime, timeb (for timestamp in millisecond) */ -#include /* gettimeofday, timeval (for timestamp in microsecond) */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include "pneum.h" - -#define vl_typedefs /* define message structures */ -#include -#undef vl_typedefs - -/* we are not linking with vlib */ -vlib_main_t vlib_global_main; -vlib_main_t **vlib_mains; - -volatile int sigterm_received = 0; -volatile u32 result_ready; -volatile u16 result_msg_id; - -/* M_NOALLOC: construct, but don't yet send a message */ - -#define M_NOALLOC(T,t) \ - do { \ - result_ready = 0; \ - memset (mp, 0, sizeof (*mp)); \ - mp->_vl_msg_id = ntohs (VL_API_##T); \ - mp->client_index = am->my_client_index; \ - } while(0); - - - -int -wrap_pneum_callback (char *data, int len) -{ - //printf("Callback %d\n", len); - result_ready = 1; - result_msg_id = ntohs(*((u16 *)data)); - return (0); -} - -int main (int argc, char ** argv) -{ - api_main_t * am = &api_main; - vl_api_show_version_t message; - vl_api_show_version_t *mp; - int async = 1; - int rv = pneum_connect("pneum_client", NULL, NULL, 32 /* rx queue-length*/); - - if (rv != 0) { - printf("Connect failed: %d\n", rv); - exit(rv); - } - - struct timeb timer_msec; - long long int timestamp_msec_start; /* timestamp in millisecond. */ - if (!ftime(&timer_msec)) { - timestamp_msec_start = ((long long int) timer_msec.time) * 1000ll + - (long long int) timer_msec.millitm; - } - else { - timestamp_msec_start = -1; - } - - - /* - * Test vpe_api_write and vpe_api_read to send and recv message for an - * API - */ - int i; - long int no_msgs = 10000; - mp = &message; - - for (i = 0; i < no_msgs; i++) { - /* Construct the API message */ - M_NOALLOC(SHOW_VERSION, show_version); - pneum_write((char *)mp, sizeof(*mp)); -#ifndef __COVERITY__ - /* As given, async is always 1. Shut up Coverity about it */ - if (!async) - while (result_ready == 0); -#endif - } - if (async) { - vl_api_control_ping_t control; - vl_api_control_ping_t *mp; - mp = &control; - M_NOALLOC(CONTROL_PING, control_ping); - pneum_write((char *)mp, sizeof(*mp)); - - while (result_msg_id != VL_API_CONTROL_PING_REPLY); - } - - long long int timestamp_msec_end; /* timestamp in millisecond. */ - if (!ftime(&timer_msec)) { - timestamp_msec_end = ((long long int) timer_msec.time) * 1000ll + - (long long int) timer_msec.millitm; - } - else { - timestamp_msec_end = -1; - } - - printf("Took %lld msec, %lld msgs/msec \n", (timestamp_msec_end - timestamp_msec_start), - no_msgs/(timestamp_msec_end - timestamp_msec_start)); - fformat(stdout, "Exiting...\n"); - pneum_disconnect(); - exit (0); -} diff --git a/src/vpp-api/python/vpp_papi.py b/src/vpp-api/python/vpp_papi.py index 81f6903b..f0d46f05 100644 --- a/src/vpp-api/python/vpp_papi.py +++ b/src/vpp-api/python/vpp_papi.py @@ -21,26 +21,26 @@ import atexit, Queue from cffi import FFI ffi = FFI() ffi.cdef(""" -typedef void (*pneum_callback_t)(unsigned char * data, int len); -typedef void (*pneum_error_callback_t)(void *, unsigned char *, int); -int pneum_connect(char * name, char * chroot_prefix, pneum_callback_t cb, +typedef void (*vac_callback_t)(unsigned char * data, int len); +typedef void (*vac_error_callback_t)(void *, unsigned char *, int); +int vac_connect(char * name, char * chroot_prefix, vac_callback_t cb, int rx_qlen); -int pneum_disconnect(void); -int pneum_read(char **data, int *l, unsigned short timeout); -int pneum_write(char *data, int len); -void pneum_free(void * msg); - -int pneum_get_msg_index(unsigned char * name); -int pneum_msg_table_size(void); -int pneum_msg_table_max_index(void); - -void pneum_rx_suspend (void); -void pneum_rx_resume (void); -void pneum_set_error_handler(pneum_error_callback_t); +int vac_disconnect(void); +int vac_read(char **data, int *l, unsigned short timeout); +int vac_write(char *data, int len); +void vac_free(void * msg); + +int vac_get_msg_index(unsigned char * name); +int vac_msg_table_size(void); +int vac_msg_table_max_index(void); + +void vac_rx_suspend (void); +void vac_rx_resume (void); +void vac_set_error_handler(vac_error_callback_t); """) # Barfs on failure, no need to check success. -vpp_api = ffi.dlopen('libpneum.so') +vpp_api = ffi.dlopen('libvppapiclient.so') def vpp_atexit(self): """Clean up VPP connection on shutdown.""" @@ -51,13 +51,13 @@ def vpp_atexit(self): vpp_object = None @ffi.callback("void(unsigned char *, int)") -def pneum_callback_sync(data, len): +def vac_callback_sync(data, len): vpp_object.msg_handler_sync(ffi.buffer(data, len)) @ffi.callback("void(unsigned char *, int)") -def pneum_callback_async(data, len): +def vac_callback_async(data, len): vpp_object.msg_handler_async(ffi.buffer(data, len)) @ffi.callback("void(void *, unsigned char *, int)") -def pneum_error_handler(arg, msg, msg_len): +def vac_error_handler(arg, msg, msg_len): vpp_object.logger.warning("PNEUM: %s", ffi.string(msg, msg_len)) class Empty(object): @@ -138,7 +138,7 @@ class VPP(): atexit.register(vpp_atexit, self) # Register error handler - vpp_api.pneum_set_error_handler(pneum_error_handler) + vpp_api.vac_set_error_handler(vac_error_handler) class ContextId(object): """Thread-safe provider of unique context IDs.""" @@ -379,7 +379,7 @@ class VPP(): if self.messages[name]['typeonly']: continue crc = self.messages[name]['crc'] n = name + '_' + crc[2:] - i = vpp_api.pneum_get_msg_index(bytes(n)) + i = vpp_api.vac_get_msg_index(bytes(n)) if i > 0: self.id_msgdef[i] = msgdef self.id_names[i] = name @@ -400,33 +400,33 @@ class VPP(): """Send a binary-packed message to VPP.""" if not self.connected: raise IOError(1, 'Not connected') - return vpp_api.pneum_write(str(buf), len(buf)) + return vpp_api.vac_write(str(buf), len(buf)) def _read (self): if not self.connected: raise IOError(1, 'Not connected') mem = ffi.new("char **") size = ffi.new("int *") - rv = vpp_api.pneum_read(mem, size, self.read_timeout) + rv = vpp_api.vac_read(mem, size, self.read_timeout) if rv: - raise IOError(rv, 'pneum_read filed') + raise IOError(rv, 'vac_read filed') msg = bytes(ffi.buffer(mem[0], size[0])) - vpp_api.pneum_free(mem[0]) + vpp_api.vac_free(mem[0]) return msg def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, async): - rv = vpp_api.pneum_connect(name, chroot_prefix, msg_handler, rx_qlen) + rv = vpp_api.vac_connect(name, chroot_prefix, msg_handler, rx_qlen) if rv != 0: raise IOError(2, 'Connect failed') self.connected = True - self.vpp_dictionary_maxid = vpp_api.pneum_msg_table_max_index() + self.vpp_dictionary_maxid = vpp_api.vac_msg_table_max_index() self._register_functions(async=async) # Initialise control ping crc = self.messages['control_ping']['crc'] self.control_ping_index = \ - vpp_api.pneum_get_msg_index( + vpp_api.vac_get_msg_index( bytes('control_ping' + '_' + crc[2:])) self.control_ping_msgdef = self.messages['control_ping'] @@ -440,8 +440,8 @@ class VPP(): rx_qlen - the length of the VPP message receive queue between client and server. """ - msg_handler = pneum_callback_sync if not async \ - else pneum_callback_async + msg_handler = vac_callback_sync if not async \ + else vac_callback_async return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen, async) @@ -459,7 +459,7 @@ class VPP(): def disconnect(self): """Detach from VPP.""" - rv = vpp_api.pneum_disconnect() + rv = vpp_api.vac_disconnect() self.connected = False return rv @@ -550,7 +550,7 @@ class VPP(): kwargs['_vl_msg_id'] = i b = self.encode(msgdef, kwargs) - vpp_api.pneum_rx_suspend() + vpp_api.vac_rx_suspend() self._write(b) if multipart: @@ -580,7 +580,7 @@ class VPP(): rl.append(r) - vpp_api.pneum_rx_resume() + vpp_api.vac_rx_resume() return rl -- cgit 1.2.3-korg From 4df97165159b3b115b31eb1cad55782ac97e3c7e Mon Sep 17 00:00:00 2001 From: Ole Troan Date: Fri, 7 Jul 2017 16:06:08 +0200 Subject: API: Add Python3 support to vpp_papi.py Change-Id: I0657b3f7578eb1b4d9a1ecabc14dc0f0e4647c65 Signed-off-by: Ole Troan --- src/vpp-api/python/vpp_papi.py | 162 ++++++++++++++++++++++++----------------- 1 file changed, 97 insertions(+), 65 deletions(-) (limited to 'src/vpp-api/python/vpp_papi.py') diff --git a/src/vpp-api/python/vpp_papi.py b/src/vpp-api/python/vpp_papi.py index f0d46f05..c1fa9e8b 100644 --- a/src/vpp-api/python/vpp_papi.py +++ b/src/vpp-api/python/vpp_papi.py @@ -15,10 +15,22 @@ # from __future__ import print_function -import sys, os, logging, collections, struct, json, threading, glob -import atexit, Queue - +import sys +import os +import logging +import collections +import struct +import json +import threading +import glob +import atexit from cffi import FFI + +if sys.version[0] == '2': + import Queue as queue +else: + import queue as queue + ffi = FFI() ffi.cdef(""" typedef void (*vac_callback_t)(unsigned char * data, int len); @@ -42,6 +54,7 @@ void vac_set_error_handler(vac_error_callback_t); # Barfs on failure, no need to check success. vpp_api = ffi.dlopen('libvppapiclient.so') + def vpp_atexit(self): """Clean up VPP connection on shutdown.""" if self.connected: @@ -50,15 +63,28 @@ def vpp_atexit(self): vpp_object = None + +def vpp_iterator(d): + if sys.version[0] == '2': + return d.iteritems() + else: + return d.items() + + @ffi.callback("void(unsigned char *, int)") def vac_callback_sync(data, len): vpp_object.msg_handler_sync(ffi.buffer(data, len)) + + @ffi.callback("void(unsigned char *, int)") def vac_callback_async(data, len): vpp_object.msg_handler_async(ffi.buffer(data, len)) + + @ffi.callback("void(void *, unsigned char *, int)") def vac_error_handler(arg, msg, msg_len): - vpp_object.logger.warning("PNEUM: %s", ffi.string(msg, msg_len)) + vpp_object.logger.warning("VPP API client:: %s", ffi.string(msg, msg_len)) + class Empty(object): pass @@ -85,8 +111,8 @@ class VPP(): provides a means to register a callback function to receive these messages in a background thread. """ - def __init__(self, apifiles = None, testmode = False, async_thread = True, - logger = logging.getLogger('vpp_papi'), loglevel = 'debug'): + def __init__(self, apifiles=None, testmode=False, async_thread=True, + logger=logging.getLogger('vpp_papi'), loglevel='debug'): """Create a VPP API object. apifiles is a list of files containing API @@ -108,11 +134,12 @@ class VPP(): self.header = struct.Struct('>HI') self.apifiles = [] self.event_callback = None - self.message_queue = Queue.Queue() + self.message_queue = queue.Queue() self.read_timeout = 0 self.vpp_api = vpp_api if async_thread: - self.event_thread = threading.Thread(target=self.thread_msg_handler) + self.event_thread = threading.Thread( + target=self.thread_msg_handler) self.event_thread.daemon = True self.event_thread.start() @@ -128,7 +155,7 @@ class VPP(): for m in api['messages']: self.add_message(m[0], m[1:]) - self.apifiles = apifiles + self.apifiles = apifiles # Basic sanity check if len(self.messages) == 0 and not testmode: @@ -144,12 +171,13 @@ class VPP(): """Thread-safe provider of unique context IDs.""" def __init__(self): self.context = 0 - self.lock = threading.Lock() + self.lock = threading.Lock() + def __call__(self): """Get a new unique (or, at least, not recently used) context.""" - with self.lock: - self.context += 1 - return self.context + with self.lock: + self.context += 1 + return self.context get_context = ContextId() def status(self): @@ -157,15 +185,14 @@ class VPP(): print('Connected') if self.connected else print('Not Connected') print('Read API definitions from', ', '.join(self.apifiles)) - def __struct (self, t, n = None, e = -1, vl = None): + def __struct(self, t, n=None, e=-1, vl=None): """Create a packing structure for a message.""" - base_types = { 'u8' : 'B', - 'u16' : 'H', - 'u32' : 'I', - 'i32' : 'i', - 'u64' : 'Q', - 'f64' : 'd', - } + base_types = {'u8': 'B', + 'u16': 'H', + 'u32': 'I', + 'i32': 'i', + 'u64': 'Q', + 'f64': 'd', } pack = None if t in base_types: pack = base_types[t] @@ -187,7 +214,7 @@ class VPP(): return struct.Struct('>' + base_types[t]) if t in self.messages: - ### Return a list in case of array ### + # Return a list in case of array if e > 0 and not vl: return [e, lambda self, encode, buf, offset, args: ( self.__struct_type(encode, self.messages[t], buf, offset, @@ -198,7 +225,8 @@ class VPP(): args))] elif e == 0: # Old style VLA - raise NotImplementedError(1, 'No support for compound types ' + t) + raise NotImplementedError(1, + 'No support for compound types ' + t) return lambda self, encode, buf, offset, args: ( self.__struct_type(encode, self.messages[t], buf, offset, args) ) @@ -220,7 +248,7 @@ class VPP(): if k not in msgdef['args']: raise ValueError(1, 'Invalid field-name in message call ' + k) - for k,v in msgdef['args'].iteritems(): + for k, v in vpp_iterator(msgdef['args']): off += size if k in kwargs: if type(v) is list: @@ -254,7 +282,6 @@ class VPP(): return off + size - offset - def __getitem__(self, name): if name in self.messages: return self.messages[name] @@ -274,19 +301,19 @@ class VPP(): res = [] off = offset size = 0 - for k,v in msgdef['args'].iteritems(): + for k, v in vpp_iterator(msgdef['args']): off += size if type(v) is list: lst = [] - if callable(v[1]): # compound type + if callable(v[1]): # compound type size = 0 - if v[0] in msgdef['args']: # vla + if v[0] in msgdef['args']: # vla e = res[v[2]] - else: # fixed array + else: # fixed array e = v[0] res.append(lst) for i in range(e): - (s,l) = v[1](self, False, buf, off + size, None) + (s, l) = v[1](self, False, buf, off + size, None) lst.append(l) size += s continue @@ -308,7 +335,7 @@ class VPP(): size += v[1].size else: if callable(v): - (s,l) = v(self, False, buf, off, None) + (s, l) = v(self, False, buf, off, None) res.append(l) size += s else: @@ -322,7 +349,7 @@ class VPP(): return self.messages[name]['return_tuple'] return None - def add_message(self, name, msgdef, typeonly = False): + def add_message(self, name, msgdef, typeonly=False): if name in self.messages: raise ValueError('Duplicate message name: ' + name) @@ -340,11 +367,12 @@ class VPP(): raise ValueError('Variable Length Array must be last: ' + name) args[field_name] = self.__struct(*f) argtypes[field_name] = field_type - if len(f) == 4: # Find offset to # elements field - args[field_name].append(args.keys().index(f[3]) - i) + if len(f) == 4: # Find offset to # elements field + idx = list(args.keys()).index(f[3]) - i + args[field_name].append(idx) fields.append(field_name) msg['return_tuple'] = collections.namedtuple(name, fields, - rename = True) + rename=True) self.messages[name] = msg self.messages[name]['args'] = args self.messages[name]['argtypes'] = argtypes @@ -352,17 +380,20 @@ class VPP(): return self.messages[name] def add_type(self, name, typedef): - return self.add_message('vl_api_' + name + '_t', typedef, typeonly=True) + return self.add_message('vl_api_' + name + '_t', typedef, + typeonly=True) def make_function(self, name, i, msgdef, multipart, async): if (async): f = lambda **kwargs: (self._call_vpp_async(i, msgdef, **kwargs)) else: - f = lambda **kwargs: (self._call_vpp(i, msgdef, multipart, **kwargs)) + f = lambda **kwargs: (self._call_vpp(i, msgdef, multipart, + **kwargs)) args = self.messages[name]['args'] argtypes = self.messages[name]['argtypes'] f.__name__ = str(name) - f.__doc__ = ", ".join(["%s %s" % (argtypes[k], k) for k in args.keys()]) + f.__doc__ = ", ".join(["%s %s" % + (argtypes[k], k) for k in args.keys()]) return f @property @@ -375,11 +406,12 @@ class VPP(): self.id_names = [None] * (self.vpp_dictionary_maxid + 1) self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1) self._api = Empty() - for name, msgdef in self.messages.iteritems(): - if self.messages[name]['typeonly']: continue + for name, msgdef in vpp_iterator(self.messages): + if self.messages[name]['typeonly']: + continue crc = self.messages[name]['crc'] n = name + '_' + crc[2:] - i = vpp_api.vac_get_msg_index(bytes(n)) + i = vpp_api.vac_get_msg_index(n.encode()) if i > 0: self.id_msgdef[i] = msgdef self.id_names[i] = name @@ -394,15 +426,16 @@ class VPP(): setattr(self, name, f) # old API stuff ends here else: - self.logger.debug('No such message type or failed CRC checksum: %s', n) + self.logger.debug( + 'No such message type or failed CRC checksum: %s', n) - def _write (self, buf): + def _write(self, buf): """Send a binary-packed message to VPP.""" if not self.connected: raise IOError(1, 'Not connected') - return vpp_api.vac_write(str(buf), len(buf)) + return vpp_api.vac_write(ffi.from_buffer(buf), len(buf)) - def _read (self): + def _read(self): if not self.connected: raise IOError(1, 'Not connected') mem = ffi.new("char **") @@ -414,8 +447,10 @@ class VPP(): vpp_api.vac_free(mem[0]) return msg - def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, async): - rv = vpp_api.vac_connect(name, chroot_prefix, msg_handler, rx_qlen) + def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, + async): + rv = vpp_api.vac_connect(name.encode(), chroot_prefix.encode(), + msg_handler, rx_qlen) if rv != 0: raise IOError(2, 'Connect failed') self.connected = True @@ -425,13 +460,12 @@ class VPP(): # Initialise control ping crc = self.messages['control_ping']['crc'] - self.control_ping_index = \ - vpp_api.vac_get_msg_index( - bytes('control_ping' + '_' + crc[2:])) + self.control_ping_index = vpp_api.vac_get_msg_index( + ('control_ping' + '_' + crc[2:]).encode()) self.control_ping_msgdef = self.messages['control_ping'] + return rv - def connect(self, name, chroot_prefix = ffi.NULL, - async = False, rx_qlen = 32): + def connect(self, name, chroot_prefix=ffi.NULL, async=False, rx_qlen=32): """Attach to VPP. name - the name of the client. @@ -440,12 +474,11 @@ class VPP(): rx_qlen - the length of the VPP message receive queue between client and server. """ - msg_handler = vac_callback_sync if not async \ - else vac_callback_async + msg_handler = vac_callback_sync if not async else vac_callback_async return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen, async) - def connect_sync (self, name, chroot_prefix = ffi.NULL, rx_qlen = 32): + def connect_sync(self, name, chroot_prefix=ffi.NULL, rx_qlen=32): """Attach to VPP in synchronous mode. Application must poll for events. name - the name of the client. @@ -517,13 +550,13 @@ class VPP(): msgname = type(r).__name__ - if self.event_callback: - self.event_callback(msgname, r) + if self.event_callback: + self.event_callback(msgname, r) def _control_ping(self, context): """Send a ping command.""" self._call_vpp_async(self.control_ping_index, - self.control_ping_msgdef, + self.control_ping_msgdef, context=context) def _call_vpp(self, i, msgdef, multipart, **kwargs): @@ -542,7 +575,7 @@ class VPP(): no response within the timeout window. """ - if not 'context' in kwargs: + if 'context' not in kwargs: context = self.get_context() kwargs['context'] = context else: @@ -563,12 +596,11 @@ class VPP(): while (True): msg = self._read() if not msg: - print('PNEUM ERROR: OH MY GOD') - raise IOError(2, 'PNEUM read failed') + raise IOError(2, 'VPP API client: read failed') r = self.decode_incoming_msg(msg) msgname = type(r).__name__ - if not context in r or r.context == 0 or context != r.context: + if context not in r or r.context == 0 or context != r.context: self.message_queue.put_nowait(r) continue @@ -593,7 +625,7 @@ class VPP(): supplied. The remainder of the kwargs are the arguments to the API call. """ - if not 'context' in kwargs: + if 'context' not in kwargs: context = self.get_context() kwargs['context'] = context else: @@ -631,5 +663,5 @@ class VPP(): while True: r = self.message_queue.get() msgname = type(r).__name__ - if self.event_callback: - self.event_callback(msgname, r) + if self.event_callback: + self.event_callback(msgname, r) -- cgit 1.2.3-korg From 6bf177ce815dc1454e8ac1b9d5bad08fde01d98d Mon Sep 17 00:00:00 2001 From: Ole Troan Date: Thu, 17 Aug 2017 10:34:32 +0200 Subject: Python API: VPP-947 Empty chroot_prefix fails on encode() Change-Id: Ide2cdc456f3ab3219930fb8e423b871810469cdc Signed-off-by: Ole Troan --- src/vpp-api/python/LICENSE.txt | 202 +++++++++++++++++++++++++++++++++++++++++ src/vpp-api/python/setup.py | 3 + src/vpp-api/python/vpp_papi.py | 8 +- 3 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 src/vpp-api/python/LICENSE.txt (limited to 'src/vpp-api/python/vpp_papi.py') diff --git a/src/vpp-api/python/LICENSE.txt b/src/vpp-api/python/LICENSE.txt new file mode 100644 index 00000000..8f71f43f --- /dev/null +++ b/src/vpp-api/python/LICENSE.txt @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/src/vpp-api/python/setup.py b/src/vpp-api/python/setup.py index 28c2ecce..a4749a88 100644 --- a/src/vpp-api/python/setup.py +++ b/src/vpp-api/python/setup.py @@ -22,6 +22,9 @@ setup (name = 'vpp_papi', description = 'VPP Python binding', author = 'Ole Troan', author_email = 'ot@cisco.com', + url = 'https://wiki.fd.io/view/VPP/Python_API', + python_requires='>=2.7, >=3.3', + license = 'Apache-2.0', test_suite = 'tests', install_requires=['cffi'], py_modules=['vpp_papi'], diff --git a/src/vpp-api/python/vpp_papi.py b/src/vpp-api/python/vpp_papi.py index c1fa9e8b..489731ad 100644 --- a/src/vpp-api/python/vpp_papi.py +++ b/src/vpp-api/python/vpp_papi.py @@ -449,8 +449,8 @@ class VPP(): def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, async): - rv = vpp_api.vac_connect(name.encode(), chroot_prefix.encode(), - msg_handler, rx_qlen) + pfx = chroot_prefix.encode() if chroot_prefix else ffi.NULL + rv = vpp_api.vac_connect(name.encode(), pfx, msg_handler, rx_qlen) if rv != 0: raise IOError(2, 'Connect failed') self.connected = True @@ -465,7 +465,7 @@ class VPP(): self.control_ping_msgdef = self.messages['control_ping'] return rv - def connect(self, name, chroot_prefix=ffi.NULL, async=False, rx_qlen=32): + def connect(self, name, chroot_prefix=None, async=False, rx_qlen=32): """Attach to VPP. name - the name of the client. @@ -478,7 +478,7 @@ class VPP(): return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen, async) - def connect_sync(self, name, chroot_prefix=ffi.NULL, rx_qlen=32): + def connect_sync(self, name, chroot_prefix=None, rx_qlen=32): """Attach to VPP in synchronous mode. Application must poll for events. name - the name of the client. -- cgit 1.2.3-korg From b0856b432b6dae09b940ef93c383c834716af391 Mon Sep 17 00:00:00 2001 From: Ole Troan Date: Thu, 17 Aug 2017 12:48:08 +0200 Subject: Python API: Fix error message typo. Change-Id: Icb67797a91a5929e57a08b79adeca226fee09de3 Signed-off-by: Ole Troan --- src/vpp-api/python/vpp_papi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vpp-api/python/vpp_papi.py') diff --git a/src/vpp-api/python/vpp_papi.py b/src/vpp-api/python/vpp_papi.py index 489731ad..55dda104 100644 --- a/src/vpp-api/python/vpp_papi.py +++ b/src/vpp-api/python/vpp_papi.py @@ -442,7 +442,7 @@ class VPP(): size = ffi.new("int *") rv = vpp_api.vac_read(mem, size, self.read_timeout) if rv: - raise IOError(rv, 'vac_read filed') + raise IOError(rv, 'vac_read failed') msg = bytes(ffi.buffer(mem[0], size[0])) vpp_api.vac_free(mem[0]) return msg -- cgit 1.2.3-korg From 68ec940a469be5e1696fa6bb8dd4cea54d092796 Mon Sep 17 00:00:00 2001 From: Ole Troan Date: Thu, 31 Aug 2017 13:18:44 +0200 Subject: VPP-960: Python API add more information in exception for invalid arguments to API calls. Change-Id: I266eef8419fd98b9b900573ac9b032a62600ab86 Signed-off-by: Ole Troan --- src/vpp-api/python/vpp_papi.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/vpp-api/python/vpp_papi.py') diff --git a/src/vpp-api/python/vpp_papi.py b/src/vpp-api/python/vpp_papi.py index 55dda104..14f367c2 100644 --- a/src/vpp-api/python/vpp_papi.py +++ b/src/vpp-api/python/vpp_papi.py @@ -246,7 +246,10 @@ class VPP(): for k in kwargs: if k not in msgdef['args']: - raise ValueError(1, 'Invalid field-name in message call ' + k) + raise ValueError(1,'Non existing argument [' + k + ']' + \ + ' used in call to: ' + \ + self.id_names[kwargs['_vl_msg_id']] + '()' ) + for k, v in vpp_iterator(msgdef['args']): off += size -- cgit 1.2.3-korg From 088f042400fe104c86c86fb0de04aeb4b8013e74 Mon Sep 17 00:00:00 2001 From: Ole Troan Date: Fri, 20 Oct 2017 13:28:20 +0200 Subject: VPP-1033: Python API support arbitrary sized input parameters. Dynamically calculate the required buffer size to pack into based on message definition. Also add input parameter length checking. Change-Id: I7633bec596e4833bb328fbf63a65b866c7985de5 Signed-off-by: Ole Troan (cherry picked from commit 895b6e8b4408108a9b5cea99dcb378c3524b18b2) --- src/vpp-api/python/vpp_papi.py | 62 +++++++++++++++++++++++++++++++++--------- test/test_acl_plugin.py | 4 +-- test/test_dhcp.py | 3 +- test/test_nat.py | 17 +++++++----- test/test_papi.py | 31 +++++++++++++++++++++ test/vpp_papi_provider.py | 4 +-- 6 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 test/test_papi.py (limited to 'src/vpp-api/python/vpp_papi.py') diff --git a/src/vpp-api/python/vpp_papi.py b/src/vpp-api/python/vpp_papi.py index 14f367c2..7b66c0f4 100644 --- a/src/vpp-api/python/vpp_papi.py +++ b/src/vpp-api/python/vpp_papi.py @@ -129,7 +129,6 @@ class VPP(): self.messages = {} self.id_names = [] self.id_msgdef = [] - self.buffersize = 10000 self.connected = False self.header = struct.Struct('>HI') self.apifiles = [] @@ -199,35 +198,45 @@ class VPP(): if not vl: if e > 0 and t == 'u8': # Fixed byte array - return struct.Struct('>' + str(e) + 's') + s = struct.Struct('>' + str(e) + 's') + return s.size, s if e > 0: # Fixed array of base type - return [e, struct.Struct('>' + base_types[t])] + s = struct.Struct('>' + base_types[t]) + return s.size, [e, s] elif e == 0: # Old style variable array - return [-1, struct.Struct('>' + base_types[t])] + s = struct.Struct('>' + base_types[t]) + return s.size, [-1, s] else: # Variable length array - return [vl, struct.Struct('>s')] if t == 'u8' else \ - [vl, struct.Struct('>' + base_types[t])] + if t == 'u8': + s = struct.Struct('>s') + return s.size, [vl, s] + else: + s = struct.Struct('>' + base_types[t]) + return s.size, [vl, s] - return struct.Struct('>' + base_types[t]) + s = struct.Struct('>' + base_types[t]) + return s.size, s if t in self.messages: + size = self.messages[t]['sizes'][0] + # Return a list in case of array if e > 0 and not vl: - return [e, lambda self, encode, buf, offset, args: ( + return size, [e, lambda self, encode, buf, offset, args: ( self.__struct_type(encode, self.messages[t], buf, offset, args))] if vl: - return [vl, lambda self, encode, buf, offset, args: ( + return size, [vl, lambda self, encode, buf, offset, args: ( self.__struct_type(encode, self.messages[t], buf, offset, args))] elif e == 0: # Old style VLA raise NotImplementedError(1, 'No support for compound types ' + t) - return lambda self, encode, buf, offset, args: ( + return size, lambda self, encode, buf, offset, args: ( self.__struct_type(encode, self.messages[t], buf, offset, args) ) @@ -250,13 +259,14 @@ class VPP(): ' used in call to: ' + \ self.id_names[kwargs['_vl_msg_id']] + '()' ) - for k, v in vpp_iterator(msgdef['args']): off += size if k in kwargs: if type(v) is list: if callable(v[1]): e = kwargs[v[0]] if v[0] in kwargs else v[0] + if e != len(kwargs[k]): + raise (ValueError(1, 'Input list length mismatch: %s (%s != %s)' % (k, e, len(kwargs[k])))) size = 0 for i in range(e): size += v[1](self, True, buf, off + size, @@ -264,6 +274,8 @@ class VPP(): else: if v[0] in kwargs: l = kwargs[v[0]] + if l != len(kwargs[k]): + raise ValueError(1, 'Input list length mistmatch: %s (%s != %s)' % (k, l, len(kwargs[k]))) else: l = len(kwargs[k]) if v[1].size == 1: @@ -278,6 +290,8 @@ class VPP(): if callable(v): size = v(self, True, buf, off, kwargs[k]) else: + if type(kwargs[k]) is str and v.size < len(kwargs[k]): + raise ValueError(1, 'Input list length mistmatch: %s (%s < %s)' % (k, v.size, len(kwargs[k]))) v.pack_into(buf, off, kwargs[k]) size = v.size else: @@ -290,9 +304,17 @@ class VPP(): return self.messages[name] return None + def get_size(self, sizes, kwargs): + total_size = sizes[0] + for e in sizes[1]: + if e in kwargs and type(kwargs[e]) is list: + total_size += len(kwargs[e]) * sizes[1][e] + return total_size + def encode(self, msgdef, kwargs): # Make suitably large buffer - buf = bytearray(self.buffersize) + size = self.get_size(msgdef['sizes'], kwargs) + buf = bytearray(size) offset = 0 size = self.__struct_type(True, msgdef, buf, offset, kwargs) return buf[:offset + size] @@ -360,6 +382,8 @@ class VPP(): argtypes = collections.OrderedDict() fields = [] msg = {} + total_size = 0 + sizes = {} for i, f in enumerate(msgdef): if type(f) is dict and 'crc' in f: msg['crc'] = f['crc'] @@ -368,7 +392,18 @@ class VPP(): field_name = f[1] if len(f) == 3 and f[2] == 0 and i != len(msgdef) - 2: raise ValueError('Variable Length Array must be last: ' + name) - args[field_name] = self.__struct(*f) + size, s = self.__struct(*f) + args[field_name] = s + if type(s) == list and type(s[0]) == int and type(s[1]) == struct.Struct: + if s[0] < 0: + sizes[field_name] = size + else: + sizes[field_name] = size + total_size += s[0] * size + else: + sizes[field_name] = size + total_size += size + argtypes[field_name] = field_type if len(f) == 4: # Find offset to # elements field idx = list(args.keys()).index(f[3]) - i @@ -380,6 +415,7 @@ class VPP(): self.messages[name]['args'] = args self.messages[name]['argtypes'] = argtypes self.messages[name]['typeonly'] = typeonly + self.messages[name]['sizes'] = [total_size, sizes] return self.messages[name] def add_type(self, name, typedef): diff --git a/test/test_acl_plugin.py b/test/test_acl_plugin.py index 97fca1a1..cd375a2c 100644 --- a/test/test_acl_plugin.py +++ b/test/test_acl_plugin.py @@ -532,7 +532,7 @@ class TestACLplugin(VppTestCase): r[i_rule][rule_key]) # Add a deny-1234 ACL - r_deny = ({'is_permit': 0, 'is_ipv6': 0, 'proto': 17, + r_deny = [{'is_permit': 0, 'is_ipv6': 0, 'proto': 17, 'srcport_or_icmptype_first': 1234, 'srcport_or_icmptype_last': 1235, 'src_ip_prefix_len': 0, @@ -549,7 +549,7 @@ class TestACLplugin(VppTestCase): 'dstport_or_icmpcode_first': 0, 'dstport_or_icmpcode_last': 0, 'dst_ip_addr': '\x00\x00\x00\x00', - 'dst_ip_prefix_len': 0}) + 'dst_ip_prefix_len': 0}] reply = self.vapi.acl_add_replace(acl_index=4294967295, r=r_deny, tag="deny 1234;permit all") diff --git a/test/test_dhcp.py b/test/test_dhcp.py index fe97f6c9..42b80af3 100644 --- a/test/test_dhcp.py +++ b/test/test_dhcp.py @@ -19,6 +19,7 @@ from scapy.layers.dhcp6 import DHCP6, DHCP6_Solicit, DHCP6_RelayForward, \ from socket import AF_INET, AF_INET6 from scapy.utils import inet_pton, inet_ntop from scapy.utils6 import in6_ptop +from util import mactobinary DHCP4_CLIENT_PORT = 68 DHCP4_SERVER_PORT = 67 @@ -1134,7 +1135,7 @@ class TestDHCP(VppTestCase): # remove the left over ARP entry self.vapi.ip_neighbor_add_del(self.pg2.sw_if_index, - self.pg2.remote_mac, + mactobinary(self.pg2.remote_mac), self.pg2.remote_ip4, is_add=0) # diff --git a/test/test_nat.py b/test/test_nat.py index 73e9e217..44758906 100644 --- a/test/test_nat.py +++ b/test/test_nat.py @@ -16,6 +16,7 @@ from util import ppp from ipfix import IPFIX, Set, Template, Data, IPFIXDecoder from time import sleep from util import ip4_range +from util import mactobinary class MethodHolder(VppTestCase): @@ -643,7 +644,9 @@ class TestNAT44(MethodHolder): lb_sm.external_port, lb_sm.protocol, lb_sm.vrf_id, - is_add=0) + is_add=0, + local_num=0, + locals=[]) adresses = self.vapi.nat44_address_dump() for addr in adresses: @@ -1869,11 +1872,11 @@ class TestNAT44(MethodHolder): """ NAT44 interfaces without configured IP address """ self.vapi.ip_neighbor_add_del(self.pg7.sw_if_index, - self.pg7.remote_mac, + mactobinary(self.pg7.remote_mac), self.pg7.remote_ip4n, is_static=1) self.vapi.ip_neighbor_add_del(self.pg8.sw_if_index, - self.pg8.remote_mac, + mactobinary(self.pg8.remote_mac), self.pg8.remote_ip4n, is_static=1) @@ -1911,11 +1914,11 @@ class TestNAT44(MethodHolder): """ NAT44 interfaces without configured IP address - 1:1 NAT """ self.vapi.ip_neighbor_add_del(self.pg7.sw_if_index, - self.pg7.remote_mac, + mactobinary(self.pg7.remote_mac), self.pg7.remote_ip4n, is_static=1) self.vapi.ip_neighbor_add_del(self.pg8.sw_if_index, - self.pg8.remote_mac, + mactobinary(self.pg8.remote_mac), self.pg8.remote_ip4n, is_static=1) @@ -1957,11 +1960,11 @@ class TestNAT44(MethodHolder): self.icmp_id_out = 30608 self.vapi.ip_neighbor_add_del(self.pg7.sw_if_index, - self.pg7.remote_mac, + mactobinary(self.pg7.remote_mac), self.pg7.remote_ip4n, is_static=1) self.vapi.ip_neighbor_add_del(self.pg8.sw_if_index, - self.pg8.remote_mac, + mactobinary(self.pg8.remote_mac), self.pg8.remote_ip4n, is_static=1) diff --git a/test/test_papi.py b/test/test_papi.py new file mode 100644 index 00000000..1a5f6ae6 --- /dev/null +++ b/test/test_papi.py @@ -0,0 +1,31 @@ +import binascii +from framework import VppTestCase + +""" TestPAPI is a subclass of VPPTestCase classes. + +Basic test for sanity check of the Python API binding. + +""" + + +class TestPAPI(VppTestCase): + """ PAPI Test Case """ + + @classmethod + def setUpClass(cls): + super(TestPAPI, cls).setUpClass() + cls.v = cls.vapi.papi + + def test_show_version(self): + rv = self.v.show_version() + self.assertEqual(rv.retval, 0) + + def test_show_version_invalid_param(self): + self.assertRaises(ValueError, self.v.show_version, foobar='foo') + + def test_u8_array(self): + rv = self.v.get_node_index(node_name='ip4-lookup') + self.assertEqual(rv.retval, 0) + node_name = 'X' * 100 + self.assertRaises(ValueError, self.v.get_node_index, + node_name=node_name) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index b6759ec3..2eab6c6e 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -1290,7 +1290,7 @@ class VppPapiProvider(object): protocol, vrf_id=0, local_num=0, - locals=None, + locals=[], is_add=1): """Add/delete NAT44 load balancing static mapping @@ -2004,7 +2004,7 @@ class VppPapiProvider(object): eid, eid_prefix_len=0, vni=0, - rlocs=None, + rlocs=[], rlocs_num=0, is_src_dst=0, is_add=1): -- cgit 1.2.3-korg