 ead1e536d6
			
		
	
	ead1e536d6
	
	
	
		
			
			Type: fix Since CentOS 8, RPM build script doesn't accept '#!/usr/bin/env python' as a valid shebang line. It requires scripts to explicitly chose between python2 or python3. Change all to use python3 as suggested by Paul Vinciguerra. Depends-On: https://gerrit.fd.io/r/23170 Signed-off-by: Renato Botelho do Couto <renato@netgate.com> Change-Id: Ie72af9f60fd0609e07f05b70f8d96e738b2754d1
		
			
				
	
	
		
			354 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			354 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/env python3
 | |
| """L2XC Multi-instance Test Case HLD:
 | |
| 
 | |
| **NOTES:**
 | |
|     - higher number (more than 15) of pg-l2 interfaces causes problems => only
 | |
|       14 pg-l2 interfaces and 10 cross-connects are tested
 | |
|     - jumbo packets in configuration with 14 l2-pg interfaces leads to
 | |
|       problems too
 | |
| 
 | |
| **config 1**
 | |
|     - add 14 pg-l2 interfaces
 | |
|     - add 10 cross-connects (two cross-connects per pair of l2-pg interfaces)
 | |
| 
 | |
| **test 1**
 | |
|     - send L2 MAC frames between all pairs of pg-l2 interfaces
 | |
| 
 | |
| **verify 1**
 | |
|     - all packets received correctly in case of cross-connected l2-pg
 | |
|       interfaces
 | |
|     - no packet received in case of not cross-connected l2-pg interfaces
 | |
| 
 | |
| **config 2**
 | |
|     - delete 4 cross-connects
 | |
| 
 | |
| **test 2**
 | |
|     - send L2 MAC frames between all pairs of pg-l2 interfaces
 | |
| 
 | |
| **verify 2**
 | |
|     - all packets received correctly in case of cross-connected l2-pg
 | |
|       interfaces
 | |
|     - no packet received in case of not cross-connected l2-pg interfaces
 | |
| 
 | |
| **config 3**
 | |
|     - add new 4 cross-connects
 | |
| 
 | |
| **test 3**
 | |
|     - send L2 MAC frames between all pairs of pg-l2 interfaces
 | |
| 
 | |
| **verify 3**
 | |
|     - all packets received correctly in case of cross-connected l2-pg
 | |
|       interfaces
 | |
|     - no packet received in case of not cross-connected l2-pg interfaces
 | |
| 
 | |
| **config 4**
 | |
|     - delete 10 cross-connects
 | |
| 
 | |
| **test 4**
 | |
|     - send L2 MAC frames between all pairs of pg-l2 interfaces
 | |
| 
 | |
| **verify 4**
 | |
|     - no packet received on all of l2-pg interfaces (no cross-connect created)
 | |
| """
 | |
| 
 | |
| import unittest
 | |
| import random
 | |
| 
 | |
| from scapy.packet import Raw
 | |
| from scapy.layers.l2 import Ether
 | |
| from scapy.layers.inet import IP, UDP
 | |
| 
 | |
| from framework import VppTestCase, VppTestRunner
 | |
| from util import Host, ppp
 | |
| 
 | |
| 
 | |
| class TestL2xcMultiInst(VppTestCase):
 | |
|     """ L2XC Multi-instance Test Case """
 | |
| 
 | |
|     @classmethod
 | |
|     def setUpClass(cls):
 | |
|         """
 | |
|         Perform standard class setup (defined by class method setUpClass in
 | |
|         class VppTestCase) before running the test case, set test case related
 | |
|         variables and configure VPP.
 | |
|         """
 | |
|         super(TestL2xcMultiInst, cls).setUpClass()
 | |
| 
 | |
|         try:
 | |
|             # Create pg interfaces
 | |
|             cls.create_pg_interfaces(range(14))
 | |
| 
 | |
|             # Packet flows mapping pg0 -> pg1 etc.
 | |
|             cls.flows = dict()
 | |
|             for i in range(len(cls.pg_interfaces)):
 | |
|                 delta = 1 if i % 2 == 0 else -1
 | |
|                 cls.flows[cls.pg_interfaces[i]] =\
 | |
|                     [cls.pg_interfaces[i + delta]]
 | |
| 
 | |
