blob: 7b66c0f4d37dac6e7a970b670073de2a3c53cc5e [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 = []
Ole Troana03f4ef2016-12-02 12:53:55 +0100132 self.connected = False
133 self.header = struct.Struct('>HI')
Ole Troan5016f992017-01-19 09:44:44 +0100134 self.apifiles = []
Ole Troan3d31f002017-01-26 11:13:00 +0100135 self.event_callback = None
Ole Troan4df97162017-07-07 16:06:08 +0200136 self.message_queue = queue.Queue()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100137 self.read_timeout = 0
138 self.vpp_api = vpp_api
139 if async_thread:
Ole Troan4df97162017-07-07 16:06:08 +0200140 self.event_thread = threading.Thread(
141 target=self.thread_msg_handler)
Ole Troandfc9b7c2017-03-06 23:51:57 +0100142 self.event_thread.daemon = True
143 self.event_thread.start()
Ole Troan5f9dcff2016-08-01 04:59:13 +0200144
Ole Troanf5984bd2016-12-18 13:15:08 +0100145 if not apifiles:
146 # Pick up API definitions from default directory
147 apifiles = glob.glob('/usr/share/vpp/api/*.api.json')
148
Ole Troana03f4ef2016-12-02 12:53:55 +0100149 for file in apifiles:
Ole Troana03f4ef2016-12-02 12:53:55 +0100150 with open(file) as apidef_file:
151 api = json.load(apidef_file)
152 for t in api['types']:
153 self.add_type(t[0], t[1:])
Ole Troan5f9dcff2016-08-01 04:59:13 +0200154
Ole Troana03f4ef2016-12-02 12:53:55 +0100155 for m in api['messages']:
156 self.add_message(m[0], m[1:])
Ole Troan4df97162017-07-07 16:06:08 +0200157 self.apifiles = apifiles
Ole Troan5f9dcff2016-08-01 04:59:13 +0200158
Ole Troana03f4ef2016-12-02 12:53:55 +0100159 # Basic sanity check
Ole Troanf5984bd2016-12-18 13:15:08 +0100160 if len(self.messages) == 0 and not testmode:
161 raise ValueError(1, 'Missing JSON message definitions')
Ole Troan5f9dcff2016-08-01 04:59:13 +0200162
Ole Troan5016f992017-01-19 09:44:44 +0100163 # Make sure we allow VPP to clean up the message rings.
164 atexit.register(vpp_atexit, self)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200165
Ole Troan3cc49712017-03-08 12:02:24 +0100166 # Register error handler
Damjan Marion5fec1e82017-04-13 19:13:47 +0200167 vpp_api.vac_set_error_handler(vac_error_handler)
Ole Troan3cc49712017-03-08 12:02:24 +0100168
Ole Troana03f4ef2016-12-02 12:53:55 +0100169 class ContextId(object):
Ole Troan5016f992017-01-19 09:44:44 +0100170 """Thread-safe provider of unique context IDs."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100171 def __init__(self):
172 self.context = 0
Ole Troan4df97162017-07-07 16:06:08 +0200173 self.lock = threading.Lock()
174
Ole Troana03f4ef2016-12-02 12:53:55 +0100175 def __call__(self):
Ole Troan5016f992017-01-19 09:44:44 +0100176 """Get a new unique (or, at least, not recently used) context."""
Ole Troan4df97162017-07-07 16:06:08 +0200177 with self.lock:
178 self.context += 1
179 return self.context
Ole Troana03f4ef2016-12-02 12:53:55 +0100180 get_context = ContextId()
Ole Troan5f9dcff2016-08-01 04:59:13 +0200181
Ole Troana03f4ef2016-12-02 12:53:55 +0100182 def status(self):
Ole Troan5016f992017-01-19 09:44:44 +0100183 """Debug function: report current VPP API status to stdout."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100184 print('Connected') if self.connected else print('Not Connected')
Ole Troan5016f992017-01-19 09:44:44 +0100185 print('Read API definitions from', ', '.join(self.apifiles))
Ole Troan5f9dcff2016-08-01 04:59:13 +0200186
Ole Troan4df97162017-07-07 16:06:08 +0200187 def __struct(self, t, n=None, e=-1, vl=None):
Ole Troan5016f992017-01-19 09:44:44 +0100188 """Create a packing structure for a message."""
Ole Troan4df97162017-07-07 16:06:08 +0200189 base_types = {'u8': 'B',
190 'u16': 'H',
191 'u32': 'I',
192 'i32': 'i',
193 'u64': 'Q',
194 'f64': 'd', }
Ole Troana03f4ef2016-12-02 12:53:55 +0100195 pack = None
196 if t in base_types:
197 pack = base_types[t]
198 if not vl:
Ole Troanf5984bd2016-12-18 13:15:08 +0100199 if e > 0 and t == 'u8':
Ole Troana03f4ef2016-12-02 12:53:55 +0100200 # Fixed byte array
Ole Troan895b6e82017-10-20 13:28:20 +0200201 s = struct.Struct('>' + str(e) + 's')
202 return s.size, 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
Ole Troan895b6e82017-10-20 13:28:20 +0200205 s = struct.Struct('>' + base_types[t])
206 return s.size, [e, s]
Ole Troanf5984bd2016-12-18 13:15:08 +0100207 elif e == 0:
208 # Old style variable array
Ole Troan895b6e82017-10-20 13:28:20 +0200209 s = struct.Struct('>' + base_types[t])
210 return s.size, [-1, s]
Ole Troana03f4ef2016-12-02 12:53:55 +0100211 else:
212 # Variable length array
Ole Troan895b6e82017-10-20 13:28:20 +0200213 if t == 'u8':
214 s = struct.Struct('>s')
215 return s.size, [vl, s]
216 else:
217 s = struct.Struct('>' + base_types[t])
218 return s.size, [vl, s]
Ole Troan57c3d662016-09-12 22:00:32 +0200219
Ole Troan895b6e82017-10-20 13:28:20 +0200220 s = struct.Struct('>' + base_types[t])
221 return s.size, s
Ole Troan57c3d662016-09-12 22:00:32 +0200222
Ole Troana03f4ef2016-12-02 12:53:55 +0100223 if t in self.messages:
Ole Troan895b6e82017-10-20 13:28:20 +0200224 size = self.messages[t]['sizes'][0]
225
Ole Troan4df97162017-07-07 16:06:08 +0200226 # Return a list in case of array
Ole Troanf5984bd2016-12-18 13:15:08 +0100227 if e > 0 and not vl:
Ole Troan895b6e82017-10-20 13:28:20 +0200228 return size, [e, lambda self, encode, buf, offset, args: (
Ole Troanf5984bd2016-12-18 13:15:08 +0100229 self.__struct_type(encode, self.messages[t], buf, offset,
230 args))]
Ole Troana03f4ef2016-12-02 12:53:55 +0100231 if vl:
Ole Troan895b6e82017-10-20 13:28:20 +0200232 return size, [vl, lambda self, encode, buf, offset, args: (
Ole Troanf5984bd2016-12-18 13:15:08 +0100233 self.__struct_type(encode, self.messages[t], buf, offset,
234 args))]
235 elif e == 0:
236 # Old style VLA
Ole Troan4df97162017-07-07 16:06:08 +0200237 raise NotImplementedError(1,
238 'No support for compound types ' + t)
Ole Troan895b6e82017-10-20 13:28:20 +0200239 return size, lambda self, encode, buf, offset, args: (
Ole Troana03f4ef2016-12-02 12:53:55 +0100240 self.__struct_type(encode, self.messages[t], buf, offset, args)
241 )
Ole Troanb8602b52016-10-05 11:10:50 +0200242
Ole Troanf5984bd2016-12-18 13:15:08 +0100243 raise ValueError(1, 'Invalid message type: ' + t)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200244
Ole Troana03f4ef2016-12-02 12:53:55 +0100245 def __struct_type(self, encode, msgdef, buf, offset, kwargs):
Ole Troan5016f992017-01-19 09:44:44 +0100246 """Get a message packer or unpacker."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100247 if encode:
248 return self.__struct_type_encode(msgdef, buf, offset, kwargs)
249 else:
250 return self.__struct_type_decode(msgdef, buf, offset)
Ole Troan5f9dcff2016-08-01 04:59:13 +0200251
Ole Troana03f4ef2016-12-02 12:53:55 +0100252 def __struct_type_encode(self, msgdef, buf, offset, kwargs):
253 off = offset
254 size = 0
Ole Troan7e3a8752016-12-05 10:27:09 +0100255
256 for k in kwargs:
257 if k not in msgdef['args']:
Ole Troan68ec9402017-08-31 13:18:44 +0200258 raise ValueError(1,'Non existing argument [' + k + ']' + \
259 ' used in call to: ' + \
260 self.id_names[kwargs['_vl_msg_id']] + '()' )
261
Ole Troan4df97162017-07-07 16:06:08 +0200262 for k, v in vpp_iterator(msgdef['args']):
Ole Troana03f4ef2016-12-02 12:53:55 +0100263 off += size
264 if k in kwargs:
265 if type(v) is list:
266 if callable(v[1]):
Ole Troanf5984bd2016-12-18 13:15:08 +0100267 e = kwargs[v[0]] if v[0] in kwargs else v[0]
Ole Troan895b6e82017-10-20 13:28:20 +0200268 if e != len(kwargs[k]):
269 raise (ValueError(1, 'Input list length mismatch: %s (%s != %s)' % (k, e, len(kwargs[k]))))
Ole Troana03f4ef2016-12-02 12:53:55 +0100270 size = 0
271 for i in range(e):
272 size += v[1](self, True, buf, off + size,
273 kwargs[k][i])
274 else:
275 if v[0] in kwargs:
276 l = kwargs[v[0]]
Ole Troan895b6e82017-10-20 13:28:20 +0200277 if l != len(kwargs[k]):
278 raise ValueError(1, 'Input list length mistmatch: %s (%s != %s)' % (k, l, len(kwargs[k])))
Ole Troana03f4ef2016-12-02 12:53:55 +0100279 else:
280 l = len(kwargs[k])
281 if v[1].size == 1:
282 buf[off:off + l] = bytearray(kwargs[k])
283 size = l
284 else:
285 size = 0
286 for i in kwargs[k]:
287 v[1].pack_into(buf, off + size, i)
288 size += v[1].size
289 else:
290 if callable(v):
291 size = v(self, True, buf, off, kwargs[k])
292 else:
Ole Troan895b6e82017-10-20 13:28:20 +0200293 if type(kwargs[k]) is str and v.size < len(kwargs[k]):
294 raise ValueError(1, 'Input list length mistmatch: %s (%s < %s)' % (k, v.size, len(kwargs[k])))
Ole Troana03f4ef2016-12-02 12:53:55 +0100295 v.pack_into(buf, off, kwargs[k])
296 size = v.size
297 else:
Ole Troan7e3a8752016-12-05 10:27:09 +0100298 size = v.size if not type(v) is list else 0
Ole Troan5f9dcff2016-08-01 04:59:13 +0200299
Ole Troana03f4ef2016-12-02 12:53:55 +0100300 return off + size - offset
Ole Troan5f9dcff2016-08-01 04:59:13 +0200301
Ole Troana03f4ef2016-12-02 12:53:55 +0100302 def __getitem__(self, name):
303 if name in self.messages:
304 return self.messages[name]
305 return None
Ole Troan5f9dcff2016-08-01 04:59:13 +0200306
Ole Troan895b6e82017-10-20 13:28:20 +0200307 def get_size(self, sizes, kwargs):
308 total_size = sizes[0]
309 for e in sizes[1]:
310 if e in kwargs and type(kwargs[e]) is list:
311 total_size += len(kwargs[e]) * sizes[1][e]
312 return total_size
313
Ole Troana03f4ef2016-12-02 12:53:55 +0100314 def encode(self, msgdef, kwargs):
315 # Make suitably large buffer
Ole Troan895b6e82017-10-20 13:28:20 +0200316 size = self.get_size(msgdef['sizes'], kwargs)
317 buf = bytearray(size)
Ole Troana03f4ef2016-12-02 12:53:55 +0100318 offset = 0
319 size = self.__struct_type(True, msgdef, buf, offset, kwargs)
320 return buf[:offset + size]
321
322 def decode(self, msgdef, buf):
323 return self.__struct_type(False, msgdef, buf, 0, None)[1]
324
325 def __struct_type_decode(self, msgdef, buf, offset):
326 res = []
327 off = offset
328 size = 0
Ole Troan4df97162017-07-07 16:06:08 +0200329 for k, v in vpp_iterator(msgdef['args']):
Ole Troana03f4ef2016-12-02 12:53:55 +0100330 off += size
331 if type(v) is list:
332 lst = []
Ole Troan4df97162017-07-07 16:06:08 +0200333 if callable(v[1]): # compound type
Ole Troana03f4ef2016-12-02 12:53:55 +0100334 size = 0
Ole Troan4df97162017-07-07 16:06:08 +0200335 if v[0] in msgdef['args']: # vla
Ole Troanf5984bd2016-12-18 13:15:08 +0100336 e = res[v[2]]
Ole Troan4df97162017-07-07 16:06:08 +0200337 else: # fixed array
Ole Troana03f4ef2016-12-02 12:53:55 +0100338 e = v[0]
339 res.append(lst)
340 for i in range(e):
Ole Troan4df97162017-07-07 16:06:08 +0200341 (s, l) = v[1](self, False, buf, off + size, None)
Ole Troana03f4ef2016-12-02 12:53:55 +0100342 lst.append(l)
343 size += s
344 continue
Ole Troana03f4ef2016-12-02 12:53:55 +0100345 if v[1].size == 1:
Ole Troanf5984bd2016-12-18 13:15:08 +0100346 if type(v[0]) is int:
347 size = len(buf) - off
348 else:
349 size = res[v[2]]
Ole Troana03f4ef2016-12-02 12:53:55 +0100350 res.append(buf[off:off + size])
351 else:
Ole Troanf5984bd2016-12-18 13:15:08 +0100352 e = v[0] if type(v[0]) is int else res[v[2]]
353 if e == -1:
354 e = (len(buf) - off) / v[1].size
Ole Troana03f4ef2016-12-02 12:53:55 +0100355 lst = []
356 res.append(lst)
357 size = 0
358 for i in range(e):
359 lst.append(v[1].unpack_from(buf, off + size)[0])
360 size += v[1].size
361 else:
362 if callable(v):
Ole Troan4df97162017-07-07 16:06:08 +0200363 (s, l) = v(self, False, buf, off, None)
Ole Troana03f4ef2016-12-02 12:53:55 +0100364 res.append(l)
365 size += s
366 else:
367 res.append(v.unpack_from(buf, off)[0])
368 size = v.size
369
370 return off + size - offset, msgdef['return_tuple']._make(res)
371
372 def ret_tup(self, name):
373 if name in self.messages and 'return_tuple' in self.messages[name]:
374 return self.messages[name]['return_tuple']
375 return None
376
Ole Troan4df97162017-07-07 16:06:08 +0200377 def add_message(self, name, msgdef, typeonly=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100378 if name in self.messages:
379 raise ValueError('Duplicate message name: ' + name)
380
381 args = collections.OrderedDict()
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100382 argtypes = collections.OrderedDict()
Ole Troana03f4ef2016-12-02 12:53:55 +0100383 fields = []
384 msg = {}
Ole Troan895b6e82017-10-20 13:28:20 +0200385 total_size = 0
386 sizes = {}
Ole Troanf5984bd2016-12-18 13:15:08 +0100387 for i, f in enumerate(msgdef):
Ole Troana03f4ef2016-12-02 12:53:55 +0100388 if type(f) is dict and 'crc' in f:
389 msg['crc'] = f['crc']
390 continue
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100391 field_type = f[0]
Ole Troana03f4ef2016-12-02 12:53:55 +0100392 field_name = f[1]
Ole Troanf5984bd2016-12-18 13:15:08 +0100393 if len(f) == 3 and f[2] == 0 and i != len(msgdef) - 2:
394 raise ValueError('Variable Length Array must be last: ' + name)
Ole Troan895b6e82017-10-20 13:28:20 +0200395 size, s = self.__struct(*f)
396 args[field_name] = s
397 if type(s) == list and type(s[0]) == int and type(s[1]) == struct.Struct:
398 if s[0] < 0:
399 sizes[field_name] = size
400 else:
401 sizes[field_name] = size
402 total_size += s[0] * size
403 else:
404 sizes[field_name] = size
405 total_size += size
406
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100407 argtypes[field_name] = field_type
Ole Troan4df97162017-07-07 16:06:08 +0200408 if len(f) == 4: # Find offset to # elements field
409 idx = list(args.keys()).index(f[3]) - i
410 args[field_name].append(idx)
Ole Troana03f4ef2016-12-02 12:53:55 +0100411 fields.append(field_name)
412 msg['return_tuple'] = collections.namedtuple(name, fields,
Ole Troan4df97162017-07-07 16:06:08 +0200413 rename=True)
Ole Troana03f4ef2016-12-02 12:53:55 +0100414 self.messages[name] = msg
415 self.messages[name]['args'] = args
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100416 self.messages[name]['argtypes'] = argtypes
Ole Troan3cc49712017-03-08 12:02:24 +0100417 self.messages[name]['typeonly'] = typeonly
Ole Troan895b6e82017-10-20 13:28:20 +0200418 self.messages[name]['sizes'] = [total_size, sizes]
Ole Troana03f4ef2016-12-02 12:53:55 +0100419 return self.messages[name]
420
421 def add_type(self, name, typedef):
Ole Troan4df97162017-07-07 16:06:08 +0200422 return self.add_message('vl_api_' + name + '_t', typedef,
423 typeonly=True)
Ole Troana03f4ef2016-12-02 12:53:55 +0100424
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100425 def make_function(self, name, i, msgdef, multipart, async):
Wojciech Dec64bc6122016-12-12 11:32:25 +0100426 if (async):
Ole Troan5016f992017-01-19 09:44:44 +0100427 f = lambda **kwargs: (self._call_vpp_async(i, msgdef, **kwargs))
Wojciech Dec64bc6122016-12-12 11:32:25 +0100428 else:
Ole Troan4df97162017-07-07 16:06:08 +0200429 f = lambda **kwargs: (self._call_vpp(i, msgdef, multipart,
430 **kwargs))
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100431 args = self.messages[name]['args']
432 argtypes = self.messages[name]['argtypes']
433 f.__name__ = str(name)
Ole Troan4df97162017-07-07 16:06:08 +0200434 f.__doc__ = ", ".join(["%s %s" %
435 (argtypes[k], k) for k in args.keys()])
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100436 return f
Ole Troana03f4ef2016-12-02 12:53:55 +0100437
Klement Sekera7112c542017-03-01 09:53:19 +0100438 @property
439 def api(self):
440 if not hasattr(self, "_api"):
441 raise Exception("Not connected, api definitions not available")
442 return self._api
443
Wojciech Dec64bc6122016-12-12 11:32:25 +0100444 def _register_functions(self, async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100445 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
446 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera7112c542017-03-01 09:53:19 +0100447 self._api = Empty()
Ole Troan4df97162017-07-07 16:06:08 +0200448 for name, msgdef in vpp_iterator(self.messages):
449 if self.messages[name]['typeonly']:
450 continue
Ole Troan3cc49712017-03-08 12:02:24 +0100451 crc = self.messages[name]['crc']
452 n = name + '_' + crc[2:]
Ole Troan4df97162017-07-07 16:06:08 +0200453 i = vpp_api.vac_get_msg_index(n.encode())
Ole Troan3cc49712017-03-08 12:02:24 +0100454 if i > 0:
Ole Troana03f4ef2016-12-02 12:53:55 +0100455 self.id_msgdef[i] = msgdef
456 self.id_names[i] = name
457 multipart = True if name.find('_dump') > 0 else False
Klement Sekera7112c542017-03-01 09:53:19 +0100458 f = self.make_function(name, i, msgdef, multipart, async)
459 setattr(self._api, name, FuncWrapper(f))
460
Ole Troandfc9b7c2017-03-06 23:51:57 +0100461 # old API stuff starts here - will be removed in 17.07
Klement Sekera7112c542017-03-01 09:53:19 +0100462 if hasattr(self, name):
463 raise NameError(
464 3, "Conflicting name in JSON definition: `%s'" % name)
465 setattr(self, name, f)
466 # old API stuff ends here
Ole Troan3cc49712017-03-08 12:02:24 +0100467 else:
Ole Troan4df97162017-07-07 16:06:08 +0200468 self.logger.debug(
469 'No such message type or failed CRC checksum: %s', n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100470
Ole Troan4df97162017-07-07 16:06:08 +0200471 def _write(self, buf):
Ole Troan5016f992017-01-19 09:44:44 +0100472 """Send a binary-packed message to VPP."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100473 if not self.connected:
474 raise IOError(1, 'Not connected')
Ole Troan4df97162017-07-07 16:06:08 +0200475 return vpp_api.vac_write(ffi.from_buffer(buf), len(buf))
Ole Troana03f4ef2016-12-02 12:53:55 +0100476
Ole Troan4df97162017-07-07 16:06:08 +0200477 def _read(self):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100478 if not self.connected:
479 raise IOError(1, 'Not connected')
Ole Troan3cc49712017-03-08 12:02:24 +0100480 mem = ffi.new("char **")
481 size = ffi.new("int *")
Damjan Marion5fec1e82017-04-13 19:13:47 +0200482 rv = vpp_api.vac_read(mem, size, self.read_timeout)
Ole Troan3cc49712017-03-08 12:02:24 +0100483 if rv:
Ole Troanb0856b42017-08-17 12:48:08 +0200484 raise IOError(rv, 'vac_read failed')
Ole Troan3cc49712017-03-08 12:02:24 +0100485 msg = bytes(ffi.buffer(mem[0], size[0]))
Damjan Marion5fec1e82017-04-13 19:13:47 +0200486 vpp_api.vac_free(mem[0])
Ole Troan3cc49712017-03-08 12:02:24 +0100487 return msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100488
Ole Troan4df97162017-07-07 16:06:08 +0200489 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen,
490 async):
Ole Troan6bf177c2017-08-17 10:34:32 +0200491 pfx = chroot_prefix.encode() if chroot_prefix else ffi.NULL
492 rv = vpp_api.vac_connect(name.encode(), pfx, msg_handler, rx_qlen)
Ole Troana03f4ef2016-12-02 12:53:55 +0100493 if rv != 0:
494 raise IOError(2, 'Connect failed')
Ole Troan7e3a8752016-12-05 10:27:09 +0100495 self.connected = True
Ole Troana03f4ef2016-12-02 12:53:55 +0100496
Damjan Marion5fec1e82017-04-13 19:13:47 +0200497 self.vpp_dictionary_maxid = vpp_api.vac_msg_table_max_index()
Wojciech Dec64bc6122016-12-12 11:32:25 +0100498 self._register_functions(async=async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100499
500 # Initialise control ping
Ole Troan3cc49712017-03-08 12:02:24 +0100501 crc = self.messages['control_ping']['crc']
Ole Troan4df97162017-07-07 16:06:08 +0200502 self.control_ping_index = vpp_api.vac_get_msg_index(
503 ('control_ping' + '_' + crc[2:]).encode())
Ole Troana03f4ef2016-12-02 12:53:55 +0100504 self.control_ping_msgdef = self.messages['control_ping']
Ole Troan4df97162017-07-07 16:06:08 +0200505 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100506
Ole Troan6bf177c2017-08-17 10:34:32 +0200507 def connect(self, name, chroot_prefix=None, async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100508 """Attach to VPP.
509
510 name - the name of the client.
511 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
512 async - if true, messages are sent without waiting for a reply
513 rx_qlen - the length of the VPP message receive queue between
514 client and server.
515 """
Ole Troan4df97162017-07-07 16:06:08 +0200516 msg_handler = vac_callback_sync if not async else vac_callback_async
Ole Troandfc9b7c2017-03-06 23:51:57 +0100517 return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen,
518 async)
519
Ole Troan6bf177c2017-08-17 10:34:32 +0200520 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100521 """Attach to VPP in synchronous mode. Application must poll for events.
522
523 name - the name of the client.
524 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
525 rx_qlen - the length of the VPP message receive queue between
526 client and server.
527 """
528
Ole Troan3cc49712017-03-08 12:02:24 +0100529 return self.connect_internal(name, ffi.NULL, chroot_prefix, rx_qlen,
Ole Troandfc9b7c2017-03-06 23:51:57 +0100530 async=False)
531
Ole Troana03f4ef2016-12-02 12:53:55 +0100532 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100533 """Detach from VPP."""
Damjan Marion5fec1e82017-04-13 19:13:47 +0200534 rv = vpp_api.vac_disconnect()
Ole Troan5016f992017-01-19 09:44:44 +0100535 self.connected = False
Ole Troana03f4ef2016-12-02 12:53:55 +0100536 return rv
537
Ole Troan5016f992017-01-19 09:44:44 +0100538 def msg_handler_sync(self, msg):
539 """Process an incoming message from VPP in sync mode.
540
541 The message may be a reply or it may be an async notification.
542 """
543 r = self.decode_incoming_msg(msg)
544 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100545 return
546
Ole Troan5016f992017-01-19 09:44:44 +0100547 # If we have a context, then use the context to find any
548 # request waiting for a reply
549 context = 0
550 if hasattr(r, 'context') and r.context > 0:
551 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200552
Ole Troana03f4ef2016-12-02 12:53:55 +0100553 msgname = type(r).__name__
Ole Troan57c3d662016-09-12 22:00:32 +0200554
Ole Troan5016f992017-01-19 09:44:44 +0100555 if context == 0:
556 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100557 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100558 else:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100559 raise IOError(2, 'RPC reply message received in event handler')
Ole Troan5016f992017-01-19 09:44:44 +0100560
561 def decode_incoming_msg(self, msg):
562 if not msg:
Ole Troan3cc49712017-03-08 12:02:24 +0100563 self.logger.warning('vpp_api.read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100564 return
565
566 i, ci = self.header.unpack_from(msg, 0)
567 if self.id_names[i] == 'rx_thread_exit':
568 return
569
570 #
571 # Decode message and returns a tuple.
572 #
573 msgdef = self.id_msgdef[i]
574 if not msgdef:
575 raise IOError(2, 'Reply message undefined')
576
577 r = self.decode(msgdef, msg)
578
Ole Troana03f4ef2016-12-02 12:53:55 +0100579 return r
580
Ole Troan5016f992017-01-19 09:44:44 +0100581 def msg_handler_async(self, msg):
582 """Process a message from VPP in async mode.
583
584 In async mode, all messages are returned to the callback.
585 """
586 r = self.decode_incoming_msg(msg)
587 if r is None:
588 return
589
590 msgname = type(r).__name__
591
Ole Troan4df97162017-07-07 16:06:08 +0200592 if self.event_callback:
593 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100594
595 def _control_ping(self, context):
596 """Send a ping command."""
597 self._call_vpp_async(self.control_ping_index,
Ole Troan4df97162017-07-07 16:06:08 +0200598 self.control_ping_msgdef,
Ole Troan5016f992017-01-19 09:44:44 +0100599 context=context)
600
601 def _call_vpp(self, i, msgdef, multipart, **kwargs):
602 """Given a message, send the message and await a reply.
603
604 msgdef - the message packing definition
605 i - the message type index
606 multipart - True if the message returns multiple
607 messages in return.
608 context - context number - chosen at random if not
609 supplied.
610 The remainder of the kwargs are the arguments to the API call.
611
612 The return value is the message or message array containing
613 the response. It will raise an IOError exception if there was
614 no response within the timeout window.
615 """
616
Ole Troan4df97162017-07-07 16:06:08 +0200617 if 'context' not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100618 context = self.get_context()
619 kwargs['context'] = context
620 else:
621 context = kwargs['context']
622 kwargs['_vl_msg_id'] = i
623 b = self.encode(msgdef, kwargs)
Ole Troan5016f992017-01-19 09:44:44 +0100624
Damjan Marion5fec1e82017-04-13 19:13:47 +0200625 vpp_api.vac_rx_suspend()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100626 self._write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100627
628 if multipart:
629 # Send a ping after the request - we use its response
630 # to detect that we have seen all results.
631 self._control_ping(context)
632
633 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100634 rl = []
635 while (True):
636 msg = self._read()
637 if not msg:
Ole Troan4df97162017-07-07 16:06:08 +0200638 raise IOError(2, 'VPP API client: read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100639
Ole Troandfc9b7c2017-03-06 23:51:57 +0100640 r = self.decode_incoming_msg(msg)
641 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200642 if context not in r or r.context == 0 or context != r.context:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100643 self.message_queue.put_nowait(r)
644 continue
645
646 if not multipart:
647 rl = r
648 break
649 if msgname == 'control_ping_reply':
650 break
651
652 rl.append(r)
653
Damjan Marion5fec1e82017-04-13 19:13:47 +0200654 vpp_api.vac_rx_resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100655
656 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100657
658 def _call_vpp_async(self, i, msgdef, **kwargs):
659 """Given a message, send the message and await a reply.
660
661 msgdef - the message packing definition
662 i - the message type index
663 context - context number - chosen at random if not
664 supplied.
665 The remainder of the kwargs are the arguments to the API call.
666 """
Ole Troan4df97162017-07-07 16:06:08 +0200667 if 'context' not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100668 context = self.get_context()
669 kwargs['context'] = context
670 else:
671 context = kwargs['context']
672 kwargs['_vl_msg_id'] = i
673 b = self.encode(msgdef, kwargs)
674
675 self._write(b)
676
Ole Troana03f4ef2016-12-02 12:53:55 +0100677 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100678 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100679
Ole Troan5016f992017-01-19 09:44:44 +0100680 This will be called for async notifications in sync mode,
681 and all messages in async mode. In sync mode, replies to
682 requests will not come here.
683
684 callback is a fn(msg_type_name, msg_type) that will be
685 called when a message comes in. While this function is
686 executing, note that (a) you are in a background thread and
687 may wish to use threading.Lock to protect your datastructures,
688 and (b) message processing from VPP will stop (so if you take
689 a long while about it you may provoke reply timeouts or cause
690 VPP to fill the RX buffer). Passing None will disable the
691 callback.
692 """
693 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100694
695 def thread_msg_handler(self):
696 """Python thread calling the user registerd message handler.
697
698 This is to emulate the old style event callback scheme. Modern
699 clients should provide their own thread to poll the event
700 queue.
701 """
702 while True:
703 r = self.message_queue.get()
704 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200705 if self.event_callback:
706 self.event_callback(msgname, r)