From f6e3dc4778ef910d4ae6114783bd8f50887e6d0d Mon Sep 17 00:00:00 2001 From: Pavel Kotucek Date: Fri, 4 Nov 2016 09:58:01 +0100 Subject: span: add feature (rx only) (VPP-185) Change-Id: I0f7cbf06b5a5acd745d13c9f5c761ea18132107b Signed-off-by: marek Signed-off-by: Damjan Marion Signed-off-by: Pavel Kotucek Signed-off-by: Damjan Marion --- test/test_span.py | 186 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 test/test_span.py (limited to 'test/test_span.py') diff --git a/test/test_span.py b/test/test_span.py new file mode 100644 index 00000000..59ef5efc --- /dev/null +++ b/test/test_span.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python + +import unittest +import random + +from scapy.packet import Raw +from scapy.layers.l2 import Ether +from scapy.layers.inet import IP, UDP +from logging import * + +from framework import VppTestCase, VppTestRunner +from util import Host + + +class TestSpan(VppTestCase): + """ SPAN Test Case """ + + # Test variables + hosts_nr = 10 # Number of hosts + pkts_per_burst = 257 # Number of packets per burst + + @classmethod + def setUpClass(cls): + super(TestSpan, cls).setUpClass() + + def setUp(self): + super(TestSpan, self).setUp() + + # create 3 pg interfaces + self.create_pg_interfaces(range(3)) + + # packet flows mapping pg0 -> pg1, pg2 -> pg3, etc. + self.flows = dict() + self.flows[self.pg0] = [self.pg1] + + # packet sizes + self.pg_if_packet_sizes = [64, 512] #, 1518, 9018] + + self.interfaces = list(self.pg_interfaces) + + # Create host MAC and IPv4 lists + # self.MY_MACS = dict() + # self.MY_IP4S = dict() + self.create_host_lists(TestSpan.hosts_nr) + + # Create bi-directional cross-connects between pg0 and pg1 + self.vapi.sw_interface_set_l2_xconnect( + self.pg0.sw_if_index, self.pg1.sw_if_index, enable=1) + self.vapi.sw_interface_set_l2_xconnect( + self.pg1.sw_if_index, self.pg0.sw_if_index, enable=1) + + # setup all interfaces + for i in self.interfaces: + i.admin_up() + i.config_ip4() + i.resolve_arp() + + # Enable SPAN on pg0 (mirrored to pg2) + self.vapi.sw_interface_span_enable_disable(self.pg0.sw_if_index, self.pg2.sw_if_index) + + def tearDown(self): + super(TestSpan, self).tearDown() + + def create_host_lists(self, count): + """ Method to create required number of MAC and IPv4 addresses. + Create required number of host MAC addresses and distribute them among + interfaces. Create host IPv4 address for every host MAC address too. + + :param count: Number of hosts to create MAC and IPv4 addresses for. + """ + # mapping between packet-generator index and lists of test hosts + self.hosts_by_pg_idx = dict() + + for pg_if in self.pg_interfaces: + # self.MY_MACS[i.sw_if_index] = [] + # self.MY_IP4S[i.sw_if_index] = [] + self.hosts_by_pg_idx[pg_if.sw_if_index] = [] + hosts = self.hosts_by_pg_idx[pg_if.sw_if_index] + for j in range(0, count): + host = Host( + "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j), + "172.17.1%02x.%u" % (pg_if.sw_if_index, j)) + hosts.append(host) + + def create_stream(self, src_if, packet_sizes): + pkts = [] + for i in range(0, TestSpan.pkts_per_burst): + dst_if = self.flows[src_if][0] + dst_host = random.choice(self.hosts_by_pg_idx[dst_if.sw_if_index]) + src_host = random.choice(self.hosts_by_pg_idx[src_if.sw_if_index]) + pkt_info = self.create_packet_info( + src_if.sw_if_index, dst_if.sw_if_index) + payload = self.info_to_payload(pkt_info) + p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / + IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) / + UDP(sport=1234, dport=1234) / + Raw(payload)) + pkt_info.data = p.copy() + size = packet_sizes[(i / 2) % len(packet_sizes)] + self.extend_packet(p, size) + pkts.append(p) + return pkts + + def verify_capture(self, dst_if, capture_pg1, capture_pg2): + last_info = dict() + for i in self.interfaces: + last_info[i.sw_if_index] = None + dst_sw_if_index = dst_if.sw_if_index + if len(capture_pg1) != len(capture_pg2): + error("Diffrent number of outgoing and mirrored packets : %u != %u" + % (len(capture_pg1), len(capture_pg2))) + raise + for pkt_pg1, pkt_pg2 in zip(capture_pg1, capture_pg2): + try: + ip1 = pkt_pg1[IP] + udp1 = pkt_pg1[UDP] + raw1 = pkt_pg1[Raw] + + if pkt_pg1[Ether] != pkt_pg2[Ether]: + error("Diffrent ethernet header of outgoing and mirrored packet") + raise + if ip1 != pkt_pg2[IP]: + error("Diffrent ip header of outgoing and mirrored packet") + raise + if udp1 != pkt_pg2[UDP]: + error("Diffrent udp header of outgoing and mirrored packet") + raise + if raw1 != pkt_pg2[Raw]: + error("Diffrent raw data of outgoing and mirrored packet") + raise + + payload_info = self.payload_to_info(str(raw1)) + packet_index = payload_info.index + self.assertEqual(payload_info.dst, dst_sw_if_index) + debug("Got packet on port %s: src=%u (id=%u)" % + (dst_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(ip1.src, saved_packet[IP].src) + self.assertEqual(ip1.dst, saved_packet[IP].dst) + self.assertEqual(udp1.sport, saved_packet[UDP].sport) + self.assertEqual(udp1.dport, saved_packet[UDP].dport) + except: + error("Unexpected or invalid packet:") + pkt_pg1.show() + pkt_pg2.show() + raise + for i in self.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 test_span(self): + """ SPAN test + + Test scenario: + 1. config + 3 interfaces, pg0 l2xconnected with pg1 + 2. sending l2 eth packets between 2 interfaces (pg0, pg1) and mirrored to pg2 + 64B, 512B, 1518B, 9018B (ether_size) + burst of packets per interface + """ + + # Create incoming packet streams for packet-generator interfaces + pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes) + self.pg0.add_stream(pkts) + + # Enable packet capturing and start packet sending + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # Verify packets outgoing packet streams on mirrored interface (pg2) + info("Verifying capture on interfaces %s and %s" % (self.pg1.name, self.pg2.name)) + self.verify_capture(self.pg1, self.pg1.get_capture(), self.pg2.get_capture()) + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) -- cgit 1.2.3-korg From 7bb873a4cc068a6cc3c9d0e1d32987c5f8003904 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Fri, 18 Nov 2016 07:38:42 +0100 Subject: make test: fix missing log/packet messages Change-Id: Idb3119792943664748c4abc3829ad723f4156dfe Signed-off-by: Klement Sekera --- test/framework.py | 2 +- test/test_ip4.py | 12 +++++------- test/test_ip6.py | 13 ++++++------- test/test_l2bd.py | 7 +++---- test/test_l2xc.py | 5 ++--- test/test_lb.py | 7 +++---- test/test_mpls.py | 12 ++++++------ test/test_span.py | 49 +++++++++++++++++++++++++++-------------------- test/test_vxlan.py | 3 +-- test/util.py | 14 ++++++++++++++ test/vpp_interface.py | 12 +++++------- test/vpp_papi_provider.py | 11 +++++++---- test/vpp_pg_interface.py | 46 +++++++++++++++++++++++++------------------- 13 files changed, 107 insertions(+), 86 deletions(-) (limited to 'test/test_span.py') diff --git a/test/framework.py b/test/framework.py index 5a9aba2c..b3cbb08a 100644 --- a/test/framework.py +++ b/test/framework.py @@ -193,7 +193,7 @@ class VppTestCase(unittest.TestCase): cls.vpp_stderr_reader_thread = Thread(target=pump_output, args=( cls.vpp.stderr, cls.vpp_stderr_queue)) cls.vpp_stderr_reader_thread.start() - cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix) + cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls) if cls.step: hook = StepHook(cls) else: diff --git a/test/test_ip4.py b/test/test_ip4.py index 36a907a6..d219ec9f 100644 --- a/test/test_ip4.py +++ b/test/test_ip4.py @@ -4,12 +4,12 @@ import unittest import socket from framework import VppTestCase, VppTestRunner -from vpp_interface import VppInterface from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint from scapy.packet import Raw from scapy.layers.l2 import Ether, Dot1Q from scapy.layers.inet import IP, UDP +from util import ppp class TestIPv4(VppTestCase): @@ -164,16 +164,14 @@ class TestIPv4(VppTestCase): self.assertEqual(udp.sport, saved_packet[UDP].sport) self.assertEqual(udp.dport, saved_packet[UDP].dport) except: - self.logger.error("Unexpected or invalid packet:") - self.logger.error(packet.show()) + self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise for i in self.interfaces: remaining_packet = self.get_next_packet_info_for_interface2( i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index]) - self.assertTrue( - remaining_packet is None, - "Interface %s: Packet expected from interface %s didn't arrive" % - (dst_if.name, i.name)) + self.assertTrue(remaining_packet is None, + "Interface %s: Packet expected from interface %s " + "didn't arrive" % (dst_if.name, i.name)) def test_fib(self): """ IPv4 FIB test diff --git a/test/test_ip6.py b/test/test_ip6.py index bff829b7..06b15f94 100644 --- a/test/test_ip6.py +++ b/test/test_ip6.py @@ -9,6 +9,7 @@ from vpp_sub_interface import VppSubInterface, VppDot1QSubint from scapy.packet import Raw from scapy.layers.l2 import Ether, Dot1Q from scapy.layers.inet6 import IPv6, UDP +from util import ppp class TestIPv6(VppTestCase): @@ -103,7 +104,7 @@ class TestIPv6(VppTestCase): counter += 1 if counter / count * 100 > percent: self.logger.info("Configure %d FIB entries .. %d%% done" % - (count, percent)) + (count, percent)) percent += 1 def create_stream(self, src_if, packet_sizes): @@ -171,16 +172,14 @@ class TestIPv6(VppTestCase): self.assertEqual(udp.sport, saved_packet[UDP].sport) self.assertEqual(udp.dport, saved_packet[UDP].dport) except: - self.logger.error("Unexpected or invalid packet:") - self.logger.error(packet.show()) + self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise for i in self.interfaces: remaining_packet = self.get_next_packet_info_for_interface2( i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index]) - self.assertTrue( - remaining_packet is None, - "Interface %s: Packet expected from interface %s didn't arrive" % - (dst_if.name, i.name)) + self.assertTrue(remaining_packet is None, + "Interface %s: Packet expected from interface %s " + "didn't arrive" % (dst_if.name, i.name)) def test_fib(self): """ IPv6 FIB test diff --git a/test/test_l2bd.py b/test/test_l2bd.py index 46ba2e49..50720e64 100644 --- a/test/test_l2bd.py +++ b/test/test_l2bd.py @@ -8,7 +8,7 @@ from scapy.layers.l2 import Ether, Dot1Q from scapy.layers.inet import IP, UDP from framework import VppTestCase, VppTestRunner -from util import Host +from util import Host, ppp from vpp_sub_interface import VppDot1QSubint, VppDot1ADSubint @@ -109,7 +109,7 @@ class TestL2bd(VppTestCase): if not self.vpp_dead: self.logger.info(self.vapi.ppcli("show l2fib verbose")) self.logger.info(self.vapi.ppcli("show bridge-domain %s detail" % - self.bd_id)) + self.bd_id)) @classmethod def create_hosts_and_learn(cls, count): @@ -217,8 +217,7 @@ class TestL2bd(VppTestCase): self.assertEqual(udp.sport, saved_packet[UDP].sport) self.assertEqual(udp.dport, saved_packet[UDP].dport) except: - self.logger.error("Unexpected or invalid packet:") - self.logger.error(packet.show()) + 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( diff --git a/test/test_l2xc.py b/test/test_l2xc.py index 49ca9968..37893042 100644 --- a/test/test_l2xc.py +++ b/test/test_l2xc.py @@ -8,7 +8,7 @@ from scapy.layers.l2 import Ether from scapy.layers.inet import IP, UDP from framework import VppTestCase, VppTestRunner -from util import Host +from util import Host, ppp class TestL2xc(VppTestCase): @@ -169,8 +169,7 @@ class TestL2xc(VppTestCase): self.assertEqual(udp.sport, saved_packet[UDP].sport) self.assertEqual(udp.dport, saved_packet[UDP].dport) except: - self.logger.error("Unexpected or invalid packet:") - self.logger.error(packet.show()) + self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise for i in self.interfaces: remaining_packet = self.get_next_packet_info_for_interface2( diff --git a/test/test_lb.py b/test/test_lb.py index fa4900d2..3e7f5e13 100644 --- a/test/test_lb.py +++ b/test/test_lb.py @@ -1,5 +1,4 @@ import socket -from logging import * from scapy.layers.inet import IP, UDP from scapy.layers.inet6 import IPv6 @@ -7,6 +6,7 @@ from scapy.layers.l2 import Ether, GRE from scapy.packet import Raw from framework import VppTestCase +from util import ppp """ TestLB is a subclass of VPPTestCase classes. @@ -57,7 +57,7 @@ class TestLB(VppTestCase): def tearDown(self): super(TestLB, self).tearDown() if not self.vpp_dead: - info(self.vapi.cli("show lb vip verbose")) + self.logger.info(self.vapi.cli("show lb vip verbose")) def getIPv4Flow(self, id): return (IP(dst="90.0.%u.%u" % (id / 255, id % 255), @@ -139,8 +139,7 @@ class TestLB(VppTestCase): self.checkInner(gre, isv4) load[asid] += 1 except: - error("Unexpected or invalid packet:") - p.show() + self.logger.error(ppp("Unexpected or invalid packet:", p)) raise # This is just to roughly check that the balancing algorithm diff --git a/test/test_mpls.py b/test/test_mpls.py index d1b1b919..08cd55b7 100644 --- a/test/test_mpls.py +++ b/test/test_mpls.py @@ -1,18 +1,18 @@ #!/usr/bin/env python import unittest -import socket -from logging import * from framework import VppTestCase, VppTestRunner from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint from vpp_ip_route import IpRoute, RoutePath, MplsRoute, MplsIpBind from scapy.packet import Raw -from scapy.layers.l2 import Ether, Dot1Q, ARP +from scapy.layers.l2 import Ether from scapy.layers.inet import IP, UDP -from scapy.layers.inet6 import ICMPv6ND_NS, IPv6, UDP +from scapy.layers.inet6 import IPv6 from scapy.contrib.mpls import MPLS +from util import ppp + class TestMPLS(VppTestCase): @@ -621,8 +621,8 @@ class TestMPLS(VppTestCase): try: self.assertEqual(0, len(rx)) except: - error("MPLS TTL=0 packets forwarded") - error(packet.show()) + self.logger.error("MPLS TTL=0 packets forwarded") + self.logger.error(ppp("", rx)) raise # diff --git a/test/test_span.py b/test/test_span.py index 59ef5efc..e42fbd77 100644 --- a/test/test_span.py +++ b/test/test_span.py @@ -1,15 +1,13 @@ #!/usr/bin/env python import unittest -import random from scapy.packet import Raw from scapy.layers.l2 import Ether from scapy.layers.inet import IP, UDP -from logging import * from framework import VppTestCase, VppTestRunner -from util import Host +from util import Host, ppp class TestSpan(VppTestCase): @@ -34,7 +32,7 @@ class TestSpan(VppTestCase): self.flows[self.pg0] = [self.pg1] # packet sizes - self.pg_if_packet_sizes = [64, 512] #, 1518, 9018] + self.pg_if_packet_sizes = [64, 512] # , 1518, 9018] self.interfaces = list(self.pg_interfaces) @@ -56,7 +54,8 @@ class TestSpan(VppTestCase): i.resolve_arp() # Enable SPAN on pg0 (mirrored to pg2) - self.vapi.sw_interface_span_enable_disable(self.pg0.sw_if_index, self.pg2.sw_if_index) + self.vapi.sw_interface_span_enable_disable( + self.pg0.sw_if_index, self.pg2.sw_if_index) def tearDown(self): super(TestSpan, self).tearDown() @@ -86,8 +85,6 @@ class TestSpan(VppTestCase): pkts = [] for i in range(0, TestSpan.pkts_per_burst): dst_if = self.flows[src_if][0] - dst_host = random.choice(self.hosts_by_pg_idx[dst_if.sw_if_index]) - src_host = random.choice(self.hosts_by_pg_idx[src_if.sw_if_index]) pkt_info = self.create_packet_info( src_if.sw_if_index, dst_if.sw_if_index) payload = self.info_to_payload(pkt_info) @@ -107,8 +104,9 @@ class TestSpan(VppTestCase): last_info[i.sw_if_index] = None dst_sw_if_index = dst_if.sw_if_index if len(capture_pg1) != len(capture_pg2): - error("Diffrent number of outgoing and mirrored packets : %u != %u" - % (len(capture_pg1), len(capture_pg2))) + self.logger.error( + "Different number of outgoing and mirrored packets : %u != %u" % + (len(capture_pg1), len(capture_pg2))) raise for pkt_pg1, pkt_pg2 in zip(capture_pg1, capture_pg2): try: @@ -117,23 +115,28 @@ class TestSpan(VppTestCase): raw1 = pkt_pg1[Raw] if pkt_pg1[Ether] != pkt_pg2[Ether]: - error("Diffrent ethernet header of outgoing and mirrored packet") + self.logger.error("Different ethernet header of " + "outgoing and mirrored packet") raise if ip1 != pkt_pg2[IP]: - error("Diffrent ip header of outgoing and mirrored packet") + self.logger.error( + "Different ip header of outgoing and mirrored packet") raise if udp1 != pkt_pg2[UDP]: - error("Diffrent udp header of outgoing and mirrored packet") + self.logger.error( + "Different udp header of outgoing and mirrored packet") raise if raw1 != pkt_pg2[Raw]: - error("Diffrent raw data of outgoing and mirrored packet") + self.logger.error( + "Different raw data of outgoing and mirrored packet") raise payload_info = self.payload_to_info(str(raw1)) packet_index = payload_info.index self.assertEqual(payload_info.dst, dst_sw_if_index) - debug("Got packet on port %s: src=%u (id=%u)" % - (dst_if.name, payload_info.src, packet_index)) + self.logger.debug( + "Got packet on port %s: src=%u (id=%u)" % + (dst_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]) @@ -147,9 +150,9 @@ class TestSpan(VppTestCase): self.assertEqual(udp1.sport, saved_packet[UDP].sport) self.assertEqual(udp1.dport, saved_packet[UDP].dport) except: - error("Unexpected or invalid packet:") - pkt_pg1.show() - pkt_pg2.show() + self.logger.error("Unexpected or invalid packets:") + self.logger.error(ppp("pg1 packet:", pkt_pg1)) + self.logger.error(ppp("pg2 packet:", pkt_pg2)) raise for i in self.interfaces: remaining_packet = self.get_next_packet_info_for_interface2( @@ -164,7 +167,8 @@ class TestSpan(VppTestCase): Test scenario: 1. config 3 interfaces, pg0 l2xconnected with pg1 - 2. sending l2 eth packets between 2 interfaces (pg0, pg1) and mirrored to pg2 + 2. sending l2 eth packets between 2 interfaces (pg0, pg1) and + mirrored to pg2 64B, 512B, 1518B, 9018B (ether_size) burst of packets per interface """ @@ -178,8 +182,11 @@ class TestSpan(VppTestCase): self.pg_start() # Verify packets outgoing packet streams on mirrored interface (pg2) - info("Verifying capture on interfaces %s and %s" % (self.pg1.name, self.pg2.name)) - self.verify_capture(self.pg1, self.pg1.get_capture(), self.pg2.get_capture()) + self.logger.info("Verifying capture on interfaces %s and %s" % + (self.pg1.name, self.pg2.name)) + self.verify_capture(self.pg1, + self.pg1.get_capture(), + self.pg2.get_capture()) if __name__ == '__main__': diff --git a/test/test_vxlan.py b/test/test_vxlan.py index cb7e7acf..ac435852 100644 --- a/test/test_vxlan.py +++ b/test/test_vxlan.py @@ -1,7 +1,6 @@ #!/usr/bin/env python import unittest -from logging import * from framework import VppTestCase, VppTestRunner from template_bd import BridgeDomain @@ -94,7 +93,7 @@ class TestVxlan(BridgeDomain, VppTestCase): def tearDown(self): super(TestVxlan, self).tearDown() if not self.vpp_dead: - info(self.vapi.cli("show bridge-domain 1 detail")) + self.logger.info(self.vapi.cli("show bridge-domain 1 detail")) if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/util.py b/test/util.py index 6e7e275c..643377f5 100644 --- a/test/util.py +++ b/test/util.py @@ -1,4 +1,18 @@ import socket +import sys +from cStringIO import StringIO + + +def ppp(headline, packet): + """ Return string containing the output of scapy packet.show() call. """ + o = StringIO() + old_stdout = sys.stdout + sys.stdout = o + print(headline) + packet.show() + sys.stdout = old_stdout + return o.getvalue() + class Host(object): """ Generic test host "connected" to VPPs interface. """ diff --git a/test/vpp_interface.py b/test/vpp_interface.py index a450560e..024aeb5f 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -1,8 +1,6 @@ from abc import abstractmethod, ABCMeta import socket -from util import Host - class VppInterface(object): """Generic VPP interface.""" @@ -127,7 +125,8 @@ class VppInterface(object): self._hosts_by_mac = {} self._hosts_by_ip4 = {} self._hosts_by_ip6 = {} - for i in range(2, count+2): # 0: network address, 1: local vpp address + for i in range( + 2, count + 2): # 0: network address, 1: local vpp address mac = "02:%02x:00:00:ff:%02x" % (self.sw_if_index, i) ip4 = "172.16.%u.%u" % (self.sw_if_index, i) ip6 = "fd01:%04x::%04x" % (self.sw_if_index, i) @@ -158,10 +157,9 @@ class VppInterface(object): for intf in r: if intf.sw_if_index == self.sw_if_index: self._name = intf.interface_name.split(b'\0', 1)[0] - self._local_mac = ':'.join( - intf.l2_address.encode('hex')[i:i + 2] - for i in range(0, 12, 2) - ) + self._local_mac =\ + ':'.join(intf.l2_address.encode('hex')[i:i + 2] + for i in range(0, 12, 2)) self._dump = intf break else: diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index dc90289e..51cc20ba 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -1,6 +1,5 @@ import os import array -from logging import error from hook import Hook do_import = True @@ -32,10 +31,11 @@ class VppPapiProvider(object): """ - def __init__(self, name, shm_prefix): + def __init__(self, name, shm_prefix, test_class): self.hook = Hook("vpp-papi-provider") self.name = name self.shm_prefix = shm_prefix + self.test_class = test_class def register_hook(self, hook): """Replace hook registration with new hook @@ -68,7 +68,7 @@ class VppPapiProvider(object): if hasattr(reply, 'retval') and reply.retval != expected_retval: msg = "API call failed, expected retval == %d, got %s" % ( expected_retval, repr(reply)) - error(msg) + self.test_class.test_instance.logger.error(msg) raise Exception(msg) self.hook.after_api(api_fn.__name__, api_args) return reply @@ -497,7 +497,8 @@ class VppPapiProvider(object): ) ) - def sw_interface_span_enable_disable(self, sw_if_index_from, sw_if_index_to, enable=1): + def sw_interface_span_enable_disable( + self, sw_if_index_from, sw_if_index_to, enable=1): """ :param sw_if_index_from: @@ -683,3 +684,5 @@ class VppPapiProvider(object): next_hop_table_id, stack)) + return self.api(vpp_papi.sw_interface_span_enable_disable, + (sw_if_index_from, sw_if_index_to, enable)) diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py index 81a0fba9..533c4603 100644 --- a/test/vpp_pg_interface.py +++ b/test/vpp_pg_interface.py @@ -1,12 +1,12 @@ import os import time -from logging import error, info from scapy.utils import wrpcap, rdpcap from vpp_interface import VppInterface from scapy.layers.l2 import Ether, ARP from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6ND_NA, \ ICMPv6NDOptSrcLLAddr, ICMPv6NDOptDstLLAddr +from util import ppp class VppPGInterface(VppInterface): @@ -127,8 +127,8 @@ class VppPGInterface(VppInterface): try: output = rdpcap(self.out_path) except IOError: # TODO - error("File %s does not exist, probably because no" - " packets arrived" % self.out_path) + self.test.logger.error("File %s does not exist, probably because no" + " packets arrived" % self.out_path) return [] return output @@ -154,16 +154,18 @@ class VppPGInterface(VppInterface): """ if pg_interface is None: pg_interface = self - info("Sending ARP request for %s on port %s" % - (self.local_ip4, pg_interface.name)) + self.test.logger.info("Sending ARP request for %s on port %s" % + (self.local_ip4, pg_interface.name)) arp_req = self.create_arp_req() pg_interface.add_stream(arp_req) pg_interface.enable_capture() self.test.pg_start() - info(self.test.vapi.cli("show trace")) + self.test.logger.info(self.test.vapi.cli("show trace")) arp_reply = pg_interface.get_capture() if arp_reply is None or len(arp_reply) == 0: - info("No ARP received on port %s" % pg_interface.name) + self.test.logger.info( + "No ARP received on port %s" % + pg_interface.name) return arp_reply = arp_reply[0] # Make Dot1AD packet content recognizable to scapy @@ -172,14 +174,16 @@ class VppPGInterface(VppInterface): arp_reply = Ether(str(arp_reply)) try: if arp_reply[ARP].op == ARP.is_at: - info("VPP %s MAC address is %s " % - (self.name, arp_reply[ARP].hwsrc)) + self.test.logger.info("VPP %s MAC address is %s " % + (self.name, arp_reply[ARP].hwsrc)) self._local_mac = arp_reply[ARP].hwsrc else: - info("No ARP received on port %s" % pg_interface.name) + self.test.logger.info( + "No ARP received on port %s" % + pg_interface.name) except: - error("Unexpected response to ARP request:") - error(arp_reply.show()) + self.test.logger.error( + ppp("Unexpected response to ARP request:", arp_reply)) raise def resolve_ndp(self, pg_interface=None): @@ -191,16 +195,18 @@ class VppPGInterface(VppInterface): """ if pg_interface is None: pg_interface = self - info("Sending NDP request for %s on port %s" % - (self.local_ip6, pg_interface.name)) + self.test.logger.info("Sending NDP request for %s on port %s" % + (self.local_ip6, pg_interface.name)) ndp_req = self.create_ndp_req() pg_interface.add_stream(ndp_req) pg_interface.enable_capture() self.test.pg_start() - info(self.test.vapi.cli("show trace")) + self.test.logger.info(self.test.vapi.cli("show trace")) ndp_reply = pg_interface.get_capture() if ndp_reply is None or len(ndp_reply) == 0: - info("No NDP received on port %s" % pg_interface.name) + self.test.logger.info( + "No NDP received on port %s" % + pg_interface.name) return ndp_reply = ndp_reply[0] # Make Dot1AD packet content recognizable to scapy @@ -210,10 +216,10 @@ class VppPGInterface(VppInterface): try: ndp_na = ndp_reply[ICMPv6ND_NA] opt = ndp_na[ICMPv6NDOptDstLLAddr] - info("VPP %s MAC address is %s " % - (self.name, opt.lladdr)) + self.test.logger.info("VPP %s MAC address is %s " % + (self.name, opt.lladdr)) self._local_mac = opt.lladdr except: - error("Unexpected response to NDP request:") - error(ndp_reply.show()) + self.test.logger.error( + ppp("Unexpected response to NDP request:", ndp_reply)) raise -- cgit 1.2.3-korg From dab231a11ec96e829b22ff80c612333edc5a93e6 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Wed, 21 Dec 2016 08:50:14 +0100 Subject: make test: improve handling of packet captures Perform accounting of expected packets based on created packet infos. Use this accounting info to automatically expect (and verify) the correct number of packets to be captured. Automatically retry the read of the capture file if scapy raises an exception while doing so to handle rare cases when capture file is read while only partially written during busy wait. Don't fail assert_nothing_captured if only junk packets arrived. Change-Id: I16ec2e9410ef510d313ec16b7e13c57d0b2a63f5 Signed-off-by: Klement Sekera --- test/framework.py | 53 ++++++----- test/template_bd.py | 10 +-- test/test_bfd.py | 8 +- test/test_classifier.py | 85 ++++++++++-------- test/test_gre.py | 33 +++---- test/test_ip4.py | 19 ++-- test/test_ip4_irb.py | 17 ++-- test/test_ip6.py | 3 +- test/test_l2_fib.py | 10 +-- test/test_l2bd.py | 5 +- test/test_l2bd_multi_instance.py | 15 +--- test/test_l2xc.py | 8 +- test/test_l2xc_multi_instance.py | 5 +- test/test_lb.py | 11 ++- test/test_mpls.py | 41 +++++---- test/test_snat.py | 55 ++++++------ test/test_span.py | 11 +-- test/util.py | 17 ++-- test/vpp_pg_interface.py | 186 +++++++++++++++++++++++++++------------ 19 files changed, 321 insertions(+), 271 deletions(-) (limited to 'test/test_span.py') diff --git a/test/framework.py b/test/framework.py index 1b745ff3..324a64ce 100644 --- a/test/framework.py +++ b/test/framework.py @@ -10,6 +10,7 @@ from threading import Thread from inspect import getdoc from hook import StepHook, PollHook from vpp_pg_interface import VppPGInterface +from vpp_sub_interface import VppSubInterface from vpp_lo_interface import VppLoInterface from vpp_papi_provider import VppPapiProvider from scapy.packet import Raw @@ -63,9 +64,13 @@ class VppTestCase(unittest.TestCase): """List of packet infos""" return self._packet_infos - @packet_infos.setter - def packet_infos(self, value): - self._packet_infos = value + @classmethod + def get_packet_count_for_if_idx(cls, dst_if_index): + """Get the number of packet info for specified destination if index""" + if dst_if_index in cls._packet_count_for_dst_if_idx: + return cls._packet_count_for_dst_if_idx[dst_if_index] + else: + return 0 @classmethod def instance(cls): @@ -184,9 +189,9 @@ class VppTestCase(unittest.TestCase): cls.logger.info("Temporary dir is %s, shm prefix is %s", cls.tempdir, cls.shm_prefix) cls.setUpConstants() + cls.reset_packet_infos() cls._captures = [] cls._zombie_captures = [] - cls.packet_infos = {} cls.verbose = 0 cls.vpp_dead = False print(double_line_delim) @@ -394,31 +399,37 @@ class VppTestCase(unittest.TestCase): if extend > 0: packet[Raw].load += ' ' * extend - def add_packet_info_to_list(self, info): - """ - Add packet info to the testcase's packet info list - - :param info: packet info - - """ - info.index = len(self.packet_infos) - self.packet_infos[info.index] = info + @classmethod + def reset_packet_infos(cls): + """ Reset the list of packet info objects and packet counts to zero """ + cls._packet_infos = {} + cls._packet_count_for_dst_if_idx = {} - def create_packet_info(self, src_pg_index, dst_pg_index): + @classmethod + def create_packet_info(cls, src_if, dst_if): """ Create packet info object containing the source and destination indexes and add it to the testcase's packet info list - :param src_pg_index: source packet-generator index - :param dst_pg_index: destination packet-generator index + :param VppInterface src_if: source interface + :param VppInterface dst_if: destination interface :returns: _PacketInfo object """ info = _PacketInfo() - self.add_packet_info_to_list(info) - info.src = src_pg_index - info.dst = dst_pg_index + info.index = len(cls._packet_infos) + info.src = src_if.sw_if_index + info.dst = dst_if.sw_if_index + if isinstance(dst_if, VppSubInterface): + dst_idx = dst_if.parent.sw_if_index + else: + dst_idx = dst_if.sw_if_index + if dst_idx in cls._packet_count_for_dst_if_idx: + cls._packet_count_for_dst_if_idx[dst_idx] += 1 + else: + cls._packet_count_for_dst_if_idx[dst_idx] = 1 + cls._packet_infos[info.index] = info return info @staticmethod @@ -462,10 +473,10 @@ class VppTestCase(unittest.TestCase): next_index = 0 else: next_index = info.index + 1 - if next_index == len(self.packet_infos): + if next_index == len(self._packet_infos): return None else: - return self.packet_infos[next_index] + return self._packet_infos[next_index] def get_next_packet_info_for_interface(self, src_index, info): """ diff --git a/test/template_bd.py b/test/template_bd.py index 01e8b855..d70648b4 100644 --- a/test/template_bd.py +++ b/test/template_bd.py @@ -56,10 +56,7 @@ class BridgeDomain(object): self.pg_start() # Pick first received frame and check if it's the non-encapsulated frame - out = self.pg1.get_capture() - self.assertEqual(len(out), 1, - 'Invalid number of packets on ' - 'output: {}'.format(len(out))) + out = self.pg1.get_capture(1) pkt = out[0] # TODO: add error messages @@ -83,10 +80,7 @@ class BridgeDomain(object): self.pg_start() # Pick first received frame and check if it's corectly encapsulated. - out = self.pg0.get_capture() - self.assertEqual(len(out), 1, - 'Invalid number of packets on ' - 'output: {}'.format(len(out))) + out = self.pg0.get_capture(1) pkt = out[0] self.check_encapsulation(pkt) diff --git a/test/test_bfd.py b/test/test_bfd.py index 87a5ea4b..1ea69f55 100644 --- a/test/test_bfd.py +++ b/test/test_bfd.py @@ -184,10 +184,10 @@ class BFDTestCase(VppTestCase): self.pg_enable_capture([self.pg0]) expected_packets = 3 self.logger.info("BFD: Waiting for %d BFD packets" % expected_packets) - self.wait_for_bfd_packet() + self.wait_for_bfd_packet(2) for i in range(expected_packets): before = time.time() - self.wait_for_bfd_packet() + self.wait_for_bfd_packet(2) after = time.time() # spec says the range should be <0.75, 1>, allow extra 0.05 margin # to work around timing issues @@ -198,7 +198,7 @@ class BFDTestCase(VppTestCase): def test_zero_remote_min_rx(self): """ no packets when zero BFD RemoteMinRxInterval """ self.pg_enable_capture([self.pg0]) - p = self.wait_for_bfd_packet() + p = self.wait_for_bfd_packet(2) self.test_session.update(my_discriminator=randint(0, 40000000), your_discriminator=p[BFD].my_discriminator, state=BFDState.init, @@ -216,7 +216,7 @@ class BFDTestCase(VppTestCase): def bfd_session_up(self): self.pg_enable_capture([self.pg0]) self.logger.info("BFD: Waiting for slow hello") - p = self.wait_for_bfd_packet() + p = self.wait_for_bfd_packet(2) self.logger.info("BFD: Sending Init") self.test_session.update(my_discriminator=randint(0, 40000000), your_discriminator=p[BFD].my_discriminator, diff --git a/test/test_classifier.py b/test/test_classifier.py index 0923387c..302430f8 100644 --- a/test/test_classifier.py +++ b/test/test_classifier.py @@ -11,6 +11,7 @@ from scapy.layers.l2 import Ether from scapy.layers.inet import IP, UDP from util import ppp + class TestClassifier(VppTestCase): """ Classifier Test Case """ @@ -84,8 +85,7 @@ class TestClassifier(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(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) / @@ -150,8 +150,8 @@ class TestClassifier(VppTestCase): :param str dst_port: destination port number <0-ffff> """ - return ('{:0>20}{:0>12}{:0>8}{:0>12}{:0>4}'.format(proto, src_ip, - dst_ip, src_port, dst_port)).rstrip('0') + return ('{:0>20}{:0>12}{:0>8}{:0>12}{:0>4}'.format( + proto, src_ip, dst_ip, src_port, dst_port)).rstrip('0') @staticmethod def build_ip_match(proto='', src_ip='', dst_ip='', @@ -164,11 +164,13 @@ class TestClassifier(VppTestCase): :param str src_port: source port number <0-ffff> :param str dst_port: destination port number <0-ffff> """ - if src_ip: src_ip = socket.inet_aton(src_ip).encode('hex') - if dst_ip: dst_ip = socket.inet_aton(dst_ip).encode('hex') + if src_ip: + src_ip = socket.inet_aton(src_ip).encode('hex') + if dst_ip: + dst_ip = socket.inet_aton(dst_ip).encode('hex') - return ('{:0>20}{:0>12}{:0>8}{:0>12}{:0>4}'.format(proto, src_ip, - dst_ip, src_port, dst_port)).rstrip('0') + return ('{:0>20}{:0>12}{:0>8}{:0>12}{:0>4}'.format( + proto, src_ip, dst_ip, src_port, dst_port)).rstrip('0') @staticmethod def build_mac_mask(dst_mac='', src_mac='', ether_type=''): @@ -180,7 +182,7 @@ class TestClassifier(VppTestCase): """ return ('{:0>12}{:0>12}{:0>4}'.format(dst_mac, src_mac, - ether_type)).rstrip('0') + ether_type)).rstrip('0') @staticmethod def build_mac_match(dst_mac='', src_mac='', ether_type=''): @@ -190,11 +192,13 @@ class TestClassifier(VppTestCase): :param str src_mac: destination MAC address :param str ether_type: ethernet type <0-ffff> """ - if dst_mac: dst_mac = dst_mac.replace(':', '') - if src_mac: src_mac = src_mac.replace(':', '') + if dst_mac: + dst_mac = dst_mac.replace(':', '') + if src_mac: + src_mac = src_mac.replace(':', '') return ('{:0>12}{:0>12}{:0>4}'.format(dst_mac, src_mac, - ether_type)).rstrip('0') + ether_type)).rstrip('0') def create_classify_table(self, key, mask, data_offset=0, is_add=1): """Create Classify Table @@ -206,12 +210,12 @@ class TestClassifier(VppTestCase): - create(1) or delete(0) """ r = self.vapi.classify_add_del_table( - is_add, - binascii.unhexlify(mask), - match_n_vectors=(len(mask)-1)//32 + 1, - miss_next_index=0, - current_data_flag=1, - current_data_offset=data_offset) + is_add, + binascii.unhexlify(mask), + match_n_vectors=(len(mask) - 1) // 32 + 1, + miss_next_index=0, + current_data_flag=1, + current_data_offset=data_offset) self.assertIsNotNone(r, msg='No response msg for add_del_table') self.acl_tbl_idx[key] = r.new_table_index @@ -228,12 +232,12 @@ class TestClassifier(VppTestCase): - create(1) or delete(0) """ r = self.vapi.classify_add_del_session( - is_add, - table_index, - binascii.unhexlify(match), - opaque_index=0, - action=pbr_option, - metadata=vrfid) + is_add, + table_index, + binascii.unhexlify(match), + opaque_index=0, + action=pbr_option, + metadata=vrfid) self.assertIsNotNone(r, msg='No response msg for add_del_session') def input_acl_set_interface(self, intf, table_index, is_add=1): @@ -245,9 +249,9 @@ class TestClassifier(VppTestCase): - enable(1) or disable(0) """ r = self.vapi.input_acl_set_interface( - is_add, - intf.sw_if_index, - ip4_table_index=table_index) + is_add, + intf.sw_if_index, + ip4_table_index=table_index) self.assertIsNotNone(r, msg='No response msg for acl_set_interface') def test_acl_ip(self): @@ -264,14 +268,15 @@ class TestClassifier(VppTestCase): self.pg0.add_stream(pkts) self.create_classify_table('ip', self.build_ip_mask(src_ip='ffffffff')) - self.create_classify_session(self.pg0, self.acl_tbl_idx.get('ip'), - self.build_ip_match(src_ip=self.pg0.remote_ip4)) + self.create_classify_session( + self.pg0, self.acl_tbl_idx.get('ip'), + self.build_ip_match(src_ip=self.pg0.remote_ip4)) self.input_acl_set_interface(self.pg0, self.acl_tbl_idx.get('ip')) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - pkts = self.pg1.get_capture() + pkts = self.pg1.get_capture(len(pkts)) self.verify_capture(self.pg1, pkts) self.input_acl_set_interface(self.pg0, self.acl_tbl_idx.get('ip'), 0) self.pg0.assert_nothing_captured(remark="packets forwarded") @@ -291,16 +296,17 @@ class TestClassifier(VppTestCase): pkts = self.create_stream(self.pg0, self.pg2, self.pg_if_packet_sizes) self.pg0.add_stream(pkts) - self.create_classify_table('mac', - self.build_mac_mask(src_mac='ffffffffffff'), data_offset=-14) - self.create_classify_session(self.pg0, self.acl_tbl_idx.get('mac'), - self.build_mac_match(src_mac=self.pg0.remote_mac)) + self.create_classify_table( + 'mac', self.build_mac_mask(src_mac='ffffffffffff'), data_offset=-14) + self.create_classify_session( + self.pg0, self.acl_tbl_idx.get('mac'), + self.build_mac_match(src_mac=self.pg0.remote_mac)) self.input_acl_set_interface(self.pg0, self.acl_tbl_idx.get('mac')) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - pkts = self.pg2.get_capture() + pkts = self.pg2.get_capture(len(pkts)) self.verify_capture(self.pg2, pkts) self.input_acl_set_interface(self.pg0, self.acl_tbl_idx.get('mac'), 0) self.pg0.assert_nothing_captured(remark="packets forwarded") @@ -322,16 +328,17 @@ class TestClassifier(VppTestCase): self.create_classify_table('pbr', self.build_ip_mask(src_ip='ffffffff')) pbr_option = 1 - self.create_classify_session(self.pg0, self.acl_tbl_idx.get('pbr'), - self.build_ip_match(src_ip=self.pg0.remote_ip4), - pbr_option, self.pbr_vrfid) + self.create_classify_session( + self.pg0, self.acl_tbl_idx.get('pbr'), + self.build_ip_match(src_ip=self.pg0.remote_ip4), + pbr_option, self.pbr_vrfid) self.config_pbr_fib_entry(self.pg3) self.input_acl_set_interface(self.pg0, self.acl_tbl_idx.get('pbr')) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - pkts = self.pg3.get_capture() + pkts = self.pg3.get_capture(len(pkts)) self.verify_capture(self.pg3, pkts) self.input_acl_set_interface(self.pg0, self.acl_tbl_idx.get('pbr'), 0) self.pg0.assert_nothing_captured(remark="packets forwarded") diff --git a/test/test_gre.py b/test/test_gre.py index f00e4467..b1313044 100644 --- a/test/test_gre.py +++ b/test/test_gre.py @@ -43,8 +43,7 @@ class TestGRE(VppTestCase): def create_stream_ip4(self, src_if, src_ip, dst_ip): pkts = [] for i in range(0, 257): - info = self.create_packet_info(src_if.sw_if_index, - src_if.sw_if_index) + info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=src_ip, dst=dst_ip) / @@ -59,8 +58,7 @@ class TestGRE(VppTestCase): src_ip, dst_ip): pkts = [] for i in range(0, 257): - info = self.create_packet_info(src_if.sw_if_index, - src_if.sw_if_index) + info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=tunnel_src, dst=tunnel_dst) / @@ -77,8 +75,7 @@ class TestGRE(VppTestCase): src_ip, dst_ip): pkts = [] for i in range(0, 257): - info = self.create_packet_info(src_if.sw_if_index, - src_if.sw_if_index) + info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=tunnel_src, dst=tunnel_dst) / @@ -94,8 +91,7 @@ class TestGRE(VppTestCase): tunnel_src, tunnel_dst): pkts = [] for i in range(0, 257): - info = self.create_packet_info(src_if.sw_if_index, - src_if.sw_if_index) + info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=tunnel_src, dst=tunnel_dst) / @@ -113,8 +109,7 @@ class TestGRE(VppTestCase): tunnel_src, tunnel_dst, vlan): pkts = [] for i in range(0, 257): - info = self.create_packet_info(src_if.sw_if_index, - src_if.sw_if_index) + info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=tunnel_src, dst=tunnel_dst) / @@ -342,7 +337,7 @@ class TestGRE(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - rx = self.pg0.get_capture() + rx = self.pg0.get_capture(len(tx)) self.verify_tunneled_4o4(self.pg0, rx, tx, self.pg0.local_ip4, "1.1.1.2") @@ -361,7 +356,7 @@ class TestGRE(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - rx = self.pg0.get_capture() + rx = self.pg0.get_capture(len(tx)) self.verify_decapped_4o4(self.pg0, rx, tx) # @@ -426,7 +421,7 @@ class TestGRE(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - rx = self.pg0.get_capture() + rx = self.pg0.get_capture(len(tx)) self.verify_decapped_6o4(self.pg0, rx, tx) # @@ -483,7 +478,7 @@ class TestGRE(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - rx = self.pg1.get_capture() + rx = self.pg1.get_capture(len(tx)) self.verify_tunneled_4o4(self.pg1, rx, tx, self.pg1.local_ip4, "2.2.2.2") @@ -503,7 +498,7 @@ class TestGRE(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - rx = self.pg0.get_capture() + rx = self.pg0.get_capture(len(tx)) self.verify_decapped_4o4(self.pg0, rx, tx) # @@ -564,7 +559,7 @@ class TestGRE(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - rx = self.pg0.get_capture() + rx = self.pg0.get_capture(len(tx)) self.verify_tunneled_l2o4(self.pg0, rx, tx, self.pg0.local_ip4, "2.2.2.3") @@ -578,7 +573,7 @@ class TestGRE(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - rx = self.pg0.get_capture() + rx = self.pg0.get_capture(len(tx)) self.verify_tunneled_l2o4(self.pg0, rx, tx, self.pg0.local_ip4, "2.2.2.2") @@ -635,7 +630,7 @@ class TestGRE(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - rx = self.pg0.get_capture() + rx = self.pg0.get_capture(len(tx)) self.verify_tunneled_vlano4(self.pg0, rx, tx, self.pg0.local_ip4, "2.2.2.3", @@ -651,7 +646,7 @@ class TestGRE(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - rx = self.pg0.get_capture() + rx = self.pg0.get_capture(len(tx)) self.verify_tunneled_vlano4(self.pg0, rx, tx, self.pg0.local_ip4, "2.2.2.2", diff --git a/test/test_ip4.py b/test/test_ip4.py index f67c3b9c..df93533d 100644 --- a/test/test_ip4.py +++ b/test/test_ip4.py @@ -108,8 +108,7 @@ class TestIPv4(VppTestCase): pkts = [] for i in range(0, 257): dst_if = self.flows[src_if][i % 2] - 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(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) / @@ -254,15 +253,13 @@ class TestIPv4FibCrud(VppTestCase): for _ in range(count): dst_addr = random.choice(dst_ips) - 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(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=src_if.remote_ip4, dst=dst_addr) / UDP(sport=1234, dport=1234) / Raw(payload)) info.data = p.copy() - size = random.choice(self.pg_if_packet_sizes) self.extend_packet(p, random.choice(self.pg_if_packet_sizes)) pkts.append(p) @@ -270,7 +267,8 @@ class TestIPv4FibCrud(VppTestCase): def _find_ip_match(self, find_in, pkt): for p in find_in: - if self.payload_to_info(str(p[Raw])) == self.payload_to_info(str(pkt[Raw])): + if self.payload_to_info(str(p[Raw])) == \ + self.payload_to_info(str(pkt[Raw])): if p[IP].src != pkt[IP].src: break if p[IP].dst != pkt[IP].dst: @@ -357,7 +355,7 @@ class TestIPv4FibCrud(VppTestCase): def setUp(self): super(TestIPv4FibCrud, self).setUp() - self.packet_infos = {} + self.reset_packet_infos() def test_1_add_routes(self): """ Add 1k routes @@ -381,10 +379,9 @@ class TestIPv4FibCrud(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - pkts = self.pg0.get_capture() + pkts = self.pg0.get_capture(len(self.stream_1) + len(self.stream_2)) self.verify_capture(self.pg0, pkts, self.stream_1 + self.stream_2) - def test_2_del_routes(self): """ Delete 100 routes @@ -411,7 +408,7 @@ class TestIPv4FibCrud(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - pkts = self.pg0.get_capture() + pkts = self.pg0.get_capture(len(self.stream_1) + len(self.stream_2)) self.verify_capture(self.pg0, pkts, self.stream_1 + self.stream_2) def test_3_add_new_routes(self): @@ -446,7 +443,7 @@ class TestIPv4FibCrud(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - pkts = self.pg0.get_capture() + pkts = self.pg0.get_capture(len(self.stream_1) + len(self.stream_2)) self.verify_capture(self.pg0, pkts, self.stream_1 + self.stream_2) def test_4_del_routes(self): diff --git a/test/test_ip4_irb.py b/test/test_ip4_irb.py index cf2bb150..bbec7ca7 100644 --- a/test/test_ip4_irb.py +++ b/test/test_ip4_irb.py @@ -103,8 +103,7 @@ class TestIpIrb(VppTestCase): pkts = [] for i in range(0, 257): remote_dst_host = choice(dst_ip_if.remote_hosts) - info = self.create_packet_info( - src_ip_if.sw_if_index, dst_ip_if.sw_if_index) + info = self.create_packet_info(src_ip_if, dst_ip_if) payload = self.info_to_payload(info) p = (Ether(dst=src_ip_if.local_mac, src=src_ip_if.remote_mac) / IP(src=src_ip_if.remote_ip4, @@ -121,14 +120,13 @@ class TestIpIrb(VppTestCase): packet_sizes): pkts = [] for i in range(0, 257): - info = self.create_packet_info( - src_ip_if.sw_if_index, dst_ip_if.sw_if_index) + info = self.create_packet_info(src_ip_if, dst_ip_if) payload = self.info_to_payload(info) host = choice(src_l2_if.remote_hosts) p = (Ether(src=host.mac, - dst = src_ip_if.local_mac) / + dst=src_ip_if.local_mac) / IP(src=host.ip4, dst=dst_ip_if.remote_ip4) / UDP(sport=1234, dport=1234) / @@ -152,7 +150,6 @@ class TestIpIrb(VppTestCase): ip = packet[IP] udp = packet[IP][UDP] payload_info = self.payload_to_info(str(packet[IP][UDP][Raw])) - packet_index = payload_info.index self.assertEqual(payload_info.dst, dst_ip_sw_if_index) @@ -231,8 +228,10 @@ class TestIpIrb(VppTestCase): self.pg_enable_capture(self.pg_interfaces) self.pg_start() - rcvd1 = self.pg0.get_capture() - rcvd2 = self.pg1.get_capture() + packet_count = self.get_packet_count_for_if_idx(self.loop0.sw_if_index) + + rcvd1 = self.pg0.get_capture(packet_count) + rcvd2 = self.pg1.get_capture(packet_count) self.verify_capture(self.loop0, self.pg2, rcvd1) self.verify_capture(self.loop0, self.pg2, rcvd2) @@ -259,8 +258,6 @@ class TestIpIrb(VppTestCase): rcvd = self.pg2.get_capture() self.verify_capture_l2_to_ip(self.pg2, self.loop0, rcvd) - self.assertEqual(len(stream1) + len(stream2), len(rcvd.res)) - if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/test_ip6.py b/test/test_ip6.py index 06b15f94..e8b12f68 100644 --- a/test/test_ip6.py +++ b/test/test_ip6.py @@ -116,8 +116,7 @@ class TestIPv6(VppTestCase): pkts = [] for i in range(0, 257): dst_if = self.flows[src_if][i % 2] - 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(dst=src_if.local_mac, src=src_if.remote_mac) / IPv6(src=src_if.remote_ip6, dst=dst_if.remote_ip6) / diff --git a/test/test_l2_fib.py b/test/test_l2_fib.py index 4855a3ea..d4ef3f4a 100644 --- a/test/test_l2_fib.py +++ b/test/test_l2_fib.py @@ -130,11 +130,8 @@ class TestL2fib(VppTestCase): raise def setUp(self): - """ - Clear trace and packet infos before running each test. - """ super(TestL2fib, self).setUp() - self.packet_infos = {} + self.reset_packet_infos() def tearDown(self): """ @@ -236,8 +233,7 @@ class TestL2fib(VppTestCase): 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.sw_if_index, dst_if.sw_if_index) + 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) / @@ -314,7 +310,7 @@ class TestL2fib(VppTestCase): # Test # Create incoming packet streams for packet-generator interfaces for # deleted MAC addresses - self.packet_infos = {} + self.reset_packet_infos() for i in self.pg_interfaces: pkts = self.create_stream(i, self.pg_if_packet_sizes, deleted=True) i.add_stream(pkts) diff --git a/test/test_l2bd.py b/test/test_l2bd.py index 50720e64..30708a46 100644 --- a/test/test_l2bd.py +++ b/test/test_l2bd.py @@ -99,7 +99,7 @@ class TestL2bd(VppTestCase): Clear trace and packet infos before running each test. """ super(TestL2bd, self).setUp() - self.packet_infos = {} + self.reset_packet_infos() def tearDown(self): """ @@ -158,8 +158,7 @@ class TestL2bd(VppTestCase): dst_if = self.flows[src_if][i % 2] dst_host = random.choice(self.hosts_by_pg_idx[dst_if.sw_if_index]) src_host = random.choice(self.hosts_by_pg_idx[src_if.sw_if_index]) - pkt_info = self.create_packet_info( - src_if.sw_if_index, dst_if.sw_if_index) + 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) / diff --git a/test/test_l2bd_multi_instance.py b/test/test_l2bd_multi_instance.py index 1272d765..a1226222 100644 --- a/test/test_l2bd_multi_instance.py +++ b/test/test_l2bd_multi_instance.py @@ -138,7 +138,6 @@ class TestL2bdMultiInst(VppTestCase): Clear trace and packet infos before running each test. """ super(TestL2bdMultiInst, self).setUp() - self.packet_infos = {} def tearDown(self): """ @@ -243,8 +242,7 @@ class TestL2bdMultiInst(VppTestCase): 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.sw_if_index, dst_if.sw_if_index) + 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) / @@ -393,17 +391,8 @@ class TestL2bdMultiInst(VppTestCase): for pg_if in self.pg_interfaces: capture = pg_if.get_capture() if pg_if in self.pg_in_bd: - if len(capture) == 0: - raise RuntimeError("Interface %s is in BD but the capture " - "is empty!" % pg_if.name) self.verify_capture(pg_if, capture) - elif pg_if in self.pg_not_in_bd: - try: - self.assertEqual(len(capture), 0) - except AssertionError: - raise RuntimeError("Interface %s is not in BD but " - "the capture is not empty!" % pg_if.name) - else: + elif pg_if not in self.pg_not_in_bd: self.logger.error("Unknown interface: %s" % pg_if.name) def test_l2bd_inst_01(self): diff --git a/test/test_l2xc.py b/test/test_l2xc.py index 37893042..2ec4af92 100644 --- a/test/test_l2xc.py +++ b/test/test_l2xc.py @@ -77,11 +77,8 @@ class TestL2xc(VppTestCase): raise def setUp(self): - """ - Clear trace and packet infos before running each test. - """ super(TestL2xc, self).setUp() - self.packet_infos = {} + self.reset_packet_infos() def tearDown(self): """ @@ -123,8 +120,7 @@ class TestL2xc(VppTestCase): dst_if = self.flows[src_if][0] dst_host = random.choice(self.hosts_by_pg_idx[dst_if.sw_if_index]) src_host = random.choice(self.hosts_by_pg_idx[src_if.sw_if_index]) - pkt_info = self.create_packet_info( - src_if.sw_if_index, dst_if.sw_if_index) + 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) / diff --git a/test/test_l2xc_multi_instance.py b/test/test_l2xc_multi_instance.py index 2e55674e..6c28cebb 100644 --- a/test/test_l2xc_multi_instance.py +++ b/test/test_l2xc_multi_instance.py @@ -113,7 +113,7 @@ class TestL2xcMultiInst(VppTestCase): Clear trace and packet infos before running each test. """ super(TestL2xcMultiInst, self).setUp() - self.packet_infos = {} + self.reset_packet_infos() def tearDown(self): """ @@ -205,8 +205,7 @@ class TestL2xcMultiInst(VppTestCase): 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.sw_if_index, dst_if.sw_if_index) + 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) / diff --git a/test/test_lb.py b/test/test_lb.py index 9b5baaea..e5802d99 100644 --- a/test/test_lb.py +++ b/test/test_lb.py @@ -69,10 +69,10 @@ class TestLB(VppTestCase): UDP(sport=10000 + id, dport=20000 + id)) def generatePackets(self, src_if, isv4): - self.packet_infos = {} + self.reset_packet_infos() pkts = [] for pktid in self.packets: - info = self.create_packet_info(src_if.sw_if_index, pktid) + info = self.create_packet_info(src_if, self.pg1) payload = self.info_to_payload(info) ip = self.getIPv4Flow(pktid) if isv4 else self.getIPv6Flow(pktid) packet = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / @@ -90,14 +90,13 @@ class TestLB(VppTestCase): self.assertEqual(gre.version, 0) inner = IPver(str(gre.payload)) payload_info = self.payload_to_info(str(inner[Raw])) - self.info = self.get_next_packet_info_for_interface2( - self.pg0.sw_if_index, payload_info.dst, self.info) + self.info = self.packet_infos[payload_info.index] + self.assertEqual(payload_info.src, self.pg0.sw_if_index) self.assertEqual(str(inner), str(self.info.data[IPver])) def checkCapture(self, gre4, isv4): self.pg0.assert_nothing_captured() - out = self.pg1.get_capture() - self.assertEqual(len(out), len(self.packets)) + out = self.pg1.get_capture(len(self.packets)) load = [0] * len(self.ass) self.info = None diff --git a/test/test_mpls.py b/test/test_mpls.py index 6d5eeb2b..806e69dc 100644 --- a/test/test_mpls.py +++ b/test/test_mpls.py @@ -12,6 +12,7 @@ from scapy.layers.inet import IP, UDP, ICMP from scapy.layers.inet6 import IPv6 from scapy.contrib.mpls import MPLS + class TestMPLS(VppTestCase): """ MPLS Test Case """ @@ -44,11 +45,17 @@ class TestMPLS(VppTestCase): super(TestMPLS, self).tearDown() # the default of 64 matches the IP packet TTL default - def create_stream_labelled_ip4(self, src_if, mpls_labels, mpls_ttl=255, ping=0, ip_itf=None): + def create_stream_labelled_ip4( + self, + src_if, + mpls_labels, + mpls_ttl=255, + ping=0, + ip_itf=None): + self.reset_packet_infos() pkts = [] for i in range(0, 257): - info = self.create_packet_info(src_if.sw_if_index, - src_if.sw_if_index) + info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = Ether(dst=src_if.local_mac, src=src_if.remote_mac) @@ -71,10 +78,10 @@ class TestMPLS(VppTestCase): return pkts def create_stream_ip4(self, src_if, dst_ip): + self.reset_packet_infos() pkts = [] for i in range(0, 257): - info = self.create_packet_info(src_if.sw_if_index, - src_if.sw_if_index) + info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=src_if.remote_ip4, dst=dst_ip) / @@ -85,10 +92,10 @@ class TestMPLS(VppTestCase): return pkts def create_stream_labelled_ip6(self, src_if, mpls_label, mpls_ttl): + self.reset_packet_infos() pkts = [] for i in range(0, 257): - info = self.create_packet_info(src_if.sw_if_index, - src_if.sw_if_index) + info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / MPLS(label=mpls_label, ttl=mpls_ttl) / @@ -342,7 +349,6 @@ class TestMPLS(VppTestCase): labels=[44, 45])]) route_34_eos.add_vpp_config() - self.vapi.cli("clear trace") tx = self.create_stream_labelled_ip4(self.pg0, [34]) self.pg0.add_stream(tx) @@ -628,7 +634,6 @@ class TestMPLS(VppTestCase): # a stream with a non-zero MPLS TTL # PG0 is in the default table # - self.vapi.cli("clear trace") tx = self.create_stream_labelled_ip4(self.pg0, [0]) self.pg0.add_stream(tx) @@ -692,9 +697,9 @@ class TestMPLS(VppTestCase): # A de-agg route - next-hop lookup in default table # route_34_eos = MplsRoute(self, 34, 1, - [RoutePath("0.0.0.0", - 0xffffffff, - nh_table_id=0)]) + [RoutePath("0.0.0.0", + 0xffffffff, + nh_table_id=0)]) route_34_eos.add_vpp_config() # @@ -716,9 +721,9 @@ class TestMPLS(VppTestCase): # A de-agg route - next-hop lookup in non-default table # route_35_eos = MplsRoute(self, 35, 1, - [RoutePath("0.0.0.0", - 0xffffffff, - nh_table_id=1)]) + [RoutePath("0.0.0.0", + 0xffffffff, + nh_table_id=1)]) route_35_eos.add_vpp_config() # @@ -727,13 +732,15 @@ class TestMPLS(VppTestCase): # default table and egress unlabelled in the non-default # self.vapi.cli("clear trace") - tx = self.create_stream_labelled_ip4(self.pg0, [35], ping=1, ip_itf=self.pg1) + tx = self.create_stream_labelled_ip4( + self.pg0, [35], ping=1, ip_itf=self.pg1) self.pg0.add_stream(tx) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - rx = self.pg1.get_capture() + packet_count = self.get_packet_count_for_if_idx(self.pg0.sw_if_index) + rx = self.pg1.get_capture(packet_count) self.verify_capture_ip4(self.pg1, rx, tx, ping_resp=1) route_35_eos.remove_vpp_config() diff --git a/test/test_snat.py b/test/test_snat.py index 8985c3e4..ca2e52a7 100644 --- a/test/test_snat.py +++ b/test/test_snat.py @@ -241,7 +241,7 @@ class TestSNAT(VppTestCase): self.pg0.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg1.get_capture() + capture = self.pg1.get_capture(len(pkts)) self.verify_capture_out(capture) # out2in @@ -249,7 +249,7 @@ class TestSNAT(VppTestCase): self.pg1.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg0.get_capture() + capture = self.pg0.get_capture(len(pkts)) self.verify_capture_in(capture, self.pg0) def test_static_in(self): @@ -270,7 +270,7 @@ class TestSNAT(VppTestCase): self.pg0.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg1.get_capture() + capture = self.pg1.get_capture(len(pkts)) self.verify_capture_out(capture, nat_ip, True) # out2in @@ -278,7 +278,7 @@ class TestSNAT(VppTestCase): self.pg1.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg0.get_capture() + capture = self.pg0.get_capture(len(pkts)) self.verify_capture_in(capture, self.pg0) def test_static_out(self): @@ -299,7 +299,7 @@ class TestSNAT(VppTestCase): self.pg1.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg0.get_capture() + capture = self.pg0.get_capture(len(pkts)) self.verify_capture_in(capture, self.pg0) # in2out @@ -307,7 +307,7 @@ class TestSNAT(VppTestCase): self.pg0.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg1.get_capture() + capture = self.pg1.get_capture(len(pkts)) self.verify_capture_out(capture, nat_ip, True) def test_static_with_port_in(self): @@ -333,7 +333,7 @@ class TestSNAT(VppTestCase): self.pg0.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg1.get_capture() + capture = self.pg1.get_capture(len(pkts)) self.verify_capture_out(capture) # out2in @@ -341,7 +341,7 @@ class TestSNAT(VppTestCase): self.pg1.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg0.get_capture() + capture = self.pg0.get_capture(len(pkts)) self.verify_capture_in(capture, self.pg0) def test_static_with_port_out(self): @@ -367,7 +367,7 @@ class TestSNAT(VppTestCase): self.pg1.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg0.get_capture() + capture = self.pg0.get_capture(len(pkts)) self.verify_capture_in(capture, self.pg0) # in2out @@ -375,7 +375,7 @@ class TestSNAT(VppTestCase): self.pg0.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg1.get_capture() + capture = self.pg1.get_capture(len(pkts)) self.verify_capture_out(capture) def test_static_vrf_aware(self): @@ -401,7 +401,7 @@ class TestSNAT(VppTestCase): self.pg4.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg3.get_capture() + capture = self.pg3.get_capture(len(pkts)) self.verify_capture_out(capture, nat_ip1, True) # inside interface VRF don't match SNAT static mapping VRF (packets @@ -427,7 +427,7 @@ class TestSNAT(VppTestCase): self.pg0.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg3.get_capture() + capture = self.pg3.get_capture(len(pkts)) self.verify_capture_out(capture) # out2in 1st interface @@ -435,7 +435,7 @@ class TestSNAT(VppTestCase): self.pg3.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg0.get_capture() + capture = self.pg0.get_capture(len(pkts)) self.verify_capture_in(capture, self.pg0) # in2out 2nd interface @@ -443,7 +443,7 @@ class TestSNAT(VppTestCase): self.pg1.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg3.get_capture() + capture = self.pg3.get_capture(len(pkts)) self.verify_capture_out(capture) # out2in 2nd interface @@ -451,7 +451,7 @@ class TestSNAT(VppTestCase): self.pg3.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg1.get_capture() + capture = self.pg1.get_capture(len(pkts)) self.verify_capture_in(capture, self.pg1) # in2out 3rd interface @@ -459,7 +459,7 @@ class TestSNAT(VppTestCase): self.pg2.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg3.get_capture() + capture = self.pg3.get_capture(len(pkts)) self.verify_capture_out(capture) # out2in 3rd interface @@ -467,7 +467,7 @@ class TestSNAT(VppTestCase): self.pg3.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg2.get_capture() + capture = self.pg2.get_capture(len(pkts)) self.verify_capture_in(capture, self.pg2) def test_inside_overlapping_interfaces(self): @@ -485,7 +485,7 @@ class TestSNAT(VppTestCase): self.pg4.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg3.get_capture() + capture = self.pg3.get_capture(len(pkts)) self.verify_capture_out(capture) # out2in 1st interface @@ -493,7 +493,7 @@ class TestSNAT(VppTestCase): self.pg3.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg4.get_capture() + capture = self.pg4.get_capture(len(pkts)) self.verify_capture_in(capture, self.pg4) # in2out 2nd interface @@ -501,7 +501,7 @@ class TestSNAT(VppTestCase): self.pg5.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg3.get_capture() + capture = self.pg3.get_capture(len(pkts)) self.verify_capture_out(capture) # out2in 2nd interface @@ -509,7 +509,7 @@ class TestSNAT(VppTestCase): self.pg3.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg5.get_capture() + capture = self.pg5.get_capture(len(pkts)) self.verify_capture_in(capture, self.pg5) # in2out 3rd interface @@ -517,7 +517,7 @@ class TestSNAT(VppTestCase): self.pg6.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg3.get_capture() + capture = self.pg3.get_capture(len(pkts)) self.verify_capture_out(capture) # out2in 3rd interface @@ -525,7 +525,7 @@ class TestSNAT(VppTestCase): self.pg3.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg6.get_capture() + capture = self.pg6.get_capture(len(pkts)) self.verify_capture_in(capture, self.pg6) def test_hairpinning(self): @@ -553,8 +553,7 @@ class TestSNAT(VppTestCase): self.pg0.add_stream(p) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg0.get_capture() - self.assertEqual(1, len(capture)) + capture = self.pg0.get_capture(1) p = capture[0] try: ip = p[IP] @@ -575,8 +574,7 @@ class TestSNAT(VppTestCase): self.pg0.add_stream(p) self.pg_enable_capture(self.pg_interfaces) self.pg_start() - capture = self.pg0.get_capture() - self.assertEqual(1, len(capture)) + capture = self.pg0.get_capture(1) p = capture[0] try: ip = p[IP] @@ -613,8 +611,7 @@ class TestSNAT(VppTestCase): self.pg_start() # verify number of translated packet - capture = self.pg1.get_capture() - self.assertEqual(pkts_num, len(capture)) + self.pg1.get_capture(pkts_num) def tearDown(self): super(TestSNAT, self).tearDown() diff --git a/test/test_span.py b/test/test_span.py index e42fbd77..41507092 100644 --- a/test/test_span.py +++ b/test/test_span.py @@ -85,8 +85,7 @@ class TestSpan(VppTestCase): pkts = [] for i in range(0, TestSpan.pkts_per_burst): dst_if = self.flows[src_if][0] - pkt_info = self.create_packet_info( - src_if.sw_if_index, dst_if.sw_if_index) + pkt_info = self.create_packet_info(src_if, dst_if) payload = self.info_to_payload(pkt_info) p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) / @@ -184,9 +183,11 @@ class TestSpan(VppTestCase): # Verify packets outgoing packet streams on mirrored interface (pg2) self.logger.info("Verifying capture on interfaces %s and %s" % (self.pg1.name, self.pg2.name)) - self.verify_capture(self.pg1, - self.pg1.get_capture(), - self.pg2.get_capture()) + pg2_expected = self.get_packet_count_for_if_idx(self.pg1.sw_if_index) + self.verify_capture( + self.pg1, + self.pg1.get_capture(), + self.pg2.get_capture(pg2_expected)) if __name__ == '__main__': diff --git a/test/util.py b/test/util.py index 0ac23760..6658febf 100644 --- a/test/util.py +++ b/test/util.py @@ -24,17 +24,14 @@ def ppc(headline, capture, limit=10): """ if not capture: return headline - result = headline + "\n" - count = 1 - for p in capture: - result.append(ppp("Packet #%s:" % count, p)) - count += 1 - if count >= limit: - break + tail = "" if limit < len(capture): - result.append( - "Capture contains %s packets in total, of which %s were printed" % - (len(capture), limit)) + tail = "\nPrint limit reached, %s out of %s packets printed" % ( + len(capture), limit) + limit = len(capture) + body = "".join([ppp("Packet #%s:" % count, p) + for count, p in zip(range(0, limit), capture)]) + return "%s\n%s%s" % (headline, body, tail) class NumericConstant(object): diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py index 634d7d3e..f4305275 100644 --- a/test/vpp_pg_interface.py +++ b/test/vpp_pg_interface.py @@ -1,5 +1,6 @@ import os import time +from traceback import format_exc from scapy.utils import wrpcap, rdpcap, PcapReader from vpp_interface import VppInterface @@ -130,30 +131,20 @@ class VppPGInterface(VppInterface): # FIXME this should be an API, but no such exists atm self.test.vapi.cli(self.input_cli) - def get_capture(self, remark=None, filter_fn=is_ipv6_misc): - """ - Get captured packets - - :param remark: remark printed into debug logs - :param filter_fn: filter applied to each packet, packets for which - the filter returns True are removed from capture - :returns: iterable packets - """ + def _get_capture(self, timeout, filter_out_fn=is_ipv6_misc): + """ Helper method to get capture and filter it """ try: - self.wait_for_capture_file() + if not self.wait_for_capture_file(timeout): + return None output = rdpcap(self.out_path) self.test.logger.debug("Capture has %s packets" % len(output.res)) - except IOError: # TODO - self.test.logger.debug("File %s does not exist, probably because no" - " packets arrived" % self.out_path) - if remark: - raise Exception("No packets captured on %s(%s)" % - (self.name, remark)) - else: - raise Exception("No packets captured on %s" % self.name) + except: + self.test.logger.debug("Exception in scapy.rdpcap(%s): %s" % + (self.out_path, format_exc())) + return None before = len(output.res) - if filter_fn: - output.res = [p for p in output.res if not filter_fn(p)] + if filter_out_fn: + output.res = [p for p in output.res if not filter_out_fn(p)] removed = len(output.res) - before if removed: self.test.logger.debug( @@ -161,21 +152,75 @@ class VppPGInterface(VppInterface): (removed, len(output.res))) return output - def assert_nothing_captured(self, remark=None): + def get_capture(self, expected_count=None, remark=None, timeout=1, + filter_out_fn=is_ipv6_misc): + """ Get captured packets + + :param expected_count: expected number of packets to capture, if None, + then self.test.packet_count_for_dst_pg_idx is + used to lookup the expected count + :param remark: remark printed into debug logs + :param timeout: how long to wait for packets + :param filter_out_fn: filter applied to each packet, packets for which + the filter returns True are removed from capture + :returns: iterable packets + """ + remaining_time = timeout + capture = None + name = self.name if remark is None else "%s (%s)" % (self.name, remark) + based_on = "based on provided argument" + if expected_count is None: + expected_count = \ + self.test.get_packet_count_for_if_idx(self.sw_if_index) + based_on = "based on stored packet_infos" + self.test.logger.debug("Expecting to capture %s(%s) packets on %s" % ( + expected_count, based_on, name)) + if expected_count == 0: + raise Exception( + "Internal error, expected packet count for %s is 0!" % name) + while remaining_time > 0: + before = time.time() + capture = self._get_capture(remaining_time, filter_out_fn) + elapsed_time = time.time() - before + if capture: + if len(capture.res) == expected_count: + # bingo, got the packets we expected + return capture + remaining_time -= elapsed_time + if capture: + raise Exception("Captured packets mismatch, captured %s packets, " + "expected %s packets on %s" % + (len(capture.res), expected_count, name)) + else: + raise Exception("No packets captured on %s" % name) + + def assert_nothing_captured(self, remark=None, filter_out_fn=is_ipv6_misc): + """ Assert that nothing unfiltered was captured on interface + + :param remark: remark printed into debug logs + :param filter_out_fn: filter applied to each packet, packets for which + the filter returns True are removed from capture + """ if os.path.isfile(self.out_path): try: - capture = self.get_capture(remark=remark) + capture = self.get_capture( + 0, remark=remark, filter_out_fn=filter_out_fn) + if capture: + if len(capture.res) == 0: + # junk filtered out, we're good + return self.test.logger.error( ppc("Unexpected packets captured:", capture)) except: pass if remark: raise AssertionError( - "Capture file present for interface %s(%s)" % + "Non-empty capture file present for interface %s(%s)" % (self.name, remark)) else: - raise AssertionError("Capture file present for interface %s" % - self.name) + raise AssertionError( + "Non-empty capture file present for interface %s" % + self.name) def wait_for_capture_file(self, timeout=1): """ @@ -183,15 +228,17 @@ class VppPGInterface(VppInterface): :param timeout: How long to wait for the packet (default 1s) - :raises Exception: if the capture file does not appear within timeout + :returns: True/False if the file is present or appears within timeout """ limit = time.time() + timeout if not os.path.isfile(self.out_path): - self.test.logger.debug( - "Waiting for capture file to appear, timeout is %ss", timeout) + self.test.logger.debug("Waiting for capture file %s to appear, " + "timeout is %ss" % (self.out_path, timeout)) else: - self.test.logger.debug("Capture file already exists") - return + self.test.logger.debug( + "Capture file %s already exists" % + self.out_path) + return True while time.time() < limit: if os.path.isfile(self.out_path): break @@ -201,9 +248,10 @@ class VppPGInterface(VppInterface): (time.time() - (limit - timeout))) else: self.test.logger.debug("Timeout - capture file still nowhere") - raise Exception("Capture file did not appear within timeout") + return False + return True - def wait_for_packet(self, timeout): + def wait_for_packet(self, timeout, filter_out_fn=is_ipv6_misc): """ Wait for next packet captured with a timeout @@ -212,18 +260,34 @@ class VppPGInterface(VppInterface): :returns: Captured packet if no packet arrived within timeout :raises Exception: if no packet arrives within timeout """ - limit = time.time() + timeout + deadline = time.time() + timeout if self._pcap_reader is None: - self.wait_for_capture_file(timeout) - self._pcap_reader = PcapReader(self.out_path) + if not self.wait_for_capture_file(timeout): + raise Exception("Capture file %s did not appear within " + "timeout" % self.out_path) + while time.time() < deadline: + try: + self._pcap_reader = PcapReader(self.out_path) + break + except: + self.test.logger.debug("Exception in scapy.PcapReader(%s): " + "%s" % (self.out_path, format_exc())) + if not self._pcap_reader: + raise Exception("Capture file %s did not appear within " + "timeout" % self.out_path) self.test.logger.debug("Waiting for packet") - while time.time() < limit: + while time.time() < deadline: p = self._pcap_reader.recv() if p is not None: - self.test.logger.debug("Packet received after %fs", - (time.time() - (limit - timeout))) - return p + if filter_out_fn is not None and filter_out_fn(p): + self.test.logger.debug( + "Packet received after %ss was filtered out" % + (time.time() - (deadline - timeout))) + else: + self.test.logger.debug("Packet received after %fs" % + (time.time() - (deadline - timeout))) + return p time.sleep(0) # yield self.test.logger.debug("Timeout - no packets received") raise Exception("Packet didn't arrive within timeout") @@ -258,12 +322,12 @@ class VppPGInterface(VppInterface): self.test.pg_start() self.test.logger.info(self.test.vapi.cli("show trace")) try: - arp_reply = pg_interface.get_capture(filter_fn=None) + captured_packet = pg_interface.wait_for_packet(1) except: self.test.logger.info("No ARP received on port %s" % pg_interface.name) return - arp_reply = arp_reply[0] + arp_reply = captured_packet.copy() # keep original for exception # Make Dot1AD packet content recognizable to scapy if arp_reply.type == 0x88a8: arp_reply.type = 0x8100 @@ -274,19 +338,19 @@ class VppPGInterface(VppInterface): (self.name, arp_reply[ARP].hwsrc)) self._local_mac = arp_reply[ARP].hwsrc else: - self.test.logger.info( - "No ARP received on port %s" % - pg_interface.name) + self.test.logger.info("No ARP received on port %s" % + pg_interface.name) except: self.test.logger.error( - ppp("Unexpected response to ARP request:", arp_reply)) + ppp("Unexpected response to ARP request:", captured_packet)) raise - def resolve_ndp(self, pg_interface=None): + def resolve_ndp(self, pg_interface=None, timeout=1): """Resolve NDP using provided packet-generator interface :param pg_interface: interface used to resolve, if None then this interface is used + :param timeout: how long to wait for response before giving up """ if pg_interface is None: @@ -297,17 +361,19 @@ class VppPGInterface(VppInterface): pg_interface.add_stream(ndp_req) pg_interface.enable_capture() self.test.pg_start() - self.test.logger.info(self.test.vapi.cli("show trace")) - replies = pg_interface.get_capture(filter_fn=None) - if replies is None or len(replies) == 0: - self.test.logger.info( - "No NDP received on port %s" % - pg_interface.name) - return + now = time.time() + deadline = now + timeout # Enabling IPv6 on an interface can generate more than the # ND reply we are looking for (namely MLD). So loop through # the replies to look for want we want. - for ndp_reply in replies: + while now < deadline: + try: + captured_packet = pg_interface.wait_for_packet( + deadline - now, filter_out_fn=None) + except: + self.test.logger.error("Timeout while waiting for NDP response") + raise + ndp_reply = captured_packet.copy() # keep original for exception # Make Dot1AD packet content recognizable to scapy if ndp_reply.type == 0x88a8: ndp_reply.type = 0x8100 @@ -318,9 +384,13 @@ class VppPGInterface(VppInterface): self.test.logger.info("VPP %s MAC address is %s " % (self.name, opt.lladdr)) self._local_mac = opt.lladdr + self.test.logger.debug(self.test.vapi.cli("show trace")) + # we now have the MAC we've been after + return except: self.test.logger.info( - ppp("Unexpected response to NDP request:", ndp_reply)) - # if no packets above provided the local MAC, then this failed. - if not hasattr(self, '_local_mac'): - raise + ppp("Unexpected response to NDP request:", captured_packet)) + now = time.time() + + self.test.logger.debug(self.test.vapi.cli("show trace")) + raise Exception("Timeout while waiting for NDP response") -- cgit 1.2.3-korg From da505f608e0919c45089dc80f9e3e16330a6551a Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Wed, 4 Jan 2017 12:58:53 +0100 Subject: make test: improve documentation and PEP8 compliance Change-Id: Ib4f0353aab6112fcc3c3d8f0bcbed5bc4b567b9b Signed-off-by: Klement Sekera --- test/Makefile | 4 +- test/doc/index.rst | 285 ++++++++++++++++++++++++++++++++++-- test/framework.py | 23 +-- test/hook.py | 4 +- test/template_bd.py | 4 +- test/test_classifier.py | 14 +- test/test_flowperpkt.py | 4 +- test/test_ip4.py | 17 ++- test/test_ip4_vrf_multi_instance.py | 11 +- test/test_ip6.py | 57 ++++---- test/test_l2_fib.py | 11 +- test/test_l2bd_multi_instance.py | 13 +- test/test_l2xc_multi_instance.py | 20 ++- test/test_snat.py | 20 ++- test/test_span.py | 11 +- test/vpp_interface.py | 14 +- test/vpp_ip_route.py | 52 ++++--- test/vpp_papi_provider.py | 12 +- test/vpp_pg_interface.py | 48 +++--- 19 files changed, 468 insertions(+), 156 deletions(-) (limited to 'test/test_span.py') diff --git a/test/Makefile b/test/Makefile index 204afd38..543fe913 100644 --- a/test/Makefile +++ b/test/Makefile @@ -17,7 +17,7 @@ PAPI_INSTALL_DONE=$(VPP_PYTHON_PREFIX)/papi-install.done PAPI_INSTALL_FLAGS=$(PIP_INSTALL_DONE) $(PIP_PATCH_DONE) $(PAPI_INSTALL_DONE) $(PIP_INSTALL_DONE): - @virtualenv $(PYTHON_VENV_PATH) + @virtualenv $(PYTHON_VENV_PATH) -p python2.7 @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && pip install $(PYTHON_DEPENDS)" @touch $@ @@ -54,7 +54,7 @@ wipe: reset @rm -f $(PAPI_INSTALL_FLAGS) doc: verify-python-path - @virtualenv $(PYTHON_VENV_PATH) + @virtualenv $(PYTHON_VENV_PATH) -p python2.7 @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && pip install $(PYTHON_DEPENDS) sphinx" @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && make -C doc WS_ROOT=$(WS_ROOT) BR=$(BR) NO_VPP_PAPI=1 html" diff --git a/test/doc/index.rst b/test/doc/index.rst index 8cbe961f..f51d5058 100644 --- a/test/doc/index.rst +++ b/test/doc/index.rst @@ -4,6 +4,7 @@ .. _SkipTest: https://docs.python.org/2/library/unittest.html#unittest.SkipTest .. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ .. _scapy: http://www.secdev.org/projects/scapy/ +.. _logging: https://docs.python.org/2/library/logging.html .. |vtf| replace:: VPP Test Framework @@ -39,7 +40,7 @@ Function flow when running a test case is: 3. *test_*: This is the guts of the test case. It should execute the test scenario and use the various assert functions from the unittest framework to check - necessary. + necessary. Multiple test_ methods can exist in a test case. 4. `tearDown `: The tearDown function is called after each test function with the purpose of doing partial cleanup. @@ -47,16 +48,56 @@ Function flow when running a test case is: Method called once after running all of the test functions to perform the final cleanup. +Logging +####### + +Each test case has a logger automatically created for it, stored in +'logger' property, based on logging_. Use the logger's standard methods +debug(), info(), error(), ... to emit log messages to the logger. + +All the log messages go always into a log file in temporary directory +(see below). + +To control the messages printed to console, specify the V= parameter. + +.. code-block:: shell + + make test # minimum verbosity + make test V=1 # moderate verbosity + make test V=2 # maximum verbosity + Test temporary directory and VPP life cycle ########################################### Test separation is achieved by separating the test files and vpp instances. Each test creates a temporary directory and it's name is used to create a shared memory prefix which is used to run a VPP instance. +The temporary directory name contains the testcase class name for easy +reference, so for testcase named 'TestVxlan' the directory could be named +e.g. vpp-unittest-TestVxlan-UNUP3j. This way, there is no conflict between any other VPP instances running on the box and the test VPP. Any temporary files created by the test case are stored in this temporary test directory. +The test temporary directory holds the following interesting files: + +* log.txt - this contains the logger output on max verbosity +* pg*_in.pcap - last injected packet stream into VPP, named after the interface, + so for pg0, the file will be named pg0_in.pcap +* pg*_out.pcap - last capture file created by VPP for interface, similarly, + named after the interface, so for e.g. pg1, the file will be named + pg1_out.pcap +* history files - whenever the capture is restarted or a new stream is added, + the existing files are rotated and renamed, soo all the pcap files + are always saved for later debugging if needed +* core - if vpp dumps a core, it'll be stored in the temporary directory +* vpp_stdout.txt - file containing output which vpp printed to stdout +* vpp_stderr.txt - file containing output which vpp printed to stderr + +*NOTE*: existing temporary directories named vpp-unittest-* are automatically +removed when invoking 'make test*' or 'make retest*' to keep the temporary +directory clean. + Virtual environment ################### @@ -82,6 +123,37 @@ thus: So e.g. `remote_mac ` address is the MAC address assigned to the virtual host connected to the VPP. +Automatically generated addresses +################################# + +To send packets, one needs to typically provide some addresses, otherwise +the packets will be dropped. The interface objects in |vtf| automatically +provide addresses based on (typically) their indexes, which ensures +there are no conflicts and eases debugging by making the addressing scheme +consistent. + +The developer of a test case typically doesn't need to work with the actual +numbers, rather using the properties of the objects. The addresses typically +come in two flavors: '
' and '
n' - note the 'n' suffix. +The former address is a Python string, while the latter is translated using +socket.inet_pton to raw format in network byte order - this format is suitable +for passing as an argument to VPP APIs. + +e.g. for the IPv4 address assigned to the VPP interface: + +* local_ip4 - Local IPv4 address on VPP interface (string) +* local_ip4n - Local IPv4 address - raw, suitable as API parameter. + +These addresses need to be configured in VPP to be usable using e.g. +`config_ip4` API. Please see the documentation to `VppInterface` for more +details. + +By default, there is one remote address of each kind created for L3: +remote_ip4 and remote_ip6. If the test needs more addresses, because it's +simulating more remote hosts, they can be generated using +`generate_remote_hosts` API and the entries for them inserted into the ARP +table using `configure_ipv4_neighbors` API. + Packet flow in the |vtf| ######################## @@ -93,6 +165,10 @@ using packet-generator interfaces, represented by the `VppPGInterface` class. Packets are written into a temporary .pcap file, which is then read by the VPP and the packets are injected into the VPP world. +To add a list of packets to an interface, call the `add_stream` method on that +interface. Once everything is prepared, call `pg_start` method to start +the packet generator on the VPP side. + VPP -> test framework ~~~~~~~~~~~~~~~~~~~~~ @@ -100,6 +176,72 @@ Similarly, VPP doesn't send any packets to |vtf| directly. Instead, packet capture feature is used to capture and write traffic to a temporary .pcap file, which is then read and analyzed by the |vtf|. +The following APIs are available to the test case for reading pcap files. + +* `get_capture`: this API is suitable for bulk & batch style of test, where + a list of packets is prepared & sent, then the received packets are read + and verified. The API needs the number of packets which are expected to + be captured (ignoring filtered packets - see below) to know when the pcap + file is completely written by the VPP. If using packet infos for verifying + packets, then the counts of the packet infos can be automatically used + by `get_capture` to get the proper count (in this case the default value + None can be supplied as expected_count or ommitted altogether). +* `wait_for_packet`: this API is suitable for interactive style of test, + e.g. when doing session management, three-way handsakes, etc. This API waits + for and returns a single packet, keeping the capture file in place + and remembering context. Repeated invocations return following packets + (or raise Exception if timeout is reached) from the same capture file + (= packets arriving on the same interface). + +*NOTE*: it is not recommended to mix these APIs unless you understand how they +work internally. None of these APIs rotate the pcap capture file, so calling +e.g. `get_capture` after `wait_for_packet` will return already read packets. +It is safe to switch from one API to another after calling `enable_capture` +as that API rotates the capture file. + +Automatic filtering of packets: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Both APIs (`get_capture` and `wait_for_packet`) by default filter the packet +capture, removing known uninteresting packets from it - these are IPv6 Router +Advertisments and IPv6 Router Alerts. These packets are unsolicitated +and from the point of |vtf| are random. If a test wants to receive these +packets, it should specify either None or a custom filtering function +as the value to the 'filter_out_fn' argument. + +Common API flow for sending/receiving packets: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We will describe a simple scenario, where packets are sent from pg0 to pg1 +interface, assuming that the interfaces were created using +`create_pg_interfaces` API. + +1. Create a list of packets for pg0:: + + packet_count = 10 + packets = create_packets(src=self.pg0, dst=self.pg1, + count=packet_count) + +2. Add that list of packets to the source interface:: + + self.pg0.add_stream(packets) + +3. Enable capture on the destination interface:: + + self.pg1.enable_capture() + +4. Start the packet generator:: + + self.pg_start() + +5. Wait for capture file to appear and read it:: + + capture = self.pg1.get_capture(expected_count=packet_count) + +6. Verify packets match sent packets:: + + self.verify_capture(send=packets, captured=capture) + Test framework objects ###################### @@ -113,8 +255,8 @@ common tasks easily in the test cases. * `VppSubInterface`: VPP sub-interface abstract class, containing common functionality for e.g. `VppDot1QSubint` and `VppDot1ADSubint` classes -How VPP API/CLI is called -######################### +How VPP APIs/CLIs are called +############################ Vpp provides python bindings in a python module called vpp-papi, which the test framework installs in the virtual environment. A shim layer represented by @@ -137,19 +279,144 @@ purposes: more readable. E.g. ip_add_del_route API takes ~25 parameters, of which in the common case, only 3 are needed. +Utility methods +############### + +Some interesting utility methods are: + +* `ppp`: 'Pretty Print Packet' - returns a string containing the same output + as Scapy's packet.show() would print +* `ppc`: 'Pretty Print Capture' - returns a string containing printout of + a capture (with configurable limit on the number of packets printed from it) + using `ppp` + +*NOTE*: Do not use Scapy's packet.show() in the tests, because it prints +the output to stdout. All output should go to the logger associated with +the test case. + Example: how to add a new test ############################## -In this example, we will describe how to add a new test case which tests VPP... +In this example, we will describe how to add a new test case which tests +basic IPv4 forwarding. + +1. Add a new file called test_ip4_fwd.py in the test directory, starting + with a few imports:: + + from framework import VppTestCase + from scapy.layers.l2 import Ether + from scapy.packet import Raw + from scapy.layers.inet import IP, UDP + from random import randint + +2. Create a class inherited from the VppTestCase:: + + class IP4FwdTestCase(VppTestCase): + """ IPv4 simple forwarding test case """ + +2. Add a setUpClass function containing the setup needed for our test to run:: + + @classmethod + def setUpClass(self): + super(IP4FwdTestCase, self).setUpClass() + self.create_pg_interfaces(range(2)) # create pg0 and pg1 + for i in self.pg_interfaces: + i.admin_up() # put the interface up + i.config_ip4() # configure IPv4 address on the interface + i.resolve_arp() # resolve ARP, so that we know VPP MAC + +3. Create a helper method to create the packets to send:: + + def create_stream(self, src_if, dst_if, count): + packets = [] + for i in range(count): + # create packet info stored in the test case instance + info = self.create_packet_info(src_if, dst_if) + # convert the info into packet payload + payload = self.info_to_payload(info) + # create the packet itself + p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / + IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) / + UDP(sport=randint(1000, 2000), dport=5678) / + Raw(payload)) + # store a copy of the packet in the packet info + info.data = p.copy() + # append the packet to the list + packets.append(p) + + # return the created packet list + return packets + +4. Create a helper method to verify the capture:: + + def verify_capture(self, src_if, dst_if, capture): + packet_info = None + for packet in capture: + try: + ip = packet[IP] + udp = packet[UDP] + # convert the payload to packet info object + payload_info = self.payload_to_info(str(packet[Raw])) + # make sure the indexes match + self.assert_equal(payload_info.src, src_if.sw_if_index, + "source sw_if_index") + self.assert_equal(payload_info.dst, dst_if.sw_if_index, + "destination sw_if_index") + packet_info = self.get_next_packet_info_for_interface2( + src_if.sw_if_index, + dst_if.sw_if_index, + packet_info) + # make sure we didn't run out of saved packets + self.assertIsNotNone(packet_info) + self.assert_equal(payload_info.index, packet_info.index, + "packet info index") + saved_packet = packet_info.data # fetch the saved packet + # assert the values match + self.assert_equal(ip.src, saved_packet[IP].src, + "IP source address") + # ... more assertions here + self.assert_equal(udp.sport, saved_packet[UDP].sport, + "UDP source port") + except: + self.logger.error(ppp("Unexpected or invalid packet:", + packet)) + raise + remaining_packet = self.get_next_packet_info_for_interface2( + src_if.sw_if_index, + dst_if.sw_if_index, + packet_info) + self.assertIsNone(remaining_packet, + "Interface %s: Packet expected from interface " + "%s didn't arrive" % (dst_if.name, src_if.name)) + +5. Add the test code to test_basic function:: + + def test_basic(self): + count = 10 + # create the packet stream + packets = self.create_stream(self.pg0, self.pg1, count) + # add the stream to the source interface + self.pg0.add_stream(packets) + # enable capture on both interfaces + self.pg0.enable_capture() + self.pg1.enable_capture() + # start the packet generator + self.pg_start() + # get capture - the proper count of packets was saved by + # create_packet_info() based on dst_if parameter + capture = self.pg1.get_capture() + # assert nothing captured on pg0 (always do this last, so that + # some time has already passed since pg_start()) + self.pg0.assert_nothing_captured() + # verify capture + self.verify_capture(self.pg0, self.pg1, capture) + +6. Run the test by issuing 'make test'. -1. Add a new file called... -2. Add a setUpClass function containing... -3. Add the test code in test... -4. Run the test... |vtf| module documentation ########################## - + .. toctree:: :maxdepth: 2 :glob: diff --git a/test/framework.py b/test/framework.py index 896a1e0d..b2c6b9e4 100644 --- a/test/framework.py +++ b/test/framework.py @@ -117,7 +117,8 @@ class VppTestCase(unittest.TestCase): debug_cli = "" if cls.step or cls.debug_gdb or cls.debug_gdbserver: debug_cli = "cli-listen localhost:5002" - cls.vpp_cmdline = [cls.vpp_bin, "unix", "{", "nodaemon", debug_cli, "}", + cls.vpp_cmdline = [cls.vpp_bin, + "unix", "{", "nodaemon", debug_cli, "}", "api-segment", "{", "prefix", cls.shm_prefix, "}"] if cls.plugin_path is not None: cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path]) @@ -246,8 +247,8 @@ class VppTestCase(unittest.TestCase): print(double_line_delim) print("VPP or GDB server is still running") print(single_line_delim) - raw_input("When done debugging, press ENTER to kill the process" - " and finish running the testcase...") + raw_input("When done debugging, press ENTER to kill the " + "process and finish running the testcase...") if hasattr(cls, 'vpp'): if hasattr(cls, 'vapi'): @@ -563,10 +564,10 @@ class VppTestResult(unittest.TestResult): def __init__(self, stream, descriptions, verbosity): """ - :param stream File descriptor to store where to report test results. Set - to the standard error stream by default. - :param descriptions Boolean variable to store information if to use test - case descriptions. + :param stream File descriptor to store where to report test results. + Set to the standard error stream by default. + :param descriptions Boolean variable to store information if to use + test case descriptions. :param verbosity Integer variable to store required verbosity level. """ unittest.TestResult.__init__(self, stream, descriptions, verbosity) @@ -664,12 +665,12 @@ class VppTestResult(unittest.TestResult): unittest.TestResult.stopTest(self, test) if self.verbosity > 0: self.stream.writeln(single_line_delim) - self.stream.writeln("%-60s%s" % - (self.getDescription(test), self.result_string)) + self.stream.writeln("%-60s%s" % (self.getDescription(test), + self.result_string)) self.stream.writeln(single_line_delim) else: - self.stream.writeln("%-60s%s" % - (self.getDescription(test), self.result_string)) + self.stream.writeln("%-60s%s" % (self.getDescription(test), + self.result_string)) def printErrors(self): """ diff --git a/test/hook.py b/test/hook.py index f3e5f880..247704ec 100644 --- a/test/hook.py +++ b/test/hook.py @@ -105,8 +105,8 @@ class PollHook(Hook): s = signaldict[abs(self.testcase.vpp.returncode)] else: s = "unknown" - msg = "VPP subprocess died unexpectedly with returncode %d [%s]" % ( - self.testcase.vpp.returncode, s) + msg = "VPP subprocess died unexpectedly with returncode %d [%s]" %\ + (self.testcase.vpp.returncode, s) self.logger.critical(msg) core_path = self.testcase.tempdir + '/core' if os.path.isfile(core_path): diff --git a/test/template_bd.py b/test/template_bd.py index 91d5dd71..ae171351 100644 --- a/test/template_bd.py +++ b/test/template_bd.py @@ -75,8 +75,8 @@ class BridgeDomain(object): self.pg_start() - # Pick first received frame and check if it's the - # non-encapsulated frame + # Pick first received frame and check if it's the non-encapsulated + # frame out = self.pg1.get_capture(1) pkt = out[0] self.assert_eq_pkts(pkt, self.frame_request) diff --git a/test/test_classifier.py b/test/test_classifier.py index 302430f8..faa107dc 100644 --- a/test/test_classifier.py +++ b/test/test_classifier.py @@ -114,8 +114,9 @@ class TestClassifier(VppTestCase): payload_info = self.payload_to_info(str(packet[Raw])) 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)" % - (dst_if.name, payload_info.src, packet_index)) + self.logger.debug( + "Got packet on port %s: src=%u (id=%u)" % + (dst_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]) @@ -296,8 +297,9 @@ class TestClassifier(VppTestCase): pkts = self.create_stream(self.pg0, self.pg2, self.pg_if_packet_sizes) self.pg0.add_stream(pkts) - self.create_classify_table( - 'mac', self.build_mac_mask(src_mac='ffffffffffff'), data_offset=-14) + self.create_classify_table('mac', + self.build_mac_mask(src_mac='ffffffffffff'), + data_offset=-14) self.create_classify_session( self.pg0, self.acl_tbl_idx.get('mac'), self.build_mac_match(src_mac=self.pg0.remote_mac)) @@ -326,7 +328,9 @@ class TestClassifier(VppTestCase): pkts = self.create_stream(self.pg0, self.pg3, self.pg_if_packet_sizes) self.pg0.add_stream(pkts) - self.create_classify_table('pbr', self.build_ip_mask(src_ip='ffffffff')) + self.create_classify_table( + 'pbr', self.build_ip_mask( + src_ip='ffffffff')) pbr_option = 1 self.create_classify_session( self.pg0, self.acl_tbl_idx.get('pbr'), diff --git a/test/test_flowperpkt.py b/test/test_flowperpkt.py index 29b3353b..f16bfb7e 100644 --- a/test/test_flowperpkt.py +++ b/test/test_flowperpkt.py @@ -57,8 +57,8 @@ class TestFlowperpkt(VppTestCase): 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) + # 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] diff --git a/test/test_ip4.py b/test/test_ip4.py index df93533d..9bd9a458 100644 --- a/test/test_ip4.py +++ b/test/test_ip4.py @@ -148,8 +148,9 @@ class TestIPv4(VppTestCase): payload_info = self.payload_to_info(str(packet[Raw])) 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)" % - (dst_if.name, payload_info.src, packet_index)) + self.logger.debug( + "Got packet on port %s: src=%u (id=%u)" % + (dst_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]) @@ -209,7 +210,7 @@ class TestIPv4FibCrud(VppTestCase): - add new 1k, - del 1.5k - ..note:: Python API is to slow to add many routes, needs C code replacement. + ..note:: Python API is too slow to add many routes, needs replacement. """ def config_fib_many_to_one(self, start_dest_addr, next_hop_addr, count): @@ -221,8 +222,9 @@ class TestIPv4FibCrud(VppTestCase): :return list: added ips with 32 prefix """ added_ips = [] - dest_addr = int( - socket.inet_pton(socket.AF_INET, start_dest_addr).encode('hex'), 16) + dest_addr = int(socket.inet_pton(socket.AF_INET, + start_dest_addr).encode('hex'), + 16) dest_addr_len = 32 n_next_hop_addr = socket.inet_pton(socket.AF_INET, next_hop_addr) for _ in range(count): @@ -236,8 +238,9 @@ class TestIPv4FibCrud(VppTestCase): def unconfig_fib_many_to_one(self, start_dest_addr, next_hop_addr, count): removed_ips = [] - dest_addr = int( - socket.inet_pton(socket.AF_INET, start_dest_addr).encode('hex'), 16) + dest_addr = int(socket.inet_pton(socket.AF_INET, + start_dest_addr).encode('hex'), + 16) dest_addr_len = 32 n_next_hop_addr = socket.inet_pton(socket.AF_INET, next_hop_addr) for _ in range(count): diff --git a/test/test_ip4_vrf_multi_instance.py b/test/test_ip4_vrf_multi_instance.py index 1449ef7d..b4279194 100644 --- a/test/test_ip4_vrf_multi_instance.py +++ b/test/test_ip4_vrf_multi_instance.py @@ -95,7 +95,8 @@ class TestIp4VrfMultiInst(VppTestCase): try: # Create pg interfaces - cls.create_pg_interfaces(range(cls.nr_of_vrfs * cls.pg_ifs_per_vrf)) + cls.create_pg_interfaces( + range(cls.nr_of_vrfs * cls.pg_ifs_per_vrf)) # Packet flows mapping pg0 -> pg1, pg2 etc. cls.flows = dict() @@ -308,14 +309,14 @@ class TestIp4VrfMultiInst(VppTestCase): """ 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 - bridge domains + - all packets received correctly on all pg-l2 interfaces assigned + to bridge domains - no packet received on all pg-l2 interfaces not assigned to bridge domains :raise RuntimeError: If no packet captured on l2-pg interface assigned - to the bridge domain or if any packet is captured on l2-pg interface - not assigned to the bridge domain. + to the bridge domain or if any packet is captured on l2-pg + interface not assigned to the bridge domain. """ # Test # Create incoming packet streams for packet-generator interfaces diff --git a/test/test_ip6.py b/test/test_ip6.py index cd9c4b95..ea669b70 100644 --- a/test/test_ip6.py +++ b/test/test_ip6.py @@ -8,8 +8,8 @@ from vpp_sub_interface import VppSubInterface, VppDot1QSubint from scapy.packet import Raw from scapy.layers.l2 import Ether, Dot1Q -from scapy.layers.inet6 import IPv6, UDP, ICMPv6ND_NS, ICMPv6ND_RS, ICMPv6ND_RA, \ - ICMPv6NDOptSrcLLAddr, getmacbyip6, ICMPv6MRD_Solicitation +from scapy.layers.inet6 import IPv6, UDP, ICMPv6ND_NS, ICMPv6ND_RS, \ + ICMPv6ND_RA, ICMPv6NDOptSrcLLAddr, getmacbyip6, ICMPv6MRD_Solicitation from util import ppp from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ptop, in6_islladdr, \ in6_mactoifaceid @@ -172,8 +172,9 @@ class TestIPv6(VppTestCase): payload_info = self.payload_to_info(str(packet[Raw])) 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)" % - (dst_if.name, payload_info.src, packet_index)) + self.logger.debug( + "Got packet on port %s: src=%u (id=%u)" % + (dst_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]) @@ -229,9 +230,9 @@ class TestIPv6(VppTestCase): intf.assert_nothing_captured(remark=remark) def test_ns(self): - """ IPv6 Neighbour Soliciatation Exceptions + """ IPv6 Neighbour Solicitation Exceptions - Test sceanrio: + Test scenario: - Send an NS Sourced from an address not covered by the link sub-net - Send an NS to an mcast address the router has not joined - Send NS for a target address the router does not onn. @@ -249,12 +250,13 @@ class TestIPv6(VppTestCase): ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac)) pkts = [p] - self.send_and_assert_no_replies(self.pg0, pkts, - "No response to NS source by address not on sub-net") + self.send_and_assert_no_replies( + self.pg0, pkts, + "No response to NS source by address not on sub-net") # - # An NS for sent to a solicited mcast group the router is not a member of - # FAILS + # An NS for sent to a solicited mcast group the router is + # not a member of FAILS # if 0: nsma = in6_getnsma(inet_pton(socket.AF_INET6, "fd::ffff")) @@ -266,8 +268,9 @@ class TestIPv6(VppTestCase): ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac)) pkts = [p] - self.send_and_assert_no_replies(self.pg0, pkts, - "No response to NS sent to unjoined mcast address") + self.send_and_assert_no_replies( + self.pg0, pkts, + "No response to NS sent to unjoined mcast address") # # An NS whose target address is one the router does not own @@ -307,15 +310,15 @@ class TestIPv6(VppTestCase): in6_ptop(mk_ll_addr(intf.local_mac))) def test_rs(self): - """ IPv6 Router Soliciatation Exceptions + """ IPv6 Router Solicitation Exceptions - Test sceanrio: + Test scenario: """ # - # Before we begin change the IPv6 RA responses to use the unicast address - # that way we will not confuse them with the periodic Ras which go to the Mcast - # address + # Before we begin change the IPv6 RA responses to use the unicast + # address - that way we will not confuse them with the periodic + # RAs which go to the mcast address # self.pg0.ip6_ra_config(send_unicast=1) @@ -336,8 +339,8 @@ class TestIPv6(VppTestCase): # # When we reconfiure the IPv6 RA config, we reset the RA rate limiting, - # so we need to do this before each test below so as not to drop packets for - # rate limiting reasons. Test this works here. + # so we need to do this before each test below so as not to drop + # packets for rate limiting reasons. Test this works here. # self.pg0.ip6_ra_config(send_unicast=1) self.send_and_expect_ra(self.pg0, pkts, "Rate limit reset RS") @@ -366,12 +369,12 @@ class TestIPv6(VppTestCase): self.pg0, pkts, "RS sourced from link-local", src_ip=ll) # - # Source from the unspecified address ::. This happens when the RS is sent before - # the host has a configured address/sub-net, i.e. auto-config. - # Since the sender has no IP address, the reply comes back mcast - so the - # capture needs to not filter this. - # If we happen to pick up the periodic RA at this point then so be it, it's not - # an error. + # Source from the unspecified address ::. This happens when the RS + # is sent before the host has a configured address/sub-net, + # i.e. auto-config. Since the sender has no IP address, the reply + # comes back mcast - so the capture needs to not filter this. + # If we happen to pick up the periodic RA at this point then so be it, + # it's not an error. # self.pg0.ip6_ra_config(send_unicast=1) p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / @@ -399,9 +402,9 @@ class TestIPv6(VppTestCase): @unittest.skip("Unsupported") def test_mrs(self): - """ IPv6 Multicast Router Soliciatation Exceptions + """ IPv6 Multicast Router Solicitation Exceptions - Test sceanrio: + Test scenario: """ # diff --git a/test/test_l2_fib.py b/test/test_l2_fib.py index d4ef3f4a..3d5d2129 100644 --- a/test/test_l2_fib.py +++ b/test/test_l2_fib.py @@ -106,7 +106,8 @@ class TestL2fib(VppTestCase): # Create BD with MAC learning and unknown unicast flooding disabled # and put interfaces to this BD - cls.vapi.bridge_domain_add_del(bd_id=cls.bd_id, uu_flood=0, learn=0) + cls.vapi.bridge_domain_add_del( + bd_id=cls.bd_id, uu_flood=0, learn=0) for pg_if in cls.pg_interfaces: cls.vapi.sw_interface_set_l2_bridge(pg_if.sw_if_index, bd_id=cls.bd_id) @@ -180,8 +181,8 @@ class TestL2fib(VppTestCase): end_nr = start + count / n_int for j in range(start, end_nr): host = self.hosts_by_pg_idx[pg_if.sw_if_index][j] - self.vapi.l2fib_add_del(host.mac, self.bd_id, pg_if.sw_if_index, - static_mac=1) + self.vapi.l2fib_add_del( + host.mac, self.bd_id, pg_if.sw_if_index, static_mac=1) counter += 1 percentage = counter / count * 100 if percentage > percent: @@ -202,8 +203,8 @@ class TestL2fib(VppTestCase): for pg_if in self.pg_interfaces: for j in range(count / n_int): host = self.hosts_by_pg_idx[pg_if.sw_if_index][0] - self.vapi.l2fib_add_del(host.mac, self.bd_id, pg_if.sw_if_index, - is_add=0) + self.vapi.l2fib_add_del( + host.mac, self.bd_id, pg_if.sw_if_index, is_add=0) self.deleted_hosts_by_pg_idx[pg_if.sw_if_index].append(host) del self.hosts_by_pg_idx[pg_if.sw_if_index][0] counter += 1 diff --git a/test/test_l2bd_multi_instance.py b/test/test_l2bd_multi_instance.py index a1226222..e24a8613 100644 --- a/test/test_l2bd_multi_instance.py +++ b/test/test_l2bd_multi_instance.py @@ -95,10 +95,10 @@ class TestL2bdMultiInst(VppTestCase): for i in range(0, len(cls.pg_interfaces), 3): cls.flows[cls.pg_interfaces[i]] = [cls.pg_interfaces[i + 1], cls.pg_interfaces[i + 2]] - cls.flows[cls.pg_interfaces[i + 1]] = [cls.pg_interfaces[i], - cls.pg_interfaces[i + 2]] - cls.flows[cls.pg_interfaces[i + 2]] = [cls.pg_interfaces[i], - cls.pg_interfaces[i + 1]] + cls.flows[cls.pg_interfaces[i + 1]] = \ + [cls.pg_interfaces[i], cls.pg_interfaces[i + 2]] + cls.flows[cls.pg_interfaces[i + 2]] = \ + [cls.pg_interfaces[i], cls.pg_interfaces[i + 1]] # Mapping between packet-generator index and lists of test hosts cls.hosts_by_pg_idx = dict() @@ -172,8 +172,9 @@ class TestL2bdMultiInst(VppTestCase): def create_bd_and_mac_learn(self, count, start=1): """ - Create required number of bridge domains with MAC learning enabled, put - 3 l2-pg interfaces to every bridge domain and send MAC learning packets. + Create required number of bridge domains with MAC learning enabled, + put 3 l2-pg interfaces to every bridge domain and send MAC learning + packets. :param int count: Number of bridge domains to be created. :param int start: Starting number of the bridge domain ID. diff --git a/test/test_l2xc_multi_instance.py b/test/test_l2xc_multi_instance.py index 6c28cebb..bb26f959 100644 --- a/test/test_l2xc_multi_instance.py +++ b/test/test_l2xc_multi_instance.py @@ -2,10 +2,10 @@ """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 + - 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 @@ -15,7 +15,8 @@ - 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 + - 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** @@ -25,7 +26,8 @@ - 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 + - 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** @@ -35,7 +37,8 @@ - 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 + - 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** @@ -79,7 +82,8 @@ class TestL2xcMultiInst(VppTestCase): 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]] + 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() diff --git a/test/test_snat.py b/test/test_snat.py index ca2e52a7..d23becf5 100644 --- a/test/test_snat.py +++ b/test/test_snat.py @@ -130,13 +130,15 @@ class TestSNAT(VppTestCase): if same_port: self.assertEqual(packet[TCP].sport, self.tcp_port_in) else: - self.assertNotEqual(packet[TCP].sport, self.tcp_port_in) + self.assertNotEqual( + packet[TCP].sport, self.tcp_port_in) self.tcp_port_out = packet[TCP].sport elif packet.haslayer(UDP): if same_port: self.assertEqual(packet[UDP].sport, self.udp_port_in) else: - self.assertNotEqual(packet[UDP].sport, self.udp_port_in) + self.assertNotEqual( + packet[UDP].sport, self.udp_port_in) self.udp_port_out = packet[UDP].sport else: if same_port: @@ -215,8 +217,14 @@ class TestSNAT(VppTestCase): addr_only = 0 l_ip = socket.inet_pton(socket.AF_INET, local_ip) e_ip = socket.inet_pton(socket.AF_INET, external_ip) - self.vapi.snat_add_static_mapping(l_ip, e_ip, local_port, external_port, - addr_only, vrf_id, is_add) + self.vapi.snat_add_static_mapping( + l_ip, + e_ip, + local_port, + external_port, + addr_only, + vrf_id, + is_add) def snat_add_address(self, ip, is_add=1): """ @@ -413,7 +421,9 @@ class TestSNAT(VppTestCase): self.pg3.assert_nothing_captured() def test_multiple_inside_interfaces(self): - """SNAT multiple inside interfaces with non-overlapping address space""" + """ + SNAT multiple inside interfaces with non-overlapping address space + """ self.snat_add_address(self.snat_addr) self.vapi.snat_interface_add_del_feature(self.pg0.sw_if_index) diff --git a/test/test_span.py b/test/test_span.py index 41507092..dc0110db 100644 --- a/test/test_span.py +++ b/test/test_span.py @@ -102,11 +102,12 @@ class TestSpan(VppTestCase): for i in self.interfaces: last_info[i.sw_if_index] = None dst_sw_if_index = dst_if.sw_if_index - if len(capture_pg1) != len(capture_pg2): - self.logger.error( - "Different number of outgoing and mirrored packets : %u != %u" % - (len(capture_pg1), len(capture_pg2))) - raise + self.AssertEqual( + len(capture_pg1), + len(capture_pg2), + "Different number of outgoing and mirrored packets : %u != %u" % + (len(capture_pg1), + len(capture_pg2))) for pkt_pg1, pkt_pg2 in zip(capture_pg1, capture_pg2): try: ip1 = pkt_pg1[IP] diff --git a/test/vpp_interface.py b/test/vpp_interface.py index 9d9712eb..e0a29f94 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -15,7 +15,7 @@ class VppInterface(object): @property def remote_mac(self): - """MAC-address of the remote interface "connected" to this interface.""" + """MAC-address of the remote interface "connected" to this interface""" return self._remote_hosts[0].mac @property @@ -261,19 +261,23 @@ class VppInterface(object): def admin_up(self): """Put interface ADMIN-UP.""" - self.test.vapi.sw_interface_set_flags(self.sw_if_index, admin_up_down=1) + self.test.vapi.sw_interface_set_flags(self.sw_if_index, + admin_up_down=1) def admin_down(self): """Put interface ADMIN-down.""" - self.test.vapi.sw_interface_set_flags(self.sw_if_index, admin_up_down=0) + self.test.vapi.sw_interface_set_flags(self.sw_if_index, + admin_up_down=0) def ip6_enable(self): """IPv6 Enable interface""" - self.test.vapi.ip6_sw_interface_enable_disable(self.sw_if_index, enable=1) + self.test.vapi.ip6_sw_interface_enable_disable(self.sw_if_index, + enable=1) def ip6_disable(self): """Put interface ADMIN-DOWN.""" - self.test.vapi.ip6_sw_interface_enable_disable(self.sw_if_index, enable=0) + self.test.vapi.ip6_sw_interface_enable_disable(self.sw_if_index, + enable=0) def add_sub_if(self, sub_if): """Register a sub-interface with this interface. diff --git a/test/vpp_ip_route.py b/test/vpp_ip_route.py index 75f400f1..975e3934 100644 --- a/test/vpp_ip_route.py +++ b/test/vpp_ip_route.py @@ -13,7 +13,13 @@ MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1 class RoutePath: - def __init__(self, nh_addr, nh_sw_if_index, nh_table_id=0, labels=[], nh_via_label=MPLS_LABEL_INVALID): + def __init__( + self, + nh_addr, + nh_sw_if_index, + nh_table_id=0, + labels=[], + nh_via_label=MPLS_LABEL_INVALID): self.nh_addr = socket.inet_pton(socket.AF_INET, nh_addr) self.nh_itf = nh_sw_if_index self.nh_table_id = nh_table_id @@ -36,15 +42,16 @@ class IpRoute: def add_vpp_config(self): for path in self.paths: - self._test.vapi.ip_add_del_route(self.dest_addr, - self.dest_addr_len, - path.nh_addr, - path.nh_itf, - table_id=self.table_id, - next_hop_out_label_stack=path.nh_labels, - next_hop_n_out_labels=len( - path.nh_labels), - next_hop_via_label=path.nh_via_label) + self._test.vapi.ip_add_del_route( + self.dest_addr, + self.dest_addr_len, + path.nh_addr, + path.nh_itf, + table_id=self.table_id, + next_hop_out_label_stack=path.nh_labels, + next_hop_n_out_labels=len( + path.nh_labels), + next_hop_via_label=path.nh_via_label) def remove_vpp_config(self): for path in self.paths: @@ -61,7 +68,7 @@ class MplsIpBind: MPLS to IP Binding """ - def __init__(self, test, local_label, dest_addr, dest_addr_len): + def __init__(self, test, local_label, dest_addr, dest_addr_len): self._test = test self.dest_addr = socket.inet_pton(socket.AF_INET, dest_addr) self.dest_addr_len = dest_addr_len @@ -93,17 +100,18 @@ class MplsRoute: def add_vpp_config(self): for path in self.paths: - self._test.vapi.mpls_route_add_del(self.local_label, - self.eos_bit, - 1, - path.nh_addr, - path.nh_itf, - table_id=self.table_id, - next_hop_out_label_stack=path.nh_labels, - next_hop_n_out_labels=len( - path.nh_labels), - next_hop_via_label=path.nh_via_label, - next_hop_table_id=path.nh_table_id) + self._test.vapi.mpls_route_add_del( + self.local_label, + self.eos_bit, + 1, + path.nh_addr, + path.nh_itf, + table_id=self.table_id, + next_hop_out_label_stack=path.nh_labels, + next_hop_n_out_labels=len( + path.nh_labels), + next_hop_via_label=path.nh_via_label, + next_hop_table_id=path.nh_table_id) def remove_vpp_config(self): for path in self.paths: diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 26edd4f2..b78e8613 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -5,9 +5,9 @@ from hook import Hook from collections import deque # Sphinx creates auto-generated documentation by importing the python source -# files and collecting the docstrings from them. The NO_VPP_PAPI flag allows the -# vpp_papi_provider.py file to be importable without having to build the whole -# vpp api if the user only wishes to generate the test documentation. +# files and collecting the docstrings from them. The NO_VPP_PAPI flag allows +# the vpp_papi_provider.py file to be importable without having to build +# the whole vpp api if the user only wishes to generate the test documentation. do_import = True try: no_vpp_papi = os.getenv("NO_VPP_PAPI") @@ -224,8 +224,8 @@ class VppPapiProvider(object): send_unicast,): return self.api(self.papi.sw_interface_ip6nd_ra_config, {'sw_if_index': sw_if_index, - 'suppress' : suppress, - 'send_unicast' : send_unicast}) + 'suppress': suppress, + 'send_unicast': send_unicast}) def ip6_sw_interface_enable_disable(self, sw_if_index, enable): """ @@ -972,7 +972,7 @@ class VppPapiProvider(object): """ :param is_add: :param mask: - :param match_n_vectors: (Default value = 1): + :param match_n_vectors: (Default value = 1) :param table_index: (Default value = 0xFFFFFFFF) :param nbuckets: (Default value = 2) :param memory_size: (Default value = 2097152) diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py index eeb9c1a5..756f79b5 100644 --- a/test/vpp_pg_interface.py +++ b/test/vpp_pg_interface.py @@ -13,6 +13,7 @@ from util import ppp, ppc from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ismaddr from scapy.utils import inet_pton, inet_ntop + def is_ipv6_misc(p): """ Is packet one of uninteresting IPv6 broadcasts? """ if p.haslayer(ICMPv6ND_RA): @@ -91,10 +92,12 @@ class VppPGInterface(VppInterface): self._capture_cli = "packet-generator capture pg%u pcap %s" % ( self.pg_index, self.out_path) self._cap_name = "pcap%u" % self.sw_if_index - self._input_cli = "packet-generator new pcap %s source pg%u name %s" % ( - self.in_path, self.pg_index, self.cap_name) + self._input_cli = \ + "packet-generator new pcap %s source pg%u name %s" % ( + self.in_path, self.pg_index, self.cap_name) - def rotate_out_file(self): + def enable_capture(self): + """ Enable capture on this packet-generator interface""" try: if os.path.isfile(self.out_path): os.rename(self.out_path, @@ -106,10 +109,6 @@ class VppPGInterface(VppInterface): self._out_file)) except: pass - - def enable_capture(self): - """ Enable capture on this packet-generator interface""" - self.rotate_out_file() # FIXME this should be an API, but no such exists atm self.test.vapi.cli(self.capture_cli) self._pcap_reader = None @@ -151,7 +150,7 @@ class VppPGInterface(VppInterface): before = len(output.res) if filter_out_fn: output.res = [p for p in output.res if not filter_out_fn(p)] - removed = before - len(output.res) + removed = len(output.res) - before if removed: self.test.logger.debug( "Filtered out %s packets from capture (returning %s)" % @@ -181,9 +180,13 @@ class VppPGInterface(VppInterface): based_on = "based on stored packet_infos" if expected_count == 0: raise Exception( - "Internal error, expected packet count for %s is 0!" % name) + "Internal error, expected packet count for %s is 0!" % + name) self.test.logger.debug("Expecting to capture %s (%s) packets on %s" % ( expected_count, based_on, name)) + if expected_count == 0: + raise Exception( + "Internal error, expected packet count for %s is 0!" % name) while remaining_time > 0: before = time.time() capture = self._get_capture(remaining_time, filter_out_fn) @@ -192,8 +195,6 @@ class VppPGInterface(VppInterface): if len(capture.res) == expected_count: # bingo, got the packets we expected return capture - elif expected_count == 0: - return None remaining_time -= elapsed_time if capture: raise Exception("Captured packets mismatch, captured %s packets, " @@ -241,8 +242,9 @@ class VppPGInterface(VppInterface): self.test.logger.debug("Waiting for capture file %s to appear, " "timeout is %ss" % (self.out_path, timeout)) else: - self.test.logger.debug("Capture file %s already exists" % - self.out_path) + self.test.logger.debug( + "Capture file %s already exists" % + self.out_path) return True while time.time() < limit: if os.path.isfile(self.out_path): @@ -275,8 +277,10 @@ class VppPGInterface(VppInterface): self._pcap_reader = PcapReader(self.out_path) break except: - self.test.logger.debug("Exception in scapy.PcapReader(%s): " - "%s" % (self.out_path, format_exc())) + self.test.logger.debug( + "Exception in scapy.PcapReader(%s): " + "%s" % + (self.out_path, format_exc())) if not self._pcap_reader: raise Exception("Capture file %s did not appear within " "timeout" % self.out_path) @@ -290,8 +294,9 @@ class VppPGInterface(VppInterface): "Packet received after %ss was filtered out" % (time.time() - (deadline - timeout))) else: - self.test.logger.debug("Packet received after %fs" % - (time.time() - (deadline - timeout))) + self.test.logger.debug( + "Packet received after %fs" % + (time.time() - (deadline - timeout))) return p time.sleep(0) # yield self.test.logger.debug("Timeout - no packets received") @@ -335,7 +340,6 @@ class VppPGInterface(VppInterface): self.test.logger.info("No ARP received on port %s" % pg_interface.name) return - self.rotate_out_file() arp_reply = captured_packet.copy() # keep original for exception # Make Dot1AD packet content recognizable to scapy if arp_reply.type == 0x88a8: @@ -380,7 +384,8 @@ class VppPGInterface(VppInterface): captured_packet = pg_interface.wait_for_packet( deadline - now, filter_out_fn=None) except: - self.test.logger.error("Timeout while waiting for NDP response") + self.test.logger.error( + "Timeout while waiting for NDP response") raise ndp_reply = captured_packet.copy() # keep original for exception # Make Dot1AD packet content recognizable to scapy @@ -395,13 +400,12 @@ class VppPGInterface(VppInterface): self._local_mac = opt.lladdr self.test.logger.debug(self.test.vapi.cli("show trace")) # we now have the MAC we've been after - self.rotate_out_file() return except: self.test.logger.info( - ppp("Unexpected response to NDP request:", captured_packet)) + ppp("Unexpected response to NDP request:", + captured_packet)) now = time.time() self.test.logger.debug(self.test.vapi.cli("show trace")) - self.rotate_out_file() raise Exception("Timeout while waiting for NDP response") -- cgit 1.2.3-korg From 8213045f127776e92fed615148e27ea6f2c9a334 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Thu, 12 Jan 2017 03:39:42 +0100 Subject: make test: fix typo Change-Id: I70b4123129aab5770a45ccde4cef4452d06386b8 Signed-off-by: Klement Sekera --- test/test_span.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/test_span.py') diff --git a/test/test_span.py b/test/test_span.py index dc0110db..d8b65252 100644 --- a/test/test_span.py +++ b/test/test_span.py @@ -102,7 +102,7 @@ class TestSpan(VppTestCase): for i in self.interfaces: last_info[i.sw_if_index] = None dst_sw_if_index = dst_if.sw_if_index - self.AssertEqual( + self.assertEqual( len(capture_pg1), len(capture_pg2), "Different number of outgoing and mirrored packets : %u != %u" % -- cgit 1.2.3-korg From 001fd406df771f1cf73ca0dea440c8bde309e077 Mon Sep 17 00:00:00 2001 From: Eyal Bari Date: Sun, 16 Jul 2017 09:34:53 +0300 Subject: SPAN:add l2 mirror added span feature nodes for l2-input / l2-output Change-Id: Ib6e0ce60d0811901b6edd70209e6a4c4a35cd8ff Signed-off-by: Eyal Bari --- src/vat/api_format.c | 6 +- src/vnet/l2/l2_input.h | 5 +- src/vnet/l2/l2_output.c | 13 +- src/vnet/l2/l2_output.h | 8 +- src/vnet/span/node.c | 198 ++++++++++++++++++-------- src/vnet/span/span.api | 1 + src/vnet/span/span.c | 148 +++++++++++--------- src/vnet/span/span.h | 25 +++- src/vnet/span/span_api.c | 15 +- src/vpp/api/custom_dump.c | 3 + test/test_span.py | 348 +++++++++++++++++++++++++++++++++++++++++----- test/vpp_papi_provider.py | 7 +- 12 files changed, 606 insertions(+), 171 deletions(-) (limited to 'test/test_span.py') diff --git a/src/vat/api_format.c b/src/vat/api_format.c index 40eca8c5..932e162d 100644 --- a/src/vat/api_format.c +++ b/src/vat/api_format.c @@ -18082,6 +18082,7 @@ api_sw_interface_span_enable_disable (vat_main_t * vam) u32 dst_sw_if_index = ~0; u8 state = 3; int ret; + u8 is_l2 = 0; while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) { @@ -18104,6 +18105,8 @@ api_sw_interface_span_enable_disable (vat_main_t * vam) state = 2; else if (unformat (i, "both")) state = 3; + else if (unformat (i, "l2")) + is_l2 = 1; else break; } @@ -18113,6 +18116,7 @@ api_sw_interface_span_enable_disable (vat_main_t * vam) mp->sw_if_index_from = htonl (src_sw_if_index); mp->sw_if_index_to = htonl (dst_sw_if_index); mp->state = state; + mp->is_l2 = is_l2; S (mp); W (ret); @@ -20044,7 +20048,7 @@ _(set_ipfix_classify_stream, "[domain ] [src_port ]") \ _(ipfix_classify_stream_dump, "") \ _(ipfix_classify_table_add_del, "table ip4|ip6 [tcp|udp]") \ _(ipfix_classify_table_dump, "") \ -_(sw_interface_span_enable_disable, "[src | src_sw_if_index ] [disable | [[dst | dst_sw_if_index ] [both|rx|tx]]]") \ +_(sw_interface_span_enable_disable, "[l2] [src | src_sw_if_index ] [disable | [[dst | dst_sw_if_index ] [both|rx|tx]]]") \ _(sw_interface_span_dump, "") \ _(get_next_index, "node-name next-node-name ") \ _(pg_create_interface, "if_id ") \ diff --git a/src/vnet/l2/l2_input.h b/src/vnet/l2/l2_input.h index 244ef445..e6b3bc7f 100644 --- a/src/vnet/l2/l2_input.h +++ b/src/vnet/l2/l2_input.h @@ -102,7 +102,7 @@ l2input_bd_config (u32 bd_index) /* L2 input features */ -/* Mappings from feature ID to graph node name */ +/* Mappings from feature ID to graph node name in reverse order */ #define foreach_l2input_feat \ _(DROP, "feature-bitmap-drop") \ _(XCONNECT, "l2-output") \ @@ -116,7 +116,8 @@ l2input_bd_config (u32 bd_index) _(VPATH, "vpath-input-l2") \ _(ACL, "l2-input-acl") \ _(POLICER_CLAS, "l2-policer-classify") \ - _(INPUT_CLASSIFY, "l2-input-classify") + _(INPUT_CLASSIFY, "l2-input-classify") \ + _(SPAN, "span-l2-input") /* Feature bitmap positions */ typedef enum diff --git a/src/vnet/l2/l2_output.c b/src/vnet/l2/l2_output.c index b3537a35..fbee590c 100644 --- a/src/vnet/l2/l2_output.c +++ b/src/vnet/l2/l2_output.c @@ -195,8 +195,6 @@ l2output_vtr (vlib_node_runtime_t * node, l2_output_config_t * config, } -static vlib_node_registration_t l2output_node; - static_always_inline uword l2output_node_inline (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame, int do_trace) @@ -477,7 +475,7 @@ l2output_node_fn (vlib_main_t * vm, } /* *INDENT-OFF* */ -VLIB_REGISTER_NODE (l2output_node,static) = { +VLIB_REGISTER_NODE (l2output_node) = { .function = l2output_node_fn, .name = "l2-output", .vector_size = sizeof (u32), @@ -495,6 +493,8 @@ VLIB_REGISTER_NODE (l2output_node,static) = { [L2OUTPUT_NEXT_BAD_INTF] = "l2-output-bad-intf", }, }; + +VLIB_NODE_FUNCTION_MULTIARCH (l2output_node, l2output_node_fn); /* *INDENT-ON* */ @@ -601,11 +601,12 @@ VLIB_REGISTER_NODE (l2output_bad_intf_node,static) = { [0] = "error-drop", }, }; -/* *INDENT-ON* */ +VLIB_NODE_FUNCTION_MULTIARCH (l2output_bad_intf_node, l2output_bad_intf_node_fn); +/* *INDENT-ON* */ -VLIB_NODE_FUNCTION_MULTIARCH (l2output_node, l2output_node_fn) - clib_error_t *l2output_init (vlib_main_t * vm) +static clib_error_t * +l2output_init (vlib_main_t * vm) { l2output_main_t *mp = &l2output_main; diff --git a/src/vnet/l2/l2_output.h b/src/vnet/l2/l2_output.h index 6da3e303..a54b8d67 100644 --- a/src/vnet/l2/l2_output.h +++ b/src/vnet/l2/l2_output.h @@ -77,12 +77,14 @@ typedef struct l2output_main_t l2output_main; +extern vlib_node_registration_t l2output_node; + /* L2 output features */ -/* Mappings from feature ID to graph node name */ +/* Mappings from feature ID to graph node name in reverse order */ #define foreach_l2output_feat \ _(OUTPUT, "interface-output") \ - _(SPAN, "feature-bitmap-drop") \ + _(SPAN, "span-l2-output") \ _(CFM, "feature-bitmap-drop") \ _(QOS, "feature-bitmap-drop") \ _(ACL, "l2-output-acl") \ @@ -103,6 +105,8 @@ typedef enum L2OUTPUT_N_FEAT, } l2output_feat_t; +STATIC_ASSERT (L2OUTPUT_N_FEAT <= 32, "too many l2 output features"); + /* Feature bit masks */ typedef enum { diff --git a/src/vnet/span/node.c b/src/vnet/span/node.c index 3a461b0a..9d83d4ef 100644 --- a/src/vnet/span/node.c +++ b/src/vnet/span/node.c @@ -18,6 +18,9 @@ #include #include +#include +#include +#include #include #include @@ -59,21 +62,19 @@ static char *span_error_strings[] = { static_always_inline void span_mirror (vlib_main_t * vm, vlib_node_runtime_t * node, u32 sw_if_index0, - vlib_buffer_t * b0, vlib_frame_t ** mirror_frames, int is_rx) + vlib_buffer_t * b0, vlib_frame_t ** mirror_frames, + vlib_rx_or_tx_t rxtx, span_feat_t sf) { vlib_buffer_t *c0; span_main_t *sm = &span_main; vnet_main_t *vnm = &vnet_main; - span_interface_t *si0 = 0; u32 *to_mirror_next = 0; u32 i; - si0 = vec_elt_at_index (sm->interfaces, sw_if_index0); + span_interface_t *si0 = vec_elt_at_index (sm->interfaces, sw_if_index0); + span_mirror_t *sm0 = &si0->mirror_rxtx[sf][rxtx]; - if (is_rx != 0 && si0->num_rx_mirror_ports == 0) - return; - - if (is_rx == 0 && si0->num_tx_mirror_ports == 0) + if (sm0->num_mirror_ports == 0) return; /* Don't do it again */ @@ -81,10 +82,15 @@ span_mirror (vlib_main_t * vm, vlib_node_runtime_t * node, u32 sw_if_index0, return; /* *INDENT-OFF* */ - clib_bitmap_foreach (i, is_rx ? si0->rx_mirror_ports : si0->tx_mirror_ports, ( + clib_bitmap_foreach (i, sm0->mirror_ports, ( { if (mirror_frames[i] == 0) - mirror_frames[i] = vnet_get_frame_to_sw_interface (vnm, i); + { + if (sf == SPAN_FEAT_L2) + mirror_frames[i] = vlib_get_frame_to_node (vnm->vlib_main, l2output_node.index); + else + mirror_frames[i] = vnet_get_frame_to_sw_interface (vnm, i); + } to_mirror_next = vlib_frame_vector_args (mirror_frames[i]); to_mirror_next += mirror_frames[i]->n_vectors; /* This can fail */ @@ -93,6 +99,8 @@ span_mirror (vlib_main_t * vm, vlib_node_runtime_t * node, u32 sw_if_index0, { vnet_buffer (c0)->sw_if_index[VLIB_TX] = i; c0->flags |= VNET_BUFFER_F_SPAN_CLONE; + if (sf == SPAN_FEAT_L2) + vnet_buffer (c0)->l2.feature_bitmap = L2OUTPUT_FEAT_OUTPUT; to_mirror_next[0] = vlib_get_buffer_index (vm, c0); mirror_frames[i]->n_vectors++; if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) @@ -108,7 +116,8 @@ span_mirror (vlib_main_t * vm, vlib_node_runtime_t * node, u32 sw_if_index0, static_always_inline uword span_node_inline_fn (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * frame, int is_rx) + vlib_frame_t * frame, vlib_rx_or_tx_t rxtx, + span_feat_t sf) { span_main_t *sm = &span_main; vnet_main_t *vnm = &vnet_main; @@ -117,7 +126,6 @@ span_node_inline_fn (vlib_main_t * vm, vlib_node_runtime_t * node, u32 next_index; u32 sw_if_index; static __thread vlib_frame_t **mirror_frames = 0; - vlib_rx_or_tx_t rxtx = is_rx ? VLIB_RX : VLIB_TX; from = vlib_frame_vector_args (frame); n_left_from = frame->n_vectors; @@ -156,11 +164,33 @@ span_node_inline_fn (vlib_main_t * vm, vlib_node_runtime_t * node, sw_if_index0 = vnet_buffer (b0)->sw_if_index[rxtx]; sw_if_index1 = vnet_buffer (b1)->sw_if_index[rxtx]; - span_mirror (vm, node, sw_if_index0, b0, mirror_frames, is_rx); - span_mirror (vm, node, sw_if_index1, b1, mirror_frames, is_rx); - - vnet_feature_next (sw_if_index0, &next0, b0); - vnet_feature_next (sw_if_index1, &next1, b1); + span_mirror (vm, node, sw_if_index0, b0, mirror_frames, rxtx, sf); + span_mirror (vm, node, sw_if_index1, b1, mirror_frames, rxtx, sf); + + switch (sf) + { + case SPAN_FEAT_L2: + if (rxtx == VLIB_RX) + { + next0 = vnet_l2_feature_next (b0, sm->l2_input_next, + L2INPUT_FEAT_SPAN); + next1 = vnet_l2_feature_next (b1, sm->l2_input_next, + L2INPUT_FEAT_SPAN); + } + else + { + next0 = vnet_l2_feature_next (b0, sm->l2_output_next, + L2OUTPUT_FEAT_SPAN); + next1 = vnet_l2_feature_next (b1, sm->l2_output_next, + L2OUTPUT_FEAT_SPAN); + } + break; + case SPAN_FEAT_DEVICE: + default: + vnet_feature_next (sw_if_index0, &next0, b0); + vnet_feature_next (sw_if_index1, &next1, b1); + break; + } /* verify speculative enqueue, maybe switch current next frame */ vlib_validate_buffer_enqueue_x2 (vm, node, next_index, @@ -184,9 +214,23 @@ span_node_inline_fn (vlib_main_t * vm, vlib_node_runtime_t * node, b0 = vlib_get_buffer (vm, bi0); sw_if_index0 = vnet_buffer (b0)->sw_if_index[rxtx]; - span_mirror (vm, node, sw_if_index0, b0, mirror_frames, is_rx); - - vnet_feature_next (sw_if_index0, &next0, b0); + span_mirror (vm, node, sw_if_index0, b0, mirror_frames, rxtx, sf); + + switch (sf) + { + case SPAN_FEAT_L2: + if (rxtx == VLIB_RX) + next0 = vnet_l2_feature_next (b0, sm->l2_input_next, + L2INPUT_FEAT_SPAN); + else + next0 = vnet_l2_feature_next (b0, sm->l2_output_next, + L2OUTPUT_FEAT_SPAN); + break; + case SPAN_FEAT_DEVICE: + default: + vnet_feature_next (sw_if_index0, &next0, b0); + break; + } /* verify speculative enqueue, maybe switch current next frame */ vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, @@ -199,11 +243,14 @@ span_node_inline_fn (vlib_main_t * vm, vlib_node_runtime_t * node, for (sw_if_index = 0; sw_if_index < vec_len (mirror_frames); sw_if_index++) { - if (mirror_frames[sw_if_index] == 0) + vlib_frame_t *f = mirror_frames[sw_if_index]; + if (f == 0) continue; - vnet_put_frame_to_sw_interface (vnm, sw_if_index, - mirror_frames[sw_if_index]); + if (sf == SPAN_FEAT_L2) + vlib_put_frame_to_node (vnm->vlib_main, l2output_node.index, f); + else + vnet_put_frame_to_sw_interface (vnm, sw_if_index, f); mirror_frames[sw_if_index] = 0; } vlib_node_increment_counter (vm, span_node.index, SPAN_ERROR_HITS, @@ -213,62 +260,103 @@ span_node_inline_fn (vlib_main_t * vm, vlib_node_runtime_t * node, } static uword -span_input_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * frame) +span_device_input_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) { - return span_node_inline_fn (vm, node, frame, 1); + return span_node_inline_fn (vm, node, frame, VLIB_RX, SPAN_FEAT_DEVICE); } static uword -span_output_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * frame) +span_device_output_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) { - return span_node_inline_fn (vm, node, frame, 0); + return span_node_inline_fn (vm, node, frame, VLIB_TX, SPAN_FEAT_DEVICE); } -/* *INDENT-OFF* */ -VLIB_REGISTER_NODE (span_input_node) = { - .function = span_input_node_fn, - .name = "span-input", - .vector_size = sizeof (u32), - .format_trace = format_span_trace, - .type = VLIB_NODE_TYPE_INTERNAL, +static uword +span_l2_input_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return span_node_inline_fn (vm, node, frame, VLIB_RX, SPAN_FEAT_L2); +} - .n_errors = ARRAY_LEN(span_error_strings), - .error_strings = span_error_strings, +static uword +span_l2_output_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return span_node_inline_fn (vm, node, frame, VLIB_TX, SPAN_FEAT_L2); +} - .n_next_nodes = 0, +#define span_node_defs \ + .vector_size = sizeof (u32), \ + .format_trace = format_span_trace, \ + .type = VLIB_NODE_TYPE_INTERNAL, \ + .n_errors = ARRAY_LEN(span_error_strings), \ + .error_strings = span_error_strings, \ + .n_next_nodes = 0, \ + .next_nodes = { \ + [0] = "error-drop" \ + } - /* edit / add dispositions here */ - .next_nodes = { - [0] = "error-drop", - }, +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (span_input_node) = { + span_node_defs, + .function = span_device_input_node_fn, + .name = "span-input", }; -VLIB_NODE_FUNCTION_MULTIARCH (span_input_node, span_input_node_fn) +VLIB_NODE_FUNCTION_MULTIARCH (span_input_node, span_device_input_node_fn) VLIB_REGISTER_NODE (span_output_node) = { - .function = span_output_node_fn, + span_node_defs, + .function = span_device_output_node_fn, .name = "span-output", - .vector_size = sizeof (u32), - .format_trace = format_span_trace, - .type = VLIB_NODE_TYPE_INTERNAL, +}; - .n_errors = ARRAY_LEN(span_error_strings), - .error_strings = span_error_strings, +VLIB_NODE_FUNCTION_MULTIARCH (span_output_node, span_device_output_node_fn) - .n_next_nodes = 0, +VLIB_REGISTER_NODE (span_l2_input_node) = { + span_node_defs, + .function = span_l2_input_node_fn, + .name = "span-l2-input", +}; - /* edit / add dispositions here */ - .next_nodes = { - [0] = "error-drop", - }, +VLIB_NODE_FUNCTION_MULTIARCH (span_l2_input_node, span_l2_input_node_fn) + +VLIB_REGISTER_NODE (span_l2_output_node) = { + span_node_defs, + .function = span_l2_output_node_fn, + .name = "span-l2-output", }; -VLIB_NODE_FUNCTION_MULTIARCH (span_output_node, span_output_node_fn) +VLIB_NODE_FUNCTION_MULTIARCH (span_l2_output_node, span_l2_output_node_fn) + +clib_error_t *span_init (vlib_main_t * vm) +{ + span_main_t *sm = &span_main; + + sm->vlib_main = vm; + sm->vnet_main = vnet_get_main (); + + /* Initialize the feature next-node indexes */ + feat_bitmap_init_next_nodes (vm, + span_l2_input_node.index, + L2INPUT_N_FEAT, + l2input_get_feat_names (), + sm->l2_input_next); + + feat_bitmap_init_next_nodes (vm, + span_l2_output_node.index, + L2OUTPUT_N_FEAT, + l2output_get_feat_names (), + sm->l2_output_next); + return 0; +} +VLIB_INIT_FUNCTION (span_init); /* *INDENT-ON* */ +#undef span_node_defs /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vnet/span/span.api b/src/vnet/span/span.api index 914fd8d0..2a762ac2 100644 --- a/src/vnet/span/span.api +++ b/src/vnet/span/span.api @@ -27,6 +27,7 @@ autoreply define sw_interface_span_enable_disable { u32 sw_if_index_from; u32 sw_if_index_to; u8 state; + u8 is_l2; }; /** \brief SPAN dump request diff --git a/src/vnet/span/span.c b/src/vnet/span/span.c index c5b43e34..6ecd1789 100644 --- a/src/vnet/span/span.c +++ b/src/vnet/span/span.c @@ -16,60 +16,81 @@ #include #include #include +#include +#include #include +typedef enum +{ + SPAN_DISABLE = 0, + SPAN_RX = 1, + SPAN_TX = 2, + SPAN_BOTH = SPAN_RX | SPAN_TX +} span_state_t; + +static_always_inline u32 +span_dst_set (span_mirror_t * sm, u32 dst_sw_if_index, int enable) +{ + sm->mirror_ports = + clib_bitmap_set (sm->mirror_ports, dst_sw_if_index, enable); + u32 last = sm->num_mirror_ports; + sm->num_mirror_ports = clib_bitmap_count_set_bits (sm->mirror_ports); + return last; +} + int span_add_delete_entry (vlib_main_t * vm, - u32 src_sw_if_index, u32 dst_sw_if_index, u8 state) + u32 src_sw_if_index, u32 dst_sw_if_index, u8 state, + span_feat_t sf) { span_main_t *sm = &span_main; - span_interface_t *si; - u32 new_num_rx_mirror_ports, new_num_tx_mirror_ports; - if (state > 3) + if (state > SPAN_BOTH) return VNET_API_ERROR_UNIMPLEMENTED; if ((src_sw_if_index == ~0) || (dst_sw_if_index == ~0 && state > 0) || (src_sw_if_index == dst_sw_if_index)) return VNET_API_ERROR_INVALID_INTERFACE; - vnet_sw_interface_t *sw_if; - - sw_if = vnet_get_sw_interface (vnet_get_main (), src_sw_if_index); - if (sw_if->type == VNET_SW_INTERFACE_TYPE_SUB) - return VNET_API_ERROR_UNIMPLEMENTED; - vec_validate_aligned (sm->interfaces, src_sw_if_index, CLIB_CACHE_LINE_BYTES); - si = vec_elt_at_index (sm->interfaces, src_sw_if_index); - - si->rx_mirror_ports = clib_bitmap_set (si->rx_mirror_ports, dst_sw_if_index, - (state & 1) != 0); - si->tx_mirror_ports = clib_bitmap_set (si->tx_mirror_ports, dst_sw_if_index, - (state & 2) != 0); - new_num_rx_mirror_ports = clib_bitmap_count_set_bits (si->rx_mirror_ports); - new_num_tx_mirror_ports = clib_bitmap_count_set_bits (si->tx_mirror_ports); + span_interface_t *si = vec_elt_at_index (sm->interfaces, src_sw_if_index); - if (new_num_rx_mirror_ports == 1 && si->num_rx_mirror_ports == 0) - vnet_feature_enable_disable ("device-input", "span-input", - src_sw_if_index, 1, 0, 0); + int rx = ! !(state & SPAN_RX); + int tx = ! !(state & SPAN_TX); - if (new_num_rx_mirror_ports == 0 && si->num_rx_mirror_ports == 1) - vnet_feature_enable_disable ("device-input", "span-input", - src_sw_if_index, 0, 0, 0); + span_mirror_t *rxm = &si->mirror_rxtx[sf][VLIB_RX]; + span_mirror_t *txm = &si->mirror_rxtx[sf][VLIB_TX]; - if (new_num_rx_mirror_ports == 1 && si->num_rx_mirror_ports == 0) - vnet_feature_enable_disable ("interface-output", "span-output", - src_sw_if_index, 1, 0, 0); + u32 last_rx_ports_count = span_dst_set (rxm, dst_sw_if_index, rx); + u32 last_tx_ports_count = span_dst_set (txm, dst_sw_if_index, tx); - if (new_num_rx_mirror_ports == 0 && si->num_rx_mirror_ports == 1) - vnet_feature_enable_disable ("interface-output", "span-output", - src_sw_if_index, 0, 0, 0); + int enable_rx = last_rx_ports_count == 0 && rxm->num_mirror_ports == 1; + int disable_rx = last_rx_ports_count == 1 && rxm->num_mirror_ports == 0; + int enable_tx = last_tx_ports_count == 0 && txm->num_mirror_ports == 1; + int disable_tx = last_tx_ports_count == 1 && txm->num_mirror_ports == 0; - si->num_rx_mirror_ports = new_num_rx_mirror_ports; - si->num_tx_mirror_ports = new_num_tx_mirror_ports; + switch (sf) + { + case SPAN_FEAT_DEVICE: + if (enable_rx || disable_rx) + vnet_feature_enable_disable ("device-input", "span-input", + src_sw_if_index, rx, 0, 0); + if (enable_tx || disable_tx) + vnet_feature_enable_disable ("interface-output", "span-output", + src_sw_if_index, tx, 0, 0); + break; + case SPAN_FEAT_L2: + if (enable_rx || disable_rx) + l2input_intf_bitmap_enable (src_sw_if_index, L2INPUT_FEAT_SPAN, rx); + if (enable_tx || disable_tx) + l2output_intf_bitmap_enable (src_sw_if_index, L2OUTPUT_FEAT_SPAN, tx); + break; + default: + return VNET_API_ERROR_UNIMPLEMENTED; + } if (dst_sw_if_index > sm->max_sw_if_index) sm->max_sw_if_index = dst_sw_if_index; @@ -85,7 +106,8 @@ set_interface_span_command_fn (vlib_main_t * vm, span_main_t *sm = &span_main; u32 src_sw_if_index = ~0; u32 dst_sw_if_index = ~0; - u8 state = 3; + u8 state = SPAN_BOTH; + span_feat_t sf = SPAN_FEAT_DEVICE; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { @@ -96,19 +118,21 @@ set_interface_span_command_fn (vlib_main_t * vm, sm->vnet_main, &dst_sw_if_index)) ; else if (unformat (input, "disable")) - state = 0; + state = SPAN_DISABLE; else if (unformat (input, "rx")) - state = 1; + state = SPAN_RX; else if (unformat (input, "tx")) - state = 2; + state = SPAN_TX; else if (unformat (input, "both")) - state = 3; + state = SPAN_BOTH; + else if (unformat (input, "l2")) + sf = SPAN_FEAT_L2; else break; } int rv = - span_add_delete_entry (vm, src_sw_if_index, dst_sw_if_index, state); + span_add_delete_entry (vm, src_sw_if_index, dst_sw_if_index, state, sf); if (rv == VNET_API_ERROR_INVALID_INTERFACE) return clib_error_return (0, "Invalid interface"); return 0; @@ -117,7 +141,7 @@ set_interface_span_command_fn (vlib_main_t * vm, /* *INDENT-OFF* */ VLIB_CLI_COMMAND (set_interface_span_command, static) = { .path = "set interface span", - .short_help = "set interface span [disable | destination [both|rx|tx]]", + .short_help = "set interface span [l2] {disable | destination [both|rx|tx]}", .function = set_interface_span_command_fn, }; /* *INDENT-ON* */ @@ -136,31 +160,44 @@ show_interfaces_span_command_fn (vlib_main_t * vm, /* *INDENT-OFF* */ vec_foreach (si, sm->interfaces) - if (si->num_rx_mirror_ports || si->num_tx_mirror_ports) + { + span_mirror_t * drxm = &si->mirror_rxtx[SPAN_FEAT_DEVICE][VLIB_RX]; + span_mirror_t * dtxm = &si->mirror_rxtx[SPAN_FEAT_DEVICE][VLIB_TX]; + + span_mirror_t * lrxm = &si->mirror_rxtx[SPAN_FEAT_L2][VLIB_RX]; + span_mirror_t * ltxm = &si->mirror_rxtx[SPAN_FEAT_L2][VLIB_TX]; + + if (drxm->num_mirror_ports || dtxm->num_mirror_ports || + lrxm->num_mirror_ports || ltxm->num_mirror_ports) { - clib_bitmap_t *b; u32 i; - b = clib_bitmap_dup_or (si->rx_mirror_ports, si->tx_mirror_ports); + clib_bitmap_t *d = clib_bitmap_dup_or (drxm->mirror_ports, dtxm->mirror_ports); + clib_bitmap_t *l = clib_bitmap_dup_or (lrxm->mirror_ports, ltxm->mirror_ports); + clib_bitmap_t *b = clib_bitmap_dup_or (d, l); if (header) { - vlib_cli_output (vm, "%-40s %s", "Source interface", - "Mirror interface (direction)"); + vlib_cli_output (vm, "%-20s %-20s %6s %6s", "Source", "Destination", + "Device", "L2"); header = 0; } s = format (s, "%U", format_vnet_sw_if_index_name, vnm, si - sm->interfaces); clib_bitmap_foreach (i, b, ( { - int state; - state = (clib_bitmap_get (si->rx_mirror_ports, i) + - clib_bitmap_get (si->tx_mirror_ports, i) * 2); + int device = (clib_bitmap_get (drxm->mirror_ports, i) + + clib_bitmap_get (dtxm->mirror_ports, i) * 2); + int l2 = (clib_bitmap_get (lrxm->mirror_ports, i) + + clib_bitmap_get (ltxm->mirror_ports, i) * 2); - vlib_cli_output (vm, "%-40v %U (%s)", s, + vlib_cli_output (vm, "%-20v %-20U (%6s) (%6s)", s, format_vnet_sw_if_index_name, vnm, i, - states[state]); + states[device], states[l2]); vec_reset_length (s); })); clib_bitmap_free (b); + clib_bitmap_free (l); + clib_bitmap_free (d); + } } /* *INDENT-ON* */ vec_free (s); @@ -175,19 +212,6 @@ VLIB_CLI_COMMAND (show_interfaces_span_command, static) = { }; /* *INDENT-ON* */ -static clib_error_t * -span_init (vlib_main_t * vm) -{ - span_main_t *sm = &span_main; - - sm->vlib_main = vm; - sm->vnet_main = vnet_get_main (); - - return 0; -} - -VLIB_INIT_FUNCTION (span_init); - /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vnet/span/span.h b/src/vnet/span/span.h index a98b010b..10de8272 100644 --- a/src/vnet/span/span.h +++ b/src/vnet/span/span.h @@ -18,17 +18,32 @@ #include #include +#include + +typedef enum +{ + SPAN_FEAT_DEVICE, + SPAN_FEAT_L2, + SPAN_FEAT_N +} span_feat_t; + +typedef struct +{ + clib_bitmap_t *mirror_ports; + u32 num_mirror_ports; +} span_mirror_t; typedef struct { - clib_bitmap_t *rx_mirror_ports; - clib_bitmap_t *tx_mirror_ports; - u32 num_rx_mirror_ports; - u32 num_tx_mirror_ports; + span_mirror_t mirror_rxtx[SPAN_FEAT_N][VLIB_N_RX_TX]; } span_interface_t; typedef struct { + /* l2 feature Next nodes */ + u32 l2_input_next[32]; + u32 l2_output_next[32]; + /* per-interface vector of span instances */ span_interface_t *interfaces; @@ -52,7 +67,7 @@ typedef struct int span_add_delete_entry (vlib_main_t * vm, u32 src_sw_if_index, - u32 dst_sw_if_index, u8 is_add); + u32 dst_sw_if_index, u8 state, span_feat_t sf); /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vnet/span/span_api.c b/src/vnet/span/span_api.c index b4565663..69fa8e97 100644 --- a/src/vnet/span/span_api.c +++ b/src/vnet/span/span_api.c @@ -56,7 +56,8 @@ static void vlib_main_t *vm = vlib_get_main (); rv = span_add_delete_entry (vm, ntohl (mp->sw_if_index_from), - ntohl (mp->sw_if_index_to), mp->state); + ntohl (mp->sw_if_index_to), mp->state, + mp->is_l2 ? SPAN_FEAT_L2 : SPAN_FEAT_DEVICE); REPLY_MACRO (VL_API_SW_INTERFACE_SPAN_ENABLE_DISABLE_REPLY); } @@ -76,11 +77,14 @@ vl_api_sw_interface_span_dump_t_handler (vl_api_sw_interface_span_dump_t * mp) /* *INDENT-OFF* */ vec_foreach (si, sm->interfaces) - if (si->num_rx_mirror_ports || si->num_tx_mirror_ports) + { + span_mirror_t * drxm = &si->mirror_rxtx[SPAN_FEAT_DEVICE][VLIB_RX]; + span_mirror_t * dtxm = &si->mirror_rxtx[SPAN_FEAT_DEVICE][VLIB_TX]; + if (drxm->num_mirror_ports || dtxm->num_mirror_ports) { clib_bitmap_t *b; u32 i; - b = clib_bitmap_dup_or (si->rx_mirror_ports, si->tx_mirror_ports); + b = clib_bitmap_dup_or (drxm->mirror_ports, dtxm->mirror_ports); clib_bitmap_foreach (i, b, ( { rmp = vl_msg_api_alloc (sizeof (*rmp)); @@ -90,13 +94,14 @@ vl_api_sw_interface_span_dump_t_handler (vl_api_sw_interface_span_dump_t * mp) rmp->sw_if_index_from = htonl (si - sm->interfaces); rmp->sw_if_index_to = htonl (i); - rmp->state = (u8) (clib_bitmap_get (si->rx_mirror_ports, i) + - clib_bitmap_get (si->tx_mirror_ports, i) * 2); + rmp->state = (u8) (clib_bitmap_get (drxm->mirror_ports, i) + + clib_bitmap_get (dtxm->mirror_ports, i) * 2); vl_msg_api_send_shmem (q, (u8 *) & rmp); })); clib_bitmap_free (b); } + } /* *INDENT-ON* */ } diff --git a/src/vpp/api/custom_dump.c b/src/vpp/api/custom_dump.c index 7f3a58d9..55a362a3 100644 --- a/src/vpp/api/custom_dump.c +++ b/src/vpp/api/custom_dump.c @@ -2227,6 +2227,9 @@ static void *vl_api_sw_interface_span_enable_disable_t_print s = format (s, "src_sw_if_index %u ", ntohl (mp->sw_if_index_from)); s = format (s, "dst_sw_if_index %u ", ntohl (mp->sw_if_index_to)); + if (mp->is_l2) + s = format (s, "l2 "); + switch (mp->state) { case 0: diff --git a/test/test_span.py b/test/test_span.py index d8b65252..f2529e8f 100644 --- a/test/test_span.py +++ b/test/test_span.py @@ -3,63 +3,121 @@ import unittest from scapy.packet import Raw -from scapy.layers.l2 import Ether +from scapy.layers.l2 import Ether, Dot1Q, GRE from scapy.layers.inet import IP, UDP +from scapy.layers.vxlan import VXLAN from framework import VppTestCase, VppTestRunner from util import Host, ppp +from vpp_sub_interface import VppDot1QSubint, VppDot1ADSubint +from vpp_gre_interface import VppGreInterface, VppGre6Interface +from vpp_papi_provider import L2_VTR_OP +from collections import namedtuple + +Tag = namedtuple('Tag', ['dot1', 'vlan']) +DOT1AD = 0x88A8 +DOT1Q = 0x8100 class TestSpan(VppTestCase): """ SPAN Test Case """ - # Test variables - hosts_nr = 10 # Number of hosts - pkts_per_burst = 257 # Number of packets per burst - @classmethod def setUpClass(cls): super(TestSpan, cls).setUpClass() - - def setUp(self): - super(TestSpan, self).setUp() - + # Test variables + cls.hosts_nr = 10 # Number of hosts + cls.pkts_per_burst = 257 # Number of packets per burst # create 3 pg interfaces - self.create_pg_interfaces(range(3)) + cls.create_pg_interfaces(range(3)) + cls.bd_id = 55 + cls.sub_if = VppDot1QSubint(cls, cls.pg0, 100) + cls.dst_sub_if = VppDot1QSubint(cls, cls.pg2, 300) + cls.dst_sub_if.set_vtr(L2_VTR_OP.L2_POP_1, tag=300) # packet flows mapping pg0 -> pg1, pg2 -> pg3, etc. - self.flows = dict() - self.flows[self.pg0] = [self.pg1] + cls.flows = dict() + cls.flows[cls.pg0] = [cls.pg1] # packet sizes - self.pg_if_packet_sizes = [64, 512] # , 1518, 9018] + cls.pg_if_packet_sizes = [64, 512] # , 1518, 9018] - self.interfaces = list(self.pg_interfaces) + cls.interfaces = list(cls.pg_interfaces) # Create host MAC and IPv4 lists - # self.MY_MACS = dict() - # self.MY_IP4S = dict() - self.create_host_lists(TestSpan.hosts_nr) - - # Create bi-directional cross-connects between pg0 and pg1 - self.vapi.sw_interface_set_l2_xconnect( - self.pg0.sw_if_index, self.pg1.sw_if_index, enable=1) - self.vapi.sw_interface_set_l2_xconnect( - self.pg1.sw_if_index, self.pg0.sw_if_index, enable=1) + # cls.MY_MACS = dict() + # cls.MY_IP4S = dict() + cls.create_host_lists(cls.hosts_nr) # setup all interfaces - for i in self.interfaces: + for i in cls.interfaces: i.admin_up() i.config_ip4() i.resolve_arp() - # Enable SPAN on pg0 (mirrored to pg2) - self.vapi.sw_interface_span_enable_disable( - self.pg0.sw_if_index, self.pg2.sw_if_index) + cls.vxlan = cls.vapi.vxlan_add_del_tunnel( + src_addr=cls.pg2.local_ip4n, + dst_addr=cls.pg2.remote_ip4n, + vni=1111, + is_add=1) + + def setUp(self): + super(TestSpan, self).setUp() + self.reset_packet_infos() def tearDown(self): super(TestSpan, self).tearDown() + if not self.vpp_dead: + self.logger.info(self.vapi.ppcli("show interface span")) + + def xconnect(self, a, b, is_add=1): + self.vapi.sw_interface_set_l2_xconnect(a, b, enable=is_add) + self.vapi.sw_interface_set_l2_xconnect(b, a, enable=is_add) + + def bridge(self, sw_if_index, is_add=1): + self.vapi.sw_interface_set_l2_bridge( + sw_if_index, bd_id=self.bd_id, enable=is_add) + + def _remove_tag(self, packet, vlan, tag_type): + self.assertEqual(packet.type, tag_type) + payload = packet.payload + self.assertEqual(payload.vlan, vlan) + inner_type = payload.type + payload = payload.payload + packet.remove_payload() + packet.add_payload(payload) + packet.type = inner_type + + def remove_tags(self, packet, tags): + for t in tags: + self._remove_tag(packet, t.vlan, t.dot1) + return packet + def decap_gre(self, pkt): + """ + Decapsulate the original payload frame by removing GRE header + """ + self.assertEqual(pkt[Ether].src, self.pg2.local_mac) + self.assertEqual(pkt[Ether].dst, self.pg2.remote_mac) + + self.assertEqual(pkt[IP].src, self.pg2.local_ip4) + self.assertEqual(pkt[IP].dst, self.pg2.remote_ip4) + + return pkt[GRE].payload + + def decap_vxlan(self, pkt): + """ + Decapsulate the original payload frame by removing VXLAN header + """ + self.assertEqual(pkt[Ether].src, self.pg2.local_mac) + self.assertEqual(pkt[Ether].dst, self.pg2.remote_mac) + + self.assertEqual(pkt[IP].src, self.pg2.local_ip4) + self.assertEqual(pkt[IP].dst, self.pg2.remote_ip4) + + return pkt[VXLAN].payload + + @classmethod def create_host_lists(self, count): """ Method to create required number of MAC and IPv4 addresses. Create required number of host MAC addresses and distribute them among @@ -81,9 +139,9 @@ class TestSpan(VppTestCase): "172.17.1%02x.%u" % (pg_if.sw_if_index, j)) hosts.append(host) - def create_stream(self, src_if, packet_sizes): + def create_stream(self, src_if, packet_sizes, do_dot1=False): pkts = [] - for i in range(0, TestSpan.pkts_per_burst): + for i in range(0, self.pkts_per_burst): dst_if = self.flows[src_if][0] pkt_info = self.create_packet_info(src_if, dst_if) payload = self.info_to_payload(pkt_info) @@ -91,6 +149,8 @@ class TestSpan(VppTestCase): IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) / UDP(sport=1234, dport=1234) / Raw(payload)) + if do_dot1: + p = self.sub_if.add_dot1_layer(p) pkt_info.data = p.copy() size = packet_sizes[(i / 2) % len(packet_sizes)] self.extend_packet(p, size) @@ -161,8 +221,8 @@ class TestSpan(VppTestCase): "Port %u: Packet expected from source %u didn't" " arrive" % (dst_sw_if_index, i.sw_if_index)) - def test_span(self): - """ SPAN test + def test_device_span(self): + """ SPAN device rx mirror test Test scenario: 1. config @@ -173,10 +233,17 @@ class TestSpan(VppTestCase): burst of packets per interface """ + # Create bi-directional cross-connects between pg0 and pg1 + self.xconnect(self.pg0.sw_if_index, self.pg1.sw_if_index) # Create incoming packet streams for packet-generator interfaces pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes) self.pg0.add_stream(pkts) + # Enable SPAN on pg0 (mirrored to pg2) + self.vapi.sw_interface_span_enable_disable( + self.pg0.sw_if_index, self.pg2.sw_if_index) + + self.logger.info(self.vapi.ppcli("show interface span")) # Enable packet capturing and start packet sending self.pg_enable_capture(self.pg_interfaces) self.pg_start() @@ -190,6 +257,225 @@ class TestSpan(VppTestCase): self.pg1.get_capture(), self.pg2.get_capture(pg2_expected)) + # Disable SPAN on pg0 (mirrored to pg2) + self.vapi.sw_interface_span_enable_disable( + self.pg0.sw_if_index, self.pg2.sw_if_index, state=0) + self.xconnect(self.pg0.sw_if_index, self.pg1.sw_if_index, is_add=0) + + def test_span_l2_rx(self): + """ SPAN l2 rx mirror test """ + + self.sub_if.admin_up() + + self.bridge(self.pg2.sw_if_index) + # Create bi-directional cross-connects between pg0 and pg1 + self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index) + # Create incoming packet streams for packet-generator interfaces + pkts = self.create_stream( + self.pg0, self.pg_if_packet_sizes, do_dot1=True) + self.pg0.add_stream(pkts) + + # Enable SPAN on pg0 (mirrored to pg2) + self.vapi.sw_interface_span_enable_disable( + self.sub_if.sw_if_index, self.pg2.sw_if_index, is_l2=1) + + self.logger.info(self.vapi.ppcli("show interface span")) + # Enable packet capturing and start packet sending + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # Verify packets outgoing packet streams on mirrored interface (pg2) + self.logger.info("Verifying capture on interfaces %s and %s" % + (self.pg1.name, self.pg2.name)) + pg2_expected = self.get_packet_count_for_if_idx(self.pg1.sw_if_index) + pg1_pkts = self.pg1.get_capture() + pg2_pkts = self.pg2.get_capture(pg2_expected) + self.verify_capture( + self.pg1, + pg1_pkts, + pg2_pkts) + + self.bridge(self.pg2.sw_if_index, is_add=0) + # Disable SPAN on pg0 (mirrored to pg2) + self.vapi.sw_interface_span_enable_disable( + self.sub_if.sw_if_index, self.pg2.sw_if_index, state=0, is_l2=1) + self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=0) + + def test_span_l2_rx_dst_vxlan(self): + """ SPAN l2 rx mirror into vxlan test """ + + self.sub_if.admin_up() + self.vapi.sw_interface_set_flags(self.vxlan.sw_if_index, + admin_up_down=1) + + self.bridge(self.vxlan.sw_if_index, is_add=1) + # Create bi-directional cross-connects between pg0 and pg1 + self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index) + # Create incoming packet streams for packet-generator interfaces + pkts = self.create_stream( + self.pg0, self.pg_if_packet_sizes, do_dot1=True) + self.pg0.add_stream(pkts) + + # Enable SPAN on pg0 sub if (mirrored to vxlan) + self.vapi.sw_interface_span_enable_disable( + self.sub_if.sw_if_index, self.vxlan.sw_if_index, is_l2=1) + + self.logger.info(self.vapi.ppcli("show interface span")) + # Enable packet capturing and start packet sending + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # Verify packets outgoing packet streams on mirrored interface (pg2) + self.logger.info("Verifying capture on interfaces %s and %s" % + (self.pg1.name, self.pg2.name)) + pg2_expected = self.get_packet_count_for_if_idx(self.pg1.sw_if_index) + pg1_pkts = self.pg1.get_capture() + pg2_pkts = [self.decap_vxlan(p) + for p in self.pg2.get_capture(pg2_expected)] + self.verify_capture( + self.pg1, + pg1_pkts, + pg2_pkts) + + self.bridge(self.vxlan.sw_if_index, is_add=0) + # Disable SPAN on pg0 sub if (mirrored to vxlan) + self.vapi.sw_interface_span_enable_disable( + self.sub_if.sw_if_index, self.vxlan.sw_if_index, state=0, is_l2=1) + self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=0) + + def test_span_l2_rx_dst_gre_subif_vtr(self): + """ SPAN l2 rx mirror into gre-subif+vtr """ + + self.sub_if.admin_up() + + gre_if = VppGreInterface(self, self.pg2.local_ip4, + self.pg2.remote_ip4, + is_teb=1) + + gre_if.add_vpp_config() + gre_if.admin_up() + + gre_sub_if = VppDot1QSubint(self, gre_if, 500) + gre_sub_if.set_vtr(L2_VTR_OP.L2_POP_1, tag=500) + gre_sub_if.admin_up() + + self.bridge(gre_sub_if.sw_if_index) + # Create bi-directional cross-connects between pg0 and pg1 + self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=1) + + # Create incoming packet streams for packet-generator interfaces + pkts = self.create_stream( + self.pg0, self.pg_if_packet_sizes, do_dot1=True) + self.pg0.add_stream(pkts) + + self.vapi.sw_interface_span_enable_disable( + self.sub_if.sw_if_index, gre_sub_if.sw_if_index, is_l2=1) + + # Enable packet capturing and start packet sending + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # Verify packets outgoing packet streams on mirrored interface (pg2) + self.logger.info("Verifying capture on interfaces %s and %s" % + (self.pg1.name, self.pg2.name)) + pg2_expected = self.get_packet_count_for_if_idx(self.pg1.sw_if_index) + pg1_pkts = self.pg1.get_capture() + pg2_pkts = self.pg2.get_capture(pg2_expected) + pg2_decaped = [self.remove_tags(self.decap_gre( + p), [Tag(dot1=DOT1Q, vlan=500)]) for p in pg2_pkts] + self.verify_capture( + self.pg1, + pg1_pkts, + pg2_decaped) + + self.bridge(gre_sub_if.sw_if_index, is_add=0) + # Disable SPAN on pg0 sub if + self.vapi.sw_interface_span_enable_disable( + self.sub_if.sw_if_index, gre_sub_if.sw_if_index, state=0, + is_l2=1) + gre_if.remove_vpp_config() + self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=0) + + def test_span_l2_rx_dst_vtr(self): + """ SPAN l2 rx mirror into subif+vtr """ + + self.sub_if.admin_up() + self.dst_sub_if.admin_up() + + self.bridge(self.dst_sub_if.sw_if_index) + # Create bi-directional cross-connects between pg0 and pg1 + self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=1) + + # Create incoming packet streams for packet-generator interfaces + pkts = self.create_stream( + self.pg0, self.pg_if_packet_sizes, do_dot1=True) + self.pg0.add_stream(pkts) + + self.vapi.sw_interface_span_enable_disable( + self.sub_if.sw_if_index, self.dst_sub_if.sw_if_index, is_l2=1) + + # Enable packet capturing and start packet sending + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # Verify packets outgoing packet streams on mirrored interface (pg2) + self.logger.info("Verifying capture on interfaces %s and %s" % + (self.pg1.name, self.pg2.name)) + pg2_expected = self.get_packet_count_for_if_idx(self.pg1.sw_if_index) + pg1_pkts = self.pg1.get_capture() + pg2_pkts = self.pg2.get_capture(pg2_expected) + pg2_untagged = [self.remove_tags(p, [Tag(dot1=DOT1Q, vlan=300)]) + for p in pg2_pkts] + self.verify_capture( + self.pg1, + pg1_pkts, + pg2_untagged) + + self.bridge(self.dst_sub_if.sw_if_index, is_add=0) + # Disable SPAN on pg0 sub if (mirrored to vxlan) + self.vapi.sw_interface_span_enable_disable( + self.sub_if.sw_if_index, self.dst_sub_if.sw_if_index, state=0, + is_l2=1) + self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=0) + + def test_l2_tx_span(self): + """ SPAN l2 tx mirror test """ + + self.sub_if.admin_up() + self.bridge(self.pg2.sw_if_index) + # Create bi-directional cross-connects between pg0 and pg1 + self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index) + # Create incoming packet streams for packet-generator interfaces + pkts = self.create_stream( + self.pg0, self.pg_if_packet_sizes, do_dot1=True) + self.pg0.add_stream(pkts) + + # Enable SPAN on pg0 (mirrored to pg2) + self.vapi.sw_interface_span_enable_disable( + self.pg1.sw_if_index, self.pg2.sw_if_index, is_l2=1, state=2) + + self.logger.info(self.vapi.ppcli("show interface span")) + # Enable packet capturing and start packet sending + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # Verify packets outgoing packet streams on mirrored interface (pg2) + self.logger.info("Verifying capture on interfaces %s and %s" % + (self.pg1.name, self.pg2.name)) + pg2_expected = self.get_packet_count_for_if_idx(self.pg1.sw_if_index) + pg1_pkts = self.pg1.get_capture() + pg2_pkts = self.pg2.get_capture(pg2_expected) + self.verify_capture( + self.pg1, + pg1_pkts, + pg2_pkts) + + self.bridge(self.pg2.sw_if_index, is_add=0) + # Disable SPAN on pg0 (mirrored to pg2) + self.vapi.sw_interface_span_enable_disable( + self.pg1.sw_if_index, self.pg2.sw_if_index, state=0, is_l2=1) + self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=0) + if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 11e16e49..204d9e31 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -847,17 +847,20 @@ class VppPapiProvider(object): ) def sw_interface_span_enable_disable( - self, sw_if_index_from, sw_if_index_to, state=1): + self, sw_if_index_from, sw_if_index_to, state=1, is_l2=0): """ :param sw_if_index_from: :param sw_if_index_to: :param state: + :param is_l2: """ return self.api(self.papi.sw_interface_span_enable_disable, {'sw_if_index_from': sw_if_index_from, 'sw_if_index_to': sw_if_index_to, - 'state': state}) + 'state': state, + 'is_l2': is_l2, + }) def gre_tunnel_add_del(self, src_address, -- cgit 1.2.3-korg