|             # Mapping between packet-generator index and lists of test hosts
 | |
|             cls.hosts_by_pg_idx = dict()
 | |
|             for pg_if in cls.pg_interfaces:
 | |
|                 cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
 | |
| 
 | |
|             # Create test host entries
 | |
|             cls.create_hosts(70)
 | |
| 
 | |
|             # Packet sizes - jumbo packet (9018 bytes) skipped
 | |
|             cls.pg_if_packet_sizes = [64, 512, 1518]
 | |
| 
 | |
|             # Set up all interfaces
 | |
|             for i in cls.pg_interfaces:
 | |
|                 i.admin_up()
 | |
| 
 | |
|             # Create list of x-connected pg_interfaces
 | |
|             cls.pg_in_xc = list()
 | |
| 
 | |
|             # Create list of not x-connected pg_interfaces
 | |
|             cls.pg_not_in_xc = list()
 | |
|             for pg_if in cls.pg_interfaces:
 | |
|                 cls.pg_not_in_xc.append(pg_if)
 | |
| 
 | |
|         except Exception:
 | |
|             super(TestL2xcMultiInst, cls).tearDownClass()
 | |
|             raise
 | |
| 
 | |
|     @classmethod
 | |
|     def tearDownClass(cls):
 | |
|         super(TestL2xcMultiInst, cls).tearDownClass()
 | |
| 
 | |
|     def setUp(self):
 | |
|         """
 | |
|         Clear trace and packet infos before running each test.
 | |
|         """
 | |
|         super(TestL2xcMultiInst, self).setUp()
 | |
|         self.reset_packet_infos()
 | |
| 
 | |
|     def tearDown(self):
 | |
|         """
 | |
|         Show various debug prints after each test.
 | |
|         """
 | |
|         super(TestL2xcMultiInst, self).tearDown()
 | |
| 
 | |
|     def show_commands_at_teardown(self):
 | |
|         self.logger.info(self.vapi.ppcli("show l2patch"))
 | |
| 
 | |
|     @classmethod
 | |
|     def create_hosts(cls, count):
 | |
|         """
 | |
|         Create required number of host MAC addresses and distribute them among
 | |
|         interfaces. Create host IPv4 address for every host MAC address.
 | |
| 
 | |
|         :param int count: Number of hosts to create MAC/IPv4 addresses for.
 | |
|         """
 | |
|         n_int = len(cls.pg_interfaces)
 | |
|         macs_per_if = count // n_int
 | |
|         i = -1
 | |
|         for pg_if in cls.pg_interfaces:
 | |
|             i += 1
 | |
|             start_nr = macs_per_if * i
 | |
|             end_nr = count if i == (n_int - 1) else macs_per_if * (i + 1)
 | |
|             hosts = cls.hosts_by_pg_idx[pg_if.sw_if_index]
 | |
|             for j in range(start_nr, end_nr):
 | |
|                 host = Host(
 | |
|                     "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
 | |
|                     "172.17.1%02u.%u" % (pg_if.sw_if_index, j))
 | |
|                 hosts.append(host)
 | |
| 
 | |
|     def create_xconnects(self, count, start=0):
 | |
|         """
 | |
|         Create required number of cross-connects (always two cross-connects per
 | |
|         pair of packet-generator interfaces).
 | |
| 
 | |
|         :param int count: Number of cross-connects to be created.
 | |
|         :param int start: Starting index of packet-generator interfaces. \
 | |
|         (Default value = 0)
 | |
|         """
 | |
|         for i in range(count):
 | |
|             rx_if = self.pg_interfaces[i + start]
 | |
|             delta = 1 if i % 2 == 0 else -1
 | |
|             tx_if = self.pg_interfaces[i + start + delta]
 | |
|             self.vapi.sw_interface_set_l2_xconnect(rx_if.sw_if_index,
 | |
|                                                    tx_if.sw_if_index, 1)
 | |
|             self.logger.info("Cross-connect from %s to %s created"
 | |
|                              % (tx_if.name, rx_if.name))
 | |
|             if self.pg_in_xc.count(rx_if) == 0:
 | |
|                 self.pg_in_xc.append(rx_if)
 | |
|             if self.pg_not_in_xc.count(rx_if) == 1:
 | |
