Python API: Change from cPython to CFFI. 66/5666/14
authorOle Troan <ot@cisco.com>
Wed, 8 Mar 2017 11:02:24 +0000 (12:02 +0100)
committerOle Troan <ot@cisco.com>
Wed, 15 Mar 2017 20:43:30 +0000 (21:43 +0100)
Change-Id: I03e52466fb3f909ae52b8fba601168f3eadbd972
Signed-off-by: Ole Troan <ot@cisco.com>
16 files changed:
Makefile
build-root/rpm/vpp.spec
src/Makefile.am
src/configure.ac
src/vpp-api.am [new file with mode: 0644]
src/vpp-api/pneum/pneum.c [moved from src/vpp-api/python/pneum/pneum.c with 97% similarity]
src/vpp-api/pneum/pneum.h [moved from src/vpp-api/python/pneum/pneum.h with 84% similarity]
src/vpp-api/pneum/test_pneum.c [moved from src/vpp-api/python/pneum/test_pneum.c with 100% similarity]
src/vpp-api/python/Makefile.am
src/vpp-api/python/setup.cfg
src/vpp-api/python/setup.py
src/vpp-api/python/vpp_papi.py [moved from src/vpp-api/python/vpp_papi/vpp_papi.py with 85% similarity]
src/vpp-api/python/vpp_papi/__init__.py [deleted file]
src/vpp-api/python/vpp_papi/pneum_wrap.c [deleted file]
test/Makefile
test/vpp_papi_provider.py

index ebf94db..f47c98a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -39,7 +39,8 @@ endif
 DEB_DEPENDS  = curl build-essential autoconf automake bison libssl-dev ccache
 DEB_DEPENDS += debhelper dkms git libtool libganglia1-dev libapr1-dev dh-systemd
 DEB_DEPENDS += libconfuse-dev git-review exuberant-ctags cscope pkg-config
-DEB_DEPENDS += python-dev python-virtualenv python-pip lcov chrpath autoconf nasm
+DEB_DEPENDS += lcov chrpath autoconf nasm
+DEB_DEPENDS += python-dev python-virtualenv python-pip libffi6
 ifeq ($(OS_VERSION_ID),14.04)
        DEB_DEPENDS += openjdk-8-jdk-headless
 else
@@ -49,7 +50,7 @@ endif
 RPM_DEPENDS_GROUPS = 'Development Tools'
 RPM_DEPENDS  = redhat-lsb glibc-static java-1.8.0-openjdk-devel yum-utils
 RPM_DEPENDS += openssl-devel https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm apr-devel
-RPM_DEPENDS += python-devel python-virtualenv lcov chrpath
+RPM_DEPENDS += python-devel python-virtualenv lcov chrpath libffi-devel
 RPM_DEPENDS += https://kojipkgs.fedoraproject.org//packages/nasm/2.12.02/2.fc26/x86_64/nasm-2.12.02-2.fc26.x86_64.rpm
 EPEL_DEPENDS = libconfuse-devel ganglia-devel
 
index 7bc18ca..7fa9788 100644 (file)
@@ -162,8 +162,8 @@ do
 done
 
 # Python bindings
-mkdir -p -m755 %{buildroot}%{python2_sitelib}
-install -p -m 666 %{_mu_build_dir}/%{_vpp_install_dir}/*/lib/python2.7/site-packages/vpp_papi-*.egg %{buildroot}%{python2_sitelib}
+cd %{_mu_build_dir}/../src/vpp-api/python
+%py2_install
 
 #
 # devel
@@ -226,15 +226,9 @@ done
 sysctl --system
 %systemd_post vpp.service
 
-%post api-python
-easy_install -z %{python2_sitelib}/vpp_papi-*.egg
-
 %preun
 %systemd_preun vpp.service
 
-%preun api-python
-easy_install -mxNq vpp_papi
-
 %postun
 %systemd_postun
 
@@ -285,7 +279,7 @@ fi
 
 %files api-python
 %defattr(644,root,root)
-%{python2_sitelib}/vpp_papi-*.egg
+%{python2_sitelib}/*
 
 %files devel
 %defattr(-,bin,bin)
index 5daaa48..41076e0 100644 (file)
@@ -72,6 +72,7 @@ include uri.am
 SUBDIRS += plugins
 
 if ENABLE_PAPI
+include vpp-api.am
 SUBDIRS += vpp-api/python
 endif
 
index d90740d..5e02adc 100644 (file)
@@ -201,6 +201,15 @@ AM_COND_IF([ENABLE_JAPI],
   AC_SUBST(JAR)
 ])
 
+###############################################################################
+# PYTHON
+###############################################################################
+
+AM_COND_IF([ENABLE_PAPI],
+[
+  AM_PATH_PYTHON
+])
+
 ###############################################################################
 # Output
 ###############################################################################
diff --git a/src/vpp-api.am b/src/vpp-api.am
new file mode 100644 (file)
index 0000000..0e05d60
--- /dev/null
@@ -0,0 +1,45 @@
+# Copyright (c) 2017 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.
+
+#
+# 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)/libvlibapi.la \
+  $(top_builddir)/libsvm.la \
+  -lpthread -lm -lrt
+
+libpneum_la_LDFLAGS = -module
+libpneum_la_CPPFLAGS =
+
+nobase_include_HEADERS += vpp-api/pneum/pneum.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)/libvlibapi.la \
+  $(top_builddir)/libsvm.la \
+  -lpthread -lm -lrt
+endif
+
+# vi:syntax=automake
similarity index 97%
rename from src/vpp-api/python/pneum/pneum.c
rename to src/vpp-api/pneum/pneum.c
index da9d69d..cbae5cf 100644 (file)
@@ -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);
+}
similarity index 84%
rename from src/vpp-api/python/pneum/pneum.h
rename to src/vpp-api/pneum/pneum.h
index c4b55ae..669298d 100644 (file)
 #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
index 5407682..6f5beb6 100644 (file)
 # 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)
index d645be7..79bc678 100644 (file)
@@ -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
index 8a34d50..28c2ecc 100644 (file)
 # 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,
 )
similarity index 85%
rename from src/vpp-api/python/vpp_papi/vpp_papi.py
rename to src/vpp-api/python/vpp_papi.py
index 0c40f17..81f6903 100644 (file)
@@ -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 (file)
index 6688ffb..0000000
+++ /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 (file)
index c5a7eea..0000000
+++ /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
-}
index 8594633..4338e09 100644 (file)
@@ -30,7 +30,7 @@ UNITTEST_EXTRA_OPTS="-f"
 endif
 
 PYTHON_VENV_PATH=$(VPP_PYTHON_PREFIX)/virtualenv
-PYTHON_DEPENDS=scapy==2.3.3 pexpect subprocess32 git+https://github.com/klement/py-lispnetworking@setup
+PYTHON_DEPENDS=scapy==2.3.3 pexpect subprocess32 cffi git+https://github.com/klement/py-lispnetworking@setup
 SCAPY_SOURCE=$(PYTHON_VENV_PATH)/lib/python2.7/site-packages/
 BUILD_COV_DIR = $(BR)/test-cov
 
index 2d683dc..7f9e2ae 100644 (file)
@@ -55,7 +55,7 @@ class VppPapiProvider(object):
             for filename in fnmatch.filter(filenames, '*.api.json'):
                 jsonfiles.append(os.path.join(root, filename))
 
-        self.vpp = VPP(jsonfiles)
+        self.vpp = VPP(jsonfiles, logger=test_class.logger)
         self._events = deque()
 
     def __enter__(self):