make test: improve test_flowperpkt 09/4509/3
authorKlement Sekera <ksekera@cisco.com>
Mon, 2 Jan 2017 06:46:14 +0000 (07:46 +0100)
committerDave Barach <openvpp@barachs.net>
Mon, 2 Jan 2017 15:15:46 +0000 (15:15 +0000)
Change-Id: Id1b2bef2a93ac931a420d7a3c322d65c1fbce055
Signed-off-by: Klement Sekera <ksekera@cisco.com>
test/test_flowperpkt.py
test/vpp_papi_provider.py

index af68a69..16fe2a3 100644 (file)
@@ -1,17 +1,13 @@
 #!/usr/bin/env python
 
 import unittest
-import socket
-import binascii
-import time
 
 from framework import VppTestCase, VppTestRunner
 
 from scapy.packet import Raw
 from scapy.layers.l2 import Ether
 from scapy.layers.inet import IP, UDP
-from scapy.utils import hexdump
-from util import ppp
+
 
 class TestFlowperpkt(VppTestCase):
     """ Flow-per-packet plugin: test both L2 and IP4 reporting """
@@ -22,7 +18,6 @@ class TestFlowperpkt(VppTestCase):
 
         **Config:**
             - create three PG interfaces
-            - create a couple of loopback interfaces
         """
         super(TestFlowperpkt, self).setUp()
 
@@ -37,11 +32,6 @@ class TestFlowperpkt(VppTestCase):
             intf.config_ip4()
             intf.resolve_arp()
 
-    def tearDown(self):
-        """Run standard test teardown"""
-        super(TestFlowperpkt, self).tearDown()
-
-
     def create_stream(self, src_if, dst_if, packet_sizes):
         """Create a packet stream to tickle the plugin
 