|                 self.pg_not_in_xc.remove(rx_if)
 | |
| 
 | |
|     def delete_xconnects(self, count, start=0):
 | |
|         """
 | |
|         Delete required number of cross-connects (always two cross-connects per
 | |
|         pair of packet-generator interfaces).
 | |
| 
 | |
|         :param int count: Number of cross-connects to be deleted.
 | |
|         :param int start: Starting index of packet-generator interfaces. \
 | |
|         (Default value = 0)
 | |
|         """
 | |
|         for i in range(count):
 | |
|             rx_if = self.pg_interfaces[i + start]
 | |
|             delta = 1 if i % 2 == 0 else -1
 | |
|             tx_if = self.pg_interfaces[i + start + delta]
 | |
|             self.vapi.sw_interface_set_l2_xconnect(rx_if.sw_if_index,
 | |
|                                                    tx_if.sw_if_index, 0)
 | |
|             self.logger.info("Cross-connect from %s to %s deleted"
 | |
|                              % (tx_if.name, rx_if.name))
 | |
|             if self.pg_not_in_xc.count(rx_if) == 0:
 | |
|                 self.pg_not_in_xc.append(rx_if)
 | |
|             if self.pg_in_xc.count(rx_if) == 1:
 | |
|                 self.pg_in_xc.remove(rx_if)
 | |
| 
 | |
|     def create_stream(self, src_if, packet_sizes):
 | |
|         """
 | |
|         Create input packet stream for defined interface using hosts list.
 | |
| 
 | |
|         :param object src_if: Interface to create packet stream for.
 | |
|         :param list packet_sizes: List of required packet sizes.
 | |
|         :return: Stream of packets.
 | |
|         """
 | |
|         pkts = []
 | |
|         src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
 | |
|         for dst_if in self.flows[src_if]:
 | |
|             dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
 | |
|             n_int = len(dst_hosts)
 | |
|             for i in range(0, n_int):
 | |
|                 dst_host = dst_hosts[i]
 | |
|                 src_host = random.choice(src_hosts)
 | |
|                 pkt_info = self.create_packet_info(src_if, dst_if)
 | |
|                 payload = self.info_to_payload(pkt_info)
 | |
|                 p = (Ether(dst=dst_host.mac, src=src_host.mac) /
 | |
|                      IP(src=src_host.ip4, dst=dst_host.ip4) /
 | |
|                      UDP(sport=1234, dport=1234) /
 | |
|                      Raw(payload))
 | |
|                 pkt_info.data = p.copy()
 | |
|                 size = random.choice(packet_sizes)
 | |
|                 self.extend_packet(p, size)
 | |
|                 pkts.append(p)
 | |
|         self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
 | |
|                           % (src_if.name, len(pkts)))
 | |
|         return pkts
 | |
| 
 | |
|     def verify_capture(self, pg_if, capture):
 | |
|         """
 | |
|         Verify captured input packet stream for defined interface.
 | |
| 
 | |
|         :param object pg_if: Interface to verify captured packet stream for.
 | |
|         :param list capture: Captured packet stream.
 | |
|         """
 | |
|         last_info = dict()
 | |
|         for i in self.pg_interfaces:
 | |
|             last_info[i.sw_if_index] = None
 | |
|         dst_sw_if_index = pg_if.sw_if_index
 | |
|         for packet in capture:
 | |
|             payload_info = self.payload_to_info(packet[Raw])
 | |
|             try:
 | |
|                 ip = packet[IP]
 | |
|                 udp = packet[UDP]
 | |
|                 packet_index = payload_info.index
 | |
|                 self.assertEqual(payload_info.dst, dst_sw_if_index)
 | |
|                 self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
 | |
|                                   (pg_if.name, payload_info.src, packet_index))
 | |
|                 next_info = self.get_next_packet_info_for_interface2(
 | |
|                     payload_info.src, dst_sw_if_index,
 | |
|                     last_info[payload_info.src])
 | |
|                 last_info[payload_info.src] = next_info
 | |
|                 self.assertTrue(next_info is not None)
 | |
|                 self.assertEqual(packet_index, next_info.index)
 | |
|                 saved_packet = next_info.data
 | |
|                 # Check standard fields
 | |
|                 self.assertEqual(ip.src, saved_packet[IP].src)
 | |
