papi: change default to use socket transport instead of shared memory transport
[vpp.git] / src / vpp-api / python / vpp_papi / vpp_papi.py
index 7f6efbb..25f4727 100644 (file)
 from __future__ import print_function
 from __future__ import absolute_import
 import ctypes
 from __future__ import print_function
 from __future__ import absolute_import
 import ctypes
+import ipaddress
 import sys
 import multiprocessing as mp
 import os
 import sys
 import multiprocessing as mp
 import os
+import queue
 import logging
 import functools
 import json
 import logging
 import functools
 import json
@@ -28,16 +30,25 @@ import fnmatch
 import weakref
 import atexit
 import time
 import weakref
 import atexit
 import time
-from . vpp_serializer import VPPType, VPPEnumType, VPPUnionType
+from . vpp_format import verify_enum_hint
+from . vpp_serializer import VPPType, VPPEnumType, VPPEnumFlagType, VPPUnionType
 from . vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias
 
 from . vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias
 
-if sys.version[0] == '2':
-    import Queue as queue
-else:
-    import queue as queue
+try:
+    import VppTransport
+except ModuleNotFoundError:
+    class V:
+        """placeholder for VppTransport as the implementation is dependent on
+        VPPAPIClient's initialization values
+        """
+
+    VppTransport = V
+
+logger = logging.getLogger('vpp_papi')
+logger.addHandler(logging.NullHandler())
 
 
-__all__ = ('FuncWrapper', 'VPP', 'VppApiDynamicMethodHolder',
-           'VppEnum', 'VppEnumType',
+__all__ = ('FuncWrapper', 'VppApiDynamicMethodHolder',
+           'VppEnum', 'VppEnumType', 'VppEnumFlag',
            'VPPIOError', 'VPPRuntimeError', 'VPPValueError',
            'VPPApiClient', )
 
            'VPPIOError', 'VPPRuntimeError', 'VPPValueError',
            'VPPApiClient', )
 
@@ -57,7 +68,12 @@ class VppEnumType(type):
 
 
 @metaclass(VppEnumType)
 
 
 @metaclass(VppEnumType)
-class VppEnum(object):
+class VppEnum:
+    pass
+
+
+@metaclass(VppEnumType)
+class VppEnumFlag:
     pass
 
 
     pass
 
 
@@ -65,23 +81,35 @@ def vpp_atexit(vpp_weakref):
     """Clean up VPP connection on shutdown."""
     vpp_instance = vpp_weakref()
     if vpp_instance and vpp_instance.transport.connected:
     """Clean up VPP connection on shutdown."""
     vpp_instance = vpp_weakref()
     if vpp_instance and vpp_instance.transport.connected:
-        vpp_instance.logger.debug('Cleaning up VPP on exit')
+        logger.debug('Cleaning up VPP on exit')
         vpp_instance.disconnect()
 
 
         vpp_instance.disconnect()
 
 
-if sys.version[0] == '2':
-    def vpp_iterator(d):
-        return d.iteritems()
-else:
-    def vpp_iterator(d):
-        return d.items()
+def add_convenience_methods():
+    # provide convenience methods to IP[46]Address.vapi_af
+    def _vapi_af(self):
+        if 6 == self._version:
+            return VppEnum.vl_api_address_family_t.ADDRESS_IP6.value
+        if 4 == self._version:
+            return VppEnum.vl_api_address_family_t.ADDRESS_IP4.value
+        raise ValueError("Invalid _version.")
+
+    def _vapi_af_name(self):
+        if 6 == self._version:
+            return 'ip6'
+        if 4 == self._version:
+            return 'ip4'
+        raise ValueError("Invalid _version.")
+
+    ipaddress._IPAddressBase.vapi_af = property(_vapi_af)
+    ipaddress._IPAddressBase.vapi_af_name = property(_vapi_af_name)
 
 
 
 
-class VppApiDynamicMethodHolder(object):
+class VppApiDynamicMethodHolder:
     pass
 
 
     pass
 
 
-class FuncWrapper(object):
+class FuncWrapper:
     def __init__(self, func):
         self._func = func
         self.__name__ = func.__name__
     def __init__(self, func):
         self._func = func
         self.__name__ = func.__name__
