blob: 55dda1044b6e46e1c0df93c9692a72bd9677c507 [file] [log] [blame]
Ole Troana03f4ef2016-12-02 12:53:55 +01001#!/usr/bin/env python
Ole Troan5f9dcff2016-08-01 04:59:13 +02002#
3# Copyright (c) 2016 Cisco and/or its affiliates.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at:
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
Ole Troana03f4ef2016-12-02 12:53:55 +010015#
Ole Troan5f9dcff2016-08-01 04:59:13 +020016
Ole Troan5f9dcff2016-08-01 04:59:13 +020017from __future__ import print_function
Ole Troan4df97162017-07-07 16:06:08 +020018import sys
19import os
20import logging
21import collections
22import struct
23import json
24import threading
25import glob
26import atexit
Ole Troan3cc49712017-03-08 12:02:24 +010027from cffi import FFI
Ole Troan4df97162017-07-07 16:06:08 +020028
29if sys.version[0] == '2':
30 import Queue as queue
31else:
32 import queue as queue
33
Ole Troan3cc49712017-03-08 12:02:24 +010034ffi = FFI()
35ffi.cdef("""
Damjan Marion5fec1e82017-04-13 19:13:47 +020036typedef void (*vac_callback_t)(unsigned char * data, int len);
37typedef void (*vac_error_callback_t)(void *, unsigned char *, int);
38int vac_connect(char * name, char * chroot_prefix, vac_callback_t cb,
Ole Troan3cc49712017-03-08 12:02:24 +010039 int rx_qlen);
Damjan Marion5fec1e82017-04-13 19:13:47 +020040int vac_disconnect(void);
41int vac_read(char **data, int *l, unsigned short timeout);
42int vac_write(char *data, int len);
43void vac_free(void * msg);
Ole Troan5f9dcff2016-08-01 04:59:13 +020044
Damjan Marion5fec1e82017-04-13 19:13:47 +020045int vac_get_msg_index(unsigned char * name);
46int vac_msg_table_size(void);
47int vac_msg_table_max_index(void);
Ole Troan3cc49712017-03-08 12:02:24 +010048
Damjan Marion5fec1e82017-04-13 19:13:47 +020049void vac_rx_suspend (void);
50void vac_rx_resume (void);
51void vac_set_error_handler(vac_error_callback_t);
Ole Troan3cc49712017-03-08 12:02:24 +010052 """)
53
54# Barfs on failure, no need to check success.
Damjan Marion5fec1e82017-04-13 19:13:47 +020055vpp_api = ffi.dlopen('libvppapiclient.so')
Ole Troan1732fc12016-08-30 21:03:51 +020056
Ole Troan4df97162017-07-07 16:06:08 +020057
Ole Troan5016f992017-01-19 09:44:44 +010058def vpp_atexit(self):
59 """Clean up VPP connection on shutdown."""
60 if self.connected:
Ole Troan3cc49712017-03-08 12:02:24 +010061 self.logger.debug('Cleaning up VPP on exit')
Ole Troan5016f992017-01-19 09:44:44 +010062 self.disconnect()
63
Ole Troan3cc49712017-03-08 12:02:24 +010064vpp_object = None
65
Ole Troan4df97162017-07-07 16:06:08 +020066
67def vpp_iterator(d):
68 if sys.version[0] == '2':
69 return d.iteritems()
70 else:
71 return d.items()
72
73
Ole Troan3cc49712017-03-08 12:02:24 +010074@ffi.callback("void(unsigned char *, int)")
Damjan Marion5fec1e82017-04-13 19:13:47 +020075def vac_callback_sync(data, len):
Ole Troan3cc49712017-03-08 12:02:24 +010076 vpp_object.msg_handler_sync(ffi.buffer(data, len))
Ole Troan4df97162017-07-07 16:06:08 +020077
78
Ole Troan3cc49712017-03-08 12:02:24 +010079@ffi.callback("void(unsigned char *, int)")
Damjan Marion5fec1e82017-04-13 19:13:47 +020080def vac_callback_async(data, len):
Ole Troan3cc49712017-03-08 12:02:24 +010081 vpp_object.msg_handler_async(ffi.buffer(data, len))
Ole Troan4df97162017-07-07 16:06:08 +020082
83
Ole Troan3cc49712017-03-08 12:02:24 +010084@ffi.callback("void(void *, unsigned char *, int)")
Damjan Marion5fec1e82017-04-13 19:13:47 +020085def vac_error_handler(arg, msg, msg_len):
Ole Troan4df97162017-07-07 16:06:08 +020086 vpp_object.logger.warning("VPP API client:: %s", ffi.string(msg, msg_len))
87
Klement Sekera7112c542017-03-01 09:53:19 +010088
89class Empty(object):
90 pass
91
92
93class FuncWrapper(object):
94 def __init__(self, func):
95 self._func = func
96 self.__name__ = func.__name__
97
98 def __call__(self, **kwargs):
99 return self._func(**kwargs)
100
101
Ole Troana03f4ef2016-12-02 12:53:55 +0100102class VPP():
Ole Troan5016f992017-01-19 09:44:44 +0100103 """VPP interface.
104
105 This class provides the APIs to VPP. The APIs are loaded
106 from provided .api.json files and makes functions accordingly.
107 These functions are documented in the VPP .api files, as they
108 are dynamically created.
109
110 Additionally, VPP can send callback messages; this class
111 provides a means to register a callback function to receive
112 these messages in a background thread.
113 """
Ole Troan4df97162017-07-07 16:06:08 +0200114 def __init__(self, apifiles=None, testmode=False, async_thread=True,
115 logger=logging.getLogger('vpp_papi'), loglevel='debug'):
Ole Troan5016f992017-01-19 09:44:44 +0100116 """Create a VPP API object.
117
118 apifiles is a list of files containing API
119 descriptions that will be loaded - methods will be
120 dynamically created reflecting these APIs. If not
121 provided this will load the API files from VPP's
122 default install location.
123 """
Ole Troan3cc49712017-03-08 12:02:24 +0100124 global vpp_object
125 vpp_object = self
126 self.logger = logger
127 logging.basicConfig(level=getattr(logging, loglevel.upper()))
128
Ole Troana03f4ef2016-12-02 12:53:55 +0100129 self.messages = {}
130 self.id_names = []
131 self.id_msgdef = []
132 self.buffersize = 10000
133 self.connected = False
134 self.header = struct.Struct('>HI')
Ole Troan5016f992017-01-19 09:44:44 +0100135 self.apifiles = []
Ole Troan3d31f002017-01-26 11:13:00 +0100136 self.event_callback = None
Ole Troan4df97162017-07-07 16:06:08 +0200137 self.message_queue = queue.Queue()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100138 self.read_timeout = 0
139 self.vpp_api = vpp_api
140 if async_thread:
Ole Troan4df97162017-07-07 16:06:08 +0200141 self.event_thread = threading.Thread(
142 target=self.thread_msg_handler)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100143 self.event_thread.daemon = True
144 self.event_thread.start()
Ole Troan5f9dcff2016-08-01 04:59:13 +0200145
Ole Troanf5984bd2016-12-18 13:15:08 +0100146 if not apifiles:
147 # Pick up API definitions from default directory
148 apifiles = glob.glob('/usr/share/vpp/api/*.api.json')
149
Ole Troana03f4ef2016-12-02 12:53:55 +0100150 for file in apifiles:
Ole Troana03f4ef2016-12-02 12:53:55 +0100151 with open(file) as apidef_file:
152 api = json.load(apidef_file)
153 for t in api['types']:
154 self.add_type(t[0], t[1:])
Ole Troan5f9dcff2016-08-01 04:59:13 +0200155
Ole Troana03f4ef2016-12-02 12:53:55 +0100156 for m in api['messages']:
157 self.add_message(m[0], m[1:])
Ole Troan4df97162017-07-07 16:06:08 +0200158 self.apifiles = apifiles
Ole Troan5f9dcff2016-08-01 04:59:13 +0200159
Ole Troana03f4ef2016-12-02 12:53:55 +0100160 # Basic sanity check
Ole Troanf5984bd2016-12-18 13:15:08 +0100161 if len(self.messages) == 0 and not testmode:
162 raise ValueError(1, 'Missing JSON message definitions')
Ole Troan5f9dcff2016-08-01 04:59:13 +0200163
Ole Troan5016f992017-01-19 09:44:44 +0100164 # Make sure we allow VPP to clean up the message rings.
165 atexit.register(vpp_atexit, self)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200166
Ole Troan3cc49712017-03-08 12:02:24 +0100167 # Register error handler
Damjan Marion5fec1e82017-04-13 19:13:47 +0200168 vpp_api.vac_set_error_handler(vac_error_handler)
Ole Troan3cc49712017-03-08 12:02:24 +0100169
Ole Troana03f4ef2016-12-02 12:53:55 +0100170 class ContextId(object):
Ole Troan5016f992017-01-19 09:44:44 +0100171 """Thread-safe provider of unique context IDs."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100172 def __init__(self):
173 self.context = 0
Ole Troan4df97162017-07-07 16:06:08 +0200174 self.lock = threading.Lock()
175
Ole Troana03f4ef2016-12-02 12:53:55 +0100176 def __call__(self):
Ole Troan5016f992017-01-19 09:44:44 +0100177 """Get a new unique (or, at least, not recently used) context."""
Ole Troan4df97162017-07-07 16:06:08 +0200178 with self.lock:
179 self.context += 1
180 return self.context
Ole Troana03f4ef2016-12-02 12:53:55 +0100181 get_context = ContextId()
Ole Troan5f9dcff2016-08-01 04:59:13 +0200182
Ole Troana03f4ef2016-12-02 12:53:55 +0100183 def status(self):
Ole Troan5016f992017-01-19 09:44:44 +0100184 """Debug function: report current VPP API status to stdout."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100185 print('Connected') if self.connected else print('Not Connected')
Ole Troan5016f992017-01-19 09:44:44 +0100186 print('Read API definitions from', ', '.join(self.apifiles))
Ole Troan5f9dcff2016-08-01 04:59:13 +0200187
Ole Troan4df97162017-07-07 16:06:08 +0200188 def __struct(self, t, n=None, e=-1, vl=None):
Ole Troan5016f992017-01-19 09:44:44 +0100189 """Create a packing structure for a message."""
Ole Troan4df97162017-07-07 16:06:08 +0200190 base_types = {'u8': 'B',
191 'u16': 'H',
192 'u32': 'I',
193 'i32': 'i',
194 'u64': 'Q',
195 'f64': 'd', }
Ole Troana03f4ef2016-12-02 12:53:55 +0100196 pack = None
197 if t in base_types:
198 pack = base_types[t]
199 if not vl:
Ole Troanf5984bd2016-12-18 13:15:08 +0100200 if e > 0 and t == 'u8':
Ole Troana03f4ef2016-12-02 12:53:55 +0100201 # Fixed byte array
202 return struct.Struct('>' + str(e) + 's')
Ole Troanf5984bd2016-12-18 13:15:08 +0100203 if e > 0:
Ole Troana03f4ef2016-12-02 12:53:55 +0100204 # Fixed array of base type
205 return [e, struct.Struct('>' + base_types[t])]
Ole Troanf5984bd2016-12-18 13:15:08 +0100206 elif e == 0:
207 # Old style variable array
208 return [-1, struct.Struct('>' + base_types[t])]
Ole Troana03f4ef2016-12-02 12:53:55 +0100209 else:
210 # Variable length array
Ole Troanf5984bd2016-12-18 13:15:08 +0100211 return [vl, struct.Struct('>s')] if t == 'u8' else \
212 [vl, struct.Struct('>' + base_types[t])]
Ole Troan57c3d662016-09-12 22:00:32 +0200213
Ole Troana03f4ef2016-12-02 12:53:55 +0100214 return struct.Struct('>' + base_types[t])
Ole Troan57c3d662016-09-12 22:00:32 +0200215
Ole Troana03f4ef2016-12-02 12:53:55 +0100216 if t in self.messages:
Ole Troan4df97162017-07-07 16:06:08 +0200217 # Return a list in case of array
Ole Troanf5984bd2016-12-18 13:15:08 +0100218 if e > 0 and not vl:
Ole Troana03f4ef2016-12-02 12:53:55 +0100219 return [e, lambda self, encode, buf, offset, args: (
Ole Troanf5984bd2016-12-18 13:15:08 +0100220 self.__struct_type(encode, self.messages[t], buf, offset,
221 args))]
Ole Troana03f4ef2016-12-02 12:53:55 +0100222 if vl:
223 return [vl, lambda self, encode, buf, offset, args: (
Ole Troanf5984bd2016-12-18 13:15:08 +0100224 self.__struct_type(encode, self.messages[t], buf, offset,
225 args))]
226 elif e == 0:
227 # Old style VLA
Ole Troan4df97162017-07-07 16:06:08 +0200228 raise NotImplementedError(1,
229 'No support for compound types ' + t)
Ole Troana03f4ef2016-12-02 12:53:55 +0100230 return lambda self, encode, buf, offset, args: (
231 self.__struct_type(encode, self.messages[t], buf, offset, args)
232 )
Ole Troanb8602b52016-10-05 11:10:50 +0200233
Ole Troanf5984bd2016-12-18 13:15:08 +0100234 raise ValueError(1, 'Invalid message type: ' + t)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200235
Ole Troana03f4ef2016-12-02 12:53:55 +0100236 def __struct_type(self, encode, msgdef, buf, offset, kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100237 """Get a message packer or unpacker."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100238 if encode:
239 return self.__struct_type_encode(msgdef, buf, offset, kwargs)
240 else:
241 return self.__struct_type_decode(msgdef, buf, offset)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200242
Ole Troana03f4ef2016-12-02 12:53:55 +0100243 def __struct_type_encode(self, msgdef, buf, offset, kwargs):
244 off = offset
245 size = 0
Ole Troan7e3a8752016-12-05 10:27:09 +0100246
247 for k in kwargs:
248 if k not in msgdef['args']:
249 raise ValueError(1, 'Invalid field-name in message call ' + k)
250
Ole Troan4df97162017-07-07 16:06:08 +0200251 for k, v in vpp_iterator(msgdef['args']):
Ole Troana03f4ef2016-12-02 12:53:55 +0100252 off += size
253 if k in kwargs:
254 if type(v) is list:
255 if callable(v[1]):
Ole Troanf5984bd2016-12-18 13:15:08 +0100256 e = kwargs[v[0]] if v[0] in kwargs else v[0]
Ole Troana03f4ef2016-12-02 12:53:55 +0100257 size = 0
258 for i in range(e):
259 size += v[1](self, True, buf, off + size,
260 kwargs[k][i])
261 else:
262 if v[0] in kwargs:
263 l = kwargs[v[0]]
264 else:
265 l = len(kwargs[k])
266 if v[1].size == 1:
267 buf[off:off + l] = bytearray(kwargs[k])
268 size = l
269 else:
270 size = 0
271 for i in kwargs[k]:
272 v[1].pack_into(buf, off + size, i)
273 size += v[1].size
274 else:
275 if callable(v):
276 size = v(self, True, buf, off, kwargs[k])
277 else:
278 v.pack_into(buf, off, kwargs[k])
279 size = v.size
280 else:
Ole Troan7e3a8752016-12-05 10:27:09 +0100281 size = v.size if not type(v) is list else 0
Ole Troan5f9dcff2016-08-01 04:59:13 +0200282
Ole Troana03f4ef2016-12-02 12:53:55 +0100283 return off + size - offset
Ole Troan5f9dcff2016-08-01 04:59:13 +0200284
Ole Troana03f4ef2016-12-02 12:53:55 +0100285 def __getitem__(self, name):
286 if name in self.messages:
287 return self.messages[name]
288 return None
Ole Troan5f9dcff2016-08-01 04:59:13 +0200289
Ole Troana03f4ef2016-12-02 12:53:55 +0100290 def encode(self, msgdef, kwargs):
291 # Make suitably large buffer
292 buf = bytearray(self.buffersize)
293 offset = 0
294 size = self.__struct_type(True, msgdef, buf, offset, kwargs)
295 return buf[:offset + size]
296
297 def decode(self, msgdef, buf):
298 return self.__struct_type(False, msgdef, buf, 0, None)[1]
299
300 def __struct_type_decode(self, msgdef, buf, offset):
301 res = []
302 off = offset
303 size = 0
Ole Troan4df97162017-07-07 16:06:08 +0200304 for k, v in vpp_iterator(msgdef['args']):
Ole Troana03f4ef2016-12-02 12:53:55 +0100305 off += size
306 if type(v) is list:
307 lst = []
Ole Troan4df97162017-07-07 16:06:08 +0200308 if callable(v[1]): # compound type
Ole Troana03f4ef2016-12-02 12:53:55 +0100309 size = 0
Ole Troan4df97162017-07-07 16:06:08 +0200310 if v[0] in msgdef['args']: # vla
Ole Troanf5984bd2016-12-18 13:15:08 +0100311 e = res[v[2]]
Ole Troan4df97162017-07-07 16:06:08 +0200312 else: # fixed array
Ole Troana03f4ef2016-12-02 12:53:55 +0100313 e = v[0]
314 res.append(lst)
315 for i in range(e):
Ole Troan4df97162017-07-07 16:06:08 +0200316 (s, l) = v[1](self, False, buf, off + size, None)
Ole Troana03f4ef2016-12-02 12:53:55 +0100317 lst.append(l)
318 size += s
319 continue
Ole Troana03f4ef2016-12-02 12:53:55 +0100320 if v[1].size == 1:
Ole Troanf5984bd2016-12-18 13:15:08 +0100321 if type(v[0]) is int:
322 size = len(buf) - off
323 else:
324 size = res[v[2]]
Ole Troana03f4ef2016-12-02 12:53:55 +0100325 res.append(buf[off:off + size])
326 else:
Ole Troanf5984bd2016-12-18 13:15:08 +0100327 e = v[0] if type(v[0]) is int else res[v[2]]
328 if e == -1:
329 e = (len(buf) - off) / v[1].size
Ole Troana03f4ef2016-12-02 12:53:55 +0100330 lst = []
331 res.append(lst)
332 size = 0
333 for i in range(e):
334 lst.append(v[1].unpack_from(buf, off + size)[0])
335 size += v[1].size
336 else:
337 if callable(v):
Ole Troan4df97162017-07-07 16:06:08 +0200338 (s, l) = v(self, False, buf, off, None)
Ole Troana03f4ef2016-12-02 12:53:55 +0100339 res.append(l)
340 size += s
341 else:
342 res.append(v.unpack_from(buf, off)[0])
343 size = v.size
344
345 return off + size - offset, msgdef['return_tuple']._make(res)
346
347 def ret_tup(self, name):
348 if name in self.messages and 'return_tuple' in self.messages[name]:
349 return self.messages[name]['return_tuple']
350 return None
351
Ole Troan4df97162017-07-07 16:06:08 +0200352 def add_message(self, name, msgdef, typeonly=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100353 if name in self.messages:
354 raise ValueError('Duplicate message name: ' + name)
355
356 args = collections.OrderedDict()
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100357 argtypes = collections.OrderedDict()
Ole Troana03f4ef2016-12-02 12:53:55 +0100358 fields = []
359 msg = {}
Ole Troanf5984bd2016-12-18 13:15:08 +0100360 for i, f in enumerate(msgdef):
Ole Troana03f4ef2016-12-02 12:53:55 +0100361 if type(f) is dict and 'crc' in f:
362 msg['crc'] = f['crc']
363 continue
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100364 field_type = f[0]
Ole Troana03f4ef2016-12-02 12:53:55 +0100365 field_name = f[1]
Ole Troanf5984bd2016-12-18 13:15:08 +0100366 if len(f) == 3 and f[2] == 0 and i != len(msgdef) - 2:
367 raise ValueError('Variable Length Array must be last: ' + name)
Ole Troana03f4ef2016-12-02 12:53:55 +0100368 args[field_name] = self.__struct(*f)
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100369 argtypes[field_name] = field_type
Ole Troan4df97162017-07-07 16:06:08 +0200370 if len(f) == 4: # Find offset to # elements field
371 idx = list(args.keys()).index(f[3]) - i
372 args[field_name].append(idx)
Ole Troana03f4ef2016-12-02 12:53:55 +0100373 fields.append(field_name)
374 msg['return_tuple'] = collections.namedtuple(name, fields,
Ole Troan4df97162017-07-07 16:06:08 +0200375 rename=True)
Ole Troana03f4ef2016-12-02 12:53:55 +0100376 self.messages[name] = msg
377 self.messages[name]['args'] = args
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100378 self.messages[name]['argtypes'] = argtypes
Ole Troan3cc49712017-03-08 12:02:24 +0100379 self.messages[name]['typeonly'] = typeonly
Ole Troana03f4ef2016-12-02 12:53:55 +0100380 return self.messages[name]
381
382 def add_type(self, name, typedef):
Ole Troan4df97162017-07-07 16:06:08 +0200383 return self.add_message('vl_api_' + name + '_t', typedef,
384 typeonly=True)
Ole Troana03f4ef2016-12-02 12:53:55 +0100385
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100386 def make_function(self, name, i, msgdef, multipart, async):
Wojciech Dec64bc6122016-12-12 11:32:25 +0100387 if (async):
Ole Troan5016f992017-01-19 09:44:44 +0100388 f = lambda **kwargs: (self._call_vpp_async(i, msgdef, **kwargs))
Wojciech Dec64bc6122016-12-12 11:32:25 +0100389 else:
Ole Troan4df97162017-07-07 16:06:08 +0200390 f = lambda **kwargs: (self._call_vpp(i, msgdef, multipart,
391 **kwargs))
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100392 args = self.messages[name]['args']
393 argtypes = self.messages[name]['argtypes']
394 f.__name__ = str(name)
Ole Troan4df97162017-07-07 16:06:08 +0200395 f.__doc__ = ", ".join(["%s %s" %
396 (argtypes[k], k) for k in args.keys()])
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100397 return f
Ole Troana03f4ef2016-12-02 12:53:55 +0100398
Klement Sekera7112c542017-03-01 09:53:19 +0100399 @property
400 def api(self):
401 if not hasattr(self, "_api"):
402 raise Exception("Not connected, api definitions not available")
403 return self._api
404
Wojciech Dec64bc6122016-12-12 11:32:25 +0100405 def _register_functions(self, async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100406 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
407 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera7112c542017-03-01 09:53:19 +0100408 self._api = Empty()
Ole Troan4df97162017-07-07 16:06:08 +0200409 for name, msgdef in vpp_iterator(self.messages):
410 if self.messages[name]['typeonly']:
411 continue
Ole Troan3cc49712017-03-08 12:02:24 +0100412 crc = self.messages[name]['crc']
413 n = name + '_' + crc[2:]
Ole Troan4df97162017-07-07 16:06:08 +0200414 i = vpp_api.vac_get_msg_index(n.encode())
Ole Troan3cc49712017-03-08 12:02:24 +0100415 if i > 0:
Ole Troana03f4ef2016-12-02 12:53:55 +0100416 self.id_msgdef[i] = msgdef
417 self.id_names[i] = name
418 multipart = True if name.find('_dump') > 0 else False
Klement Sekera7112c542017-03-01 09:53:19 +0100419 f = self.make_function(name, i, msgdef, multipart, async)
420 setattr(self._api, name, FuncWrapper(f))
421
Ole Troandfc9b7c2017-03-06 23:51:57 +0100422 # old API stuff starts here - will be removed in 17.07
Klement Sekera7112c542017-03-01 09:53:19 +0100423 if hasattr(self, name):
424 raise NameError(
425 3, "Conflicting name in JSON definition: `%s'" % name)
426 setattr(self, name, f)
427 # old API stuff ends here
Ole Troan3cc49712017-03-08 12:02:24 +0100428 else:
Ole Troan4df97162017-07-07 16:06:08 +0200429 self.logger.debug(
430 'No such message type or failed CRC checksum: %s', n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100431
Ole Troan4df97162017-07-07 16:06:08 +0200432 def _write(self, buf):
Ole Troan5016f992017-01-19 09:44:44 +0100433 """Send a binary-packed message to VPP."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100434 if not self.connected:
435 raise IOError(1, 'Not connected')
Ole Troan4df97162017-07-07 16:06:08 +0200436 return vpp_api.vac_write(ffi.from_buffer(buf), len(buf))
Ole Troana03f4ef2016-12-02 12:53:55 +0100437
Ole Troan4df97162017-07-07 16:06:08 +0200438 def _read(self):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100439 if not self.connected:
440 raise IOError(1, 'Not connected')
Ole Troan3cc49712017-03-08 12:02:24 +0100441 mem = ffi.new("char **")
442 size = ffi.new("int *")
Damjan Marion5fec1e82017-04-13 19:13:47 +0200443 rv = vpp_api.vac_read(mem, size, self.read_timeout)
Ole Troan3cc49712017-03-08 12:02:24 +0100444 if rv:
Ole Troanb0856b42017-08-17 12:48:08 +0200445 raise IOError(rv, 'vac_read failed')
Ole Troan3cc49712017-03-08 12:02:24 +0100446 msg = bytes(ffi.buffer(mem[0], size[0]))
Damjan Marion5fec1e82017-04-13 19:13:47 +0200447 vpp_api.vac_free(mem[0])
Ole Troan3cc49712017-03-08 12:02:24 +0100448 return msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100449
Ole Troan4df97162017-07-07 16:06:08 +0200450 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen,
451 async):
Ole Troan6bf177c2017-08-17 10:34:32 +0200452 pfx = chroot_prefix.encode() if chroot_prefix else ffi.NULL
453 rv = vpp_api.vac_connect(name.encode(), pfx, msg_handler, rx_qlen)
Ole Troana03f4ef2016-12-02 12:53:55 +0100454 if rv != 0:
455 raise IOError(2, 'Connect failed')
Ole Troan7e3a8752016-12-05 10:27:09 +0100456 self.connected = True
Ole Troana03f4ef2016-12-02 12:53:55 +0100457
Damjan Marion5fec1e82017-04-13 19:13:47 +0200458 self.vpp_dictionary_maxid = vpp_api.vac_msg_table_max_index()
Wojciech Dec64bc6122016-12-12 11:32:25 +0100459 self._register_functions(async=async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100460
461 # Initialise control ping
Ole Troan3cc49712017-03-08 12:02:24 +0100462 crc = self.messages['control_ping']['crc']
Ole Troan4df97162017-07-07 16:06:08 +0200463 self.control_ping_index = vpp_api.vac_get_msg_index(
464 ('control_ping' + '_' + crc[2:]).encode())
Ole Troana03f4ef2016-12-02 12:53:55 +0100465 self.control_ping_msgdef = self.messages['control_ping']
Ole Troan4df97162017-07-07 16:06:08 +0200466 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100467
Ole Troan6bf177c2017-08-17 10:34:32 +0200468 def connect(self, name, chroot_prefix=None, async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100469 """Attach to VPP.
470
471 name - the name of the client.
472 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
473 async - if true, messages are sent without waiting for a reply
474 rx_qlen - the length of the VPP message receive queue between
475 client and server.
476 """
Ole Troan4df97162017-07-07 16:06:08 +0200477 msg_handler = vac_callback_sync if not async else vac_callback_async
Ole Troandfc9b7c2017-03-06 23:51:57 +0100478 return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen,
479 async)
480
Ole Troan6bf177c2017-08-17 10:34:32 +0200481 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100482 """Attach to VPP in synchronous mode. Application must poll for events.
483
484 name - the name of the client.
485 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
486 rx_qlen - the length of the VPP message receive queue between
487 client and server.
488 """
489
Ole Troan3cc49712017-03-08 12:02:24 +0100490 return self.connect_internal(name, ffi.NULL, chroot_prefix, rx_qlen,
Ole Troandfc9b7c2017-03-06 23:51:57 +0100491 async=False)
492
Ole Troana03f4ef2016-12-02 12:53:55 +0100493 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100494 """Detach from VPP."""
Damjan Marion5fec1e82017-04-13 19:13:47 +0200495 rv = vpp_api.vac_disconnect()
Ole Troan5016f992017-01-19 09:44:44 +0100496 self.connected = False
Ole Troana03f4ef2016-12-02 12:53:55 +0100497 return rv
498
Ole Troan5016f992017-01-19 09:44:44 +0100499 def msg_handler_sync(self, msg):
500 """Process an incoming message from VPP in sync mode.
501
502 The message may be a reply or it may be an async notification.
503 """
504 r = self.decode_incoming_msg(msg)
505 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100506 return
507
Ole Troan5016f992017-01-19 09:44:44 +0100508 # If we have a context, then use the context to find any
509 # request waiting for a reply
510 context = 0
511 if hasattr(r, 'context') and r.context > 0:
512 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200513
Ole Troana03f4ef2016-12-02 12:53:55 +0100514 msgname = type(r).__name__
Ole Troan57c3d662016-09-12 22:00:32 +0200515
Ole Troan5016f992017-01-19 09:44:44 +0100516 if context == 0:
517 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100518 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100519 else:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100520 raise IOError(2, 'RPC reply message received in event handler')
Ole Troan5016f992017-01-19 09:44:44 +0100521
522 def decode_incoming_msg(self, msg):
523 if not msg:
Ole Troan3cc49712017-03-08 12:02:24 +0100524 self.logger.warning('vpp_api.read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100525 return
526
527 i, ci = self.header.unpack_from(msg, 0)
528 if self.id_names[i] == 'rx_thread_exit':
529 return
530
531 #
532 # Decode message and returns a tuple.
533 #
534 msgdef = self.id_msgdef[i]
535 if not msgdef:
536 raise IOError(2, 'Reply message undefined')
537
538 r = self.decode(msgdef, msg)
539
Ole Troana03f4ef2016-12-02 12:53:55 +0100540 return r
541
Ole Troan5016f992017-01-19 09:44:44 +0100542 def msg_handler_async(self, msg):
543 """Process a message from VPP in async mode.
544
545 In async mode, all messages are returned to the callback.
546 """
547 r = self.decode_incoming_msg(msg)
548 if r is None:
549 return
550
551 msgname = type(r).__name__
552
Ole Troan4df97162017-07-07 16:06:08 +0200553 if self.event_callback:
554 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100555
556 def _control_ping(self, context):
557 """Send a ping command."""
558 self._call_vpp_async(self.control_ping_index,
Ole Troan4df97162017-07-07 16:06:08 +0200559 self.control_ping_msgdef,
Ole Troan5016f992017-01-19 09:44:44 +0100560 context=context)
561
562 def _call_vpp(self, i, msgdef, multipart, **kwargs):
563 """Given a message, send the message and await a reply.
564
565 msgdef - the message packing definition
566 i - the message type index
567 multipart - True if the message returns multiple
568 messages in return.
569 context - context number - chosen at random if not
570 supplied.
571 The remainder of the kwargs are the arguments to the API call.
572
573 The return value is the message or message array containing
574 the response. It will raise an IOError exception if there was
575 no response within the timeout window.
576 """
577
Ole Troan4df97162017-07-07 16:06:08 +0200578 if 'context' not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100579 context = self.get_context()
580 kwargs['context'] = context
581 else:
582 context = kwargs['context']
583 kwargs['_vl_msg_id'] = i
584 b = self.encode(msgdef, kwargs)
Ole Troan5016f992017-01-19 09:44:44 +0100585
Damjan Marion5fec1e82017-04-13 19:13:47 +0200586 vpp_api.vac_rx_suspend()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100587 self._write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100588
589 if multipart:
590 # Send a ping after the request - we use its response
591 # to detect that we have seen all results.
592 self._control_ping(context)
593
594 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100595 rl = []
596 while (True):
597 msg = self._read()
598 if not msg:
Ole Troan4df97162017-07-07 16:06:08 +0200599 raise IOError(2, 'VPP API client: read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100600
Ole Troandfc9b7c2017-03-06 23:51:57 +0100601 r = self.decode_incoming_msg(msg)
602 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200603 if context not in r or r.context == 0 or context != r.context:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100604 self.message_queue.put_nowait(r)
605 continue
606
607 if not multipart:
608 rl = r
609 break
610 if msgname == 'control_ping_reply':
611 break
612
613 rl.append(r)
614
Damjan Marion5fec1e82017-04-13 19:13:47 +0200615 vpp_api.vac_rx_resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100616
617 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100618
619 def _call_vpp_async(self, i, msgdef, **kwargs):
620 """Given a message, send the message and await a reply.
621
622 msgdef - the message packing definition
623 i - the message type index
624 context - context number - chosen at random if not
625 supplied.
626 The remainder of the kwargs are the arguments to the API call.
627 """
Ole Troan4df97162017-07-07 16:06:08 +0200628 if 'context' not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100629 context = self.get_context()
630 kwargs['context'] = context
631 else:
632 context = kwargs['context']
633 kwargs['_vl_msg_id'] = i
634 b = self.encode(msgdef, kwargs)
635
636 self._write(b)
637
Ole Troana03f4ef2016-12-02 12:53:55 +0100638 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100639 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100640
Ole Troan5016f992017-01-19 09:44:44 +0100641 This will be called for async notifications in sync mode,
642 and all messages in async mode. In sync mode, replies to
643 requests will not come here.
644
645 callback is a fn(msg_type_name, msg_type) that will be
646 called when a message comes in. While this function is
647 executing, note that (a) you are in a background thread and
648 may wish to use threading.Lock to protect your datastructures,
649 and (b) message processing from VPP will stop (so if you take
650 a long while about it you may provoke reply timeouts or cause
651 VPP to fill the RX buffer). Passing None will disable the
652 callback.
653 """
654 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100655
656 def thread_msg_handler(self):
657 """Python thread calling the user registerd message handler.
658
659 This is to emulate the old style event callback scheme. Modern
660 clients should provide their own thread to poll the event
661 queue.
662 """
663 while True:
664 r = self.message_queue.get()
665 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200666 if self.event_callback:
667 self.event_callback(msgname, r)