|                 self.assertEqual(ip.dst, saved_packet[IP].dst)
 | |
|                 self.assertEqual(udp.sport, saved_packet[UDP].sport)
 | |
|                 self.assertEqual(udp.dport, saved_packet[UDP].dport)
 | |
|             except:
 | |
|                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
 | |
|                 raise
 | |
|         for i in self.pg_interfaces:
 | |
|             remaining_packet = self.get_next_packet_info_for_interface2(
 | |
|                 i, dst_sw_if_index, last_info[i.sw_if_index])
 | |
|             self.assertTrue(
 | |
|                 remaining_packet is None,
 | |
|                 "Port %u: Packet expected from source %u didn't arrive" %
 | |
|                 (dst_sw_if_index, i.sw_if_index))
 | |
| 
 | |
|     def run_verify_test(self):
 | |
|         """
 | |
|         Create packet streams for all configured l2-pg interfaces, send all \
 | |
|         prepared packet streams and verify that:
 | |
|             - all packets received correctly on all pg-l2 interfaces assigned
 | |
|               to cross-connects
 | |
|             - no packet received on all pg-l2 interfaces not assigned to
 | |
|               cross-connects
 | |
| 
 | |
|         :raise RuntimeError: if no packet captured on l2-pg interface assigned
 | |
|                              to the cross-connect or if any packet is captured
 | |
|                              on l2-pg interface not assigned to the
 | |
|                              cross-connect.
 | |
|         """
 | |
|         # Test
 | |
|         # Create incoming packet streams for packet-generator interfaces
 | |
|         for pg_if in self.pg_interfaces:
 | |
|             pkts = self.create_stream(pg_if, self.pg_if_packet_sizes)
 | |
|             pg_if.add_stream(pkts)
 | |
| 
 | |
|         # Enable packet capture and start packet sending
 | |
|         self.pg_enable_capture(self.pg_interfaces)
 | |
|         self.pg_start()
 | |
| 
 | |
|         # Verify
 | |
|         # Verify outgoing packet streams per packet-generator interface
 | |
|         for pg_if in self.pg_interfaces:
 | |
|             if pg_if in self.pg_in_xc:
 | |
|                 capture = pg_if.get_capture(
 | |
|                     remark="interface is a cross-connect sink")
 | |
|                 self.verify_capture(pg_if, capture)
 | |
|             elif pg_if in self.pg_not_in_xc:
 | |
|                 pg_if.assert_nothing_captured(
 | |
|                     remark="interface is not a cross-connect sink")
 | |
|             else:
 | |
|                 raise Exception("Unexpected interface: %s" % pg_if.name)
 | |
| 
 | |
|     def test_l2xc_inst_01(self):
 | |
|         """ L2XC Multi-instance test 1 - create 10 cross-connects
 | |
|         """
 | |
|         # Config 1
 | |
|         # Create 10 cross-connects
 | |
|         self.create_xconnects(10)
 | |
| 
 | |
|         # Test 1
 | |
|         self.run_verify_test()
 | |
| 
 | |
|     def test_l2xc_inst_02(self):
 | |
|         """ L2XC Multi-instance test 2 - delete 4 cross-connects
 | |
|         """
 | |
|         # Config 2
 | |
|         # Delete 4 cross-connects
 | |
|         self.delete_xconnects(4)
 | |
| 
 | |
|         # Test 2
 | |
|         self.run_verify_test()
 | |
| 
 | |
|     def test_l2xc_inst_03(self):
 | |
|         """ L2BD Multi-instance 3 - add new 4 cross-connects
 | |
|         """
 | |
|         # Config 3
 | |
|         # Add new 4 cross-connects
 | |
|         self.create_xconnects(4, start=10)
 | |
| 
 | |
|         # Test 3
 | |
|         self.run_verify_test()
 | |
| 
 | |
|     def test_l2xc_inst_04(self):
 | |
|         """ L2XC Multi-instance test 4 - delete 10 cross-connects
 | |
|         """
 | |
|         # Config 4
 | |
|         # Delete 10 cross-connects
 | |
|         self.delete_xconnects(10, start=4)
 | |
| 
 | |
|         # Test 4
 | |
|         self.run_verify_test()
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     unittest.main(testRunner=VppTestRunner)
 |