@@ -113,7 +141,8 @@ class VPPRuntimeError(RuntimeError):
 class VPPValueError(ValueError):
     pass
 
 class VPPValueError(ValueError):
     pass
 
-class VPPApiJSONFiles(object):
+
+class VPPApiJSONFiles:
     @classmethod
     def find_api_dir(cls, dirs):
         """Attempt to find the best directory in which API definition
     @classmethod
     def find_api_dir(cls, dirs):
         """Attempt to find the best directory in which API definition
@@ -195,7 +224,7 @@ class VPPApiJSONFiles(object):
         return None
 
     @classmethod
         return None
 
     @classmethod
-    def find_api_files(cls, api_dir=None, patterns='*'):
+    def find_api_files(cls, api_dir=None, patterns='*'):  # -> list
         """Find API definition files from the given directory tree with the
         given pattern. If no directory is given then find_api_dir() is used
         to locate one. If no pattern is given then all definition files found
         """Find API definition files from the given directory tree with the
         given pattern. If no directory is given then find_api_dir() is used
         to locate one. If no pattern is given then all definition files found
@@ -237,21 +266,54 @@ class VPPApiJSONFiles(object):
     @classmethod
     def process_json_file(self, apidef_file):
         api = json.load(apidef_file)
     @classmethod
     def process_json_file(self, apidef_file):
         api = json.load(apidef_file)
+        return self._process_json(api)
+
+    @classmethod
+    def process_json_str(self, json_str):
+        api = json.loads(json_str)
+        return self._process_json(api)
+
+    @staticmethod
+    def _process_json(api):  # -> Tuple[Dict, Dict]
         types = {}
         services = {}
         messages = {}
         types = {}
         services = {}
         messages = {}
-        for t in api['enums']:
-            t[0] = 'vl_api_' + t[0] + '_t'
-            types[t[0]] = {'type': 'enum', 'data': t}
-        for t in api['unions']:
-            t[0] = 'vl_api_' + t[0] + '_t'
-            types[t[0]] = {'type': 'union', 'data': t}
-        for t in api['types']:
-            t[0] = 'vl_api_' + t[0] + '_t'
-            types[t[0]] = {'type': 'type', 'data': t}
-        for t, v in api['aliases'].items():
-            types['vl_api_' + t + '_t'] = {'type': 'alias', 'data': v}
-        services.update(api['services'])
+        try:
+            for t in api['enums']:
+                t[0] = 'vl_api_' + t[0] + '_t'
+                types[t[0]] = {'type': 'enum', 'data': t}
+        except KeyError:
+            pass
+        try:
+            for t in api['enumflags']:
+                t[0] = 'vl_api_' + t[0] + '_t'
+                types[t[0]] = {'type': 'enum', 'data': t}
+        except KeyError:
+            pass
+        try:
+            for t in api['unions']:
+                t[0] = 'vl_api_' + t[0] + '_t'
+                types[t[0]] = {'type': 'union', 'data': t}
+        except KeyError:
+            pass
+
+        try:
+            for t in api['types']:
+                t[0] = 'vl_api_' + t[0] + '_t'
+                types[t[0]] = {'type': 'type', 'data': t}
+        except KeyError:
+            pass
+
+        try:
+            for t, v in api['aliases'].items():
+                types['vl_api_' + t + '_t'] = {'type': 'alias', 'data': v}
+        except KeyError:
+            pass
+
+        try:
+            services.update(api['services'])
+        except KeyError:
+            pass
 
         i = 0
         while True:
 
         i = 0
         while True:
@@ -264,6 +326,12 @@ class VPPApiJSONFiles(object):
                             VPPEnumType(t[0], t[1:])
                         except ValueError:
                             unresolved[k] = v
                             VPPEnumType(t[0], t[1:])
                         except ValueError:
                             unresolved[k] = v
+                if not vpp_get_type(k):
+                    if v['type'] == 'enumflag':
+                        try:
+                            VPPEnumFlagType(t[0], t[1:])
+                        except ValueError:
+                            unresolved[k] = v
                     elif v['type'] == 'union':
                         try:
                             VPPUnionType(t[0], t[1:])
                     elif v['type'] == 'union':
                         try:
                             VPPUnionType(t[0], t[1:])
@@ -286,16 +354,19 @@ class VPPApiJSONFiles(object):
                                     .format(unresolved))
             types = unresolved
             i += 1
                                     .format(unresolved))
             types = unresolved
             i += 1
