summaryrefslogtreecommitdiffstats
path: root/vpp-api/python/pneum/api-gen.py
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/pneum/api-gen.py
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/pneum/api-gen.py')
-rwxr-xr-xvpp-api/python/pneum/api-gen.py337
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)