blob: 14f367c23369ba4578ac01810ddfe99f71c2d33e [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']:
Ole Troan68ec9402017-08-31 13:18:44 +0200249 raise ValueError(1,'Non existing argument [' + k + ']' + \
250 ' used in call to: ' + \
251 self.id_names[kwargs['_vl_msg_id']] + '()' )
252
Ole Troan7e3a8752016-12-05 10:27:09 +0100253
Ole Troan4df97162017-07-07 16:06:08 +0200254 for k, v in vpp_iterator(msgdef['args']):
Ole Troana03f4ef2016-12-02 12:53:55 +0100255 off += size
256 if k in kwargs:
257 if type(v) is list:
258 if callable(v[1]):
Ole Troanf5984bd2016-12-18 13:15:08 +0100259 e = kwargs[v[0]] if v[0] in kwargs else v[0]
Ole Troana03f4ef2016-12-02 12:53:55 +0100260 size = 0
261 for i in range(e):
262 size += v[1](self, True, buf, off + size,
263 kwargs[k][i])
264 else:
265 if v[0] in kwargs:
266 l = kwargs[v[0]]
267 else:
268 l = len(kwargs[k])
269 if v[1].size == 1:
270 buf[off:off + l] = bytearray(kwargs[k])
271 size = l
272 else:
273 size = 0
274 for i in kwargs[k]:
275 v[1].pack_into(buf, off + size, i)
276 size += v[1].size
277 else:
278 if callable(v):
279 size = v(self, True, buf, off, kwargs[k])
280 else:
281 v.pack_into(buf, off, kwargs[k])
282 size = v.size
283 else:
Ole Troan7e3a8752016-12-05 10:27:09 +0100284 size = v.size if not type(v) is list else 0
Ole Troan5f9dcff2016-08-01 04:59:13 +0200285
Ole Troana03f4ef2016-12-02 12:53:55 +0100286 return off + size - offset
Ole Troan5f9dcff2016-08-01 04:59:13 +0200287
Ole Troana03f4ef2016-12-02 12:53:55 +0100288 def __getitem__(self, name):
289 if name in self.messages:
290 return self.messages[name]
291 return None
Ole Troan5f9dcff2016-08-01 04:59:13 +0200292
Ole Troana03f4ef2016-12-02 12:53:55 +0100293 def encode(self, msgdef, kwargs):
294 # Make suitably large buffer
295 buf = bytearray(self.buffersize)
296 offset = 0
297 size = self.__struct_type(True, msgdef, buf, offset, kwargs)
298 return buf[:offset + size]
299
300 def decode(self, msgdef, buf):
301 return self.__struct_type(False, msgdef, buf, 0, None)[1]
302
303 def __struct_type_decode(self, msgdef, buf, offset):
304 res = []
305 off = offset
306 size = 0
Ole Troan4df97162017-07-07 16:06:08 +0200307 for k, v in vpp_iterator(msgdef['args']):
Ole Troana03f4ef2016-12-02 12:53:55 +0100308 off += size
309 if type(v) is list:
310 lst = []
Ole Troan4df97162017-07-07 16:06:08 +0200311 if callable(v[1]): # compound type
Ole Troana03f4ef2016-12-02 12:53:55 +0100312 size = 0
Ole Troan4df97162017-07-07 16:06:08 +0200313 if v[0] in msgdef['args']: # vla
Ole Troanf5984bd2016-12-18 13:15:08 +0100314 e = res[v[2]]
Ole Troan4df97162017-07-07 16:06:08 +0200315 else: # fixed array
Ole Troana03f4ef2016-12-02 12:53:55 +0100316 e = v[0]
317 res.append(lst)
318 for i in range(e):
Ole Troan4df97162017-07-07 16:06:08 +0200319 (s, l) = v[1](self, False, buf, off + size, None)
Ole Troana03f4ef2016-12-02 12:53:55 +0100320 lst.append(l)
321 size += s
322 continue
Ole Troana03f4ef2016-12-02 12:53:55 +0100323 if v[1].size == 1:
Ole Troanf5984bd2016-12-18 13:15:08 +0100324 if type(v[0]) is int:
325 size = len(buf) - off
326 else:
327 size = res[v[2]]
Ole Troana03f4ef2016-12-02 12:53:55 +0100328 res.append(buf[off:off + size])
329 else:
Ole Troanf5984bd2016-12-18 13:15:08 +0100330 e = v[0] if type(v[0]) is int else res[v[2]]
331 if e == -1:
332 e = (len(buf) - off) / v[1].size
Ole Troana03f4ef2016-12-02 12:53:55 +0100333 lst = []
334 res.append(lst)
335 size = 0
336 for i in range(e):
337 lst.append(v[1].unpack_from(buf, off + size)[0])
338 size += v[1].size
339 else:
340 if callable(v):
Ole Troan4df97162017-07-07 16:06:08 +0200341 (s, l) = v(self, False, buf, off, None)
Ole Troana03f4ef2016-12-02 12:53:55 +0100342 res.append(l)
343 size += s
344 else:
345 res.append(v.unpack_from(buf, off)[0])
346 size = v.size
347
348 return off + size - offset, msgdef['return_tuple']._make(res)
349
350 def ret_tup(self, name):
351 if name in self.messages and 'return_tuple' in self.messages[name]:
352 return self.messages[name]['return_tuple']
353 return None
354
Ole Troan4df97162017-07-07 16:06:08 +0200355 def add_message(self, name, msgdef, typeonly=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100356 if name in self.messages:
357 raise ValueError('Duplicate message name: ' + name)
358
359 args = collections.OrderedDict()
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100360 argtypes = collections.OrderedDict()
Ole Troana03f4ef2016-12-02 12:53:55 +0100361 fields = []
362 msg = {}
Ole Troanf5984bd2016-12-18 13:15:08 +0100363 for i, f in enumerate(msgdef):
Ole Troana03f4ef2016-12-02 12:53:55 +0100364 if type(f) is dict and 'crc' in f:
365 msg['crc'] = f['crc']
366 continue
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100367 field_type = f[0]
Ole Troana03f4ef2016-12-02 12:53:55 +0100368 field_name = f[1]
Ole Troanf5984bd2016-12-18 13:15:08 +0100369 if len(f) == 3 and f[2] == 0 and i != len(msgdef) - 2:
370 raise ValueError('Variable Length Array must be last: ' + name)
Ole Troana03f4ef2016-12-02 12:53:55 +0100371 args[field_name] = self.__struct(*f)
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100372 argtypes[field_name] = field_type
Ole Troan4df97162017-07-07 16:06:08 +0200373 if len(f) == 4: # Find offset to # elements field
374 idx = list(args.keys()).index(f[3]) - i
375 args[field_name].append(idx)
Ole Troana03f4ef2016-12-02 12:53:55 +0100376 fields.append(field_name)
377 msg['return_tuple'] = collections.namedtuple(name, fields,
Ole Troan4df97162017-07-07 16:06:08 +0200378 rename=True)
Ole Troana03f4ef2016-12-02 12:53:55 +0100379 self.messages[name] = msg
380 self.messages[name]['args'] = args
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100381 self.messages[name]['argtypes'] = argtypes
Ole Troan3cc49712017-03-08 12:02:24 +0100382 self.messages[name]['typeonly'] = typeonly
Ole Troana03f4ef2016-12-02 12:53:55 +0100383 return self.messages[name]
384
385 def add_type(self, name, typedef):
Ole Troan4df97162017-07-07 16:06:08 +0200386 return self.add_message('vl_api_' + name + '_t', typedef,
387 typeonly=True)
Ole Troana03f4ef2016-12-02 12:53:55 +0100388
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100389 def make_function(self, name, i, msgdef, multipart, async):
Wojciech Dec64bc6122016-12-12 11:32:25 +0100390 if (async):
Ole Troan5016f992017-01-19 09:44:44 +0100391 f = lambda **kwargs: (self._call_vpp_async(i, msgdef, **kwargs))
Wojciech Dec64bc6122016-12-12 11:32:25 +0100392 else:
Ole Troan4df97162017-07-07 16:06:08 +0200393 f = lambda **kwargs: (self._call_vpp(i, msgdef, multipart,
394 **kwargs))
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100395 args = self.messages[name]['args']
396 argtypes = self.messages[name]['argtypes']
397 f.__name__ = str(name)
Ole Troan4df97162017-07-07 16:06:08 +0200398 f.__doc__ = ", ".join(["%s %s" %
399 (argtypes[k], k) for k in args.keys()])
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100400 return f
Ole Troana03f4ef2016-12-02 12:53:55 +0100401
Klement Sekera7112c542017-03-01 09:53:19 +0100402 @property
403 def api(self):
404 if not hasattr(self, "_api"):
405 raise Exception("Not connected, api definitions not available")
406 return self._api
407
Wojciech Dec64bc6122016-12-12 11:32:25 +0100408 def _register_functions(self, async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100409 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
410 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera7112c542017-03-01 09:53:19 +0100411 self._api = Empty()
Ole Troan4df97162017-07-07 16:06:08 +0200412 for name, msgdef in vpp_iterator(self.messages):
413 if self.messages[name]['typeonly']:
414 continue
Ole Troan3cc49712017-03-08 12:02:24 +0100415 crc = self.messages[name]['crc']
416 n = name + '_' + crc[2:]
Ole Troan4df97162017-07-07 16:06:08 +0200417 i = vpp_api.vac_get_msg_index(n.encode())
Ole Troan3cc49712017-03-08 12:02:24 +0100418 if i > 0:
Ole Troana03f4ef2016-12-02 12:53:55 +0100419 self.id_msgdef[i] = msgdef
420 self.id_names[i] = name
421 multipart = True if name.find('_dump') > 0 else False
Klement Sekera7112c542017-03-01 09:53:19 +0100422 f = self.make_function(name, i, msgdef, multipart, async)
423 setattr(self._api, name, FuncWrapper(f))
424
Ole Troandfc9b7c2017-03-06 23:51:57 +0100425 # old API stuff starts here - will be removed in 17.07
Klement Sekera7112c542017-03-01 09:53:19 +0100426 if hasattr(self, name):
427 raise NameError(
428 3, "Conflicting name in JSON definition: `%s'" % name)
429 setattr(self, name, f)
430 # old API stuff ends here
Ole Troan3cc49712017-03-08 12:02:24 +0100431 else:
Ole Troan4df97162017-07-07 16:06:08 +0200432 self.logger.debug(
433 'No such message type or failed CRC checksum: %s', n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100434
Ole Troan4df97162017-07-07 16:06:08 +0200435 def _write(self, buf):
Ole Troan5016f992017-01-19 09:44:44 +0100436 """Send a binary-packed message to VPP."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100437 if not self.connected:
438 raise IOError(1, 'Not connected')
Ole Troan4df97162017-07-07 16:06:08 +0200439 return vpp_api.vac_write(ffi.from_buffer(buf), len(buf))
Ole Troana03f4ef2016-12-02 12:53:55 +0100440
Ole Troan4df97162017-07-07 16:06:08 +0200441 def _read(self):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100442 if not self.connected:
443 raise IOError(1, 'Not connected')
Ole Troan3cc49712017-03-08 12:02:24 +0100444 mem = ffi.new("char **")
445 size = ffi.new("int *")
Damjan Marion5fec1e82017-04-13 19:13:47 +0200446 rv = vpp_api.vac_read(mem, size, self.read_timeout)
Ole Troan3cc49712017-03-08 12:02:24 +0100447 if rv:
Ole Troanb0856b42017-08-17 12:48:08 +0200448 raise IOError(rv, 'vac_read failed')
Ole Troan3cc49712017-03-08 12:02:24 +0100449 msg = bytes(ffi.buffer(mem[0], size[0]))
Damjan Marion5fec1e82017-04-13 19:13:47 +0200450 vpp_api.vac_free(mem[0])
Ole Troan3cc49712017-03-08 12:02:24 +0100451 return msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100452
Ole Troan4df97162017-07-07 16:06:08 +0200453 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen,
454 async):
Ole Troan6bf177c2017-08-17 10:34:32 +0200455 pfx = chroot_prefix.encode() if chroot_prefix else ffi.NULL
456 rv = vpp_api.vac_connect(name.encode(), pfx, msg_handler, rx_qlen)
Ole Troana03f4ef2016-12-02 12:53:55 +0100457 if rv != 0:
458 raise IOError(2, 'Connect failed')
Ole Troan7e3a8752016-12-05 10:27:09 +0100459 self.connected = True
Ole Troana03f4ef2016-12-02 12:53:55 +0100460
Damjan Marion5fec1e82017-04-13 19:13:47 +0200461 self.vpp_dictionary_maxid = vpp_api.vac_msg_table_max_index()
Wojciech Dec64bc6122016-12-12 11:32:25 +0100462 self._register_functions(async=async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100463
464 # Initialise control ping
Ole Troan3cc49712017-03-08 12:02:24 +0100465 crc = self.messages['control_ping']['crc']
Ole Troan4df97162017-07-07 16:06:08 +0200466 self.control_ping_index = vpp_api.vac_get_msg_index(
467 ('control_ping' + '_' + crc[2:]).encode())
Ole Troana03f4ef2016-12-02 12:53:55 +0100468 self.control_ping_msgdef = self.messages['control_ping']
Ole Troan4df97162017-07-07 16:06:08 +0200469 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100470
Ole Troan6bf177c2017-08-17 10:34:32 +0200471 def connect(self, name, chroot_prefix=None, async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100472 """Attach to VPP.
473
474 name - the name of the client.
475 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
476 async - if true, messages are sent without waiting for a reply
477 rx_qlen - the length of the VPP message receive queue between
478 client and server.
479 """
Ole Troan4df97162017-07-07 16:06:08 +0200480 msg_handler = vac_callback_sync if not async else vac_callback_async
Ole Troandfc9b7c2017-03-06 23:51:57 +0100481 return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen,
482 async)
483
Ole Troan6bf177c2017-08-17 10:34:32 +0200484 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100485 """Attach to VPP in synchronous mode. Application must poll for events.
486
487 name - the name of the client.
488 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
489 rx_qlen - the length of the VPP message receive queue between
490 client and server.
491 """
492
Ole Troan3cc49712017-03-08 12:02:24 +0100493 return self.connect_internal(name, ffi.NULL, chroot_prefix, rx_qlen,
Ole Troandfc9b7c2017-03-06 23:51:57 +0100494 async=False)
495
Ole Troana03f4ef2016-12-02 12:53:55 +0100496 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100497 """Detach from VPP."""
Damjan Marion5fec1e82017-04-13 19:13:47 +0200498 rv = vpp_api.vac_disconnect()
Ole Troan5016f992017-01-19 09:44:44 +0100499 self.connected = False
Ole Troana03f4ef2016-12-02 12:53:55 +0100500 return rv
501
Ole Troan5016f992017-01-19 09:44:44 +0100502 def msg_handler_sync(self, msg):
503 """Process an incoming message from VPP in sync mode.
504
505 The message may be a reply or it may be an async notification.
506 """
507 r = self.decode_incoming_msg(msg)
508 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100509 return
510
Ole Troan5016f992017-01-19 09:44:44 +0100511 # If we have a context, then use the context to find any
512 # request waiting for a reply
513 context = 0
514 if hasattr(r, 'context') and r.context > 0:
515 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200516
Ole Troana03f4ef2016-12-02 12:53:55 +0100517 msgname = type(r).__name__
Ole Troan57c3d662016-09-12 22:00:32 +0200518
Ole Troan5016f992017-01-19 09:44:44 +0100519 if context == 0:
520 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100521 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100522 else:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100523 raise IOError(2, 'RPC reply message received in event handler')
Ole Troan5016f992017-01-19 09:44:44 +0100524
525 def decode_incoming_msg(self, msg):
526 if not msg:
Ole Troan3cc49712017-03-08 12:02:24 +0100527 self.logger.warning('vpp_api.read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100528 return
529
530 i, ci = self.header.unpack_from(msg, 0)
531 if self.id_names[i] == 'rx_thread_exit':
532 return
533
534 #
535 # Decode message and returns a tuple.
536 #
537 msgdef = self.id_msgdef[i]
538 if not msgdef:
539 raise IOError(2, 'Reply message undefined')
540
541 r = self.decode(msgdef, msg)
542
Ole Troana03f4ef2016-12-02 12:53:55 +0100543 return r
544
Ole Troan5016f992017-01-19 09:44:44 +0100545 def msg_handler_async(self, msg):
546 """Process a message from VPP in async mode.
547
548 In async mode, all messages are returned to the callback.
549 """
550 r = self.decode_incoming_msg(msg)
551 if r is None:
552 return
553
554 msgname = type(r).__name__
555
Ole Troan4df97162017-07-07 16:06:08 +0200556 if self.event_callback:
557 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100558
559 def _control_ping(self, context):
560 """Send a ping command."""
561 self._call_vpp_async(self.control_ping_index,
Ole Troan4df97162017-07-07 16:06:08 +0200562 self.control_ping_msgdef,
Ole Troan5016f992017-01-19 09:44:44 +0100563 context=context)
564
565 def _call_vpp(self, i, msgdef, multipart, **kwargs):
566 """Given a message, send the message and await a reply.
567
568 msgdef - the message packing definition
569 i - the message type index
570 multipart - True if the message returns multiple
571 messages in return.
572 context - context number - chosen at random if not
573 supplied.
574 The remainder of the kwargs are the arguments to the API call.
575
576 The return value is the message or message array containing
577 the response. It will raise an IOError exception if there was
578 no response within the timeout window.
579 """
580
Ole Troan4df97162017-07-07 16:06:08 +0200581 if 'context' not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100582 context = self.get_context()
583 kwargs['context'] = context
584 else:
585 context = kwargs['context']
586 kwargs['_vl_msg_id'] = i
587 b = self.encode(msgdef, kwargs)
Ole Troan5016f992017-01-19 09:44:44 +0100588
Damjan Marion5fec1e82017-04-13 19:13:47 +0200589 vpp_api.vac_rx_suspend()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100590 self._write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100591
592 if multipart:
593 # Send a ping after the request - we use its response
594 # to detect that we have seen all results.
595 self._control_ping(context)
596
597 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100598 rl = []
599 while (True):
600 msg = self._read()
601 if not msg:
Ole Troan4df97162017-07-07 16:06:08 +0200602 raise IOError(2, 'VPP API client: read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100603
Ole Troandfc9b7c2017-03-06 23:51:57 +0100604 r = self.decode_incoming_msg(msg)
605 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200606 if context not in r or r.context == 0 or context != r.context:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100607 self.message_queue.put_nowait(r)
608 continue
609
610 if not multipart:
611 rl = r
612 break
613 if msgname == 'control_ping_reply':
614 break
615
616 rl.append(r)
617
Damjan Marion5fec1e82017-04-13 19:13:47 +0200618 vpp_api.vac_rx_resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100619
620 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100621
622 def _call_vpp_async(self, i, msgdef, **kwargs):
623 """Given a message, send the message and await a reply.
624
625 msgdef - the message packing definition
626 i - the message type index
627 context - context number - chosen at random if not
628 supplied.
629 The remainder of the kwargs are the arguments to the API call.
630 """
Ole Troan4df97162017-07-07 16:06:08 +0200631 if 'context' not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100632 context = self.get_context()
633 kwargs['context'] = context
634 else:
635 context = kwargs['context']
636 kwargs['_vl_msg_id'] = i
637 b = self.encode(msgdef, kwargs)
638
639 self._write(b)
640
Ole Troana03f4ef2016-12-02 12:53:55 +0100641 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100642 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100643
Ole Troan5016f992017-01-19 09:44:44 +0100644 This will be called for async notifications in sync mode,
645 and all messages in async mode. In sync mode, replies to
646 requests will not come here.
647
648 callback is a fn(msg_type_name, msg_type) that will be
649 called when a message comes in. While this function is
650 executing, note that (a) you are in a background thread and
651 may wish to use threading.Lock to protect your datastructures,
652 and (b) message processing from VPP will stop (so if you take
653 a long while about it you may provoke reply timeouts or cause
654 VPP to fill the RX buffer). Passing None will disable the
655 callback.
656 """
657 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100658
659 def thread_msg_handler(self):
660 """Python thread calling the user registerd message handler.
661
662 This is to emulate the old style event callback scheme. Modern
663 clients should provide their own thread to poll the event
664 queue.
665 """
666 while True:
667 r = self.message_queue.get()
668 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200669 if self.event_callback:
670 self.event_callback(msgname, r)