-
-        for m in api['messages']:
-            try:
-                messages[m[0]] = VPPMessage(m[0], m[1:])
-            except VPPNotImplementedError:
-                ### OLE FIXME
-                self.logger.error('Not implemented error for {}'.format(m[0]))
+        try:
+            for m in api['messages']:
+                try:
+                    messages[m[0]] = VPPMessage(m[0], m[1:])
+                except VPPNotImplementedError:
+                    ### OLE FIXME
+                    logger.error('Not implemented error for {}'.format(m[0]))
+        except KeyError:
+            pass
         return messages, services
 
         return messages, services
 
-class VPPApiClient(object):
+
+class VPPApiClient:
     """VPP interface.
 
     This class provides the APIs to VPP.  The APIs are loaded
     """VPP interface.
 
     This class provides the APIs to VPP.  The APIs are loaded
@@ -315,9 +386,9 @@ class VPPApiClient(object):
     VPPIOError = VPPIOError
 
 
     VPPIOError = VPPIOError
 
 
-    def __init__(self, apifiles=None, testmode=False, async_thread=True,
+    def __init__(self, *, apifiles=None, testmode=False, async_thread=True,
                  logger=None, loglevel=None,
                  logger=None, loglevel=None,
-                 read_timeout=5, use_socket=False,
+                 read_timeout=5, use_socket=True,
                  server_address='/run/vpp/api.sock'):
         """Create a VPP API object.
 
                  server_address='/run/vpp/api.sock'):
         """Create a VPP API object.
 
@@ -365,7 +436,7 @@ class VPPApiClient(object):
             # Pick up API definitions from default directory
             try:
                 apifiles = VPPApiJSONFiles.find_api_files(self.apidir)
             # Pick up API definitions from default directory
             try:
                 apifiles = VPPApiJSONFiles.find_api_files(self.apidir)
-            except RuntimeError:
+            except (RuntimeError, VPPApiError):
                 # In test mode we don't care that we can't find the API files
                 if testmode:
                     apifiles = []
                 # In test mode we don't care that we can't find the API files
                 if testmode:
                     apifiles = []
@@ -383,17 +454,21 @@ class VPPApiClient(object):
         # Basic sanity check
         if len(self.messages) == 0 and not testmode:
             raise VPPValueError(1, 'Missing JSON message definitions')
         # Basic sanity check
         if len(self.messages) == 0 and not testmode:
             raise VPPValueError(1, 'Missing JSON message definitions')
+        if not(verify_enum_hint(VppEnum.vl_api_address_family_t)):
+            raise VPPRuntimeError("Invalid address family hints. "
+                                  "Cannot continue.")
 
         self.transport = VppTransport(self, read_timeout=read_timeout,
                                       server_address=server_address)
         # Make sure we allow VPP to clean up the message rings.
         atexit.register(vpp_atexit, weakref.ref(self))
 
 
         self.transport = VppTransport(self, read_timeout=read_timeout,
                                       server_address=server_address)
         # Make sure we allow VPP to clean up the message rings.
         atexit.register(vpp_atexit, weakref.ref(self))
 
+        add_convenience_methods()
+
     def get_function(self, name):
         return getattr(self._api, name)
 
     def get_function(self, name):
         return getattr(self._api, name)
 
-
-    class ContextId(object):
+    class ContextId:
         """Multiprocessing-safe provider of unique context IDs."""
         def __init__(self):
             self.context = mp.Value(ctypes.c_uint, 0)
         """Multiprocessing-safe provider of unique context IDs."""
         def __init__(self):
             self.context = mp.Value(ctypes.c_uint, 0)
@@ -435,7 +510,7 @@ class VPPApiClient(object):
         self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
         self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
         self._api = VppApiDynamicMethodHolder()
         self.id_names = [None] * (self.vpp_dictionary_maxid + 1)
         self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1)
         self._api = VppApiDynamicMethodHolder()
-        for name, msg in vpp_iterator(self.messages):
+        for name, msg in self.messages.items():
             n = name + '_' + msg.crc[2:]
             i = self.transport.get_msg_index(n)
             if i > 0:
             n = name + '_' + msg.crc[2:]
             i = self.transport.get_msg_index(n)
             if i > 0:
@@ -444,12 +519,7 @@ class VPPApiClient(object):
 
                 # Create function for client side messages.
                 if name in self.services:
 
                 # Create function for client side messages.
                 if name in self.services:
-                    if 'stream' in self.services[name] and \
-                       self.services[name]['stream']:
-                        multipart = True
-                    else:
-                        multipart = False
-                    f = self.make_function(msg, i, multipart, do_async)
+                    f = self.make_function(msg, i, self.services[name], do_async)
                     setattr(self._api, name, FuncWrapper(f))
             else:
                 self.logger.debug(
                     setattr(self._api, name, FuncWrapper(f))
             else:
                 self.logger.debug(
@@ -555,7 +625,7 @@ class VPPApiClient(object):
 
     def decode_incoming_msg(self, msg, no_type_conversion=False):
         if not msg:
 
     def decode_incoming_msg(self, msg, no_type_conversion=False):
         if not msg:
-            self.logger.warning('vpp_api.read failed')
+            logger.warning('vpp_api.read failed')
             return
 
         (i, ci), size = self.header.unpack(msg, 0)
             return
 
         (i, ci), size = self.header.unpack(msg, 0)
@@ -616,7 +686,16 @@ class VPPApiClient(object):
                                                            n[1]['avg'], n[1]['max'])
         return s
 
                                                            n[1]['avg'], n[1]['max'])
         return s
 
-    def _call_vpp(self, i, msgdef, multipart, **kwargs):
+    def get_field_options(self, msg, fld_name):
+        # when there is an option, the msgdef has 3 elements.
+        # ['u32', 'ring_size', {'default': 1024}]
+        for _def in self.messages[msg].msgdef:
+            if isinstance(_def, list) and \
+                    len(_def) == 3 and \
+                    _def[1] == fld_name:
+                return _def[2]
+
+    def _call_vpp(self, i, msgdef, service, **kwargs):
         """Given a message, send the message and await a reply.
 
         msgdef - the message packing definition
         """Given a message, send the message and await a reply.
 
         msgdef - the message packing definition
@@ -658,10 +737,21 @@ class VPPApiClient(object):
 
         self.transport.write(b)
 
 
         self.transport.write(b)
 
-        if multipart:
-            # Send a ping after the request - we use its response
-            # to detect that we have seen all results.
-            self._control_ping(context)
+        msgreply = service['reply']
+        stream = True if 'stream' in service else False
+        if stream:
+            if 'stream_msg' in service:
+                # New service['reply'] = _reply and service['stream_message'] = _details
+                stream_message = service['stream_msg']
+                modern =True
+            else:
+                # Old  service['reply'] = _details
+                stream_message = msgreply
+                msgreply = 'control_ping_reply'
+                modern = False
+                # Send a ping after the request - we use its response
+                # to detect that we have seen all results.
+                self._control_ping(context)
 
         # Block until we get a reply.
         rl = []
 
         # Block until we get a reply.
         rl = []
@@ -674,11 +764,14 @@ class VPPApiClient(object):
                 # Message being queued
                 self.message_queue.put_nowait(r)
                 continue
                 # Message being queued
                 self.message_queue.put_nowait(r)
                 continue
-
-            if not multipart:
+            if msgname != msgreply and (stream and (msgname != stream_message)):
+                print('REPLY MISMATCH', msgreply, msgname, stream_message, stream)
+            if not stream:
                 rl = r
                 break
                 rl = r
                 break
-            if msgname == 'control_ping_reply':
+            if msgname == msgreply:
+                if modern: # Return both reply and list
+                    rl = r, rl
                 break
 
             rl.append(r)
                 break
 
             rl.append(r)
@@ -819,8 +912,13 @@ class VPPApiClient(object):
                    self.logger, self.read_timeout, self.use_socket,
                    self.server_address)
 
                    self.logger, self.read_timeout, self.use_socket,
                    self.server_address)
 
-
-# Provide the old name for backward compatibility.
-VPP = VPPApiClient
-
-# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
+    def details_iter(self, f, **kwargs):
+        cursor = 0
+        while True:
+            kwargs['cursor'] = cursor
+            rv, details = f(**kwargs)
+            for d in details:
+                yield d
+            if rv.retval == 0 or rv.retval != -165:
+                break
+            cursor = rv.cursor