VPP-1033: Python API support arbitrary sized input parameters. 36/8936/6
authorOle Troan <ot@cisco.com>
Fri, 20 Oct 2017 11:28:20 +0000 (13:28 +0200)
committerDave Wallace <dwallacelf@gmail.com>
Wed, 25 Oct 2017 17:16:56 +0000 (17:16 +0000)
Dynamically calculate the required buffer size to pack into based on
message definition. Also add input parameter length checking.

Change-Id: I7633bec596e4833bb328fbf63a65b866c7985de5
Signed-off-by: Ole Troan <ot@cisco.com>
src/vpp-api/python/vpp_papi.py
test/test_acl_plugin.py
test/test_dhcp.py
test/test_nat.py
test/test_papi.py [new file with mode: 0644]
test/vpp_papi_provider.py

index 14f367c..7b66c0f 100644 (file)
@@ -129,7 +129,6 @@ class VPP():
         self.messages = {}
         self.id_names = []
         self.id_msgdef = []
-        self.buffersize = 10000
         self.connected = False
         self.header = struct.Struct('>HI')
         self.apifiles = []
@@ -199,35 +198,45 @@ class VPP():
             if not vl:
                 if e > 0 and t == 'u8':
                     # Fixed byte array
-                    return struct.Struct('>' + str(e) + 's')
+                    s = struct.Struct('>' + str(e) + 's')
+                    return s.size, s
                 if e > 0:
                     # Fixed array of base type
-                    return [e, struct.Struct('>' + base_types[t])]
+                    s = struct.Struct('>' + base_types[t])
+                    return s.size, [e, s]
                 elif e == 0:
                     # Old style variable array
-                    return [-1, struct.Struct('>' + base_types[t])]
+                    s = struct.Struct('>' + base_types[t])
+                    return s.size, [-1, s]
             else:
                 # Variable length array
-                return [vl, struct.Struct('>s')] if t == 'u8' else \
-                    [vl, struct.Struct('>' + base_types[t])]
+                if t == 'u8':
+                    s = struct.Struct('>s')
+                    return s.size, [vl, s]
+                else:
+                    s = struct.Struct('>' + base_types[t])
+                return s.size, [vl, s]
 
-            return struct.Struct('>' + base_types[t])
+            s = struct.Struct('>' + base_types[t])
+            return s.size, s
 
         if t in self.messages:
+            size = self.messages[t]['sizes'][0]
+
             # Return a list in case of array
             if e > 0 and not vl:
-                return [e, lambda self, encode, buf, offset, args: (
+                return size, [e, lambda self, encode, buf, offset, args: (
                     self.__struct_type(encode, self.messages[t], buf, offset,
                                        args))]
             if vl:
-                return [vl, lambda self, encode, buf, offset, args: (
+                return size, [vl, lambda self, encode, buf, offset, args: (
                     self.__struct_type(encode, self.messages[t], buf, offset,
                                        args))]
             elif e == 0:
                 # Old style VLA
                 raise NotImplementedError(1,
                                           'No support for compound types ' + t)
-            return lambda self, encode, buf, offset, args: (
+            return size, lambda self, encode, buf, offset, args: (
                 self.__struct_type(encode, self.messages[t], buf, offset, args)
             )
 
@@ -250,13 +259,14 @@ class VPP():
                                  ' used in call to: ' + \
                                  self.id_names[kwargs['_vl_msg_id']] + '()' )
 
-
         for k, v in vpp_iterator(msgdef['args']):
             off += size
             if k in kwargs:
                 if type(v) is list:
                     if callable(v[1]):
                         e = kwargs[v[0]] if v[0] in kwargs else v[0]
+                        if e != len(kwargs[k]):
+                            raise (ValueError(1, 'Input list length mismatch: %s (%s != %s)' %  (k, e, len(kwargs[k]))))
                         size = 0
                         for i in range(e):
                             size += v[1](self, True, buf, off + size,
@@ -264,6 +274,8 @@ class VPP():
                     else:
                         if v[0] in kwargs:
                             l = kwargs[v[0]]
+                            if l != len(kwargs[k]):
+                                raise ValueError(1, 'Input list length mistmatch: %s (%s != %s)' % (k, l, len(kwargs[k])))
                         else:
                             l = len(kwargs[k])
                         if v[1].size == 1:
@@ -278,6 +290,8 @@ class VPP():
                     if callable(v):
                         size = v(self, True, buf, off, kwargs[k])
                     else:
+                        if type(kwargs[k]) is str and v.size < len(kwargs[k]):
+                            raise ValueError(1, 'Input list length mistmatch: %s (%s < %s)' % (k, v.size, len(kwargs[k])))
                         v.pack_into(buf, off, kwargs[k])
                         size = v.size
             else:
@@ -290,9 +304,17 @@ class VPP():
             return self.messages[name]
         return None
 
+    def get_size(self, sizes, kwargs):
+        total_size = sizes[0]
+        for e in sizes[1]:
+            if e in kwargs and type(kwargs[e]) is list:
+                total_size += len(kwargs[e]) * sizes[1][e]
+        return total_size
+
     def encode(self, msgdef, kwargs):
         # Make suitably large buffer
-        buf = bytearray(self.buffersize)
+       size = self.get_size(msgdef['sizes'], kwargs)
+        buf = bytearray(size)
         offset = 0
         size = self.__struct_type(True, msgdef, buf, offset, kwargs)
         return buf[:offset + size]
@@ -360,6 +382,8 @@ class VPP():
         argtypes = collections.OrderedDict()
         fields = []
         msg = {}
+        total_size = 0
+        sizes = {}
         for i, f in enumerate(msgdef):
             if type(f) is dict and 'crc' in f:
                 msg['crc'] = f['crc']
@@ -368,7 +392,18 @@ class VPP():
             field_name = f[1]
             if len(f) == 3 and f[2] == 0 and i != len(msgdef) - 2:
                 raise ValueError('Variable Length Array must be last: ' + name)
-            args[field_name] = self.__struct(*f)
+            size, s = self.__struct(*f)
+            args[field_name] = s
+            if type(s) == list and type(s[0]) == int and type(s[1]) == struct.Struct:
+                if s[0] < 0:
+                    sizes[field_name] = size
+                else:
+                    sizes[field_name] = size
+                    total_size += s[0] * size
+            else:
+                sizes[field_name] = size
+                total_size += size
+
             argtypes[field_name] = field_type
             if len(f) == 4:  # Find offset to # elements field
                 idx = list(args.keys()).index(f[3]) - i
@@ -380,6 +415,7 @@ class VPP():
         self.messages[name]['args'] = args
         self.messages[name]['argtypes'] = argtypes
         self.messages[name]['typeonly'] = typeonly
+        self.messages[name]['sizes'] = [total_size, sizes]
         return self.messages[name]
 
     def add_type(self, name, typedef):
index 97fca1a..cd375a2 100644 (file)
@@ -532,7 +532,7 @@ class TestACLplugin(VppTestCase):
                                  r[i_rule][rule_key])
 
         # Add a deny-1234 ACL
-        r_deny = ({'is_permit': 0, 'is_ipv6': 0, 'proto': 17,
+        r_deny = [{'is_permit': 0, 'is_ipv6': 0, 'proto': 17,
                    'srcport_or_icmptype_first': 1234,
                    'srcport_or_icmptype_last': 1235,
                    'src_ip_prefix_len': 0,
@@ -549,7 +549,7 @@ class TestACLplugin(VppTestCase):
                    'dstport_or_icmpcode_first': 0,
                    'dstport_or_icmpcode_last': 0,
                    'dst_ip_addr': '\x00\x00\x00\x00',
-                   'dst_ip_prefix_len': 0})
+                   'dst_ip_prefix_len': 0}]
 
         reply = self.vapi.acl_add_replace(acl_index=4294967295, r=r_deny,
                                           tag="deny 1234;permit all")
index fe97f6c..42b80af 100644 (file)
@@ -19,6 +19,7 @@ from scapy.layers.dhcp6 import DHCP6, DHCP6_Solicit, DHCP6_RelayForward, \
 from socket import AF_INET, AF_INET6
 from scapy.utils import inet_pton, inet_ntop
 from scapy.utils6 import in6_ptop
+from util import mactobinary
 
 DHCP4_CLIENT_PORT = 68
 DHCP4_SERVER_PORT = 67
@@ -1134,7 +1135,7 @@ class TestDHCP(VppTestCase):
 
         # remove the left over ARP entry
         self.vapi.ip_neighbor_add_del(self.pg2.sw_if_index,
-                                      self.pg2.remote_mac,
+                                      mactobinary(self.pg2.remote_mac),
                                       self.pg2.remote_ip4,
                                       is_add=0)
         #
index 2afa1dd..792b21b 100644 (file)
@@ -16,6 +16,7 @@ from util import ppp
 from ipfix import IPFIX, Set, Template, Data, IPFIXDecoder
 from time import sleep
 from util import ip4_range
+from util import mactobinary
 
 
 class MethodHolder(VppTestCase):
@@ -659,7 +660,9 @@ class TestNAT44(MethodHolder):
                 lb_sm.external_port,
                 lb_sm.protocol,
                 lb_sm.vrf_id,
-                is_add=0)
+                is_add=0,
+                local_num=0,
+                locals=[])
 
         adresses = self.vapi.nat44_address_dump()
         for addr in adresses:
@@ -1885,11 +1888,11 @@ class TestNAT44(MethodHolder):
         """ NAT44 interfaces without configured IP address """
 
         self.vapi.ip_neighbor_add_del(self.pg7.sw_if_index,
-                                      self.pg7.remote_mac,
+                                      mactobinary(self.pg7.remote_mac),
                                       self.pg7.remote_ip4n,
                                       is_static=1)
         self.vapi.ip_neighbor_add_del(self.pg8.sw_if_index,
-                                      self.pg8.remote_mac,
+                                      mactobinary(self.pg8.remote_mac),
                                       self.pg8.remote_ip4n,
                                       is_static=1)
 
@@ -1927,11 +1930,11 @@ class TestNAT44(MethodHolder):
         """ NAT44 interfaces without configured IP address - 1:1 NAT """
 
         self.vapi.ip_neighbor_add_del(self.pg7.sw_if_index,
-                                      self.pg7.remote_mac,
+                                      mactobinary(self.pg7.remote_mac),
                                       self.pg7.remote_ip4n,
                                       is_static=1)
         self.vapi.ip_neighbor_add_del(self.pg8.sw_if_index,
-                                      self.pg8.remote_mac,
+                                      mactobinary(self.pg8.remote_mac),
                                       self.pg8.remote_ip4n,
                                       is_static=1)
 
@@ -1973,11 +1976,11 @@ class TestNAT44(MethodHolder):
         self.icmp_id_out = 30608
 
         self.vapi.ip_neighbor_add_del(self.pg7.sw_if_index,
-                                      self.pg7.remote_mac,
+                                      mactobinary(self.pg7.remote_mac),
                                       self.pg7.remote_ip4n,
                                       is_static=1)
         self.vapi.ip_neighbor_add_del(self.pg8.sw_if_index,
-                                      self.pg8.remote_mac,
+                                      mactobinary(self.pg8.remote_mac),
                                       self.pg8.remote_ip4n,
                                       is_static=1)
 
diff --git a/test/test_papi.py b/test/test_papi.py
new file mode 100644 (file)
index 0000000..1a5f6ae
--- /dev/null
@@ -0,0 +1,31 @@
+import binascii
+from framework import VppTestCase
+
+""" TestPAPI is a subclass of  VPPTestCase classes.
+
+Basic test for sanity check of the Python API binding.
+
+"""
+
+
+class TestPAPI(VppTestCase):
+    """ PAPI Test Case """
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestPAPI, cls).setUpClass()
+        cls.v = cls.vapi.papi
+
+    def test_show_version(self):
+        rv = self.v.show_version()
+        self.assertEqual(rv.retval, 0)
+
+    def test_show_version_invalid_param(self):
+        self.assertRaises(ValueError, self.v.show_version, foobar='foo')
+
+    def test_u8_array(self):
+        rv = self.v.get_node_index(node_name='ip4-lookup')
+        self.assertEqual(rv.retval, 0)
+        node_name = 'X' * 100
+        self.assertRaises(ValueError, self.v.get_node_index,
+                          node_name=node_name)
index 2e970d2..68b2bd0 100644 (file)
@@ -1323,7 +1323,7 @@ class VppPapiProvider(object):
             protocol,
             vrf_id=0,
             local_num=0,
-            locals=None,
+            locals=[],
             is_add=1):
         """Add/delete NAT44 load balancing static mapping
 
@@ -2037,7 +2037,7 @@ class VppPapiProvider(object):
                             eid,
                             eid_prefix_len=0,
                             vni=0,
-                            rlocs=None,
+                            rlocs=[],
                             rlocs_num=0,
                             is_src_dst=0,
                             is_add=1):