vpp_papi: Add custom exceptions. 29/16229/3
authorPaul Vinciguerra <pvinci@vinciconsulting.com>
Tue, 27 Nov 2018 16:15:22 +0000 (08:15 -0800)
committerOle Trøan <otroan@employees.org>
Thu, 29 Nov 2018 19:18:07 +0000 (19:18 +0000)
This patchset adds and raises the following custom exception classes:

* class VPPApiError(Exception):
* class VPPNotImplementedError(NotImplementedError):
* class VPPIOError(IOError):
* class VPPRuntimeError(RuntimeError):
* class VPPValueError(ValueError):

* class VPPSerializerValueError(ValueError):

* class VPPStatsIOError(IOError):
* class VPPStatsClientLoadError(RuntimeError):

* class VppTransportShmemIOError(IOError):

* class VppTransportSocketIOError(IOError)

Change-Id: Ia40900fd2dcef148d01125d6c691329fc666901e
Signed-off-by: Paul Vinciguerra <pvinci@vinciconsulting.com>
src/vpp-api/python/vpp_papi/vpp_format.py
src/vpp-api/python/vpp_papi/vpp_papi.py
src/vpp-api/python/vpp_papi/vpp_serializer.py
src/vpp-api/python/vpp_papi/vpp_stats.py
src/vpp-api/python/vpp_papi/vpp_transport_shmem.py
src/vpp-api/python/vpp_papi/vpp_transport_socket.py

index d020df9..c6f9477 100644 (file)
 #
 
 from socket import inet_pton, inet_ntop, AF_INET6, AF_INET
+import socket
+
+
+class VPPFormatError(Exception):
+    pass
 
 
 class VPPFormat(object):
+    VPPFormatError = VPPFormatError
+
     @staticmethod
     def format_vl_api_ip6_prefix_t(args):
         prefix, len = args.split('/')
@@ -52,7 +59,7 @@ class VPPFormat(object):
         try:
             return {'un': {'ip6': {'address': inet_pton(AF_INET6, args)}},
                     'af': int(1)}
-        except Exception as e:
+        except socket.error as e:
             return {'un': {'ip4': {'address': inet_pton(AF_INET, args)}},
                     'af': int(0)}
 
@@ -62,7 +69,7 @@ class VPPFormat(object):
             return inet_ntop(AF_INET6, arg.un.ip6.address)
         if arg.af == 0:
             return inet_ntop(AF_INET, arg.un.ip4.address)
-        raise
+        raise VPPFormatError
 
     @staticmethod
     def format_vl_api_prefix_t(args):
@@ -80,7 +87,7 @@ class VPPFormat(object):
             return "{}/{}".format(inet_ntop(AF_INET,
                                             arg.address.un.ip4.address),
                                   arg.address_length)
-        raise
+        raise VPPFormatError
 
     @staticmethod
     def format_u8(args):
@@ -101,7 +108,7 @@ class VPPFormat(object):
     def unformat_bytes(args):
         try:
             return args.decode('utf-8')
-        except Exception as e:
+        except ValueError as e:
             return args
 
     @staticmethod
index bd2682f..3c3eb70 100644 (file)
@@ -77,6 +77,26 @@ class FuncWrapper(object):
         return self._func(**kwargs)
 
 
+class VPPApiError(Exception):
+    pass
+
+
+class VPPNotImplementedError(NotImplementedError):
+    pass
+
+
+class VPPIOError(IOError):
+    pass
+
+
+class VPPRuntimeError(RuntimeError):
+    pass
+
+
+class VPPValueError(ValueError):
+    pass
+
+
 class VPP(object):
     """VPP interface.
 
@@ -89,6 +109,11 @@ class VPP(object):
     provides a means to register a callback function to receive
     these messages in a background thread.
     """
+    VPPApiError = VPPApiError
+    VPPRuntimeError = VPPRuntimeError
+    VPPValueError = VPPValueError
+    VPPNotImplementedError = VPPNotImplementedError
+    VPPIOError = VPPIOError
 
     def process_json_file(self, apidef_file):
         api = json.load(apidef_file)
@@ -134,15 +159,15 @@ class VPP(object):
             if len(unresolved) == 0:
                 break
             if i > 3:
-                raise ValueError('Unresolved type definitions {}'
-                                 .format(unresolved))
+                raise VPPValueError('Unresolved type definitions {}'
+                                    .format(unresolved))
             types = unresolved
             i += 1
 
         for m in api['messages']:
             try:
                 self.messages[m[0]] = VPPMessage(m[0], m[1:])