@@ -51,8 +41,7 @@ class TestFlowperpkt(VppTestCase):
         """
         pkts = []
         for size in packet_sizes:
-            info = self.create_packet_info(src_if.sw_if_index, 
-                                           dst_if.sw_if_index)
+            info = self.create_packet_info(src_if, dst_if)
             payload = self.info_to_payload(info)
             p = (Ether(src=src_if.local_mac, dst=dst_if.remote_mac) /
                  IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) /
@@ -63,119 +52,84 @@ class TestFlowperpkt(VppTestCase):
             pkts.append(p)
         return pkts
 
+    @staticmethod
+    def compare_with_mask(payload, masked_expected_data):
+        if len(payload) * 2 != len(masked_expected_data):
+            return False
+
+        # iterate over pairs: raw byte from payload and ASCII code for that byte
+        # from masked payload (or XX if masked)
+        for i in range(len(payload)):
+            p = payload[i]
+            m = masked_expected_data[2 * i:2 * i + 2]
+            if m != "XX":
+                if "%02x" % ord(p) != m:
+                    return False
+        return True
+
     def verify_ipfix(self, collector_if):
         """Check the ipfix capture"""
-        found_data_packet = 0
-        found_template_packet = 0
-        found_l2_data_packet = 0
-        found_l2_template_packet = 0
+        found_data_packet = False
+        found_template_packet = False
+        found_l2_data_packet = False
+        found_l2_template_packet = False
 
         # Scapy, of course, understands ipfix not at all...
         # These data vetted by manual inspection in wireshark
         # X'ed out fields are timestamps, which will absolutely
-        # fail to compare. At L2, kill the pg src MAC address, which
-        # is random.
-        
-        data_udp_string = "1283128300370000000a002fXXXXXXXX00000000000000010100001f0000000100000002ac100102ac10020200XXXXXXXXXXXXXXXX0092"
+        # fail to compare.
 
-        template_udp_string = "12831283003c0000000a0034XXXXXXXX00000002000000010002002401000007000a0004000e000400080004000c000400050001009c000801380002"
+        data_udp_string = "1283128300370000000a002fXXXXXXXX000000000000000101"\
+            "00001f0000000100000002ac100102ac10020200XXXXXXXXXXXXXXXX0092"
 
-        l2_data_udp_string =  "12831283003c0000000a0034XXXXXXXX0000000100000001010100240000000100000002XXXXXXXXXXXX02020000ff020008XXXXXXXXXXXXXXXX0092"
+        template_udp_string = "12831283003c0000000a0034XXXXXXXX00000002000000"\
+            "010002002401000007000a0004000e000400080004000c000400050001009c00"\
+            "0801380002"
 
-        l2_template_udp_string = "12831283003c0000000a0034XXXXXXXX00000002000000010002002401010007000a0004000e0004003800060050000601000002009c000801380002"
+        l2_data_udp_string = "12831283003c0000000a0034XXXXXXXX000000010000000"\
+            "1010100240000000100000002%s02020000ff020008XXXXXXXXXXX"\
+            "XXXXX0092" % self.pg1.local_mac.translate(None, ":")
 
-        cap_x = "X"
-        data_udp_len = len(data_udp_string)
-        template_udp_len = len(template_udp_string)
-        l2_data_udp_len = len(l2_data_udp_string)
-        l2_template_udp_len = len(l2_template_udp_string)
+        l2_template_udp_string = "12831283003c0000000a0034XXXXXXXX00000002000"\
+            "000010002002401010007000a0004000e0004003800060050000601000002009"\
+            "c000801380002"
 
-        self.logger.info("Look for ipfix packets on %s sw_if_index %d " 
+        self.logger.info("Look for ipfix packets on %s sw_if_index %d "
                          % (collector_if.name, collector_if.sw_if_index))
-        capture = collector_if.get_capture()
+        # expecting 4 packets on collector interface based on traffic on other
+        # interfaces
+        capture = collector_if.get_capture(4)
 
         for p in capture:
-            data_result = ""
-            template_result = ""
-            l2_data_result = ""
-            l2_template_result = ""
-            unmasked_result = ""
             ip = p[IP]
             udp = p[UDP]
             self.logger.info("src %s dst %s" % (ip.src, ip.dst))
-            self.logger.info(" udp src_port %s dst_port %s" 
+            self.logger.info(" udp src_port %s dst_port %s"
                              % (udp.sport, udp.dport))
 
-            # Hex-dump the UDP datagram 4 ways in parallel
-            # X'ing out incomparable fields
-            # Python completely bites at this sort of thing, of course
-
-            x = str(udp)
-            l = len(x)
-            i = 0
-            while i < l:
-                # If current index within range
-                if i < data_udp_len/2:
-                    # See if we're supposed to don't care the data
-                    if ord(data_udp_string[i*2]) == ord(cap_x[0]):
-                        data_result = data_result + "XX"
-                    else:
-                        data_result = data_result + ("%02x" % ord(x[i]))
-                else:
-                    # index out of range, emit actual data
-                    # The test will fail, but it may help debug, etc.
-                    data_result = data_result + ("%02x" % ord(x[i]))
-                    
-                if i < template_udp_len/2:
-                    if ord(template_udp_string[i*2]) == ord(cap_x[0]):
-                        template_result = template_result + "XX"
-                    else:
-                        template_result = template_result + ("%02x" % ord(x[i]))
-                else:
-                    template_result = template_result + ("%02x" % ord(x[i]))
-
-                if i < l2_data_udp_len/2:
-                    # See if we're supposed to don't care the data
-                    if ord(l2_data_udp_string[i*2]) == ord(cap_x[0]):
-                        l2_data_result = l2_data_result + "XX"
-                    else:
-                        l2_data_result = l2_data_result + ("%02x" % ord(x[i]))
-                else:
-                    # index out of range, emit actual data
-                    # The test will fail, but it may help debug, etc.
-                    l2_data_result = l2_data_result + ("%02x" % ord(x[i]))
-                
-                if i < l2_template_udp_len/2:
-                    if ord(l2_template_udp_string[i*2]) == ord(cap_x[0]):
-                        l2_template_result = l2_template_result + "XX"
-                    else:
-                        l2_template_result = l2_template_result + ("%02x" % ord(x[i]))
-                else:
-                    l2_template_result = l2_template_result + ("%02x" % ord(x[i]))
-                # In case we need to 
-                unmasked_result = unmasked_result + ("%02x" % ord(x[i]))
-
-                i = i + 1
-
-            if data_result == data_udp_string:
-                self.logger.info ("found ip4 data packet")
-                found_data_packet = 1
-            elif template_result == template_udp_string:
-                self.logger.info ("found ip4 template packet")
-                found_template_packet = 1
-            elif l2_data_result == l2_data_udp_string:
-                self.logger.info ("found l2 data packet")
-                found_l2_data_packet = 1
-            elif l2_template_result == l2_template_udp_string:
-                self.logger.info ("found l2 template packet")
-                found_l2_template_packet = 1
+            payload = str(udp)
+
+            if self.compare_with_mask(payload, data_udp_string):
+                self.logger.info("found ip4 data packet")
+                found_data_packet = True
+            elif self.compare_with_mask(payload, template_udp_string):
+                self.logger.info("found ip4 template packet")
+                found_template_packet = True
+            elif self.compare_with_mask(payload, l2_data_udp_string):
+                self.logger.info("found l2 data packet")
+                found_l2_data_packet = True
+            elif self.compare_with_mask(payload, l2_template_udp_string):
+                self.logger.info("found l2 template packet")
+                found_l2_template_packet = True
             else:
-                self.logger.info ("unknown pkt '%s'" % unmasked_result)
-                
-        self.assertTrue (found_data_packet == 1)
-        self.assertTrue (found_template_packet == 1)
-        self.assertTrue (found_l2_data_packet == 1)
-        self.assertTrue (found_l2_template_packet == 1)
+                unmasked_payload = "".join(["%02x" % ord(c) for c in payload])
+                self.logger.error("unknown pkt '%s'" % unmasked_payload)
+
+        self.assertTrue(found_data_packet, "Data packet not found")
+        self.assertTrue(found_template_packet, "Template packet not found")
+        self.assertTrue(found_l2_data_packet, "L2 data packet not found")
+        self.assertTrue(found_l2_template_packet,
+                        "L2 template packet not found")
 
     def test_L3_fpp(self):
         """ Flow per packet L3 test """
@@ -186,37 +140,34 @@ class TestFlowperpkt(VppTestCase):
         # an ARP request
 
         self.pg_enable_capture(self.pg_interfaces)
-        self.vapi.cli("set ip arp pg2 172.16.3.2 dead.beef.0002")
-        self.logger.info(self.vapi.cli("set ipfix exporter collector 172.16.3.2 src 172.16.3.1 path-mtu 1450 template-interval 1"))
+        self.pg2.configure_ipv4_neighbors()
+        self.vapi.set_ipfix_exporter(collector_address=self.pg2.remote_ip4n,
+                                     src_address=self.pg2.local_ip4n,
+                                     path_mtu=1450,
+                                     template_interval=1)
 
         # Export flow records for all pkts transmitted on pg1
-
-        self.logger.info(self.vapi.cli("flowperpkt feature add-del pg1"))
-        self.logger.info(self.vapi.cli("flowperpkt feature add-del pg1 l2"))
+        self.vapi.cli("flowperpkt feature add-del pg1")
+        self.vapi.cli("flowperpkt feature add-del pg1 l2")
 
         # Arrange to minimally trace generated ipfix packets
-        self.logger.info(self.vapi.cli("trace add flowperpkt-ipv4 10"))
-        self.logger.info(self.vapi.cli("trace add flowperpkt-l2 10"))
+        self.vapi.cli("trace add flowperpkt-ipv4 10")
+        self.vapi.cli("trace add flowperpkt-l2 10")
 
         # Create a stream from pg0 -> pg1, which causes
         # an ipfix packet to be transmitted on pg2
-        
-        pkts = self.create_stream(self.pg0, self.pg1, 
+
+        pkts = self.create_stream(self.pg0, self.pg1,
                                   self.pg_if_packet_sizes)
         self.pg0.add_stream(pkts)
         self.pg_start()
-        
+
         # Flush the ipfix collector, so we don't need any
         # asinine time.sleep(5) action
+        self.vapi.cli("ipfix flush")  # FIXME this should be an API call
 
-        self.logger.info(self.vapi.cli("ipfix flush"))
-        
         # Make sure the 4 pkts we expect actually showed up
         self.verify_ipfix(self.pg2)
 
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)
-            
-        
-    
-        
index 3279a27..bdbcc3d 100644 (file)
@@ -901,7 +901,6 @@ class VppPapiProvider(object):
             miss_next_index=0xFFFFFFFF,
             current_data_flag=0,
             current_data_offset=0):
-
         """
         :param is_add:
         :param mask:
@@ -918,17 +917,17 @@ class VppPapiProvider(object):
 
         return self.api(
             self.papi.classify_add_del_table,
-            {'is_add' : is_add,
-             'table_index' : table_index,
-             'nbuckets' : nbuckets,
+            {'is_add': is_add,
+             'table_index': table_index,
+             'nbuckets': nbuckets,
              'memory_size': memory_size,
-             'skip_n_vectors' : skip_n_vectors,
-             'match_n_vectors' : match_n_vectors,
-             'next_table_index' : next_table_index,
-             'miss_next_index' : miss_next_index,
-             'current_data_flag' : current_data_flag,
-             'current_data_offset' : current_data_offset,
-             'mask' : mask})
+             'skip_n_vectors': skip_n_vectors,
+             'match_n_vectors': match_n_vectors,
+             'next_table_index': next_table_index,
+             'miss_next_index': miss_next_index,
+             'current_data_flag': current_data_flag,
+             'current_data_offset': current_data_offset,
+             'mask': mask})
 
     def classify_add_del_session(
             self,
@@ -953,14 +952,14 @@ class VppPapiProvider(object):
 
         return self.api(
             self.papi.classify_add_del_session,
-            {'is_add' : is_add,
-             'table_index' : table_index,
-             'hit_next_index' : hit_next_index,
-             'opaque_index' : opaque_index,
-             'advance' : advance,
-             'action' : action,
-             'metadata' : metadata,
-             'match' : match})
+            {'is_add': is_add,
+             'table_index': table_index,
+             'hit_next_index': hit_next_index,
+             'opaque_index': opaque_index,
+             'advance': advance,
+             'action': action,
+             'metadata': metadata,
+             'match': match})
 
     def input_acl_set_interface(
             self,
@@ -979,8 +978,29 @@ class VppPapiProvider(object):
 
         return self.api(
             self.papi.input_acl_set_interface,
-            {'sw_if_index' : sw_if_index,
-             'ip4_table_index' : ip4_table_index,
-             'ip6_table_index' : ip6_table_index,
-             'l2_table_index' : l2_table_index,
-             'is_add' : is_add})
+            {'sw_if_index': sw_if_index,
+             'ip4_table_index': ip4_table_index,
+             'ip6_table_index': ip6_table_index,
+             'l2_table_index': l2_table_index,
+             'is_add': is_add})
+
+    def set_ipfix_exporter(
+            self,
+            collector_address,
+            src_address,
+            path_mtu,
+            template_interval,
+            vrf_id=0,
+            collector_port=4739,
+            udp_checksum=0):
+        return self.api(
+            self.papi.set_ipfix_exporter,
+            {
+                'collector_address': collector_address,
+                'collector_port': collector_port,
+                'src_address': src_address,
+                'vrf_id': vrf_id,
+                'path_mtu': path_mtu,
+                'template_interval': template_interval,
+                'udp_checksum': udp_checksum,
+            })