diff options
author | Ole Troan <ot@cisco.com> | 2016-04-09 03:16:30 +0200 |
---|---|---|
committer | Dave Wallace <dwallacelf@gmail.com> | 2016-04-20 16:50:29 +0000 |
commit | 6855f6cdfee8c479f1e0ae440ce87a91ff41a708 (patch) | |
tree | e438e21c8107a675dc3a3141c6af6ba0ab458992 /vpp-api/python/pneum | |
parent | 633951c3d8dc1640813b4778f3e35463d08f3795 (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/pneum')
-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 |
4 files changed, 736 insertions, 0 deletions
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); +} |