diff options
author | Ole Troan <ot@cisco.com> | 2017-03-08 12:02:24 +0100 |
---|---|---|
committer | Ole Troan <ot@cisco.com> | 2017-03-15 21:43:30 +0100 |
commit | 3cc4971882235a539bc6177e8e4b4d92129b3a12 (patch) | |
tree | c850b1ea3348da7cd632e782008fbe4e3c6b4c87 /src/vpp-api | |
parent | 9d676afbb779da5186cb3869925ef6d7d3d04db1 (diff) |
Python API: Change from cPython to CFFI.
Change-Id: I03e52466fb3f909ae52b8fba601168f3eadbd972
Signed-off-by: Ole Troan <ot@cisco.com>
Diffstat (limited to 'src/vpp-api')
-rw-r--r-- | src/vpp-api/pneum/pneum.c (renamed from src/vpp-api/python/pneum/pneum.c) | 25 | ||||
-rw-r--r-- | src/vpp-api/pneum/pneum.h (renamed from src/vpp-api/python/pneum/pneum.h) | 9 | ||||
-rw-r--r-- | src/vpp-api/pneum/test_pneum.c (renamed from src/vpp-api/python/pneum/test_pneum.c) | 0 | ||||
-rw-r--r-- | src/vpp-api/python/Makefile.am | 57 | ||||
-rw-r--r-- | src/vpp-api/python/setup.cfg | 2 | ||||
-rw-r--r-- | src/vpp-api/python/setup.py | 15 | ||||
-rw-r--r-- | src/vpp-api/python/vpp_papi.py (renamed from src/vpp-api/python/vpp_papi/vpp_papi.py) | 124 | ||||
-rw-r--r-- | src/vpp-api/python/vpp_papi/__init__.py | 3 | ||||
-rw-r--r-- | src/vpp-api/python/vpp_papi/pneum_wrap.c | 226 |
9 files changed, 123 insertions, 338 deletions
diff --git a/src/vpp-api/python/pneum/pneum.c b/src/vpp-api/pneum/pneum.c index da9d69df..cbae5cff 100644 --- a/src/vpp-api/python/pneum/pneum.c +++ b/src/vpp-api/pneum/pneum.c @@ -241,7 +241,7 @@ pneum_rx_resume (void) pthread_mutex_unlock(&pm->queue_lock); } -uword * +static uword * pneum_msg_table_get_hash (void) { api_main_t *am = &api_main; @@ -460,8 +460,29 @@ pneum_write (char *p, int l) return (rv); } -uint32_t +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/python/pneum/pneum.h b/src/vpp-api/pneum/pneum.h index c4b55ae0..669298df 100644 --- a/src/vpp-api/python/pneum/pneum.h +++ b/src/vpp-api/pneum/pneum.h @@ -19,16 +19,19 @@ #include <vppinfra/types.h> 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); -uword * pneum_msg_table_get_hash (void); + +int pneum_get_msg_index(unsigned char * name); int pneum_msg_table_size(void); -uint32_t pneum_get_msg_index(unsigned char * name); +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/python/pneum/test_pneum.c b/src/vpp-api/pneum/test_pneum.c index 334e58e9..334e58e9 100644 --- a/src/vpp-api/python/pneum/test_pneum.c +++ b/src/vpp-api/pneum/test_pneum.c diff --git a/src/vpp-api/python/Makefile.am b/src/vpp-api/python/Makefile.am index 54076822..6f5beb69 100644 --- a/src/vpp-api/python/Makefile.am +++ b/src/vpp-api/python/Makefile.am @@ -11,52 +11,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -AUTOMAKE_OPTIONS = foreign -ACLOCAL_AMFLAGS = -I m4 -AM_LIBTOOLFLAGS = --quiet -AM_CFLAGS = -Wall -I${top_srcdir} -I${top_builddir} - -BUILT_SOURCES = -bin_PROGRAMS = -CLEANFILES = -lib_LTLIBRARIES = -noinst_PROGRAMS = -nobase_include_HEADERS = pneum/pneum.h - -# -# Python / C extension -# -lib_LTLIBRARIES += libpneum.la -libpneum_la_SOURCES = pneum/pneum.c -libpneum_la_LIBADD = \ - $(top_builddir)/libvppinfra.la \ - $(top_builddir)/libvlibmemoryclient.la \ - $(top_builddir)/libvlibapi.la \ - $(top_builddir)/libsvm.la \ - -lpthread -lm -lrt - -libpneum_la_LDFLAGS = -module -libpneum_la_CPPFLAGS = - -# TODO: Support both Python 2 and 3. -install-exec-local: $(lib_LTLIBRARIES) - cd $(srcdir); \ - mkdir -p $(pythondir); \ - mkdir -p $(pyexecdir); \ - PYTHONUSERBASE=$(prefix) \ - python setup.py build_ext -L $(libdir) \ - -I $(prefix)/include/ install --user - -# -# Test client -# -if ENABLE_TESTS -noinst_PROGRAMS += test_pneum -test_pneum_SOURCES = pneum/pneum.c pneum/test_pneum.c -test_pneum_LDADD = \ - $(top_builddir)/libvppinfra.la \ - $(top_builddir)/libvlibmemoryclient.la \ - $(top_builddir)/libvlibapi.la \ - $(top_builddir)/libsvm.la \ - -lpthread -lm -lrt -endif +install-exec-local: + (cd $(srcdir) ; $(PYTHON) $(srcdir)/setup.py build \ + --build-base $(shell readlink -f $(builddir))/build \ + install \ + --root / \ + --prefix $(DESTDIR)$(prefix) \ + --single-version-externally-managed \ + --verbose) diff --git a/src/vpp-api/python/setup.cfg b/src/vpp-api/python/setup.cfg index d645be77..79bc6784 100644 --- a/src/vpp-api/python/setup.cfg +++ b/src/vpp-api/python/setup.cfg @@ -2,4 +2,4 @@ # This flag says that the code is written to work on both Python 2 and Python # 3. If at all possible, it is good practice to do this. If you cannot, you # will need to generate wheels for each Python version that you support. -universal=0 +universal=1 diff --git a/src/vpp-api/python/setup.py b/src/vpp-api/python/setup.py index 8a34d501..28c2ecce 100644 --- a/src/vpp-api/python/setup.py +++ b/src/vpp-api/python/setup.py @@ -13,23 +13,18 @@ # limitations under the License. try: - from setuptools import setup, Extension + from setuptools import setup except ImportError: - from distutils.core import setup, Extension + from distutils.core import setup setup (name = 'vpp_papi', - version = '1.3', + version = '1.4', description = 'VPP Python binding', author = 'Ole Troan', author_email = 'ot@cisco.com', test_suite = 'tests', - packages=['vpp_papi'], - ext_modules = [ - Extension( - 'vpp_api', - sources = ['vpp_papi/pneum_wrap.c'], - libraries = ['pneum'], - )], + install_requires=['cffi'], + py_modules=['vpp_papi'], long_description = '''VPP Python language binding.''', zip_safe = True, ) diff --git a/src/vpp-api/python/vpp_papi/vpp_papi.py b/src/vpp-api/python/vpp_papi.py index 0c40f171..81f6903b 100644 --- a/src/vpp-api/python/vpp_papi/vpp_papi.py +++ b/src/vpp-api/python/vpp_papi.py @@ -18,19 +18,47 @@ from __future__ import print_function import sys, os, logging, collections, struct, json, threading, glob import atexit, Queue -logging.basicConfig(level=logging.DEBUG) -import vpp_api - -def eprint(*args, **kwargs): - """Print critical diagnostics to stderr.""" - print(*args, file=sys.stderr, **kwargs) +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: - eprint ('Cleaning up VPP on exit') + 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 @@ -57,7 +85,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): + 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 @@ -66,6 +95,11 @@ class VPP(): 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 = [] @@ -103,6 +137,9 @@ class VPP(): # 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): @@ -285,7 +322,7 @@ class VPP(): return self.messages[name]['return_tuple'] return None - def add_message(self, name, msgdef): + def add_message(self, name, msgdef, typeonly = False): if name in self.messages: raise ValueError('Duplicate message name: ' + name) @@ -311,10 +348,11 @@ class VPP(): 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) + return self.add_message('vl_api_' + name + '_t', typedef, typeonly=True) def make_function(self, name, i, msgdef, multipart, async): if (async): @@ -338,12 +376,11 @@ class VPP(): self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1) self._api = Empty() for name, msgdef in self.messages.iteritems(): - if name in self.vpp_dictionary: - if self.messages[name]['crc'] != self.vpp_dictionary[name]['crc']: - raise ValueError(3, 'Failed CRC checksum ' + name + - ' ' + self.messages[name]['crc'] + - ' ' + self.vpp_dictionary[name]['crc']) - i = self.vpp_dictionary[name]['id'] + 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 @@ -356,46 +393,45 @@ class VPP(): 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.write(str(buf)) + return vpp_api.pneum_write(str(buf), len(buf)) def _read (self): if not self.connected: raise IOError(1, 'Not connected') - - return vpp_api.read(self.read_timeout) - - def _load_dictionary(self): - self.vpp_dictionary = {} - self.vpp_dictionary_maxid = 0 - d = vpp_api.msg_table() - - if not d: - raise IOError(3, 'Cannot get VPP API dictionary') - for i,n in d: - name, crc = n.rsplit('_', 1) - crc = '0x' + crc - self.vpp_dictionary[name] = { 'id' : i, 'crc' : crc } - self.vpp_dictionary_maxid = max(self.vpp_dictionary_maxid, i) + 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.connect(name, msg_handler, chroot_prefix, rx_qlen) + rv = vpp_api.pneum_connect(name, chroot_prefix, msg_handler, rx_qlen) if rv != 0: raise IOError(2, 'Connect failed') self.connected = True - self._load_dictionary() + self.vpp_dictionary_maxid = vpp_api.pneum_msg_table_max_index() self._register_functions(async=async) # Initialise control ping - self.control_ping_index = self.vpp_dictionary['control_ping']['id'] + 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 = None, 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. @@ -404,12 +440,12 @@ class VPP(): rx_qlen - the length of the VPP message receive queue between client and server. """ - msg_handler = self.msg_handler_sync if not async \ - else self.msg_handler_async + 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 = None, 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. @@ -418,12 +454,12 @@ class VPP(): client and server. """ - return self.connect_internal(name, None, chroot_prefix, rx_qlen, + return self.connect_internal(name, ffi.NULL, chroot_prefix, rx_qlen, async=False) def disconnect(self): """Detach from VPP.""" - rv = vpp_api.disconnect() + rv = vpp_api.pneum_disconnect() self.connected = False return rv @@ -452,7 +488,7 @@ class VPP(): def decode_incoming_msg(self, msg): if not msg: - eprint('vpp_api.read failed') + self.logger.warning('vpp_api.read failed') return i, ci = self.header.unpack_from(msg, 0) @@ -514,7 +550,7 @@ class VPP(): kwargs['_vl_msg_id'] = i b = self.encode(msgdef, kwargs) - vpp_api.suspend() + vpp_api.pneum_rx_suspend() self._write(b) if multipart: @@ -544,7 +580,7 @@ class VPP(): rl.append(r) - vpp_api.resume() + vpp_api.pneum_rx_resume() return rl diff --git a/src/vpp-api/python/vpp_papi/__init__.py b/src/vpp-api/python/vpp_papi/__init__.py deleted file mode 100644 index 6688ffb8..00000000 --- a/src/vpp-api/python/vpp_papi/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -__import__('pkg_resources').declare_namespace(__name__) -from . vpp_papi import * - diff --git a/src/vpp-api/python/vpp_papi/pneum_wrap.c b/src/vpp-api/python/vpp_papi/pneum_wrap.c deleted file mode 100644 index c5a7eea1..00000000 --- a/src/vpp-api/python/vpp_papi/pneum_wrap.c +++ /dev/null @@ -1,226 +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 <Python.h> -#include "../pneum/pneum.h" -#include <vppinfra/hash.h> - -static PyObject *pneum_callback = NULL; - -static void -wrap_pneum_callback (unsigned char * data, int len) -{ - PyGILState_STATE gstate; - PyObject *result;//, *arglist; - - gstate = PyGILState_Ensure(); - - /* Time to call the callback */ -#if PY_VERSION_HEX >= 0x03000000 - result = PyObject_CallFunction(pneum_callback, "y#", data, len); -#else - result = PyObject_CallFunction(pneum_callback, "s#", data, len); -#endif - if (result) - Py_DECREF(result); - else - PyErr_Print(); - - PyGILState_Release(gstate); -} - -static PyObject * -wrap_connect (PyObject *self, PyObject *args, PyObject *kw) -{ - char * name, * chroot_prefix = NULL; - int rx_qlen = 32; /* default rx queue length */ - int rv; - PyObject * temp = NULL; - pneum_callback_t cb = NULL; - - if (!PyArg_ParseTuple(args, "sOzi:wrap_connect", - &name, &temp, &chroot_prefix, &rx_qlen)) - return (NULL); - - if (temp != Py_None) - { - if (!PyCallable_Check(temp)) - { - PyErr_SetString(PyExc_TypeError, "parameter must be callable"); - return NULL; - } - - Py_XINCREF(temp); /* Add a reference to new callback */ - Py_XDECREF(pneum_callback); /* Dispose of previous callback */ - pneum_callback = temp; /* Remember new callback */ - cb = wrap_pneum_callback; - } - Py_BEGIN_ALLOW_THREADS - rv = pneum_connect(name, chroot_prefix, cb, rx_qlen); - Py_END_ALLOW_THREADS - return PyLong_FromLong(rv); -} - -static PyObject * -wrap_disconnect (PyObject *self, PyObject *args) -{ - int rv; - Py_BEGIN_ALLOW_THREADS - rv = pneum_disconnect(); - Py_END_ALLOW_THREADS - return PyLong_FromLong(rv); -} - -static PyObject * -wrap_write (PyObject *self, PyObject *args) -{ - char *data; - int len, rv; - - if (!PyArg_ParseTuple(args, "s#", &data, &len)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - rv = pneum_write(data, len); - Py_END_ALLOW_THREADS - - return PyLong_FromLong(rv); -} - -static PyObject * -wrap_read (PyObject *self, PyObject *args) -{ - char *data; - int len, rv; - unsigned short timeout; - - if (!PyArg_ParseTuple(args, "H", &timeout)) - return (NULL); - Py_BEGIN_ALLOW_THREADS - rv = pneum_read(&data, &len, timeout); - Py_END_ALLOW_THREADS - - if (rv != 0) { Py_RETURN_NONE; } -#if PY_VERSION_HEX >= 0x03000000 - PyObject *ret = Py_BuildValue("y#", data, len); -#else - PyObject *ret = Py_BuildValue("s#", data, len); -#endif - pneum_free(data); - if (!ret) { Py_RETURN_NONE; } - - return ret; -} - -static PyObject * -wrap_msg_table (PyObject *self, PyObject *args) -{ - int i = 0, rv = 0; - hash_pair_t *hp; - uword *h = pneum_msg_table_get_hash(); - PyObject *ret = PyList_New(pneum_msg_table_size()); - if (!ret) goto error; - hash_foreach_pair (hp, h, - ({ - PyObject *item = PyTuple_New(2); - if (!item) goto error; - rv = PyTuple_SetItem(item, 0, PyLong_FromLong((u32)hp->value[0])); - if (rv) goto error; - rv = PyTuple_SetItem(item, 1, PyString_FromString((char *)hp->key)); - if (rv) goto error; - PyList_SetItem(ret, i, item); - i++; - })); - - return ret; - - error: - /* TODO: Raise exception */ - printf("msg_table failed"); - Py_RETURN_NONE; -} - -static PyObject * -wrap_suspend (PyObject *self, PyObject *args) -{ - Py_BEGIN_ALLOW_THREADS - pneum_rx_suspend(); - Py_END_ALLOW_THREADS - Py_RETURN_NONE; -} - -static PyObject * -wrap_resume (PyObject *self, PyObject *args) -{ - Py_BEGIN_ALLOW_THREADS - pneum_rx_resume(); - Py_END_ALLOW_THREADS - Py_RETURN_NONE; -} - -static PyMethodDef vpp_api_Methods[] = { - {"connect", wrap_connect, METH_VARARGS, "Connect to the VPP API."}, - {"disconnect", wrap_disconnect, METH_VARARGS, "Disconnect from the VPP API."}, - {"write", wrap_write, METH_VARARGS, "Write data to the VPP API."}, - {"read", wrap_read, METH_VARARGS, "Read data from the VPP API."}, - {"msg_table", wrap_msg_table, METH_VARARGS, "Get API dictionary."}, - {"suspend", wrap_suspend, METH_VARARGS, "Suspend RX thread."}, - {"resume", wrap_resume, METH_VARARGS, "Resume RX thread."}, - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - -#if PY_VERSION_HEX >= 0x03000000 -PyMODINIT_FUNC -PyInit_vpp_api (void) -#else -void -initvpp_api (void) -#endif -{ -#if PY_VERSION_HEX >= 0x03000000 - static struct PyModuleDef vpp_api_module = { -#if PY_VERSION_HEX >= 0x03020000 - PyModuleDef_HEAD_INIT, -#else - { - PyObject_HEAD_INIT(NULL) - NULL, /* m_init */ - 0, /* m_index */ - NULL, /* m_copy */ - }, -#endif - (char *) "vpp_api", - NULL, - -1, - vpp_api_Methods, - NULL, - NULL, - NULL, - NULL - }; -#endif - - /* Ensure threading is initialised */ - if (!PyEval_ThreadsInitialized()) { - PyEval_InitThreads(); - } - -#if PY_VERSION_HEX >= 0x03000000 - return PyModule_Create(&vpp_api_module); -#else - Py_InitModule((char *) "vpp_api", vpp_api_Methods); - return; -#endif -} |