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/api-gen.py | |
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/api-gen.py')
-rwxr-xr-x | vpp-api/python/pneum/api-gen.py | 337 |
1 files changed, 337 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) |