diff options
Diffstat (limited to 'vpp-api/python')
-rw-r--r-- | vpp-api/python/Makefile.am | 49 | ||||
-rw-r--r-- | vpp-api/python/README.rst | 0 | ||||
-rwxr-xr-x | vpp-api/python/pneum/api-gen.py | 337 | ||||
-rw-r--r-- | vpp-api/python/pneum/pneum.c | 239 | ||||
-rw-r--r-- | vpp-api/python/pneum/pneum.h | 24 | ||||
-rw-r--r-- | vpp-api/python/pneum/test_pneum.c | 136 | ||||
-rw-r--r-- | vpp-api/python/setup.py | 21 | ||||
-rwxr-xr-x | vpp-api/python/tests/test_papi.py | 125 | ||||
-rw-r--r-- | vpp-api/python/vpp_papi/__init__.py | 2 | ||||
-rw-r--r-- | vpp-api/python/vpp_papi/pneum_wrap.c | 120 |
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); +} |