blob: 67ac22a4d324b6bdcef928e42649afd72ba2e290 [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 Troanca3b6f12017-10-31 14:50:13 +0100363 size = 0
Ole Troan4df97162017-07-07 16:06:08 +0200364 (s, l) = v(self, False, buf, off, None)
Ole Troana03f4ef2016-12-02 12:53:55 +0100365 res.append(l)
366 size += s
367 else:
368 res.append(v.unpack_from(buf, off)[0])
369 size = v.size
370
371 return off + size - offset, msgdef['return_tuple']._make(res)
372
373 def ret_tup(self, name):
374 if name in self.messages and 'return_tuple' in self.messages[name]:
375 return self.messages[name]['return_tuple']
376 return None
377
Ole Troan4df97162017-07-07 16:06:08 +0200378 def add_message(self, name, msgdef, typeonly=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100379 if name in self.messages:
380 raise ValueError('Duplicate message name: ' + name)
381
382 args = collections.OrderedDict()
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100383 argtypes = collections.OrderedDict()
Ole Troana03f4ef2016-12-02 12:53:55 +0100384 fields = []
385 msg = {}
Ole Troan895b6e82017-10-20 13:28:20 +0200386 total_size = 0
387 sizes = {}
Ole Troanf5984bd2016-12-18 13:15:08 +0100388 for i, f in enumerate(msgdef):
Ole Troana03f4ef2016-12-02 12:53:55 +0100389 if type(f) is dict and 'crc' in f:
390 msg['crc'] = f['crc']
391 continue
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100392 field_type = f[0]
Ole Troana03f4ef2016-12-02 12:53:55 +0100393 field_name = f[1]
Ole Troanf5984bd2016-12-18 13:15:08 +0100394 if len(f) == 3 and f[2] == 0 and i != len(msgdef) - 2:
395 raise ValueError('Variable Length Array must be last: ' + name)
Ole Troan895b6e82017-10-20 13:28:20 +0200396 size, s = self.__struct(*f)
397 args[field_name] = s
398 if type(s) == list and type(s[0]) == int and type(s[1]) == struct.Struct:
399 if s[0] < 0:
400 sizes[field_name] = size
401 else:
402 sizes[field_name] = size
403 total_size += s[0] * size
404 else:
405 sizes[field_name] = size
406 total_size += size
407
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100408 argtypes[field_name] = field_type
Ole Troan4df97162017-07-07 16:06:08 +0200409 if len(f) == 4: # Find offset to # elements field
410 idx = list(args.keys()).index(f[3]) - i
411 args[field_name].append(idx)
Ole Troana03f4ef2016-12-02 12:53:55 +0100412 fields.append(field_name)
413 msg['return_tuple'] = collections.namedtuple(name, fields,
Ole Troan4df97162017-07-07 16:06:08 +0200414 rename=True)
Ole Troana03f4ef2016-12-02 12:53:55 +0100415 self.messages[name] = msg
416 self.messages[name]['args'] = args
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100417 self.messages[name]['argtypes'] = argtypes
Ole Troan3cc49712017-03-08 12:02:24 +0100418 self.messages[name]['typeonly'] = typeonly
Ole Troan895b6e82017-10-20 13:28:20 +0200419 self.messages[name]['sizes'] = [total_size, sizes]
Ole Troana03f4ef2016-12-02 12:53:55 +0100420 return self.messages[name]
421
422 def add_type(self, name, typedef):
Ole Troan4df97162017-07-07 16:06:08 +0200423 return self.add_message('vl_api_' + name + '_t', typedef,
424 typeonly=True)
Ole Troana03f4ef2016-12-02 12:53:55 +0100425
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100426 def make_function(self, name, i, msgdef, multipart, async):
Wojciech Dec64bc6122016-12-12 11:32:25 +0100427 if (async):
Ole Troan5016f992017-01-19 09:44:44 +0100428 f = lambda **kwargs: (self._call_vpp_async(i, msgdef, **kwargs))
Wojciech Dec64bc6122016-12-12 11:32:25 +0100429 else:
Ole Troan4df97162017-07-07 16:06:08 +0200430 f = lambda **kwargs: (self._call_vpp(i, msgdef, multipart,
431 **kwargs))
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100432 args = self.messages[name]['args']
433 argtypes = self.messages[name]['argtypes']
434 f.__name__ = str(name)
Ole Troan4df97162017-07-07 16:06:08 +0200435 f.__doc__ = ", ".join(["%s %s" %
436 (argtypes[k], k) for k in args.keys()])
Christophe Fontaine04f4b782016-12-09 15:53:47 +0100437 return f
Ole Troana03f4ef2016-12-02 12:53:55 +0100438
Klement Sekera7112c542017-03-01 09:53:19 +0100439 @property
440 def api(self):
441 if not hasattr(self, "_api"):
442 raise Exception("Not connected, api definitions not available")
443 return self._api
444
Wojciech Dec64bc6122016-12-12 11:32:25 +0100445 def _register_functions(self, async=False):
Ole Troana03f4ef2016-12-02 12:53:55 +0100446 self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
447 self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
Klement Sekera7112c542017-03-01 09:53:19 +0100448 self._api = Empty()
Ole Troan4df97162017-07-07 16:06:08 +0200449 for name, msgdef in vpp_iterator(self.messages):
450 if self.messages[name]['typeonly']:
451 continue
Ole Troan3cc49712017-03-08 12:02:24 +0100452 crc = self.messages[name]['crc']
453 n = name + '_' + crc[2:]
Ole Troan4df97162017-07-07 16:06:08 +0200454 i = vpp_api.vac_get_msg_index(n.encode())
Ole Troan3cc49712017-03-08 12:02:24 +0100455 if i > 0:
Ole Troana03f4ef2016-12-02 12:53:55 +0100456 self.id_msgdef[i] = msgdef
457 self.id_names[i] = name
458 multipart = True if name.find('_dump') > 0 else False
Klement Sekera7112c542017-03-01 09:53:19 +0100459 f = self.make_function(name, i, msgdef, multipart, async)
460 setattr(self._api, name, FuncWrapper(f))
461
Ole Troandfc9b7c2017-03-06 23:51:57 +0100462 # old API stuff starts here - will be removed in 17.07
Klement Sekera7112c542017-03-01 09:53:19 +0100463 if hasattr(self, name):
464 raise NameError(
465 3, "Conflicting name in JSON definition: `%s'" % name)
466 setattr(self, name, f)
467 # old API stuff ends here
Ole Troan3cc49712017-03-08 12:02:24 +0100468 else:
Ole Troan4df97162017-07-07 16:06:08 +0200469 self.logger.debug(
470 'No such message type or failed CRC checksum: %s', n)
Ole Troana03f4ef2016-12-02 12:53:55 +0100471
Ole Troan4df97162017-07-07 16:06:08 +0200472 def _write(self, buf):
Ole Troan5016f992017-01-19 09:44:44 +0100473 """Send a binary-packed message to VPP."""
Ole Troana03f4ef2016-12-02 12:53:55 +0100474 if not self.connected:
475 raise IOError(1, 'Not connected')
Ole Troan4df97162017-07-07 16:06:08 +0200476 return vpp_api.vac_write(ffi.from_buffer(buf), len(buf))
Ole Troana03f4ef2016-12-02 12:53:55 +0100477
Ole Troan4df97162017-07-07 16:06:08 +0200478 def _read(self):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100479 if not self.connected:
480 raise IOError(1, 'Not connected')
Ole Troan3cc49712017-03-08 12:02:24 +0100481 mem = ffi.new("char **")
482 size = ffi.new("int *")
Damjan Marion5fec1e82017-04-13 19:13:47 +0200483 rv = vpp_api.vac_read(mem, size, self.read_timeout)
Ole Troan3cc49712017-03-08 12:02:24 +0100484 if rv:
Ole Troanb0856b42017-08-17 12:48:08 +0200485 raise IOError(rv, 'vac_read failed')
Ole Troan3cc49712017-03-08 12:02:24 +0100486 msg = bytes(ffi.buffer(mem[0], size[0]))
Damjan Marion5fec1e82017-04-13 19:13:47 +0200487 vpp_api.vac_free(mem[0])
Ole Troan3cc49712017-03-08 12:02:24 +0100488 return msg
Ole Troana03f4ef2016-12-02 12:53:55 +0100489
Ole Troan4df97162017-07-07 16:06:08 +0200490 def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen,
491 async):
Ole Troan6bf177c2017-08-17 10:34:32 +0200492 pfx = chroot_prefix.encode() if chroot_prefix else ffi.NULL
493 rv = vpp_api.vac_connect(name.encode(), pfx, msg_handler, rx_qlen)
Ole Troana03f4ef2016-12-02 12:53:55 +0100494 if rv != 0:
495 raise IOError(2, 'Connect failed')
Ole Troan7e3a8752016-12-05 10:27:09 +0100496 self.connected = True
Ole Troana03f4ef2016-12-02 12:53:55 +0100497
Damjan Marion5fec1e82017-04-13 19:13:47 +0200498 self.vpp_dictionary_maxid = vpp_api.vac_msg_table_max_index()
Wojciech Dec64bc6122016-12-12 11:32:25 +0100499 self._register_functions(async=async)
Ole Troana03f4ef2016-12-02 12:53:55 +0100500
501 # Initialise control ping
Ole Troan3cc49712017-03-08 12:02:24 +0100502 crc = self.messages['control_ping']['crc']
Ole Troan4df97162017-07-07 16:06:08 +0200503 self.control_ping_index = vpp_api.vac_get_msg_index(
504 ('control_ping' + '_' + crc[2:]).encode())
Ole Troana03f4ef2016-12-02 12:53:55 +0100505 self.control_ping_msgdef = self.messages['control_ping']
Ole Troan4df97162017-07-07 16:06:08 +0200506 return rv
Ole Troana03f4ef2016-12-02 12:53:55 +0100507
Ole Troan6bf177c2017-08-17 10:34:32 +0200508 def connect(self, name, chroot_prefix=None, async=False, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100509 """Attach to VPP.
510
511 name - the name of the client.
512 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
513 async - if true, messages are sent without waiting for a reply
514 rx_qlen - the length of the VPP message receive queue between
515 client and server.
516 """
Ole Troan4df97162017-07-07 16:06:08 +0200517 msg_handler = vac_callback_sync if not async else vac_callback_async
Ole Troandfc9b7c2017-03-06 23:51:57 +0100518 return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen,
519 async)
520
Ole Troan6bf177c2017-08-17 10:34:32 +0200521 def connect_sync(self, name, chroot_prefix=None, rx_qlen=32):
Ole Troandfc9b7c2017-03-06 23:51:57 +0100522 """Attach to VPP in synchronous mode. Application must poll for events.
523
524 name - the name of the client.
525 chroot_prefix - if VPP is chroot'ed, the prefix of the jail
526 rx_qlen - the length of the VPP message receive queue between
527 client and server.
528 """
529
Ole Troan3cc49712017-03-08 12:02:24 +0100530 return self.connect_internal(name, ffi.NULL, chroot_prefix, rx_qlen,
Ole Troandfc9b7c2017-03-06 23:51:57 +0100531 async=False)
532
Ole Troana03f4ef2016-12-02 12:53:55 +0100533 def disconnect(self):
Ole Troan5016f992017-01-19 09:44:44 +0100534 """Detach from VPP."""
Damjan Marion5fec1e82017-04-13 19:13:47 +0200535 rv = vpp_api.vac_disconnect()
Ole Troan5016f992017-01-19 09:44:44 +0100536 self.connected = False
Ole Troana03f4ef2016-12-02 12:53:55 +0100537 return rv
538
Ole Troan5016f992017-01-19 09:44:44 +0100539 def msg_handler_sync(self, msg):
540 """Process an incoming message from VPP in sync mode.
541
542 The message may be a reply or it may be an async notification.
543 """
544 r = self.decode_incoming_msg(msg)
545 if r is None:
Ole Troana03f4ef2016-12-02 12:53:55 +0100546 return
547
Ole Troan5016f992017-01-19 09:44:44 +0100548 # If we have a context, then use the context to find any
549 # request waiting for a reply
550 context = 0
551 if hasattr(r, 'context') and r.context > 0:
552 context = r.context
Ole Troan5f9dcff2016-08-01 04:59:13 +0200553
Ole Troana03f4ef2016-12-02 12:53:55 +0100554 msgname = type(r).__name__
Ole Troan57c3d662016-09-12 22:00:32 +0200555
Ole Troan5016f992017-01-19 09:44:44 +0100556 if context == 0:
557 # No context -> async notification that we feed to the callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100558 self.message_queue.put_nowait(r)
Ole Troana03f4ef2016-12-02 12:53:55 +0100559 else:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100560 raise IOError(2, 'RPC reply message received in event handler')
Ole Troan5016f992017-01-19 09:44:44 +0100561
562 def decode_incoming_msg(self, msg):
563 if not msg:
Ole Troan3cc49712017-03-08 12:02:24 +0100564 self.logger.warning('vpp_api.read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100565 return
566
567 i, ci = self.header.unpack_from(msg, 0)
568 if self.id_names[i] == 'rx_thread_exit':
569 return
570
571 #
572 # Decode message and returns a tuple.
573 #
574 msgdef = self.id_msgdef[i]
575 if not msgdef:
576 raise IOError(2, 'Reply message undefined')
577
578 r = self.decode(msgdef, msg)
579
Ole Troana03f4ef2016-12-02 12:53:55 +0100580 return r
581
Ole Troan5016f992017-01-19 09:44:44 +0100582 def msg_handler_async(self, msg):
583 """Process a message from VPP in async mode.
584
585 In async mode, all messages are returned to the callback.
586 """
587 r = self.decode_incoming_msg(msg)
588 if r is None:
589 return
590
591 msgname = type(r).__name__
592
Ole Troan4df97162017-07-07 16:06:08 +0200593 if self.event_callback:
594 self.event_callback(msgname, r)
Ole Troan5016f992017-01-19 09:44:44 +0100595
596 def _control_ping(self, context):
597 """Send a ping command."""
598 self._call_vpp_async(self.control_ping_index,
Ole Troan4df97162017-07-07 16:06:08 +0200599 self.control_ping_msgdef,
Ole Troan5016f992017-01-19 09:44:44 +0100600 context=context)
601
602 def _call_vpp(self, i, msgdef, multipart, **kwargs):
603 """Given a message, send the message and await a reply.
604
605 msgdef - the message packing definition
606 i - the message type index
607 multipart - True if the message returns multiple
608 messages in return.
609 context - context number - chosen at random if not
610 supplied.
611 The remainder of the kwargs are the arguments to the API call.
612
613 The return value is the message or message array containing
614 the response. It will raise an IOError exception if there was
615 no response within the timeout window.
616 """
617
Ole Troan4df97162017-07-07 16:06:08 +0200618 if 'context' not in kwargs:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100619 context = self.get_context()
620 kwargs['context'] = context
621 else:
622 context = kwargs['context']
623 kwargs['_vl_msg_id'] = i
624 b = self.encode(msgdef, kwargs)
Ole Troan5016f992017-01-19 09:44:44 +0100625
Damjan Marion5fec1e82017-04-13 19:13:47 +0200626 vpp_api.vac_rx_suspend()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100627 self._write(b)
Ole Troan5016f992017-01-19 09:44:44 +0100628
629 if multipart:
630 # Send a ping after the request - we use its response
631 # to detect that we have seen all results.
632 self._control_ping(context)
633
634 # Block until we get a reply.
Ole Troandfc9b7c2017-03-06 23:51:57 +0100635 rl = []
636 while (True):
637 msg = self._read()
638 if not msg:
Ole Troan4df97162017-07-07 16:06:08 +0200639 raise IOError(2, 'VPP API client: read failed')
Ole Troan5016f992017-01-19 09:44:44 +0100640
Ole Troandfc9b7c2017-03-06 23:51:57 +0100641 r = self.decode_incoming_msg(msg)
642 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200643 if context not in r or r.context == 0 or context != r.context:
Ole Troandfc9b7c2017-03-06 23:51:57 +0100644 self.message_queue.put_nowait(r)
645 continue
646
647 if not multipart:
648 rl = r
649 break
650 if msgname == 'control_ping_reply':
651 break
652
653 rl.append(r)
654
Damjan Marion5fec1e82017-04-13 19:13:47 +0200655 vpp_api.vac_rx_resume()
Ole Troandfc9b7c2017-03-06 23:51:57 +0100656
657 return rl
Ole Troan5016f992017-01-19 09:44:44 +0100658
659 def _call_vpp_async(self, i, msgdef, **kwargs):
660 """Given a message, send the message and await a reply.
661
662 msgdef - the message packing definition
663 i - the message type index
664 context - context number - chosen at random if not
665 supplied.
666 The remainder of the kwargs are the arguments to the API call.
667 """
Ole Troan4df97162017-07-07 16:06:08 +0200668 if 'context' not in kwargs:
Ole Troan7e3a8752016-12-05 10:27:09 +0100669 context = self.get_context()
670 kwargs['context'] = context
671 else:
672 context = kwargs['context']
673 kwargs['_vl_msg_id'] = i
674 b = self.encode(msgdef, kwargs)
675
676 self._write(b)
677
Ole Troana03f4ef2016-12-02 12:53:55 +0100678 def register_event_callback(self, callback):
Ole Troan5016f992017-01-19 09:44:44 +0100679 """Register a callback for async messages.
Ole Troana03f4ef2016-12-02 12:53:55 +0100680
Ole Troan5016f992017-01-19 09:44:44 +0100681 This will be called for async notifications in sync mode,
682 and all messages in async mode. In sync mode, replies to
683 requests will not come here.
684
685 callback is a fn(msg_type_name, msg_type) that will be
686 called when a message comes in. While this function is
687 executing, note that (a) you are in a background thread and
688 may wish to use threading.Lock to protect your datastructures,
689 and (b) message processing from VPP will stop (so if you take
690 a long while about it you may provoke reply timeouts or cause
691 VPP to fill the RX buffer). Passing None will disable the
692 callback.
693 """
694 self.event_callback = callback
Ole Troandfc9b7c2017-03-06 23:51:57 +0100695
696 def thread_msg_handler(self):
697 """Python thread calling the user registerd message handler.
698
699 This is to emulate the old style event callback scheme. Modern
700 clients should provide their own thread to poll the event
701 queue.
702 """
703 while True:
704 r = self.message_queue.get()
705 msgname = type(r).__name__
Ole Troan4df97162017-07-07 16:06:08 +0200706 if self.event_callback:
707 self.event_callback(msgname, r)