-            except NotImplementedError:
+            except VPPNotImplementedError:
                 self.logger.error('Not implemented error for {}'.format(m[0]))
 
     def __init__(self, apifiles=None, testmode=False, async_thread=True,
@@ -192,7 +217,7 @@ class VPP(object):
                 if testmode:
                     apifiles = []
                 else:
-                    raise
+                    raise VPPRuntimeError
 
         for file in apifiles:
             with open(file) as apidef_file:
@@ -202,7 +227,7 @@ class VPP(object):
 
         # Basic sanity check
         if len(self.messages) == 0 and not testmode:
-            raise ValueError(1, 'Missing JSON message definitions')
+            raise VPPValueError(1, 'Missing JSON message definitions')
 
         self.transport = VppTransport(self, read_timeout=read_timeout,
                                       server_address=server_address)
@@ -333,7 +358,7 @@ class VPP(object):
         if api_dir is None:
             api_dir = cls.find_api_dir()
             if api_dir is None:
-                raise RuntimeError("api_dir cannot be located")
+                raise VPPApiError("api_dir cannot be located")
 
         if isinstance(patterns, list) or isinstance(patterns, tuple):
             patterns = [p.strip() + '.api.json' for p in patterns]
@@ -352,7 +377,7 @@ class VPP(object):
     @property
     def api(self):
         if not hasattr(self, "_api"):
-            raise Exception("Not connected, api definitions not available")
+            raise VPPApiError("Not connected, api definitions not available")
         return self._api
 
     def make_function(self, msg, i, multipart, do_async):
@@ -393,7 +418,7 @@ class VPP(object):
 
         rv = self.transport.connect(name.encode(), pfx, msg_handler, rx_qlen)
         if rv != 0:
-            raise IOError(2, 'Connect failed')
+            raise VPPIOError(2, 'Connect failed')
         self.vpp_dictionary_maxid = self.transport.msg_table_max_index()
         self._register_functions(do_async=do_async)
 
@@ -459,7 +484,7 @@ class VPP(object):
             # No context -> async notification that we feed to the callback
             self.message_queue.put_nowait(r)
         else:
-            raise IOError(2, 'RPC reply message received in event handler')
+            raise VPPIOError(2, 'RPC reply message received in event handler')
 
     def decode_incoming_msg(self, msg):
         if not msg:
@@ -474,7 +499,7 @@ class VPP(object):
         #
         msgobj = self.id_msgdef[i]
         if not msgobj:
-            raise IOError(2, 'Reply message undefined')
+            raise VPPIOError(2, 'Reply message undefined')
 
         r, size = msgobj.unpack(msg)
         return r
@@ -502,7 +527,7 @@ class VPP(object):
     def validate_args(self, msg, kwargs):
         d = set(kwargs.keys()) - set(msg.field_by_name.keys())
         if d:
-            raise ValueError('Invalid argument {} to {}'
+            raise VPPValueError('Invalid argument {} to {}'
                              .format(list(d), msg.name))
 
     def _call_vpp(self, i, msg, multipart, **kwargs):
@@ -549,7 +574,7 @@ class VPP(object):
         while (True):
             msg = self.transport.read()
             if not msg:
-                raise IOError(2, 'VPP API client: read failed')
+                raise VPPIOError(2, 'VPP API client: read failed')
             r = self.decode_incoming_msg(msg)
             msgname = type(r).__name__
             if context not in r or r.context == 0 or context != r.context:
index a001cca..f789729 100644 (file)
@@ -55,14 +55,10 @@ class BaseTypes(object):
         return self.packer.unpack_from(data, offset)[0], self.packer.size
 
 
-types = {}
-types['u8'] = BaseTypes('u8')
-types['u16'] = BaseTypes('u16')
-types['u32'] = BaseTypes('u32')
-types['i32'] = BaseTypes('i32')
-types['u64'] = BaseTypes('u64')
-types['f64'] = BaseTypes('f64')
-types['bool'] = BaseTypes('bool')
+types = {'u8': BaseTypes('u8'), 'u16': BaseTypes('u16'),
+         'u32': BaseTypes('u32'), 'i32': BaseTypes('i32'),
+         'u64': BaseTypes('u64'), 'f64': BaseTypes('f64'),
+         'bool': BaseTypes('bool')}
 
 
 def vpp_get_type(name):
@@ -72,6 +68,10 @@ def vpp_get_type(name):
         return None
 
 
+class VPPSerializerValueError(ValueError):
+    pass
+
+
 class FixedList_u8(object):
     def __init__(self, name, field_type, num):
         self.name = name
@@ -85,16 +85,18 @@ class FixedList_u8(object):
         if not list:
             return b'\x00' * self.size
         if len(list) > self.num:
-            raise ValueError('Fixed list length error for "{}", got: {}'
-                             ' expected: {}'
-                             .format(self.name, len(list), self.num))
+            raise VPPSerializerValueError(
+                'Fixed list length error for "{}", got: {}'
+                ' expected: {}'
+                .format(self.name, len(list), self.num))
         return self.packer.pack(list)
 
     def unpack(self, data, offset=0, result=None):
         if len(data[offset:]) < self.num:
-            raise ValueError('Invalid array length for "{}" got {}'
-                             ' expected {}'
-                             .format(self.name, len(data[offset:]), self.num))
+            raise VPPSerializerValueError(
+                'Invalid array length for "{}" got {}'
+                ' expected {}'
+                .format(self.name, len(data[offset:]), self.num))
         return self.packer.unpack(data, offset)
 
 
@@ -106,8 +108,9 @@ class FixedList(object):
 
     def pack(self, list, kwargs):
         if len(list) != self.num:
-            raise ValueError('Fixed list length error, got: {} expected: {}'
-                             .format(len(list), self.num))
+            raise VPPSerializerValueError(
+                'Fixed list length error, got: {} expected: {}'
+                .format(len(list), self.num))
         b = bytes()
         for e in list:
             b += self.packer.pack(e)
@@ -137,8 +140,9 @@ class VLAList(object):
         if not list:
             return b""
         if len(list) != kwargs[self.length_field]:
-            raise ValueError('Variable length error, got: {} expected: {}'
-                             .format(len(list), kwargs[self.length_field]))
+            raise VPPSerializerValueError(
+                'Variable length error, got: {} expected: {}'
+                .format(len(list), kwargs[self.length_field]))
         b = bytes()
 
         # u8 array
@@ -187,7 +191,8 @@ class VLAList_legacy():
         total = 0
         # Return a list of arguments
         if (len(data) - offset) % self.packer.size:
-            raise ValueError('Legacy Variable Length Array length mismatch.')
+            raise VPPSerializerValueError(
+                'Legacy Variable Length Array length mismatch.')
         elements = int((len(data) - offset) / self.packer.size)
         r = []
         for e in range(elements):
@@ -241,7 +246,8 @@ class VPPUnionType(object):
             f_type, f_name = f
             if f_type not in types:
                 logger.debug('Unknown union type {}'.format(f_type))
-                raise ValueError('Unknown message type {}'.format(f_type))
+                raise VPPSerializerValueError(
+                    'Unknown message type {}'.format(f_type))
             fields.append(f_name)
             size = types[f_type].size
             self.packers[f_name] = types[f_type]
@@ -309,7 +315,8 @@ class VPPType(object):
             self.fieldtypes.append(f_type)
             if f_type not in types:
                 logger.debug('Unknown type {}'.format(f_type))
-                raise ValueError('Unknown message type {}'.format(f_type))
+                raise VPPSerializerValueError(
+                    'Unknown message type {}'.format(f_type))
             if len(f) == 3:  # list
                 list_elements = f[2]
                 if list_elements == 0:
@@ -345,8 +352,9 @@ class VPPType(object):
 
             # Try one of the format functions
             if data and type(data) is not dict and a not in data:
-                raise ValueError("Invalid argument: {} expected {}.{}".
-                                 format(data, self.name, a))
+                raise VPPSerializerValueError(
+                    "Invalid argument: {} expected {}.{}".
+                    format(data, self.name, a))
 
             # Defaulting to zero.
             if not data or a not in data:  # Default to 0
index 8344de0..bb6cdf5 100644 (file)
@@ -138,12 +138,26 @@ def stat_entry_to_python(api, e):
     return None
 
 
+class VPPStatsIOError(IOError):
+    pass
+
+
+class VPPStatsClientLoadError(RuntimeError):
+    pass
+
+
 class VPPStats(object):
-    def __init__(self, socketname='/var/run/stats.sock', timeout=10):
+    VPPStatsIOError = VPPStatsIOError
+
+    default_socketname = '/var/run/stats.sock'
+    sharedlib_name = 'libvppapiclient.so'
+
+    def __init__(self, socketname=default_socketname, timeout=10):
         try:
-            self.api = ffi.dlopen('libvppapiclient.so')
+            self.api = ffi.dlopen(VPPStats.sharedlib_name)
         except Exception:
-            raise RuntimeError("Could not open: libvppapiclient.so")
+            raise VPPStatsClientLoadError("Could not open: %s" %
+                                          VPPStats.sharedlib_name)
         self.client = self.api.stat_client_get()
 
         poll_end_time = time.time() + timeout
@@ -154,7 +168,7 @@ class VPPStats(object):
                 break
 
         if rv != 0:
-            raise IOError()
+            raise VPPStatsIOError()
 
     def heartbeat(self):
         return self.api.stat_segment_heartbeat_r(self.client)
@@ -169,7 +183,7 @@ class VPPStats(object):
         rv = self.api.stat_segment_dump_r(counters, self.client)
         # Raise exception and retry
         if rv == ffi.NULL:
-            raise IOError()
+            raise VPPStatsIOError()
         rv_len = self.api.stat_segment_vec_len(rv)
         for i in range(rv_len):
             n = ffi.string(rv[i].name).decode()
@@ -184,7 +198,7 @@ class VPPStats(object):
             try:
                 dir = self.ls(name)
                 return self.dump(dir).values()[0]
-            except Exception as e:
+            except VPPStatsIOError as e:
                 if retries > 10:
                     return None
                 retries += 1
@@ -201,7 +215,7 @@ class VPPStats(object):
                 error_names = self.ls(['/err/'])
                 error_counters = self.dump(error_names)
                 break
-            except Exception as e:
+            except VPPStatsIOError as e:
                 if retries > 10:
                     return None
                 retries += 1
index 5920cd1..53ae775 100644 (file)
@@ -47,7 +47,13 @@ def vac_error_handler(arg, msg, msg_len):
     vpp_object.logger.warning("VPP API client:: %s", ffi.string(msg, msg_len))
 
 
+class VppTransportShmemIOError(IOError):
+    pass
+
+
 class VppTransport(object):
+    VppTransportShmemIOError = VppTransportShmemIOError
+
     def __init__(self, parent, read_timeout, server_address):
         self.connected = False
         self.read_timeout = read_timeout
@@ -95,23 +101,23 @@ class VppTransport(object):
     def _write_new_cffi(self, buf):
         """Send a binary-packed message to VPP."""
         if not self.connected:
-            raise IOError(1, 'Not connected')
+            raise VppTransportShmemIOError(1, 'Not connected')
         return vpp_api.vac_write(ffi.from_buffer(buf), len(buf))
 
     def _write_legacy_cffi(self, buf):
         """Send a binary-packed message to VPP."""
         if not self.connected:
-            raise IOError(1, 'Not connected')
+            raise VppTransportShmemIOError(1, 'Not connected')
         return vpp_api.vac_write(bytes(buf), len(buf))
 
     def read(self):
         if not self.connected:
-            raise IOError(1, 'Not connected')
+            raise VppTransportShmemIOError(1, 'Not connected')
         mem = ffi.new("char **")
         size = ffi.new("int *")
         rv = vpp_api.vac_read(mem, size, self.read_timeout)
         if rv:
-            raise IOError(rv, 'vac_read failed')
+            raise VppTransportShmemIOError(rv, 'vac_read failed')
         msg = bytes(ffi.buffer(mem[0], size[0]))
         vpp_api.vac_free(mem[0])
         return msg
index 49e5659..393e2e9 100644 (file)
@@ -13,7 +13,13 @@ except ImportError:
 import logging
 
 
+class VppTransportSocketIOError(IOError):
+    pass
+
+
 class VppTransport(object):
+    VppTransportSocketIOError = VppTransportSocketIOError
+
     def __init__(self, parent, read_timeout, server_address):
         self.connected = False
         self.read_timeout = read_timeout if read_timeout > 0 else 1
@@ -59,7 +65,8 @@ class VppTransport(object):
                     else:
                         self.parent.msg_handler_async(msg)
                 else:
-                    raise IOError(2, 'Unknown response from select')
+                    raise VppTransportSocketIOError(
+                        2, 'Unknown response from select')
 
     def connect(self, name, pfx, msg_handler, rx_qlen):
 
@@ -87,7 +94,7 @@ class VppTransport(object):
         msg = self._read()
         hdr, length = self.parent.header.unpack(msg, 0)
         if hdr.msgid != 16:
-            raise IOError('Invalid reply message')
+            raise VppTransportSocketIOError('Invalid reply message')
 
         r, length = sockclnt_create_reply.unpack(msg)
         self.socket_index = r.index
@@ -136,7 +143,7 @@ class VppTransport(object):
     def write(self, buf):
         """Send a binary-packed message to VPP."""
         if not self.connected:
-            raise IOError(1, 'Not connected')
+            raise VppTransportSocketIOError(1, 'Not connected')
 
         # Send header
         header = self.header.pack(0, len(buf), 0)
@@ -177,7 +184,7 @@ class VppTransport(object):
 
     def read(self):
         if not self.connected:
-            raise IOError(1, 'Not connected')
+            raise VppTransportSocketIOError(1, 'Not connected')
         try:
             return self.q.get(True, self.read_timeout)
         except queue.Empty: