summaryrefslogtreecommitdiffstats
path: root/vpp-api/python
diff options
context:
space:
mode:
authorOle Troan <ot@cisco.com>2016-04-09 03:16:30 +0200
committerDave Wallace <dwallacelf@gmail.com>2016-04-20 16:50:29 +0000
commit6855f6cdfee8c479f1e0ae440ce87a91ff41a708 (patch)
treee438e21c8107a675dc3a3141c6af6ba0ab458992 /vpp-api/python
parent633951c3d8dc1640813b4778f3e35463d08f3795 (diff)
Python-API: Inital commit of Python bindings for the VPP API.
See: https://wiki.fd.io/view/VPP/Python_API Change-Id: If135fc32208c7031787e1935b399d930e0e1ea1f Signed-off-by: Ole Troan <ot@cisco.com>
Diffstat (limited to 'vpp-api/python')
-rw-r--r--vpp-api/python/Makefile.am49
-rw-r--r--vpp-api/python/README.rst0
-rwxr-xr-xvpp-api/python/pneum/api-gen.py337
-rw-r--r--vpp-api/python/pneum/pneum.c239
-rw-r--r--vpp-api/python/pneum/pneum.h24
-rw-r--r--vpp-api/python/pneum/test_pneum.c136
-rw-r--r--vpp-api/python/setup.py21
-rwxr-xr-xvpp-api/python/tests/test_papi.py125
-rw-r--r--vpp-api/python/vpp_papi/__init__.py2
-rw-r--r--vpp-api/python/vpp_papi/pneum_wrap.c120
10 files changed, 1053 insertions, 0 deletions
diff --git a/vpp-api/python/Makefile.am b/vpp-api/python/Makefile.am
new file mode 100644
index 00000000000..b96ff3d9201
--- /dev/null
+++ b/vpp-api/python/Makefile.am
@@ -0,0 +1,49 @@
+# 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.
+
+AUTOMAKE_OPTIONS = foreign subdir-objects
+ACLOCAL_AMFLAGS = -I m4
+AM_CFLAGS = -Wall
+
+BUILT_SOURCES =
+bin_PROGRAMS =
+CLEANFILES =
+lib_LTLIBRARIES =
+noinst_PROGRAMS = test_pneum
+nobase_include_HEADERS = pneum/pneum.h
+
+#
+# Python binding
+#
+lib_LTLIBRARIES += libpneum.la
+libpneum_la_SOURCES = pneum/pneum.c
+libpneum_la_LIBADD = -lvlibmemoryclient -lvlibapi -lsvm -lvppinfra -lpthread -lm -lrt
+libpneum_la_LDFLAGS = -module
+libpneum_la_CPPFLAGS =
+
+BUILT_SOURCES += vpp_papi.py
+
+vpp_papi.py: $(prefix)/../vpp/api/vpe.api pneum/api-gen.py
+ @echo " PYTHON API"; \
+ $(CC) $(CPPFLAGS) -E -P -C -x c $< \
+ | vppapigen --input - --python defs_$@; \
+ echo "#include <api/vpe_msg_enum.h>" \
+ | $(CC) $(CPPFLAGS) -E -P -x c - | grep VL_API \
+ | @srcdir@/pneum/api-gen.py -i defs_$@ > @srcdir@/vpp_papi/$@
+
+#
+# Test client
+#
+noinst_PROGRAMS += test_pneum
+test_pneum_SOURCES = pneum/pneum.c pneum/test_pneum.c
+test_pneum_LDADD = -lvlibmemoryclient -lvlibapi -lsvm -lvppinfra -lpthread -lm -lrt
diff --git a/vpp-api/python/README.rst b/vpp-api/python/README.rst
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/vpp-api/python/README.rst
diff --git a/vpp-api/python/pneum/api-gen.py b/vpp-api/python/pneum/api-gen.py
new file mode 100755
index 00000000000..bbc9c3290ac
--- /dev/null
+++ b/vpp-api/python/pneum/api-gen.py
@@ -0,0 +1,337 @@
+#!/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.
+#
+
+import argparse, sys, os, importlib, pprint
+
+parser = argparse.ArgumentParser(description='VPP Python API generator')
+parser.add_argument('-i', action="store", dest="inputfile")
+parser.add_argument('-c', '--cfile', action="store")
+args = parser.parse_args()
+
+sys.path.append(".")
+
+inputfile = args.inputfile.replace('.py', '')
+cfg = importlib.import_module(inputfile, package=None)
+
+# https://docs.python.org/3/library/struct.html
+format_struct = {'u8': 'B',
+ 'u16' : 'H',
+ 'u32' : 'I',
+ 'i32' : 'i',
+ 'u64' : 'Q',
+ 'f64' : 'd',
+ 'vl_api_ip4_fib_counter_t' : 'IBQQ',
+ 'vl_api_ip6_fib_counter_t' : 'QQBQQ',
+ };
+type_size = {'u8': 1,
+ 'u16' : 2,
+ 'u32' : 4,
+ 'i32' : 4,
+ 'u64' : 8,
+ 'f64' : 8,
+ 'vl_api_ip4_fib_counter_t' : 21,
+ 'vl_api_ip6_fib_counter_t' : 33,
+};
+
+def get_args(t):
+ args = None
+ for i in t:
+ arg = i[1]
+ arg = arg.replace('_','')
+ if args == None:
+ args = arg
+ continue
+ args = args + ', ' + arg
+ return args
+
+def get_pack(t):
+ bytecount = 0
+ pack = '>'
+ tup = u''
+ j = -1
+ for i in t:
+ j += 1
+ if len(i) is 3:
+ size = type_size[i[0]]
+ bytecount += size * int(i[2])
+ if i[2] == '0':
+ tup += 'msg[' + str(bytecount) + ':],'
+ continue
+ if size == 1:
+ n = i[2] * size
+ pack += str(n) + 's'
+ tup += 'tr[' + str(j) + '],'
+ continue
+ pack += format_struct[i[0]] * int(i[2])
+ tup += 'tr[' + str(j) + ':' + str(j + int(i[2])) + '],'
+ j += int(i[2]) - 1
+ else:
+ bytecount += type_size[i[0]]
+ pack += format_struct[i[0]]
+ tup += 'tr[' + str(j) + '],'
+ return pack, bytecount, tup
+
+def get_reply_func(f):
+ if f['name']+'_reply' in func_name:
+ return func_name[f['name']+'_reply']
+ if f['name'].find('_dump') > 0:
+ r = f['name'].replace('_dump','_details')
+ if r in func_name:
+ return func_name[r]
+ return None
+
+def get_enums():
+ # Read enums from stdin
+ enums_by_name = {}
+ enums_by_index = {}
+ i = 1
+ for l in sys.stdin:
+ l = l.replace(',\n','')
+ print l, "=", i
+
+ l = l.replace('VL_API_','').lower()
+ enums_by_name[l] = i
+ enums_by_index[i] = l
+
+ i += 1
+ return enums_by_name, enums_by_index
+
+def get_definitions():
+ # Pass 1
+ func_list = []
+ func_name = {}
+ i = 1
+ for a in cfg.vppapidef:
+ pack, packlen, tup = get_pack(a[1:])
+ func_name[a[0]] = dict([('name', a[0]), ('args', get_args(a[4:])), ('full_args', get_args(a[1:])), ('pack', pack), ('packlen', packlen), ('tup', tup)])
+ func_list.append(func_name[a[0]]) # Indexed by name
+ return func_list, func_name
+
+def generate_c_macros(func_list, enums_by_name):
+ file = open(args.cfile, 'w+')
+ print >>file, "#define foreach_api_msg \\"
+ for f in func_list:
+ if not f['name'] in enums_by_name:
+ continue
+ print >>file, "_(" + f['name'].upper() + ", " + f['name'] + ") \\"
+ print >>file, '''
+void pneum_set_handlers(void) {
+#define _(N,n) \\
+ api_func_table[VL_API_##N] = sizeof(vl_api_##n##_t);
+ foreach_api_msg;
+#undef _
+}
+ '''
+
+#
+# XXX:Deal with empty arrays
+# Print array with a hash of 'decode' and 'multipart'
+# Simplify to do only decode for now. And deduce multipart from _dump?
+#
+def decode_function_print(name, args, pack, packlen, tup):
+
+ print(u'def ' + name + u'_decode(msg):')
+ print(u" n = namedtuple('" + name + "', '" + args + "')" +
+ '''
+ if not n:
+ return None
+ ''')
+ print(u" tr = unpack('" + pack + "', msg[:" + str(packlen) + "])")
+ print(u" r = n._make((" + tup + "))" +
+ '''
+ if not r:
+ return None
+ return r
+ ''')
+
+def function_print(name, id, args, pack, multipart):
+ if not args:
+ args = ""
+ print "def", name + "(async = False):"
+ else:
+ print "def", name + "(" + args + ",async = False):"
+ print " global waiting_for_reply"
+ print " context = get_context(" + id + ")"
+
+ print '''
+ results[context] = {}
+ results[context]['e'] = threading.Event()
+ results[context]['e'].clear()
+ results[context]['r'] = []
+ waiting_for_reply = True
+ '''
+ if multipart == True:
+ print " results[context]['m'] = True"
+
+ print " vpp_api.write(pack('" + pack + "', " + id + ", 0, context, " + args + "))"
+
+ if multipart == True:
+ print " vpp_api.write(pack('>HII', VL_API_CONTROL_PING, 0, context))"
+
+ print '''
+ if not async:
+ results[context]['e'].wait(5)
+ return results[context]['r']
+ return context
+ '''
+
+#
+# Should dynamically create size
+#
+def api_table_print (name, msg_id):
+ f = name + '_decode'
+ print('api_func_table[' + msg_id + '] = ' + f)
+
+#
+# Generate the main Python file
+#
+
+print '''#!/usr/bin/env python3
+
+import sys, time, threading, signal, os, logging
+from struct import *
+from collections import namedtuple
+
+#
+# Import C API shared object
+#
+import vpp_api
+
+context = 0
+results = {}
+waiting_for_reply = False
+
+#
+# XXX: Make this return a unique number
+#
+def get_context(id):
+ global context
+ context += 1
+ return context
+
+def msg_handler(msg):
+ global result, context, event_callback, waiting_for_reply
+ if not msg:
+ logging.warning('vpp_api.read failed')
+ return
+
+ id = unpack('>H', msg[0:2])
+ logging.debug('Received message', id[0])
+ if id[0] == VL_API_RX_THREAD_EXIT:
+ logging.info("We got told to leave")
+ return;
+
+ #
+ # Decode message and returns a tuple.
+ #
+ logging.debug('api_func', api_func_table[id[0]])
+ r = api_func_table[id[0]](msg)
+ if not r:
+ logging.warning('Message decode failed', id[0])
+ return
+
+ if 'context' in r._asdict():
+ if r.context > 0:
+ context = r.context
+
+ #
+ # XXX: Call provided callback for event
+ # Are we guaranteed to not get an event during processing of other messages?
+ # How to differentiate what's a callback message and what not? Context = 0?
+ #
+ logging.debug('R:', context, r, waiting_for_reply)
+ if waiting_for_reply == False:
+ event_callback(r)
+ return
+
+ #
+ # Collect results until control ping
+ #
+ if id[0] == VL_API_CONTROL_PING_REPLY:
+ results[context]['e'].set()
+ waiting_for_reply = False
+ return
+ if not context in results:
+ logging.warning('Not expecting results for this context', context)
+ return
+ if 'm' in results[context]:
+ results[context]['r'].append(r)
+ return
+
+ results[context]['r'] = r
+ results[context]['e'].set()
+ waiting_for_reply = False
+
+def connect(name):
+ signal.alarm(3) # 3 second
+ rv = vpp_api.connect(name, msg_handler)
+ signal.alarm(0)
+ logging.info("Connect:", rv)
+ return rv
+
+def disconnect():
+ rv = vpp_api.disconnect()
+ logging.info("Disconnected")
+ return rv
+
+def register_event_callback(callback):
+ global event_callback
+ event_callback = callback
+'''
+
+enums_by_name, enums_by_index = get_enums()
+func_list, func_name = get_definitions()
+
+#
+# Not needed with the new msg_size field.
+# generate_c_macros(func_list, enums_by_name)
+#
+
+pp = pprint.PrettyPrinter(indent=4)
+#print 'enums_by_index =', pp.pprint(enums_by_index)
+#print 'func_name =', pp.pprint(func_name)
+
+# Pass 2
+
+#
+# 1) The VPE API lacks a clear definition of what messages are reply messages
+# 2) Length is missing, and has to be pre-known or in case of variable sized ones calculated per message type
+#
+for f in func_list:
+ #if f['name'].find('_reply') > 0 or f['name'].find('_details') > 0:
+ decode_function_print(f['name'], f['full_args'], f['pack'], f['packlen'], f['tup'])
+
+ #r = get_reply_func(f)
+ #if not r:
+ # #
+ # # XXX: Functions here are not taken care of. E.g. events
+ # #
+ # print('Missing function', f)
+ # continue
+
+ if f['name'].find('_dump') > 0:
+ f['multipart'] = True
+ else:
+ f['multipart'] = False
+ msg_id_in = 'VL_API_' + f['name'].upper()
+ function_print(f['name'], msg_id_in, f['args'], f['pack'], f['multipart'])
+
+
+print "api_func_table = [0] * 10000"
+for f in func_list:
+ # if f['name'].find('_reply') > 0 or f['name'].find('_details') > 0:
+ msg_id_in = 'VL_API_' + f['name'].upper()
+ api_table_print(f['name'], msg_id_in)
diff --git a/vpp-api/python/pneum/pneum.c b/vpp-api/python/pneum/pneum.c
new file mode 100644
index 00000000000..971c79bf6aa
--- /dev/null
+++ b/vpp-api/python/pneum/pneum.c
@@ -0,0 +1,239 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <stdbool.h>
+
+#include <vnet/vnet.h>
+#include <vlib/vlib.h>
+#include <vlib/unix/unix.h>
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+
+#include <api/vpe_msg_enum.h>
+
+#include "pneum.h"
+
+#define vl_typedefs /* define message structures */
+#include <api/vpe_all_api_h.h>
+#undef vl_typedefs
+
+#define vl_endianfun /* define message structures */
+#include <api/vpe_all_api_h.h>
+#undef vl_endianfun
+
+typedef struct {
+ u8 rx_thread_jmpbuf_valid;
+ u8 connected_to_vlib;
+ jmp_buf rx_thread_jmpbuf;
+ pthread_t rx_thread_handle;
+} pneum_main_t;
+
+pneum_main_t pneum_main;
+
+extern int wrap_pneum_callback(char *data, int len);
+
+/*
+ * Satisfy external references when -lvlib is not available.
+ */
+void vlib_cli_output (struct vlib_main_t * vm, char * fmt, ...)
+{
+ clib_warning ("vlib_cli_output callled...");
+}
+
+#define vl_api_version(n,v) static u32 vpe_api_version = v;
+#include <api/vpe.api.h>
+#undef vl_api_version
+void
+vl_client_add_api_signatures (vl_api_memclnt_create_t *mp)
+{
+ /*
+ * Send the main API signature in slot 0. This bit of code must
+ * match the checks in ../vpe/api/api.c: vl_msg_api_version_check().
+ */
+ mp->api_versions[0] = clib_host_to_net_u32 (vpe_api_version);
+}
+
+static void
+pneum_api_handler (void *msg)
+{
+ u16 id = ntohs(*((u16 *)msg));
+
+ if (id == VL_API_RX_THREAD_EXIT) {
+ pneum_main_t *pm = &pneum_main;
+ vl_msg_api_free(msg);
+ longjmp(pm->rx_thread_jmpbuf, 1);
+ }
+ 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 */
+ (void)wrap_pneum_callback(msg, l);
+ vl_msg_api_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;
+
+ /* So we can make the rx thread terminate cleanly */
+ if (setjmp(pm->rx_thread_jmpbuf) == 0) {
+ pm->rx_thread_jmpbuf_valid = 1;
+ while (1)
+ while (!unix_shared_memory_queue_sub(q, (u8 *)&msg, 0))
+ pneum_api_handler((void *)msg);
+ }
+ pthread_exit(0);
+}
+
+int
+pneum_connect (char *name)
+{
+ int rv = 0;
+ pneum_main_t *pm = &pneum_main;
+
+ /*
+ * Bail out now if we're not running as root
+ */
+ if (geteuid() != 0)
+ return (-1);
+
+ 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, 32) < 0) {
+ vl_client_api_unmap();
+ return (-1);
+ }
+
+ /* 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);
+ }
+
+ pm->connected_to_vlib = 1;
+
+ return (0);
+}
+
+int
+pneum_disconnect (void)
+{
+ api_main_t *am = &api_main;
+ pneum_main_t *pm = &pneum_main;
+
+ fformat (stdout,"disconnecting from vpe \n");
+
+ if (pm->rx_thread_jmpbuf_valid) {
+ 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);
+ pthread_join(pm->rx_thread_handle, (void **) &junk);
+ }
+ if (pm->connected_to_vlib) {
+ vl_client_disconnect();
+ vl_client_api_unmap();
+ }
+ memset (pm, 0, sizeof (*pm));
+
+ return (0);
+}
+
+int
+pneum_read (char **p, int *l)
+{
+ unix_shared_memory_queue_t *q;
+ api_main_t *am = &api_main;
+ uword msg;
+
+ *l = 0;
+
+ if (am->our_pid == 0) return (-1);
+
+ 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));
+ msgbuf_t *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);
+ return (-1);
+ }
+ *p = (char *)msg;
+ } else {
+ printf("Read failed with %d\n", rv);
+ }
+ return (rv);
+}
+
+/*
+ * 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;
+
+ 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) {
+ printf("vpe_api_write fails: %d\n", rv);
+ /* Clear message */
+ vl_msg_api_free(mp);
+ }
+ return (rv);
+}
diff --git a/vpp-api/python/pneum/pneum.h b/vpp-api/python/pneum/pneum.h
new file mode 100644
index 00000000000..b99cbd4e3ec
--- /dev/null
+++ b/vpp-api/python/pneum/pneum.h
@@ -0,0 +1,24 @@
+/*
+ * 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__
+
+unsigned int vpe_client_index(void);
+int pneum_connect(char *name);
+int pneum_disconnect(void);
+int pneum_read(char **data, int *l);
+int pneum_write(char *data, int len);
+
+#endif
diff --git a/vpp-api/python/pneum/test_pneum.c b/vpp-api/python/pneum/test_pneum.c
new file mode 100644
index 00000000000..18627b3f2c5
--- /dev/null
+++ b/vpp-api/python/pneum/test_pneum.c
@@ -0,0 +1,136 @@
+/*
+ *------------------------------------------------------------------
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <time.h> /* time_t, time (for timestamp in second) */
+#include <sys/timeb.h> /* ftime, timeb (for timestamp in millisecond) */
+#include <sys/time.h> /* gettimeofday, timeval (for timestamp in microsecond) */
+
+#include <vnet/vnet.h>
+#include <vlib/vlib.h>
+#include <vlib/unix/unix.h>
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+#include <vnet/ip/ip.h>
+
+#include <api/vpe_msg_enum.h>
+#include <signal.h>
+#include <setjmp.h>
+#include "pneum.h"
+
+#define vl_typedefs /* define message structures */
+#include <api/vpe_all_api_h.h>
+#undef vl_typedefs
+
+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");
+
+ 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));
+ if (!async)
+ while (result_ready == 0);
+ }
+ 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/vpp-api/python/setup.py b/vpp-api/python/setup.py
new file mode 100644
index 00000000000..d890ba709dc
--- /dev/null
+++ b/vpp-api/python/setup.py
@@ -0,0 +1,21 @@
+from distutils.core import setup, Extension
+
+module1 = Extension('vpp_api',
+ define_macros = [('MAJOR_VERSION', '1'),
+ ('MINOR_VERSION', '0')],
+ include_dirs = ['pneum'],
+ libraries = ['pneum'],
+ library_dirs = ['../../build-root/install-vpp_debug-native/vpp-api/lib64'],
+ sources = ['vpp_papi/pneum_wrap.c'])
+
+setup (name = 'vpp_papi',
+ version = '1.0',
+ description = 'VPP Python binding',
+ author = 'Ole Troan',
+ author_email = 'ot@cisco.com',
+ #url = 'https://docs.python.org/extending/building',
+ packages=['vpp_papi'],
+ long_description = '''
+VPP Python language binding.
+''',
+ ext_modules = [module1])
diff --git a/vpp-api/python/tests/test_papi.py b/vpp-api/python/tests/test_papi.py
new file mode 100755
index 00000000000..ec51f0936cb
--- /dev/null
+++ b/vpp-api/python/tests/test_papi.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+
+import vpp_papi
+import unittest, sys, time, threading, struct, logging
+from ipaddress import *
+
+papi_event = threading.Event()
+def papi_event_handler(result):
+ if result.vlmsgid == vpp_papi.VL_API_SW_INTERFACE_SET_FLAGS:
+ papi_event.set()
+ return
+ if result.vlmsgid == vpp_papi.VL_API_VNET_INTERFACE_COUNTERS:
+ format = '>' + str(int(len(result.data) / 8)) + 'Q'
+ counters = struct.unpack(format, result.data)
+ print('Counters:', counters)
+ return
+
+ print('Unknown message id:', result.vlmsgid)
+
+class TestPAPI(unittest.TestCase):
+
+ def setUp(self):
+ r = vpp_papi.connect("test_papi")
+ self.assertEqual(r, 0)
+
+ def tearDown(self):
+ r = vpp_papi.disconnect()
+ self.assertEqual(r, 0)
+
+ def test_show_version(self):
+ t = vpp_papi.show_version()
+ program = t.program.decode().rstrip('\x00')
+ self.assertEqual('vpe', program)
+
+ #
+ # Add a few MAP domains, then dump them later
+ #
+ def test_map(self):
+ t = vpp_papi.map_summary_stats()
+ print(t)
+ ip6 = IPv6Address(u'2001:db8::1').packed
+ ip4 = IPv4Address(u'10.0.0.0').packed
+ ip6_src = IPv6Address(u'2001:db9::1').packed
+ t = vpp_papi.map_add_domain(ip6, ip4, ip6_src, 32, 24, 128, 0, 0, 6, 0, 0)
+ print(t)
+ self.assertEqual(t.retval, 0)
+
+ ip4 = IPv4Address(u'10.0.1.0').packed
+ t = vpp_papi.map_add_domain(ip6, ip4, ip6_src, 32, 24, 128, 0, 0, 6, 0, 0)
+ print(t)
+ self.assertEqual(t.retval, 0)
+
+ t = vpp_papi.map_summary_stats()
+ print(t)
+ self.assertEqual(t.totalbindings, 2)
+
+ t = vpp_papi.map_domain_dump()
+ print (t)
+ self.assertEqual(len(t), 2)
+
+ def test_sw_interface_dump(self):
+ #
+ # Dump interfaces
+ #
+ t = vpp_papi.sw_interface_dump(0, b'ignored')
+ for interface in t:
+ if interface.vlmsgid == vpp_papi.VL_API_SW_INTERFACE_DETAILS:
+ print(interface.interfacename.decode())
+
+ def test_want_interface_events(self):
+ pid = 123
+ vpp_papi.register_event_callback(papi_event_handler)
+ papi_event.clear()
+ t = vpp_papi.want_interface_events(True, pid)
+ print (t)
+ print('Setting interface up')
+ t = vpp_papi.sw_interface_set_flags(0, 1, 1, 0)
+ print (t)
+ self.assertEqual(papi_event.wait(5), True)
+ t = vpp_papi.sw_interface_set_flags(0, 0, 0, 0)
+ print (t)
+ self.assertEqual(papi_event.wait(5), True)
+
+ @unittest.skip("not quite ready yet")
+ def test_want_stats(self):
+ pid = 123
+ vpp_papi.register_event_callback(papi_event_handler)
+ papi_event.clear()
+ t = vpp_papi.want_stats(True, pid)
+
+ print (t)
+
+ #
+ # Wait for some stats
+ #
+ self.assertEqual(papi_event.wait(30), True)
+ t = vpp_papi.want_stats(False, pid)
+ print (t)
+
+ def test_tap(self):
+ pid = 123
+ vpp_papi.register_event_callback(papi_event_handler)
+ papi_event.clear()
+ t = vpp_papi.want_stats(True, pid)
+
+ print (t)
+
+ t = vpp_papi.tap_connect(1, b'tap', b'foo', 1, 0)
+ print (t)
+ self.assertEqual(t.retval, 0)
+ swifindex = t.swifindex
+
+ t = vpp_papi.sw_interface_set_flags(swifindex, 1, 1, 0)
+ print (t)
+ self.assertEqual(t.retval, 0)
+
+ ip6 = IPv6Address(u'2001:db8::1').packed
+ t = vpp_papi.sw_interface_add_del_address(swifindex, 1, 1, 0, 16, ip6)
+ print (t)
+ time.sleep(40)
+
+
+if __name__ == '__main__':
+ #logging.basicConfig(level=logging.DEBUG)
+ unittest.main()
diff --git a/vpp-api/python/vpp_papi/__init__.py b/vpp-api/python/vpp_papi/__init__.py
new file mode 100644
index 00000000000..8be644d7cb7
--- /dev/null
+++ b/vpp-api/python/vpp_papi/__init__.py
@@ -0,0 +1,2 @@
+__import__('pkg_resources').declare_namespace(__name__)
+from .vpp_papi import *
diff --git a/vpp-api/python/vpp_papi/pneum_wrap.c b/vpp-api/python/vpp_papi/pneum_wrap.c
new file mode 100644
index 00000000000..09d972d447c
--- /dev/null
+++ b/vpp-api/python/vpp_papi/pneum_wrap.c
@@ -0,0 +1,120 @@
+#include <Python.h>
+#include "pneum.h"
+
+static PyObject *pneum_callback = NULL;
+
+int
+wrap_pneum_callback (char *data, int len)
+{
+ PyGILState_STATE gstate;
+ PyObject *result;//, *arglist;
+
+ gstate = PyGILState_Ensure();
+
+ /* Time to call the callback */
+ result = PyObject_CallFunction(pneum_callback, "y#", data, len);
+ if (result)
+ Py_DECREF(result);
+ else
+ PyErr_Print();
+
+ PyGILState_Release(gstate);
+ return (0);
+}
+
+static PyObject *
+wrap_connect (PyObject *self, PyObject *args)
+{
+ char *name;
+ int rv;
+ PyObject *temp;
+
+ if (!PyArg_ParseTuple(args, "sO:set_callback", &name, &temp))
+ return (NULL);
+
+ 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 */
+
+ Py_BEGIN_ALLOW_THREADS
+ rv = pneum_connect(name);
+ 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);
+}
+
+void vl_msg_api_free(void *);
+
+static PyObject *
+wrap_read (PyObject *self, PyObject *args)
+{
+ char *data;
+ int len, rv;
+
+ Py_BEGIN_ALLOW_THREADS
+ rv = pneum_read(&data, &len);
+ Py_END_ALLOW_THREADS
+
+ if (rv != 0) { Py_RETURN_NONE; }
+
+ PyObject *ret = Py_BuildValue("y#", data, len);
+ if (!ret) { Py_RETURN_NONE; }
+
+ vl_msg_api_free(data);
+ return ret;
+}
+
+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."},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+static struct PyModuleDef vpp_api_module = {
+ PyModuleDef_HEAD_INIT,
+ "vpp_api", /* name of module */
+ NULL, /* module documentation, may be NULL */
+ -1, /* size of per-interpreter state of the module,
+ or -1 if the module keeps state in global variables. */
+ vpp_api_Methods
+};
+
+PyMODINIT_FUNC
+PyInit_vpp_api (void)
+{
+ /* Ensure threading is initialised */
+ if (!PyEval_ThreadsInitialized()) {
+ PyEval_InitThreads();
+ }
+ return PyModule_Create(&vpp_api_module);
+}