diff --git a/ansible/roles/test/files/helpers/bfd_responder.py b/ansible/roles/test/files/helpers/bfd_responder.py index ab38adf08a7..59b55206423 100644 --- a/ansible/roles/test/files/helpers/bfd_responder.py +++ b/ansible/roles/test/files/helpers/bfd_responder.py @@ -180,3 +180,4 @@ def main(): if __name__ == '__main__': main() + diff --git a/ansible/roles/test/files/ptftests/bfd_responder.py b/ansible/roles/test/files/ptftests/bfd_responder.py new file mode 100644 index 00000000000..59aa8f303fc --- /dev/null +++ b/ansible/roles/test/files/ptftests/bfd_responder.py @@ -0,0 +1,169 @@ +# PTF bfd responder. Responds to any BFD packet that we received. +# Uses a monitor file as input. The monitor file has 2 lines: +# Line 1: list of port indices to monitor +# Line 2: list of ip addresses to respond to. + +import ptf +import time +import ptf.packet as scapy +from ptf.base_tests import BaseTest +from scapy.contrib.bfd import BFD +from ptf.testutils import (send_packet, test_params_get) +from ipaddress import ip_address, IPv4Address, IPv6Address +session_timeout = 1 + + +class BFD_Responder(BaseTest): + def __init__(self): + BaseTest.__init__(self) + self.DEFAULT_PKT_LEN = 100 + self.sessions = {} + self.local_disc_base = 0xcdba0000 + self.local_src_port = 14000 + + def setUp(self): + self.dataplane = ptf.dataplane_instance + self.test_params = test_params_get() + self.dut_mac = self.test_params['dut_mac'] + self.dut_loop_ips = self.test_params['dut_loop_ips'] + for ipaddr in self.dut_loop_ips: + if isinstance(ip_address(ipaddr.decode()), IPv4Address): + self.dut_loop_ipv4 = ipaddr + if isinstance(ip_address(ipaddr.decode()), IPv6Address): + self.dut_loop_ipv6 = ipaddr + self.monitor_file = self.test_params['monitor_file'] + + def respond_to_packet(self, port_number, received_pkt): + received_pkt = scapy.Ether(received_pkt) + args = {} + args['dst_mac'] = received_pkt['Ether'].dst + args['version'] = received_pkt['BFD'].version + args['diag'] = received_pkt['BFD'].diag + args['sta'] = received_pkt['BFD'].sta + args['flags'] = received_pkt['BFD'].flags + args['detect_multi'] = received_pkt['BFD'].detect_multi + args['len'] = received_pkt['BFD'].len + args['my_discriminator'] = received_pkt['BFD'].my_discriminator + args['your_discriminator'] = received_pkt['BFD'].your_discriminator + args['min_tx_interval'] = received_pkt['BFD'].min_tx_interval + args['min_rx_interval'] = received_pkt['BFD'].min_rx_interval + args['echo_rx_interval'] = received_pkt['BFD'].echo_rx_interval + + pkt = BFD(args) + count = send_packet(self, port_number, str(pkt)) + if count == 0: + raise RuntimeError( + "send_packet failed args:port_number{}, " + "dp_tuple:{}".format(port_number, str(pkt))) + + def runTest(self): + while True: + valid_monit_file = True + with open(self.monitor_file) as fd: + full_strings = fd.readlines() + try: + ports_to_monitor = full_strings[0].strip() + all_monitored_addresses = full_strings[1].strip() + except IndexError: + valid_monit_file = False + if ports_to_monitor == "" or all_monitored_addresses == "": + valid_monit_file = False + + if not valid_monit_file: + time.sleep(1) + continue + ports_to_monitor = [int(x) for x in ports_to_monitor.split(',')] + all_monitored_addresses = all_monitored_addresses.split(',') + + result = self.dataplane.poll(device_number=0, timeout=0.1) + if not isinstance(result, self.dataplane.PollSuccess) or \ + result.port not in ports_to_monitor or \ + "UDP" not in scapy.Ether(result.packet): + continue + if scapy.Ether(result.packet)['UDP'].dport != 4784: + continue + received_pkt = result.packet + port_number = result.port + mac_src, mac_dst, ip_src, ip_dst, bfd_remote_disc, bfd_state = \ + self.extract_bfd_info(received_pkt) + if ip_dst not in all_monitored_addresses: + continue + try: + session = self.sessions[ip_dst] + except KeyError: + self.sessions[ip_dst] = {} + + if bfd_state == 3: + count = send_packet(self, result.port, str(session["pkt"])) + if count == 0: + raise RuntimeError( + "send_packet failed args:port_number{}, " + "dp_tuple:{}".format(port_number, str(session['pkt']))) + + if bfd_state == 2: + continue + + session = {} + session['addr'] = ip_dst + session['remote_addr'] = ip_src + session['intf'] = result.port + session['multihop'] = True + session['mac'] = mac_dst + session['pkt'] = '' + session["src_port"] = self.local_src_port + self.local_disc_base += 1 + self.local_src_port += 1 + session['my_disc'] = self.local_disc_base + session["other_disc"] = bfd_remote_disc + + bfd_pkt_init = self.craft_bfd_packet( + session['my_disc'], + received_pkt, + mac_src, + mac_dst, + ip_src, + ip_dst, + bfd_remote_disc, + 2) + count = send_packet(self, session['intf'], str(bfd_pkt_init)) + if count == 0: + raise RuntimeError( + "send_packet failed args:port_number{}, " + "dp_tuple:{}".format(port_number, str(bfd_pkt_init))) + bfd_pkt_init.payload.payload.payload.load.sta = 3 + session["pkt"] = bfd_pkt_init + self.sessions[ip_dst] = session + + def extract_bfd_info(self, data): + # remote_mac, remote_ip, request_ip, op_type + ether = scapy.Ether(data) + mac_src = ether.src + mac_dst = ether.dst + ip_src = ether.payload.src + ip_dst = ether.payload.dst + bfdpkt = BFD(ether.payload.payload.payload.load) + bfd_remote_disc = bfdpkt.my_discriminator + bfd_state = bfdpkt.sta + return mac_src, mac_dst, ip_src, ip_dst, bfd_remote_disc, bfd_state + + def craft_bfd_packet(self, + my_discriminator, + data, + mac_src, + mac_dst, + ip_src, + ip_dst, + bfd_remote_disc, + bfd_state): + ethpart = scapy.Ether(data) + bfdpart = BFD(ethpart.payload.payload.payload.load) + bfdpart.my_discriminator = my_discriminator + bfdpart.your_discriminator = bfd_remote_disc + bfdpart.sta = bfd_state + + ethpart.payload.payload.payload.load = bfdpart + ethpart.src = mac_dst + ethpart.dst = mac_src + ethpart.payload.src = ip_dst + ethpart.payload.dst = ip_src + return ethpart diff --git a/ansible/roles/test/files/ptftests/vxlan_traffic.py b/ansible/roles/test/files/ptftests/vxlan_traffic.py index ee57b3b4f26..c6d4bfa4e9a 100644 --- a/ansible/roles/test/files/ptftests/vxlan_traffic.py +++ b/ansible/roles/test/files/ptftests/vxlan_traffic.py @@ -1,51 +1,116 @@ -# ptf --test-dir ptftests vxlan_traffic.VXLAN --platform-dir ptftests --qlen=1000 --platform remote \ -# -t 't2_ports=[16, 17, 0, 1, 4, 5, 21, 20];dut_mac=u"64:3a:ea:c1:73:f8";expect_encap_success=True;packet_count=10; \ -# vxlan_port=4789;topo_file="/tmp/vxlan_topo_file.json";config_file="/tmp/vxlan-config-TC1-v6_in_v4.json";t0_ports=[u"Ethernet42"]' --relax --debug info \ -# --log-file /tmp/vxlan-tests.TC1.v6_in_v4.log - -# The test checks vxlan encapsulation: -# The test runs three tests for each vlan on the DUT: -# 'test_encap' : Sends regular packets to T0-facing interface and expects to see the encapsulated packets on the T2-facing interfaces. -# -# The test has the following parameters: -# 1. 'config_file' is a filename of a file which contains all necessary information to run the test. The file is populated by ansible. This parameter is mandatory. - -import sys +# VxLAN Traffic Script, to be run in PTF container. Usage: +# ptf --test-dir ptftests vxlan_traffic.VXLAN --platform-dir ptftests +# --qlen=1000 --platform remote -t +# 't2_ports=[16, 17, 0, 1, 4, 5, 21, 20];dut_mac=u"64:3a:ea:c1:73:f8";\ +# expect_encap_success=True;packet_count=10;downed_endpoints=["100.0.1.10"]\ +# vxlan_port=4789;topo_file="/tmp/vxlan_topo_file.json";config_file=\ +# "/tmp/vxlan-config-TC1-v6_in_v4.json";t0_ports=[u"Ethernet42"];\ +# random_src_ip=False;random_dport=True;random_dport=False' --relax +# --debug info --log-file /tmp/vxlan-tests.TC1.v6_in_v4.log + +''' + The test checks vxlan encapsulation: + 'test_encap' : Sends regular packets to T0-facing interface and expects to + see the encapsulated packets on the T2-facing interfaces. + + The test has the following parameters: + config_file : is a filename of a file which contains all + necessary information to run the test. The file is + populated by ansible. This parameter is mandatory. + t2_ports : The list of PTF port indices facing T2 Neighbors, + AKA ports to expect the encapsulated packets to + come in. + dut_mac : The MAC address of the dut, given by "show + platform summary". + expect_encap_success : Is the encapsulation expected to succeed ? + True/False. + packet_count : Number of packets per endpoint to try. Default 10 + downned_endpoints : The list of IP addresses which are down, due to BFD + being disabled. + vxlan_port : The global VxLAN port setup in the DUT. + Default: 4789 + topo_file : The file that contains the topology information, + like minigraph data, connections, and so on. + t0_ports : The DUT intf into which we will inject payload + packets. + random_src_ip : Should we use random src IP addresses for the + payload packets? Default:False + random_dport : Should we use random dest port for the payload + packets? Default:True + random_sport : Should we use random src port for the payload + packets? Default:False +''' + import os.path import json +from datetime import datetime +import logging +import random +from ipaddress import ip_address, IPv4Address, IPv6Address import ptf import ptf.packet as scapy from ptf.base_tests import BaseTest -from ptf import config -from ptf.testutils import (simple_tcp_packet, simple_tcpv6_packet, simple_vxlan_packet, simple_vxlanv6_packet, - verify_packet_any_port, verify_no_packet_any, - send_packet, test_params_get) -from ptf.dataplane import match_exp_pkt +from ptf.testutils import ( + simple_tcp_packet, + simple_tcpv6_packet, + simple_vxlan_packet, + simple_vxlanv6_packet, + verify_no_packet_any, + send_packet, + test_params_get, + dp_poll) from ptf.mask import Mask -import datetime -import subprocess -import logging -from ipaddress import ip_address, IPv4Address, IPv6Address -import random VARS = {} VARS['tcp_sport'] = 1234 VARS['tcp_dport'] = 5000 -logger = logging.getLogger(__name__) +Logger = logging.getLogger(__name__) # Some constants used in this code MIN_PACKET_COUNT = 4 MINIMUM_PACKETS_FOR_ECMP_VALIDATION = 300 TEST_ECN = True +Address_Count = 0 + + +def get_ip_address(af, hostid=1, netid=100): + ''' + Get a new IP address to use based on the arguments. + hostid : The last octet in the Address. + netid : The first octet in the Address. + ''' + global Address_Count + third_octet = Address_Count % 255 + second_octet = (Address_Count / 255) % 255 + first_octet = netid + (Address_Count / 65025) + Address_Count = Address_Count + 1 + if af == 'v4': + return "{}.{}.{}.{}".format( + first_octet, second_octet, third_octet, hostid).decode() + if af == 'v6': + # :0: gets removed in the IPv6 addresses. + # Adding a to octets, to avoid it. + return "fddd:a{}:a{}::a{}:{}".format( + first_octet, second_octet, third_octet, hostid).decode() + + def get_incremental_value(key): + ''' + Global function to keep track of the tcp/udp port numbers used in + payload. + ''' global VARS # We would like to use the ports from 1234 to 65535 VARS[key] = max(1234, (VARS[key] + 1) % 65535) return VARS[key] + def read_ptf_macs(): + ''' + Get the list of mac addresses of all interfaces in the PTF. + ''' addrs = {} for intf in os.listdir('/sys/class/net'): if os.path.isdir('/sys/class/net/%s' % intf): @@ -54,22 +119,40 @@ def read_ptf_macs(): return addrs + class VXLAN(BaseTest): + ''' + Testcase for VxLAN. Currently implements encap testcase. + decap is TBD. + ''' def __init__(self): BaseTest.__init__(self) - self.DEFAULT_PKT_LEN = 100 def setUp(self): + ''' + Setup the internal structures for running the test. + 1. Parse the command line arguments. + 2. Load the configs from the input files. + 3. Ready the mapping of destination->nexthops. + ''' self.dataplane = ptf.dataplane_instance self.test_params = test_params_get() + self.random_src_ip = self.test_params['random_src_ip'] + self.random_dport = self.test_params['random_dport'] + self.random_sport = self.test_params['random_sport'] + self.tolerance = self.test_params['tolerance'] self.dut_mac = self.test_params['dut_mac'] self.vxlan_port = self.test_params['vxlan_port'] self.expect_encap_success = self.test_params['expect_encap_success'] self.packet_count = self.test_params['packet_count'] + self.downed_endpoints = self.test_params['downed_endpoints'] + self.t2_ports = self.test_params['t2_ports'] # The ECMP check fails occasionally if there is not enough packets. # We should keep the packet count atleast MIN_PACKET_COUNT. if self.packet_count < MIN_PACKET_COUNT: - logger.warning("Packet_count is below minimum, resetting to {}", MIN_PACKET_COUNT) + Logger.warning( + "Packet_count is below minimum, resetting to %s", + MIN_PACKET_COUNT) self.packet_count = MIN_PACKET_COUNT self.random_mac = "00:aa:bb:cc:dd:ee" @@ -80,7 +163,6 @@ def setUp(self): self.topo_data = json.load(fp) self.fill_loopback_ip() - self.t2_ports = self.test_params['t2_ports'] self.nbr_info = self.config_data['neighbors'] self.packets = [] self.dataplane.flush() @@ -88,12 +170,19 @@ def setUp(self): return def tearDown(self): + ''' + Close the packet capture file. + ''' if self.vxlan_enabled: json.dump(self.packets, open("/tmp/vnet_pkts.json", 'w')) return def fill_loopback_ip(self): - loop_config_data = self.topo_data['minigraph_facts']['minigraph_lo_interfaces'] + ''' + Get the DUT's Loopback ipv4 ipv6 addresses from minigraph. + ''' + loop_config_data = \ + self.topo_data['minigraph_facts']['minigraph_lo_interfaces'] for entry in loop_config_data: if isinstance(ip_address(entry['addr']), IPv4Address): self.loopback_ipv4 = entry['addr'] @@ -101,131 +190,208 @@ def fill_loopback_ip(self): self.loopback_ipv6 = entry['addr'] def runTest(self): + ''' + Main code of this script. + Run the encap test for every destination, and its nexthops. + ''' + mg_facts = self.topo_data['minigraph_facts'] for t0_intf in self.test_params['t0_ports']: - # find the list of neigh addresses for the t0_ports. For each neigh address(Addr1): - # for each destination address(Addr2) in the same Vnet as t0_intf, + # find the list of neigh addresses for the t0_ports. + # For each neigh address(Addr1): + # For each destination address(Addr2) in the same Vnet as t0_intf, # send traffic from Add1 to it. If there - # are multiple nexthops for the Addr2, then send that many different - # streams(different tcp ports). + # are multiple nexthops for the Addr2, then send that + # many different streams(different tcp ports). neighbors = [self.config_data['neighbors'][t0_intf]] - ptf_port = self.topo_data['minigraph_facts']['minigraph_ptf_indices'][t0_intf] + ptf_port = mg_facts['minigraph_ptf_indices'][t0_intf] vnet = self.config_data['vnet_intf_map'][t0_intf] - vni = self.config_data['vnet_vni_map'][vnet] + vni = self.config_data['vnet_vni_map'][vnet] for addr in neighbors: - for destination,nh in self.config_data['dest_to_nh_map'][vnet].iteritems(): - self.test_encap(ptf_port, vni, addr, destination, nh, test_ecn=TEST_ECN) - - def cmd(self, cmds): - process = subprocess.Popen(cmds, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout, stderr = process.communicate() - return_code = process.returncode - - return stdout, stderr, return_code - - def read_ptf_macs(self): - addrs = {} - for intf in os.listdir('/sys/class/net'): - if os.path.isdir('/sys/class/net/%s' % intf): - with open('/sys/class/net/%s/address' % intf) as fp: - addrs[intf] = fp.read().strip() - - return addrs + for destination, nexthops in \ + self.config_data['dest_to_nh_map'][vnet].iteritems(): + self.test_encap( + ptf_port, + vni, + addr, + destination, + nexthops, + test_ecn=TEST_ECN, + random_dport=self.random_dport, + random_sport=self.random_sport, + random_src_ip=self.random_src_ip) - def verify_all_addresses_used_equally(self, nhs, returned_ip_addresses): + def verify_all_addresses_used_equally(self, + nhs, + returned_ip_addresses, + packet_count, + downed_endpoints=[]): ''' Verify the ECMP functionality using 2 checks. Check 1 verifies every nexthop address has been used. - Check 2 verifies the distribution of number of packets among the nexthops. - Params: nhs: the nexthops that are configured. - returned_ip_addresses: The dict containing the nh addresses and corresponding packet counts. + Check 2 verifies the distribution of number of packets among the + nexthops. + Params: + nhs : the nexthops that are configured. + returned_ip_addresses : The dict containing the nh addresses + and corresponding packet counts. ''' - # Check #1 : All addresses have been used. + + if downed_endpoints: + for down_endpoint in downed_endpoints: + if down_endpoint in nhs: + nhs.remove(down_endpoint) + if down_endpoint in returned_ip_addresses: + raise RuntimeError( + "We received traffic with a downed endpoint({}), " + "unexpected.".format(down_endpoint)) + + # Check #1 : All addresses have been used, except the downed ones. if set(nhs) - set(returned_ip_addresses.keys()) == set([]): - logger.info(" Each address has been used") - logger.info("Packets sent:{} distribution:".format(self.packet_count)) + Logger.info(" Each valid endpoint address has been used") + Logger.info("Packets sent:%s distribution:", packet_count) for nh_address in returned_ip_addresses.keys(): - logger.info(" {} : {}".format(nh_address, returned_ip_addresses[nh_address])) + Logger.info(" %s : %s", + nh_address, + returned_ip_addresses[nh_address]) # Check #2 : The packets are almost equally distributed. - # Every next-hop should have received within 1% of the packets that we sent per nexthop(which is self.packet_count). - # This check is valid only if there are large enough number of packets(300). Any lower number will need higher tolerance(more than 2%). - if self.packet_count > MINIMUM_PACKETS_FOR_ECMP_VALIDATION: - tolerance = 0.01 + # Every next-hop should have received within {tolerance}% of the + # packets that we sent per nexthop(which is packet_count). This + # check is valid only if there are large enough number of + # packets(300). Any lower number will need higher + # tolerance(more than 2%). + if packet_count > MINIMUM_PACKETS_FOR_ECMP_VALIDATION: for nh_address in returned_ip_addresses.keys(): - if (1.0-tolerance) * self.packet_count <= returned_ip_addresses[nh_address] <= (1.0+tolerance) * self.packet_count: + if (1.0-self.tolerance) * packet_count <= \ + returned_ip_addresses[nh_address] <= \ + (1.0+self.tolerance) * packet_count: pass else: - raise RuntimeError("ECMP nexthop address: {} received too less or too many of the " - "packets expected. Expected:{}, received on that address:{}".format(nh_address, self.packet_count, returned_ip_addresses[nh_address])) + raise RuntimeError( + "ECMP nexthop address: {} received too less or too" + " many of the packets expected. Expected:{}, " + "received on that address:{}".format( + nh_address, + packet_count, + returned_ip_addresses[nh_address])) + else: + raise RuntimeError( + "Not all addresses were used. Here are the unused ones:{}," + "expected:{}, got:{}".format( + set(nhs) - set(returned_ip_addresses.keys()), + nhs, + returned_ip_addresses)) - def test_encap(self, ptf_port, vni, ptf_addr, destination, nhs, test_ecn=False, vlan=0): - rv = True + def test_encap( + self, + ptf_port, + vni, + ptf_addr, + destination, + nhs, + test_ecn=False, + random_dport=True, + random_sport=False, + random_src_ip=False): + ''' + Test the encapsulation of packets works correctly. + 1. Send a TCP packet to the DUT port. + 2. Verify that the DUT returns an encapsulated packet correctly. + 3. Optionally: Perform if the ECMP is working(all nexthops are used + equally). + ''' try: - pkt_len = self.DEFAULT_PKT_LEN - if 'vlan' != 0: - tagged = True - pkt_len += 4 - else: - tagged = False - - options = {'ip_ecn' : 0} - options_v6 = {'ipv6_ecn' : 0} + pkt_len = 100 + + options = {'ip_ecn': 0} + options_v6 = {'ipv6_ecn': 0} if test_ecn: ecn = random.randint(0, 3) - options = {'ip_ecn' : ecn} - options_v6 = {'ipv6_ecn' : ecn} + options = {'ip_ecn': ecn} + options_v6 = {'ipv6_ecn': ecn} - # ECMP support, assume it is a string of comma seperated list of addresses. - returned_ip_addresses = {} + # ECMP support, assume it is a string of comma seperated list of + # addresses. check_ecmp = False - for host_address in nhs: + working_nhs = list(set(nhs) - set(self.downed_endpoints)) + expect_success = self.expect_encap_success + test_nhs = working_nhs + packet_count = self.packet_count + if not working_nhs: + # Since there is no NH that is up for this destination, + # we can't expect success here. + expect_success = False + test_nhs = nhs + # Also reduce the packet count, since this script has to wait + # 1 second per packet(1000 packets is 20 minutes). + packet_count = 4 + returned_ip_addresses = {} + for host_address in test_nhs: check_ecmp = True # This will ensure that every nh is used atleast once. - for i in range(self.packet_count): - tcp_sport = get_incremental_value('tcp_sport') - valid_combination = True - if isinstance(ip_address(destination), IPv4Address) and isinstance(ip_address(ptf_addr), IPv4Address): + Logger.info( + "Sending %s packets from port %s to %s", + packet_count, + str(ptf_port), + destination) + for _ in range(packet_count): + if random_sport: + tcp_sport = get_incremental_value('tcp_sport') + else: + tcp_sport = VARS['tcp_sport'] + if random_dport: + tcp_dport = get_incremental_value('tcp_dport') + else: + tcp_dport = VARS['tcp_dport'] + if isinstance(ip_address(destination), IPv4Address) and \ + isinstance(ip_address(ptf_addr), IPv4Address): + if random_src_ip: + ptf_addr = get_ip_address( + "v4", hostid=3, netid=170) pkt_opts = { "pktlen": pkt_len, "eth_dst": self.dut_mac, "eth_src": self.ptf_mac_addrs['eth%d' % ptf_port], - "ip_dst":destination, - "ip_src":ptf_addr, - "ip_id":105, - "ip_ttl":64, - "tcp_sport":tcp_sport, - "tcp_dport":VARS['tcp_dport']} + "ip_dst": destination, + "ip_src": ptf_addr, + "ip_id": 105, + "ip_ttl": 64, + "tcp_sport": tcp_sport, + "tcp_dport": tcp_dport} pkt_opts.update(options) pkt = simple_tcp_packet(**pkt_opts) pkt_opts['ip_ttl'] = 63 pkt_opts['eth_src'] = self.dut_mac exp_pkt = simple_tcp_packet(**pkt_opts) - elif isinstance(ip_address(destination), IPv6Address) and isinstance(ip_address(ptf_addr), IPv6Address): + elif isinstance(ip_address(destination), IPv6Address) and \ + isinstance(ip_address(ptf_addr), IPv6Address): + if random_src_ip: + ptf_addr = get_ip_address( + "v6", hostid=4, netid=170) pkt_opts = { - "pktlen":pkt_len, - "eth_dst":self.dut_mac, - "eth_src":self.ptf_mac_addrs['eth%d' % ptf_port], - "ipv6_dst":destination, - "ipv6_src":ptf_addr, - "ipv6_hlim":64, - "tcp_sport":tcp_sport, - "tcp_dport":VARS['tcp_dport']} + "pktlen": pkt_len, + "eth_dst": self.dut_mac, + "eth_src": self.ptf_mac_addrs['eth%d' % ptf_port], + "ipv6_dst": destination, + "ipv6_src": ptf_addr, + "ipv6_hlim": 64, + "tcp_sport": tcp_sport, + "tcp_dport": VARS['tcp_dport']} pkt_opts.update(options_v6) pkt = simple_tcpv6_packet(**pkt_opts) pkt_opts['ipv6_hlim'] = 63 pkt_opts['eth_src'] = self.dut_mac exp_pkt = simple_tcpv6_packet(**pkt_opts) else: - valid_combination = False - udp_sport = 1234 # Use entropy_hash(pkt), it will be ignored in the test later. + raise RuntimeError( + "Invalid mapping of destination and PTF address.") + udp_sport = 1234 # it will be ignored in the test later. udp_dport = self.vxlan_port if isinstance(ip_address(host_address), IPv4Address): encap_pkt = simple_vxlan_packet( eth_src=self.dut_mac, eth_dst=self.random_mac, ip_id=0, + ip_ihl=5, ip_src=self.loopback_ipv4, ip_dst=host_address, ip_ttl=128, @@ -250,45 +416,102 @@ def test_encap(self, ptf_port, vni, ptf_addr, destination, nhs, test_ecn=False, **options_v6) send_packet(self, ptf_port, str(pkt)) + # After we sent all packets, wait for the responses. + if expect_success: + wait_timeout = 2 + loop_timeout = max(packet_count * 5, 1000) # milliseconds + start_time = datetime.now() + vxlan_count = 0 + Logger.info("Loop time:out %s milliseconds", loop_timeout) + while (datetime.now() - start_time).total_seconds() *\ + 1000 < loop_timeout and vxlan_count < packet_count: + result = dp_poll( + self, timeout=wait_timeout + ) + if isinstance(result, self.dataplane.PollSuccess): + if not isinstance( + result, self.dataplane.PollSuccess) or \ + result.port not in self.t2_ports or \ + "VXLAN" not in scapy.Ether(result.packet): + continue + else: + vxlan_count += 1 + scapy_pkt = scapy.Ether(result.packet) + # Store every destination that was received. + if isinstance( + ip_address(host_address), IPv6Address): + dest_ip = scapy_pkt['IPv6'].dst + else: + dest_ip = scapy_pkt['IP'].dst + try: + returned_ip_addresses[dest_ip] = \ + returned_ip_addresses[dest_ip] + 1 + except KeyError: + returned_ip_addresses[dest_ip] = 1 + else: + Logger.info("No packet came in %s seconds", + wait_timeout) + break + if not vxlan_count or not returned_ip_addresses: + raise RuntimeError( + "Didnot get any reply for this destination:{}" + " Its active endpoints:{}".format( + destination, test_nhs)) + Logger.info( + "Vxlan packets received:%s, loop time:%s " + "seconds", vxlan_count, + (datetime.now() - start_time).total_seconds()) + Logger.info("received = {}".format(returned_ip_addresses)) + + else: + check_ecmp = False + Logger.info("Verifying no packet") + masked_exp_pkt = Mask(encap_pkt) + masked_exp_pkt.set_ignore_extra_bytes() masked_exp_pkt.set_do_not_care_scapy(scapy.Ether, "src") masked_exp_pkt.set_do_not_care_scapy(scapy.Ether, "dst") if isinstance(ip_address(host_address), IPv4Address): masked_exp_pkt.set_do_not_care_scapy(scapy.IP, "ttl") - masked_exp_pkt.set_do_not_care_scapy(scapy.IP, "chksum") + masked_exp_pkt.set_do_not_care_scapy(scapy.IP, + "chksum") masked_exp_pkt.set_do_not_care_scapy(scapy.IP, "dst") else: - masked_exp_pkt.set_do_not_care_scapy(scapy.IPv6, "hlim") - masked_exp_pkt.set_do_not_care_scapy(scapy.IPv6, "dst") - masked_exp_pkt.set_do_not_care_scapy(scapy.UDP, "sport") - masked_exp_pkt.set_do_not_care_scapy(scapy.UDP, "chksum") - - logger.info("Sending packet from port " + str(ptf_port) + " to " + destination) - - if self.expect_encap_success: - _, received_pkt = verify_packet_any_port(self, masked_exp_pkt, self.t2_ports) - scapy_pkt = scapy.Ether(received_pkt) - # Store every destination that was received. - if isinstance(ip_address(host_address), IPv6Address): - dest_ip = scapy_pkt['IPv6'].dst - else: - dest_ip = scapy_pkt['IP'].dst - try: - returned_ip_addresses[dest_ip] = returned_ip_addresses[dest_ip] + 1 - except KeyError: - returned_ip_addresses[dest_ip] = 1 + masked_exp_pkt.set_do_not_care_scapy(scapy.IPv6, + "hlim") + masked_exp_pkt.set_do_not_care_scapy(scapy.IPv6, + "chksum") + masked_exp_pkt.set_do_not_care_scapy(scapy.IPv6, + "dst") + masked_exp_pkt.set_do_not_care_scapy(scapy.UDP, + "sport") + masked_exp_pkt.set_do_not_care_scapy(scapy.UDP, + "chksum") - else: - check_ecmp = False - logger.info("Verifying no packet") - verify_no_packet_any(self, masked_exp_pkt, self.t2_ports) + try: + verify_no_packet_any( + self, + masked_exp_pkt, + self.t2_ports) + except BaseException: + raise RuntimeError( + "Verify_no_packet failed. Args:ports:{} sent:{}\n," + "expected:{}\n, encap_pkt:{}\n".format( + self.t2_ports, + repr(pkt), + repr(exp_pkt), + repr(encap_pkt))) # Verify ECMP: if check_ecmp: - self.verify_all_addresses_used_equally(nhs, returned_ip_addresses) + self.verify_all_addresses_used_equally( + nhs, + returned_ip_addresses, + packet_count, + self.downed_endpoints) pkt.load = '0' * 60 + str(len(self.packets)) self.packets.append((ptf_port, str(pkt).encode("base64"))) finally: - logger.info("") + Logger.info("") diff --git a/tests/templates/bfd_responder.conf.j2 b/tests/templates/bfd_responder.conf.j2 index d7e8b813c9e..e6ea48203b5 100644 --- a/tests/templates/bfd_responder.conf.j2 +++ b/tests/templates/bfd_responder.conf.j2 @@ -8,3 +8,4 @@ autostart=false autorestart=true startsecs=1 numprocs=1 + diff --git a/tests/vxlan/bfd_sniffer.py b/tests/vxlan/bfd_sniffer.py new file mode 100644 index 00000000000..4844f6c7592 --- /dev/null +++ b/tests/vxlan/bfd_sniffer.py @@ -0,0 +1,52 @@ +""" +This bfd sniffer captures bfd packets on all PTF interfaces and +creates a json file for bfd_responder to use +""" +from scapy.all import * +import sys +import json +try: + delete_member_a1 = sys.argv[1] + delete_member_a2 = sys.argv[2] +except: + print("Test will proceed even if we do not pass argument") +ifaces = get_if_list() +def return_bfd_interfaces(): + # Captures 5 BFD packets on every ptf port & returns a dictionary with interfaces & source address + bfd_interfaces = {} + for iface in ifaces: + if iface.startswith("eth"): + print("Verification of BFD packets on interface - {}".format(iface)) + output = sniff(iface=iface,filter="udp", count=5, timeout=5) + for pkt in output: + if pkt.haslayer(UDP): + if pkt.haslayer(IP): + bfd_interfaces[pkt[IP].dst] = {"src":pkt[IP].src, "iface":iface} + elif pkt.haslayer(IPv6): + bfd_interfaces[pkt[IPv6].dst] = {"src":pkt[IPv6].src, "iface":iface} + return bfd_interfaces +bfd_interfaces = return_bfd_interfaces() +print(bfd_interfaces) +ptf_config = [] +for key,value in bfd_interfaces.items(): + ptf_config.append( + { + "neighbor_addr": value['src'], + "local_addr" : key , + "multihop" : "true", + "ptf_intf" : value['iface'] + } + ) +if delete_member_a1 is not None: + for i in range(len(ptf_config)): + if ptf_config[i]['local_addr'] == delete_member_a1: + del ptf_config[i] + break +if delete_member_a2 is not None: + for i in range(len(ptf_config)): + if ptf_config[i]['local_addr'] == delete_member_a2: + del ptf_config[i] + break +print(ptf_config) +with open("/tmp/ptf_config.json", "w") as f: + json.dump(ptf_config, f) \ No newline at end of file diff --git a/tests/vxlan/conftest.py b/tests/vxlan/conftest.py index c72bb9b7b91..27c299a80fd 100644 --- a/tests/vxlan/conftest.py +++ b/tests/vxlan/conftest.py @@ -1,14 +1,38 @@ - +import argparse +from os.path import join +import pytest import logging import yaml +from tests.vxlan.vnet_utils import ( + safe_open_template, + combine_dicts + ) +from tests.vxlan.vnet_constants import ( + NUM_VNET_KEY, + NUM_ROUTES_KEY, + NUM_ENDPOINTS_KEY, + VXLAN_UDP_SPORT_KEY, + VXLAN_UDP_SPORT_MASK_KEY, + VXLAN_RANGE_ENABLE_KEY, + IPV6_VXLAN_TEST_KEY, + CLEANUP_KEY, + APPLY_NEW_CONFIG_KEY, + NUM_INTF_PER_VNET_KEY, + TEMPLATE_DIR +) -import pytest +logger = logging.getLogger(__name__) -from os import path -from tests.vxlan.vnet_utils import combine_dicts, safe_open_template -from tests.vxlan.vnet_constants import * -logger = logging.getLogger(__name__) +def str2bool(v): + if isinstance(v, bool): + return v + if v.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif v.lower() in ('no', 'false', 'f', 'n', '0'): + return False + else: + raise argparse.ArgumentTypeError('Boolean value expected.') def pytest_addoption(parser): @@ -23,13 +47,14 @@ def pytest_addoption(parser): action="store", default=4789, type=int, - help="The UDP port to use for VxLAN. It must be a viable UDP port - not one of the already used standard protocol ports" + help="The UDP port to use for VxLAN. It must be a viable UDP port " + "- not one of the already used standard protocol ports" ) vxlan_group.addoption( "--num_vnet", action="store", - default=8, + default=1, type=int, help="number of VNETs for VNET VxLAN test" ) @@ -58,10 +83,44 @@ def pytest_addoption(parser): help="number of VLAN interfaces per VNET" ) + vxlan_group.addoption( + "--ipv4_in_ipv4", + action="store", + default=True, + type=str2bool, + help="Test IPv4 in IPv4" + ) + vxlan_group.addoption( "--ipv6_vxlan_test", - action="store_true", - help="Use IPV6 for VxLAN test" + action="store", + default=True, + type=str2bool, + help="Test IPV6 encap" + ) + + vxlan_group.addoption( + "--ipv6_in_ipv4", + action="store", + default=True, + type=str2bool, + help="Test IPV6 in IPv4" + ) + + vxlan_group.addoption( + "--ipv4_in_ipv6", + action="store", + default=True, + type=str2bool, + help="Test IPv4 in IPv6" + ) + + vxlan_group.addoption( + "--ipv6_in_ipv6", + action="store", + type=str2bool, + default=True, + help="Test IPV6 in IPv6" ) vxlan_group.addoption( @@ -90,6 +149,15 @@ def pytest_addoption(parser): help="Expected base VXLAN UDP src port mask" ) + # BFD options + vxlan_group.addoption( + "--bfd", + action="store", + default=False, + type=bool, + help="BFD Status" + ) + # ECMP options vxlan_group.addoption( "--total_number_of_endpoints", @@ -104,13 +172,16 @@ def pytest_addoption(parser): action="store", default=1, type=int, - help="ECMP: Number of tunnel endpoints to provide for each tunnel destination" + help="ECMP: Number of tunnel endpoints to provide for each tunnel" + " destination" ) vxlan_group.addoption( "--debug_enabled", action="store_true", - help="Enable debugging the script. The config file names will *not* be time-stamped, every run of the script will over-write the previously created config files." + help="Enable debugging the script. The config file names will " + "*not* be time-stamped. Every run of the script will over-write " + "the previously created config files." ) vxlan_group.addoption( @@ -123,16 +194,34 @@ def pytest_addoption(parser): "--dut_hostid", default=1, type=int, - help="This is the host part of the IP addresses for interfaces in the DUT to be used in this script." + help="This is the host part of the IP addresses for interfaces in " + "the DUT to be used in this script." ) # This will decide the number of destinations. vxlan_group.addoption( "--total_number_of_nexthops", action="store", - default=2, # Max: 32k, 64K, or 128 K + default=2, # Max: 32k, 64K, or 128 K type=int, - help="ECMP: Number of tunnel nexthops to be tested. (number of nhs_per_destination X number_of_destinations)" + help="ECMP: Number of tunnel nexthops to be tested. (number of " + "nhs_per_destination X number_of_destinations)" + ) + + vxlan_group.addoption( + "--include_crm", + action="store", + default=False, + type=bool, + help="Enable CRM tests." + ) + + vxlan_group.addoption( + "--include_long_tests", + action="store", + default=False, + type=bool, + help="Run the long-running testcases." ) @@ -140,12 +229,11 @@ def pytest_addoption(parser): def scaled_vnet_params(request): """ Fixture to get CLI parameters for scaled vnet testing - Args: request: Pytest fixture containing parsed CLI parameters - Returns: - A dictionary holding each scaled vnet parameter with the parameter name as the key + A dictionary holding each scaled vnet parameter with the parameter + name as the key. * num_vnet * num_routes * num_endpoints @@ -157,35 +245,41 @@ def scaled_vnet_params(request): params[NUM_ENDPOINTS_KEY] = request.config.option.num_endpoints return params + @pytest.fixture(scope="module") def vnet_test_params(duthost, request): """ Fixture to get CLI parameters for vnet testing - Args: request: Pytest fixture containing parsed CLI parameters - Returns: A dictionary holding each parameter with the parameter name as the key - * ipv6_vxlan_test - whether to include ipv6 functionality in testing - * cleanup - whether to remove test data/configs after test is finished - * apply_new_config - whether to apply new configurations that were pushed to the DUT + * ipv6_vxlan_test - whether to include ipv6 functionality + in testing + * cleanup - whether to remove test data/configs after test is + finished + * apply_new_config - whether to apply new configurations that were + pushed to the DUT """ params = {} params[VXLAN_UDP_SPORT_KEY] = 0 params[VXLAN_UDP_SPORT_MASK_KEY] = 0 - vxlan_range_enable = duthost.shell('redis-cli -n 4 hget "DEVICE_METADATA|localhost" vxlan_port_range')['stdout'] == "enable" + vxlan_range_enable = duthost.shell( + 'redis-cli -n 4 hget "DEVICE_METADATA|localhost" \ + vxlan_port_range')['stdout'] == "enable" - if request.config.option.udp_src_port is not None or request.config.option.udp_src_port_mask is not None: + if request.config.option.udp_src_port is not None or \ + request.config.option.udp_src_port_mask is not None: vxlan_range_enable = True if request.config.option.udp_src_port: params[VXLAN_UDP_SPORT_KEY] = request.config.option.udp_src_port if request.config.option.udp_src_port_mask: - params[VXLAN_UDP_SPORT_MASK_KEY] = request.config.option.udp_src_port_mask + params[VXLAN_UDP_SPORT_MASK_KEY] = \ + request.config.option.udp_src_port_mask params[VXLAN_RANGE_ENABLE_KEY] = vxlan_range_enable params[IPV6_VXLAN_TEST_KEY] = request.config.option.ipv6_vxlan_test @@ -194,14 +288,13 @@ def vnet_test_params(duthost, request): params[NUM_INTF_PER_VNET_KEY] = request.config.option.num_intf_per_vnet return params + @pytest.fixture(scope="module") def minigraph_facts(duthosts, rand_one_dut_hostname, tbinfo): """ Fixture to get minigraph facts - Args: duthost: DUT host object - Returns: Dictionary containing minigraph information """ @@ -209,24 +302,31 @@ def minigraph_facts(duthosts, rand_one_dut_hostname, tbinfo): return duthost.get_extended_minigraph_facts(tbinfo) + @pytest.fixture(scope="module") def vnet_config(minigraph_facts, vnet_test_params, scaled_vnet_params): """ Fixture to generate vnet configuration from templates/vnet_config.j2 - Args: minigraph_facts: minigraph information/facts vnet_test_params: Dictionary holding vnet test parameters scaled_vnet_params: Dictionary holding scaled vnet testing parameters - Returns: A dictionary containing the generated vnet configuration information """ - num_rifs = vnet_test_params[NUM_INTF_PER_VNET_KEY] * scaled_vnet_params[NUM_VNET_KEY] + num_rifs = vnet_test_params[NUM_INTF_PER_VNET_KEY] * \ + scaled_vnet_params[NUM_VNET_KEY] if num_rifs > 128: - logger.warning("Total number of configured interfaces will be greater than 128. This is not a supported test scenario") - - combined_args = combine_dicts(minigraph_facts, vnet_test_params, scaled_vnet_params) - return yaml.safe_load(safe_open_template(path.join(TEMPLATE_DIR, "vnet_config.j2")).render(combined_args)) + logger.warning( + "Total number of configured interfaces will be greater" + "than 128. This is not a supported test scenario") + + combined_args = combine_dicts( + minigraph_facts, + vnet_test_params, + scaled_vnet_params) + return yaml.safe_load( + safe_open_template( + join(TEMPLATE_DIR, "vnet_config.j2")).render(combined_args)) diff --git a/tests/vxlan/test_vxlan_ecmp.py b/tests/vxlan/test_vxlan_ecmp.py index 052b928639e..0020214b01d 100644 --- a/tests/vxlan/test_vxlan_ecmp.py +++ b/tests/vxlan/test_vxlan_ecmp.py @@ -5,63 +5,72 @@ https://github.com/sonic-net/SONiC/blob/8ca1ac93c8912fda7b09de9bfd51498e5038c292/doc/vxlan/Overlay%20ECMP%20with%20BFD.md#test-cases To test functionality: - ./run_tests.sh -n ucs-m5-2 -d mth64-m5-2 -O -u -e -s -e --disable_loganalyzer -m individual -p /home/vxr/vxlan/logs/ -c 'vxlan/test_vxlan_ecmp.py' + ./run_tests.sh -n ucs-m5-2 -d mth64-m5-2 -O -u -e -s \ + -m individual -p /home/vxr/vxlan/logs/ -c 'vxlan/test_vxlan_ecmp.py' To test ECMP with 2 paths per destination: - ./run_tests.sh -n ucs-m5-2 -d mth64-m5-2 -O -u -e -s -e --disable_loganalyzer -m individual -p /home/vxr/vxlan/logs/ -c 'vxlan/test_vxlan_ecmp.py' -e '--nhs_per_destination=2' + ./run_tests.sh -n ucs-m5-2 -d mth64-m5-2 -O -u -e -s -m individual \ + -p /home/vxr/vxlan/logs/ -c 'vxlan/test_vxlan_ecmp.py' \ + -e '--nhs_per_destination=2' To test ECMP+Scale(for all 4 types of encap): - ./run_tests.sh -n ucs-m5-2 -d mth64-m5-2 -O -u -e -s -e --disable_loganalyzer -m individual -p /home/vxr/vxlan/logs/ -c 'vxlan/test_vxlan_ecmp.py::Test_VxLAN_route_tests::test_vxlan_single_endpoint' \ - -e '--ecmp_nhs_per_destination=128' -e '--total_number_of_nexthops=32000' -e '--total_number_of_endpoints=1024' + ./run_tests.sh -n ucs-m5-2 -d mth64-m5-2 -O -u -e -s -m individual \ + -p /home/vxr/vxlan/logs/ \ + -c 'vxlan/test_vxlan_ecmp.py::Test_VxLAN_route_tests::\ + test_vxlan_single_endpoint' \ + -e '--ecmp_nhs_per_destination=128 --total_number_of_nexthops=32000' \ + -e '--total_number_of_endpoints=1024' To keep the temporary config files created in the DUT: - ./run_tests.sh -n ucs-m5-2 -d mth64-m5-2 -O -u -e -s -e --keep_temp_files -c 'vxlan/test_vxlan_ecmp.py' + ./run_tests.sh -n ucs-m5-2 -d mth64-m5-2 -O -u -e -s -e --keep_temp_files \ + -c 'vxlan/test_vxlan_ecmp.py' Other options: - keep_temp_files : Keep the temporary files created in the DUT. Default: False - debug_enabled : Enable debug mode, for debugging script. The temp files will not have timestamped names. Default: False - dut_hostid : An integer in the range of 1 - 100 to be used as the host part of the IP address for DUT. Default: 1 + keep_temp_files : Keep the temporary files created in the + DUT. Default: False + debug_enabled : Enable debug mode, for debugging + script. The temp files will + not have timestamped names. + Default: False + dut_hostid : An integer in the range of 1 - 100 to be + used as the host + part of the IP address for DUT. Default:1 ecmp_nhs_per_destination : Number of ECMP next-hops per destination. - total_number_of_endpoints : Number of Endpoints (a pool of this number of ip addresses will used for next-hops). Default:2 - total_number_of_nexthops : Maximum number of all nexthops for every destination combined(per encap_type). - vxlan_port : Global vxlan port (UDP port) to be used for the DUT. Default: 4789 + total_number_of_endpoints : Number of Endpoints (a pool of this + number of ip addresses will used for + next-hops). Default:2 + total_number_of_nexthops : Maximum number of all nexthops for every + destination combined(per encap_type). + vxlan_port : Global vxlan port (UDP port) to be used + for the DUT. Default: 4789 + bfd : Set it to True if you want to run all + VXLAN cases with BFD Default: False + include_crm : Include the CRM testcases. Default:False + include_long_tests : Include the entropy, random-hash + testcases, that take longer time. + Default: False ''' import time -import re -import ipaddress -import json import logging from datetime import datetime -from sys import getsizeof - +import json import pytest -from tests.common.fixtures.ptfhost_utils import copy_ptftests_directory # lgtm[py/unused-import] from tests.common.helpers.assertions import pytest_assert +from tests.common.fixtures.ptfhost_utils \ + import copy_ptftests_directory # noqa: F401 from tests.common.utilities import wait_until from tests.ptf_runner import ptf_runner +from tests.vxlan.vxlan_ecmp_utils import Ecmp_Utils -logger = logging.getLogger(__name__) - -# Some of the Constants used in this script. -Constants = {} - -# Mapping the version to the python module. -IP_TYPE = { - 'v4' : ipaddress.IPv4Address, - 'v6' : ipaddress.IPv6Address -} - -# This is the mask values to use for destination -# in the vnet routes. -HOST_MASK = {'v4' : 32, 'v6' : 128} +Logger = logging.getLogger(__name__) +ecmp_utils = Ecmp_Utils() # This is the list of encapsulations that will be tested in this script. # v6_in_v4 means: V6 payload is encapsulated inside v4 outer layer. # This list is used in many locations in the script. SUPPORTED_ENCAP_TYPES = ['v4_in_v4', 'v4_in_v6', 'v6_in_v4', 'v6_in_v6'] - # Starting prefixes to be used for the destinations and End points. DESTINATION_PREFIX = 150 NEXTHOP_PREFIX = 100 @@ -72,502 +81,101 @@ pytest.mark.sanity_check(post_check=True) ] -def create_vxlan_tunnel(duthost, minigraph_data, af, tunnel_name=None, src_ip=None): - ''' - Function to create a vxlan tunnel. The arguments: - duthost : the DUT ansible host object. - minigraph_data: minigraph facts from the dut host. - tunnel_name : A name for the Tunnel, default: tunnel_ - src_ip : Source ip address of the tunnel. It has to be a local ip address in the DUT. Default: Loopback ip address. - af : Address family : v4 or v6. - ''' - if tunnel_name is None: - tunnel_name = "tunnel_{}".format(af) - - if src_ip is None: - src_ip = get_dut_loopback_address(duthost, minigraph_data, af) - - config = '''{{ - "VXLAN_TUNNEL": {{ - "{}": {{ - "src_ip": "{}" - }} - }} - }}'''.format(tunnel_name, src_ip) - - apply_config_in_dut(duthost, config, name="vxlan_tunnel_"+ af) - return tunnel_name - -def apply_config_in_dut(duthost, config, name="vxlan"): - ''' - The given json(config) will be copied to the DUT and loaded up. - ''' - if Constants['DEBUG']: - filename = "/tmp/" + name + ".json" - else: - filename = "/tmp/" + name + "-" + str(time.time()) + ".json" - duthost.copy(content=config, dest=filename) - duthost.shell("sudo config load {} -y".format(filename)) - time.sleep(1) - if not Constants['KEEP_TEMP_FILES']: - duthost.shell("rm {}".format(filename)) - -def get_dut_loopback_address(duthost, minigraph_data, af): - ''' - Returns the IP address of the Loopback interface in DUT, from minigraph. - Arguments: - duthost : DUT Ansible Host object. - minigraph_data: Minigraph facts from the DUT. - af : Address Family(v4 or v6). - ''' - lo_ip = minigraph_data['minigraph_lo_interfaces'] - for intf in lo_ip: - if isinstance(ipaddress.ip_address(intf['addr']), IP_TYPE[af]): - return intf['addr'] - - raise RuntimeError("Couldnot find the {} loopback address for the DUT:{} from minigraph.".format(af, duthost.hostname)) - -def select_required_interfaces(duthost, number_of_required_interfaces, minigraph_data, af): - ''' - Pick the required number of interfaces to use for tests. - These interfaces will be selected based on if they are currently running a established BGP. - The interfaces will be picked from the T0 facing side. - ''' - bgp_interfaces = get_all_interfaces_running_bgp(duthost, minigraph_data, "T0") - interface_ip_table = minigraph_data['minigraph_interfaces'] - if interface_ip_table: - available_interfaces = interface_ip_table - elif minigraph_data['minigraph_portchannels']: - available_interfaces = minigraph_data['minigraph_portchannel_interfaces'] - else: - raise RuntimeError("Couldn't find a viable interface: No Ethernet, No PortChannels in the minigraph file.") - - # Randomly pick the interface from the above list - list_of_bgp_ips = [] - for neigh_ip_address in bgp_interfaces.keys(): - if isinstance(ipaddress.ip_address(neigh_ip_address), IP_TYPE[af]): - list_of_bgp_ips.append(neigh_ip_address) - - ret_interface_list = [] - available_number = len(list_of_bgp_ips) - # Confirm there are enough interfaces (basicaly more than or equal to the number of vnets). - if available_number <= number_of_required_interfaces+1: - raise RuntimeError('''There are not enough interfaces needed to perform the test. - We need atleast {} interfaces, but only {} are available.'''.format(number_of_required_interfaces+1, available_number)) - for index in range(number_of_required_interfaces): - neigh_ip_address = list_of_bgp_ips[index] - current_interface_name = bgp_interfaces[neigh_ip_address].keys()[0] - ret_interface_list.append(current_interface_name) - - if ret_interface_list: - return ret_interface_list - else: - raise RuntimeError("There is no Ethernet interface running BGP. Pls run this test on any T1 topology.") - -def get_portchannels_to_neighbors(duthost, neighbor_type, minigraph_data): - ''' - A function to get the list of portchannels connected to BGP neighbors of given type(T0 or T2). - It returns a list of portchannels+minigraph_lag_facts_of_that portchannel. - Arguments: - duthost : DUT Ansible Host object - localhost : Localhost Ansible Host object. - neighbor_type: T0 or T2. - ''' - lag_facts = duthost.lag_facts(host=duthost.sonichost.mgmt_ip) - names = lag_facts['ansible_facts']['lag_facts']['names'] - lags = lag_facts['ansible_facts']['lag_facts']['lags'] - - return_list = {} - pattern = re.compile("{}$".format(neighbor_type)) - for pc_name in names: - port_struct = lags[pc_name]['po_config']['ports'] - if lags[pc_name]['po_intf_stat'] == "Up": - intf = port_struct.keys()[0] - neighbor = minigraph_data['minigraph_neighbors'][intf]['name'] - match = pattern.search(neighbor) - if match: - # We found an interface that has a given neighbor_type. Let us use this. - return_list[pc_name] = port_struct - - return return_list - -def get_ethernet_to_neighbors(neighbor_type, minigraph_data): - ''' - A function to get the list of Ethernet interfaces connected to BGP neighbors of given type(T0 or T2). - It returns a list of ports. - Arguments: - duthost : DUT Ansible Host object - neighbor_type: T0 or T2. - ''' - - pattern = re.compile("{}$".format(neighbor_type)) - ret_list = [] - - for intf in minigraph_data['minigraph_neighbors']: - if pattern.search(minigraph_data['minigraph_neighbors'][intf]['name']): - ret_list.append(intf) - - return ret_list - -def assign_intf_ip_address(selected_interfaces, af): - intf_ip_map = {} - for intf in selected_interfaces: - ip = get_ip_address(af=af, hostid=Constants['DUT_HOSTID'], netid=201) - intf_ip_map[intf] = ip - return intf_ip_map -def get_all_interfaces_running_bgp(duthost, minigraph_data, neighbor_type): - bgp_neigh_list = duthost.bgp_facts()['ansible_facts']['bgp_neighbors'] - minigraph_ip_interfaces = minigraph_data['minigraph_interfaces'] + minigraph_data['minigraph_portchannel_interfaces'] - peer_addr_map = {} - pattern = re.compile("{}$".format(neighbor_type)) - for x in minigraph_ip_interfaces: - peer_addr_map[x['peer_addr']] = {x['attachto'] : x['addr']} - - ret_list = {} - for x, entry in peer_addr_map.iteritems(): - if bgp_neigh_list[x]['state'] == 'established' and pattern.search(bgp_neigh_list[x]['description']): - ret_list[x] = entry - - return ret_list - -def configure_vnet_neighbors(duthost, intf_to_ip_map, minigraph_data, af): +@pytest.fixture( + name="encap_type", + scope="module", + params=SUPPORTED_ENCAP_TYPES) +def fixture_encap_type(request): ''' - setup the vnet neighbor ip addresses. + This fixture forces the script to perform one encap_type at a time. + So this script doesn't support multiple encap types at the same. ''' - family = "IPv4" - if af == "v6": - family = "IPv6" - - return_dict = {} - - config_list = [] - for intf, addr in intf_to_ip_map.iteritems(): - # If the given address is "net.1", the return address is "net.101" - # THE ASSUMPTION HERE IS THAT THE DUT ADDRESSES ARE ENDING IN ".1". - ptf_ip = str(ipaddress.ip_address(unicode(addr))+100) - - if "Ethernet" in intf: - return_dict[intf] = ptf_ip - elif "PortChannel" in intf: - for member in get_ethernet_ports([intf], minigraph_data): - return_dict[member] = ptf_ip - - config_list.append('''"{}|{}": {{ - "family": "{}" - }}'''.format(intf, ptf_ip, family)) - - full_config = '''{ - "NEIGH" : { - ''' + ",\n".join(config_list) + '''\n}\n}''' - - apply_config_in_dut(duthost, full_config, name="vnet_nbr_"+af) - - return return_dict - -def create_vnets(duthost, tunnel_name, vnet_count=1, scope=None, vni_base=10000, vnet_name_prefix="Vnet"): - return_dict = {} - scope_entry = "" - if scope: - scope_entry = '''"scope": "{}",'''.format(scope) - config_list = [] - for i in range(vnet_count): - name = vnet_name_prefix + "-" + str(i) - vni = vni_base+i - return_dict[name] = vni - config_list.append('''"{}": {{ - "vxlan_tunnel": "{}", - {}"vni": "{}", - "peer_list": "" - }}'''.format(name, tunnel_name, scope_entry, vni)) - - full_config = '{\n"VNET": {' + ",\n".join(config_list) + '\n}\n}' - - apply_config_in_dut(duthost, full_config, "vnets_"+tunnel_name) - return return_dict - -def setup_vnet_intf(duthost, selected_interfaces, vnet_list, minigraph_data): - if len(selected_interfaces) != len(vnet_list): - raise RuntimeError("Different number of interfaces and vnets, not supported yet") - - ret_list = {} - intf_config_list = [] - po_config_list = [] - for count in range(len(selected_interfaces)): - intf = selected_interfaces[count] - config = (''' - "{}" : {{ - "vnet_name": "{}" - }} - '''.format(intf, vnet_list[count])) - - if "Ethernet" in intf: - intf_config_list.append(config) - ret_list[intf] = vnet_list[count] - elif "PortChannel" in intf: - po_config_list.append(config) - for member in get_ethernet_ports([intf], minigraph_data): - ret_list[member] = vnet_list[count] - - full_config_list = [] - if intf_config_list: - full_config_list.append( - '''"INTERFACE": {\n''' + ",\n".join(intf_config_list) + '''}''') - if po_config_list: - full_config_list.append( - '''"PORTCHANNEL_INTERFACE": {\n''' + ",\n".join(po_config_list) + '''}''') - - full_config = '''{\n''' + ",\n".join(full_config_list) + '''}''' - apply_config_in_dut(duthost, full_config, "vnet_intf") - return ret_list - -def configure_vxlan_switch(duthost, vxlan_port=4789, dutmac=None): - if dutmac == None: - #dutmac = duthost.facts['router_mac'] - dutmac = "aa:bb:cc:dd:ee:ff" - - switch_config = ''' -[ - {{ - "SWITCH_TABLE:switch": {{ - "vxlan_port": "{}", - "vxlan_router_mac": "{}" - }}, - "OP": "SET" - }} -] -'''.format(vxlan_port, dutmac) - apply_config_in_swss(duthost, switch_config, "vnet_switch") + yield request.param -def apply_config_in_swss(duthost, config, name="swss_"): - if Constants['DEBUG']: - filename = name + ".json" - else: - filename = name + "-" + str(time.time()) + ".json" - duthost.copy(content=config, dest="/tmp/{}".format(filename)) - duthost.shell('docker exec -i swss swssconfig /dev/stdin < /tmp/{}'.format(filename)) - time.sleep(int(0.0005*getsizeof(config)) + 1) - if not Constants['KEEP_TEMP_FILES']: - duthost.shell("rm /tmp/{}".format(filename)) +@pytest.fixture(autouse=True) +def ignore_route_sync_errlogs(rand_one_dut_hostname, loganalyzer): + """Ignore expected failures logs during test execution.""" + if loganalyzer: + loganalyzer[rand_one_dut_hostname].ignore_regex.extend( + [ + ".*Unaccounted_ROUTE_ENTRY_TABLE_entries.*", + ".*missed_in_asic_db_routes.*", + ".*Look at reported mismatches above.*", + ".*Unaccounted_ROUTE_ENTRY_TABLE_entries.*", + ".*'vnetRouteCheck' status failed.*", + ".*Vnet Route Mismatch reported.*" + ]) + return -def get_list_of_nexthops(number, af, prefix=100): - nexthop_list = [] - for i in range(number): - nexthop_list.append(get_ip_address(af=af, netid=prefix, hostid=10)) - return nexthop_list -def create_vnet_routes(duthost, vnet_list, dest_af, nh_af, nhs_per_destination=1, number_of_available_nexthops=100, number_of_ecmp_nhs=1000, dest_net_prefix=150, nexthop_prefix=100): +@pytest.fixture(name="setUp", scope="module") +def fixture_setUp(duthosts, + ptfhost, + request, + rand_one_dut_hostname, + minigraph_facts, + tbinfo, + encap_type): ''' - This configures the VNET_TUNNEL_ROUTES structure. It precalculates the required number of - destinations based on the given "number_of_ecmp_nhs" and the "nhs_per_destination". - - inputs: - number_of_available_nexthops : Total number of unique NextHops available for use. - nhs_per_destination : Number of ECMP nexthops to use per destination. - number_of_ecmp_nhs : Maximum number of all NextHops put together(for all destinations). + Setup for the entire script. + The basic steps in VxLAN configs are: + 1. Configure VxLAN tunnel. + 2. Configure Vnet and its VNI. + 3. Attach the Vnet to an interface(optional). + 4. Configure routes for the Vnet. The setup does all the above. + + The testcases are focused on the "configure routes" step. They add, + delete, modify, the routes. Some cases modify the underlay itself, + by add/delete bgp, or shut/start interfaces etc. ''' - if number_of_available_nexthops < nhs_per_destination: - raise RuntimeError("The number of available nexthops ip addresses is not enough to cover even one destination." \ - "Pls rerun with total_number_of_endpoints({}) > ecmp_nhs_per_destination({})".format(number_of_available_nexthops, nhs_per_destination)) - - available_nexthops = get_list_of_nexthops(number=number_of_available_nexthops, af=nh_af, prefix=nexthop_prefix) - - number_of_destinations = int(number_of_ecmp_nhs / nhs_per_destination) - no_of_dests_per_vnet = int(number_of_destinations / len(vnet_list)) - available_nexthop_count = 0 - dest_to_nh_map = {} - for vnet in vnet_list: - for i in range(no_of_dests_per_vnet): - dest = get_ip_address(af=dest_af, netid=dest_net_prefix) - my_nhs = [] - for j in range(nhs_per_destination): - my_nhs.append(available_nexthops[available_nexthop_count % number_of_available_nexthops]) - available_nexthop_count = available_nexthop_count + 1 - if available_nexthop_count > number_of_ecmp_nhs: - break - try: - dest_to_nh_map[vnet] - except KeyError: - dest_to_nh_map[vnet] = {} - dest_to_nh_map[vnet][dest] = my_nhs - - set_routes_in_dut(duthost, dest_to_nh_map, dest_af, "SET") - return dest_to_nh_map - -def get_outer_layer_version(encap_type): - match = re.search("in_(v[46])", encap_type) - if match: - return match.group(1) - else: - raise RuntimeError("Invalid format for encap_type:{}".format(encap_type)) - -def get_payload_version(encap_type): - match = re.search("(v[46])_in_v", encap_type) - if match: - return match.group(1) - else: - raise RuntimeError("Invalid format for encap_type:{}".format(encap_type)) - -def create_single_route(vnet, dest, mask, nhs, op): - ''' - Create a single route entry for vnet, for the given dest, through the endpoints:nhs, op:SET/DEL - ''' - return '''{{ - "VNET_ROUTE_TUNNEL_TABLE:{}:{}/{}": {{ - "endpoint": "{}" - }}, - "OP": "{}" - }}'''.format(vnet, dest, mask, ",".join(nhs), op) - -Address_Count = 0 -def get_ip_address(af, hostid=1, netid=100): - global Address_Count - third_octet = Address_Count % 255 - second_octet = (Address_Count / 255) % 255 - first_octet = netid + (Address_Count / 65025) - Address_Count = Address_Count + 1 - if af == 'v4': - return "{}.{}.{}.{}".format(first_octet, second_octet, third_octet, hostid) - if af == 'v6': - # :0: gets removed in the IPv6 addresses. Adding a to octets, to avoid it. - return "fddd:a{}:a{}::a{}:{}".format(first_octet, second_octet, third_octet, hostid) - -def set_routes_in_dut(duthost, dest_to_nh_map, dest_af, op): - config_list = [] - for vnet in dest_to_nh_map.keys(): - for dest in dest_to_nh_map[vnet].keys(): - config_list.append(create_single_route(vnet, dest, HOST_MASK[dest_af], dest_to_nh_map[vnet][dest], op)) - - full_config = '[' + "\n,".join(config_list) + '\n]' - apply_config_in_swss(duthost, full_config, op+"_routes") - -def get_t2_ports(duthost, minigraph_data): - ''' - In T1 topology, any port connected to the T2 BGP neighbors are needed. - In T0, any port connected to the T1 BGP neighbors are needed. - ''' - list_of_portchannels_to_T2 = get_portchannels_to_neighbors(duthost, "T2", minigraph_data) - list_of_interfaces = [] - if list_of_portchannels_to_T2: - for pc_name in list_of_portchannels_to_T2: - list_of_interfaces.extend(list_of_portchannels_to_T2[pc_name]) + data = {} + asic_type = duthosts[rand_one_dut_hostname].facts["asic_type"] + if asic_type == "cisco-8000": + data['tolerance'] = 0.03 else: - list_of_interfaces = get_ethernet_to_neighbors("T2", minigraph_data) - - ret_list = [] - for iface in list_of_interfaces: - ret_list.append(minigraph_data["minigraph_ptf_indices"][iface]) - return ret_list - -def bgp_established(duthost, down_list=[]): - bgp_facts = duthost.bgp_facts()['ansible_facts'] - for k, v in bgp_facts['bgp_neighbors'].items(): - if v['state'] == 'established': - if k in down_list: - # The neighbor is supposed to be down, and is actually up. - logger.info("Neighbor %s is established, but should be down.", k) - return False - else: - # The neighbor is supposed to be up, and is actually up. - continue - else: - if k in down_list: - # The neighbor is supposed to be down, and is actually down. - continue - else: - # The neighbor is supposed to be up, but is actually down. - logger.info("Neighbor %s is not yet established, has state: %s", k, v['state']) - return False - - # Now wait for the routes to be updated. - time.sleep(30) - return True - -def get_downed_bgp_neighbors(shut_intf_list, minigraph_data): - ''' - Get the list of bgp neighbors that should be down, - based on the interfaces that are shutdown. - ''' - ret_list = [] - for intf in shut_intf_list: - for m_intf in minigraph_data['minigraph_portchannel_interfaces']+minigraph_data['minigraph_interfaces']: - if m_intf['attachto'] == intf: - ret_list.append(m_intf['peer_addr']) - return ret_list - -def get_corresponding_ports(shut_intf_list, minigraph_data): - ''' - This is for tests that shutdown some of the T2 ports. - This function will check which ports are to be ignored for the encap packets coming - back to the PTF. If the encap packet comes in any of these ports, it is a bug. - ''' - eth_ifaces_list = [] - for intf in shut_intf_list: - if "Ethernet" in intf: - eth_ifaces_list.append(intf) - elif "PortChannel" in intf: - for port in get_ethernet_ports([intf], minigraph_data): - eth_ifaces_list.append(port) - return_list = [minigraph_data["minigraph_ptf_indices"][iface] for iface in eth_ifaces_list] - return return_list - -def get_ethernet_ports(intf_list, minigraph_data): - ''' - The given interface list can be either Ethernet or Portchannel. - This function will return a flat list of Ethernet ports corresponding to - the given intf_list itself, or members of Portchannels. - ''' - ret_list = [] - for intf in intf_list: - if "Ethernet" in intf: - ret_list.append(intf) - elif "PortChannel" in intf: - ret_list.extend(minigraph_data['minigraph_portchannels'][intf]['members']) - - return ret_list - - -@pytest.fixture(scope="module", params=SUPPORTED_ENCAP_TYPES) -def encap_type(request): - yield request.param - + raise RuntimeError("Pls update this script for your platform.") -@pytest.fixture(scope="module") -def setUp(duthosts, ptfhost, request, rand_one_dut_hostname, minigraph_facts, - tbinfo, encap_type): - - global Constants # Should I keep the temporary files copied to DUT? - Constants['KEEP_TEMP_FILES'] = request.config.option.keep_temp_files + ecmp_utils.Constants['KEEP_TEMP_FILES'] = \ + request.config.option.keep_temp_files # Is debugging going on, or is it a production run? If it is a # production run, use time-stamped file names for temp files. - Constants['DEBUG'] = request.config.option.debug_enabled + ecmp_utils.Constants['DEBUG'] = request.config.option.debug_enabled # The host id in the ip addresses for DUT. It can be anything, # but helps to keep as a single number that is easy to identify # as DUT. - Constants['DUT_HOSTID'] = request.config.option.dut_hostid - - logger.info("Constants to be used in the script:%s", Constants) + ecmp_utils.Constants['DUT_HOSTID'] = request.config.option.dut_hostid - SUPPORTED_ENCAP_TYPES = [encap_type] + Logger.info("Constants to be used in the script:%s", ecmp_utils.Constants) - data = {} + data['enable_bfd'] = request.config.option.bfd + data['include_crm'] = request.config.option.include_crm + data['include_long_tests'] = request.config.option.include_long_tests + data['monitor_file'] = '/tmp/bfd_responder_monitor_file.txt' data['ptfhost'] = ptfhost data['tbinfo'] = tbinfo data['duthost'] = duthosts[rand_one_dut_hostname] - data['minigraph_facts'] = data['duthost'].get_extended_minigraph_facts(tbinfo) + data['minigraph_facts'] = \ + data['duthost'].get_extended_minigraph_facts(tbinfo) data['dut_mac'] = data['duthost'].facts['router_mac'] data['vxlan_port'] = request.config.option.vxlan_port - configure_vxlan_switch(data['duthost'], vxlan_port=data['vxlan_port'], dutmac=data['dut_mac']) - - selected_interfaces = {} - for encap_type in SUPPORTED_ENCAP_TYPES: - outer_layer_version = get_outer_layer_version(encap_type) - selected_interfaces[encap_type] = select_required_interfaces( + data['crm'] = data['duthost'].get_crm_resources()['main_resources'] + ecmp_utils.configure_vxlan_switch( + data['duthost'], + vxlan_port=data['vxlan_port'], + dutmac=data['dut_mac']) + data['list_of_bfd_monitors'] = set() + data['list_of_downed_endpoints'] = set() + + outer_layer_version = ecmp_utils.get_outer_layer_version(encap_type) + encap_type_data = {} + encap_type_data['selected_interfaces'] = \ + ecmp_utils.select_required_interfaces( data['duthost'], number_of_required_interfaces=1, minigraph_data=minigraph_facts, @@ -577,304 +185,809 @@ def setUp(duthosts, ptfhost, request, rand_one_dut_hostname, minigraph_facts, tunnel_names = {} # To track the vnets for every outer_layer_version. vnet_af_map = {} - for encap_type in SUPPORTED_ENCAP_TYPES: - outer_layer_version = get_outer_layer_version(encap_type) - try: - tunnel_names[outer_layer_version] - except KeyError: - tunnel_names[outer_layer_version] = create_vxlan_tunnel(data['duthost'], minigraph_data=minigraph_facts, af=outer_layer_version) + outer_layer_version = ecmp_utils.get_outer_layer_version(encap_type) + try: + tunnel_names[outer_layer_version] + except KeyError: + tunnel_names[outer_layer_version] = ecmp_utils.create_vxlan_tunnel( + data['duthost'], + minigraph_data=minigraph_facts, + af=outer_layer_version) - payload_version = get_payload_version(encap_type) - encap_type = "{}_in_{}".format(payload_version, outer_layer_version) - encap_type_data = {} - encap_type_data['selected_interfaces'] = selected_interfaces[encap_type] + payload_version = ecmp_utils.get_payload_version(encap_type) + encap_type = "{}_in_{}".format(payload_version, outer_layer_version) - try: - encap_type_data['vnet_vni_map'] = vnet_af_map[outer_layer_version] - except KeyError: - vnet_af_map[outer_layer_version] = create_vnets(data['duthost'], - tunnel_name=tunnel_names[outer_layer_version], - vnet_count=1, # default scope can take only one vnet. - vnet_name_prefix="Vnet_" + encap_type, - scope="default", - vni_base=10000) - encap_type_data['vnet_vni_map'] = vnet_af_map[outer_layer_version] - - encap_type_data['vnet_intf_map'] = setup_vnet_intf(data['duthost'], - selected_interfaces=encap_type_data['selected_interfaces'], - vnet_list=encap_type_data['vnet_vni_map'].keys(), - minigraph_data=minigraph_facts) - encap_type_data['intf_to_ip_map'] = assign_intf_ip_address(selected_interfaces=encap_type_data['selected_interfaces'], af=payload_version) - encap_type_data['t2_ports'] = get_t2_ports(data['duthost'], minigraph_facts) - encap_type_data['neighbor_config'] = configure_vnet_neighbors(data['duthost'], encap_type_data['intf_to_ip_map'], minigraph_data=minigraph_facts, af=payload_version) - encap_type_data['dest_to_nh_map'] = create_vnet_routes(data['duthost'], encap_type_data['vnet_vni_map'].keys(), - nhs_per_destination=request.config.option.ecmp_nhs_per_destination, - number_of_available_nexthops=request.config.option.total_number_of_endpoints, - number_of_ecmp_nhs=request.config.option.total_number_of_nexthops, - dest_af=payload_version, - dest_net_prefix=DESTINATION_PREFIX, - nexthop_prefix=NEXTHOP_PREFIX, - nh_af=outer_layer_version) - - data[encap_type] = encap_type_data + try: + encap_type_data['vnet_vni_map'] = vnet_af_map[outer_layer_version] + except KeyError: + vnet_af_map[outer_layer_version] = ecmp_utils.create_vnets( + data['duthost'], + tunnel_name=tunnel_names[outer_layer_version], + vnet_count=1, # default scope can take only one vnet. + vnet_name_prefix="Vnet_" + encap_type, + scope="default", + vni_base=10000) + encap_type_data['vnet_vni_map'] = vnet_af_map[outer_layer_version] + + encap_type_data['vnet_intf_map'] = ecmp_utils.setup_vnet_intf( + selected_interfaces=encap_type_data['selected_interfaces'], + vnet_list=encap_type_data['vnet_vni_map'].keys(), + minigraph_data=minigraph_facts) + encap_type_data['intf_to_ip_map'] = ecmp_utils.assign_intf_ip_address( + selected_interfaces=encap_type_data['selected_interfaces'], + af=payload_version) + encap_type_data['t2_ports'] = ecmp_utils.get_t2_ports( + data['duthost'], + minigraph_facts) + encap_type_data['neighbor_config'] = ecmp_utils.configure_vnet_neighbors( + data['duthost'], + encap_type_data['intf_to_ip_map'], + minigraph_data=minigraph_facts, + af=payload_version) + encap_type_data['dest_to_nh_map'] = ecmp_utils.create_vnet_routes( + data['duthost'], encap_type_data['vnet_vni_map'].keys(), + nhs_per_destination=request.config.option.ecmp_nhs_per_destination, + number_of_available_nexthops=request.config.option. + total_number_of_endpoints, + number_of_ecmp_nhs=request.config.option.total_number_of_nexthops, + dest_af=payload_version, + dest_net_prefix=DESTINATION_PREFIX, + nexthop_prefix=NEXTHOP_PREFIX, + nh_af=outer_layer_version, + bfd=request.config.option.bfd) + + data[encap_type] = encap_type_data + for vnet in encap_type_data['dest_to_nh_map'].keys(): + for dest in encap_type_data['dest_to_nh_map'][vnet].keys(): + data['list_of_bfd_monitors'] = data['list_of_bfd_monitors'] |\ + set(encap_type_data['dest_to_nh_map'][vnet][dest]) + + # Setting up bfd responder is needed only once per script run. + loopback_addresses = \ + [str(x['addr']) for x in minigraph_facts[u'minigraph_lo_interfaces']] + if request.config.option.bfd: + ecmp_utils.start_bfd_responder( + data['ptfhost'], + data['dut_mac'], + loopback_addresses, + monitor_file=data['monitor_file']) + # Add all endpoint_monitors to the bfd responder monitor. + ecmp_utils.update_monitor_file( + data['ptfhost'], + data['monitor_file'], + data[encap_type]['t2_ports'], + list(data['list_of_bfd_monitors'])) # This data doesn't change per testcase, so we copy # it as a seperate file. The test-specific config # data will be copied on testase basis. data['ptfhost'].copy(content=json.dumps( { - 'minigraph_facts': data['minigraph_facts'], - 'tbinfo' : data['tbinfo'] + 'minigraph_facts': data['minigraph_facts'], + 'tbinfo': data['tbinfo'] }, indent=4), dest="/tmp/vxlan_topo_info.json") + data['downed_endpoints'] = [] yield data # Cleanup code. - for encap_type in SUPPORTED_ENCAP_TYPES: - outer_layer_version = get_outer_layer_version(encap_type) - payload_version = get_payload_version(encap_type) - - encap_type = "{}_in_{}".format(payload_version, outer_layer_version) - set_routes_in_dut(data['duthost'], data[encap_type]['dest_to_nh_map'], payload_version, "DEL") - - for intf in data[encap_type]['selected_interfaces']: - redis_string = "INTERFACE" - if "PortChannel" in intf: - redis_string = "PORTCHANNEL_INTERFACE" - data['duthost'].shell("redis-cli -n 4 hdel \"{}|{}\" vnet_name".format(redis_string, intf)) + outer_layer_version = ecmp_utils.get_outer_layer_version(encap_type) + payload_version = ecmp_utils.get_payload_version(encap_type) + + ecmp_utils.set_routes_in_dut( + data['duthost'], + data[encap_type]['dest_to_nh_map'], + payload_version, + "DEL") + + for intf in data[encap_type]['selected_interfaces']: + redis_string = "INTERFACE" + if "PortChannel" in intf: + redis_string = "PORTCHANNEL_INTERFACE" + data['duthost'].shell("redis-cli -n 4 hdel \"{}|{}\"" + "vnet_name".format(redis_string, intf)) + data['duthost'].shell( + "for i in `redis-cli -n 4 --scan --pattern \"NEIGH|{}|*\" `; " + "do redis-cli -n 4 del $i ; done".format(intf)) # This script's setup code re-uses same vnets for v4inv4 and v6inv4. # There will be same vnet in multiple encap types. # So remove vnets *after* removing the routes first. - for encap_type in SUPPORTED_ENCAP_TYPES: - for vnet in data[encap_type]['vnet_vni_map'].keys(): - data['duthost'].shell("redis-cli -n 4 del \"VNET|{}\"".format(vnet)) + for vnet in data[encap_type]['vnet_vni_map'].keys(): + data['duthost'].shell("redis-cli -n 4 del \"VNET|{}\"".format(vnet)) time.sleep(5) for tunnel in tunnel_names.values(): - data['duthost'].shell("redis-cli -n 4 del \"VXLAN_TUNNEL|{}\"".format(tunnel)) + data['duthost'].shell( + "redis-cli -n 4 del \"VXLAN_TUNNEL|{}\"".format(tunnel)) + time.sleep(1) + if request.config.option.bfd: + ecmp_utils.stop_bfd_responder(data['ptfhost']) -class Test_VxLAN: - def dump_self_info_and_run_ptf(self, tcname, encap_type, expect_encap_success, packet_count=4): +class Test_VxLAN(): + ''' + Base class for all VxLAN+BFD tests. + ''' + setup = {} + + def dump_self_info_and_run_ptf(self, + tcname, + encap_type, + expect_encap_success, + packet_count=4, + random_dport=True, + random_sport=False, + random_src_ip=False, + tolerance=None): ''' - Just a wrapper for dump_info_to_ptf to avoid entering 30 lines everytime. + Just a wrapper for dump_info_to_ptf to avoid entering 30 lines + everytime. ''' - if Constants['DEBUG']: + if tolerance is None: + tolerance = self.setup['tolerance'] + if ecmp_utils.Constants['DEBUG']: config_filename = "/tmp/vxlan_configs.json" else: - config_filename = "/tmp/vxlan_configs." + tcname + "-" + encap_type + "-" + str(time.time()) + ".json" + config_filename = "/tmp/vxlan_configs." + tcname +\ + "-" + encap_type + "-" + str(time.time()) + ".json" self.setup['ptfhost'].copy(content=json.dumps( { - 'vnet_vni_map' : self.setup[encap_type]['vnet_vni_map'], - 'vnet_intf_map' : self.setup[encap_type]['vnet_intf_map'], + 'vnet_vni_map': self.setup[encap_type]['vnet_vni_map'], + 'vnet_intf_map': self.setup[encap_type]['vnet_intf_map'], 'dest_to_nh_map': self.setup[encap_type]['dest_to_nh_map'], - 'neighbors' : self.setup[encap_type]['neighbor_config'], + 'neighbors': self.setup[encap_type]['neighbor_config'], 'intf_to_ip_map': self.setup[encap_type]['intf_to_ip_map'], }, indent=4), dest=config_filename) + Logger.info("Recording current DUT state.") + cmds = [ + "show vxlan tunnel", + "show vnet route all", + "show ip bgp summary", + "show ipv6 bgp summary"] + if self.setup['enable_bfd']: + cmds.append("show bfd summary") + for cmd in cmds: + self.setup['duthost'].shell(cmd) + + ptf_params = { + "topo_file": "/tmp/vxlan_topo_info.json", + "config_file": config_filename, + "t0_ports": ecmp_utils.get_ethernet_ports( + self.setup[encap_type]['selected_interfaces'], + self.setup['minigraph_facts']), + "t2_ports": self.setup[encap_type]['t2_ports'], + "dut_mac": self.setup['dut_mac'], + "vxlan_port": self.setup['vxlan_port'], + "expect_encap_success": expect_encap_success, + "packet_count": packet_count, + "random_dport": random_dport, + "random_sport": random_sport, + "random_src_ip": random_src_ip, + "tolerance": tolerance, + "downed_endpoints": list(self.setup['list_of_downed_endpoints']) + } + Logger.info("ptf arguments:%s", ptf_params) + Logger.info( + "dest->nh mapping:%s", self.setup[encap_type]['dest_to_nh_map']) + ptf_runner(self.setup['ptfhost'], "ptftests", "vxlan_traffic.VXLAN", platform_dir="ptftests", - params={ - "topo_file": "/tmp/vxlan_topo_info.json", - "config_file": config_filename, - "t0_ports":get_ethernet_ports(self.setup[encap_type]['selected_interfaces'], self.setup['minigraph_facts']), - "t2_ports":self.setup[encap_type]['t2_ports'], - "dut_mac":self.setup['dut_mac'], - "vxlan_port": self.setup['vxlan_port'], - "expect_encap_success":expect_encap_success, - "packet_count":packet_count - }, + params=ptf_params, qlen=1000, - log_file="/tmp/vxlan-tests.{}.{}.{}.log".format(tcname, encap_type, datetime.now().strftime('%Y-%m-%d-%H:%M:%S'))) + log_file="/tmp/vxlan-tests.{}.{}.{}.log".format( + tcname, + encap_type, + datetime.now().strftime('%Y-%m-%d-%H:%M:%S'))) + def update_monitor_list(self, bfd_enable, encap_type, ip_address_list): + ''' + Local function to update the bfd_responder's monitor file that + tracks which interfaces and ip addresses the bfd_responder will + work with. + ''' + if not bfd_enable: + return + if isinstance(ip_address_list, str): + ip_address_list = [ip_address_list] + self.setup['list_of_bfd_monitors'] = \ + self.setup['list_of_bfd_monitors'] | set(ip_address_list) + ecmp_utils.update_monitor_file( + self.setup['ptfhost'], + self.setup['monitor_file'], + self.setup[encap_type]['t2_ports'], + list(self.setup['list_of_bfd_monitors'])) + + def update_down_list(self, bfd_enable, encap_type, ip_address_list): + ''' + Local function to keep track of endpoint monitors that are down. + The bfd_responder will not be replying to any packet with these + addresses. + ''' + if not bfd_enable: + return + if isinstance(ip_address_list, str): + ip_address_list = [ip_address_list] + self.setup['list_of_downed_endpoints'] = \ + self.setup['list_of_downed_endpoints'] | set(ip_address_list) + self.setup['list_of_bfd_monitors'] = \ + self.setup['list_of_bfd_monitors'] - set(ip_address_list) + ecmp_utils.update_monitor_file( + self.setup['ptfhost'], + self.setup['monitor_file'], + self.setup[encap_type]['t2_ports'], + list(self.setup['list_of_bfd_monitors'])) -@pytest.fixture -def ignore_route_sync_errlogs(rand_one_dut_hostname, loganalyzer): - """Ignore expected failures logs during test execution.""" - if loganalyzer: - loganalyzer[rand_one_dut_hostname].ignore_regex.extend([".*Unaccounted_ROUTE_ENTRY_TABLE_entries.*"]) - return class Test_VxLAN_route_tests(Test_VxLAN): + ''' + Common class for the basic route test cases. + ''' def test_vxlan_single_endpoint(self, setUp, encap_type): ''' - tc1:Create a tunnel route to a single endpoint a. Send packets to the route prefix dst. + tc1:Create a tunnel route to a single endpoint a. + Send packets to the route prefix dst. ''' self.setup = setUp self.dump_self_info_and_run_ptf("tc1", encap_type, True) - def test_vxlan_modify_route_different_endpoint(self, setUp, request, encap_type): + def test_vxlan_modify_route_different_endpoint( + self, setUp, request, encap_type): ''' - tc2: change the route to different endpoint. packets are received only at endpoint b.") + tc2: change the route to different endpoint. + Packets are received only at endpoint b.") ''' self.setup = setUp - logger.info("Choose a vnet") + Logger.info("Choose a vnet") vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] - logger.info("Choose a destination, which is already present.") + Logger.info("Choose a destination, which is already present.") tc2_dest = self.setup[encap_type]['dest_to_nh_map'][vnet].keys()[0] - logger.info("Create a new endpoint, or endpoint-list.") + Logger.info("Create a new endpoint, or endpoint-list.") tc2_new_end_point_list = [] - for i in range(int(request.config.option.ecmp_nhs_per_destination)): - tc2_new_end_point_list.append(get_ip_address(af=get_outer_layer_version(encap_type), netid=NEXTHOP_PREFIX)) + for _ in range(int(request.config.option.ecmp_nhs_per_destination)): + tc2_new_end_point_list.append(ecmp_utils.get_ip_address( + af=ecmp_utils.get_outer_layer_version(encap_type), + netid=NEXTHOP_PREFIX)) - logger.info("Map the destination to the new endpoint(s).") - self.setup[encap_type]['dest_to_nh_map'][vnet][tc2_dest] = tc2_new_end_point_list + Logger.info("Map the destination to the new endpoint(s).") + self.setup[encap_type]['dest_to_nh_map'][vnet][tc2_dest] = \ + tc2_new_end_point_list - logger.info("Create the json and apply the config in the DUT swss.") + Logger.info("Create the json and apply the config in the DUT swss.") # The config looks like: # [ # { # "VNET_ROUTE_TUNNEL_TABLE:vnet:tc2_dest/32": { # "endpoint": "{tc2_new_end_point_list}" + # "endpoint_monitor": "{tc2_new_end_point_list}" # }, # "OP": "{}" # } # ] - tc2_full_config = '[\n' + create_single_route(vnet, tc2_dest, HOST_MASK[get_payload_version(encap_type)], tc2_new_end_point_list, "SET") + '\n]' - apply_config_in_swss(self.setup['duthost'], tc2_full_config, "vnet_route_tc2_"+encap_type) - - logger.info("Copy the new set of configs to the PTF and run the tests.") + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc2_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + tc2_new_end_point_list, + "SET", + bfd=self.setup['enable_bfd']) + self.update_monitor_list( + self.setup['enable_bfd'], + encap_type, + tc2_new_end_point_list) + + Logger.info( + "Copy the new set of configs to the PTF and run the tests.") self.dump_self_info_and_run_ptf("tc2", encap_type, True) def test_vxlan_remove_all_route(self, setUp, encap_type): ''' - tc3: remove the tunnel route. send packets to the route prefix dst. packets should not be received at any ports with dst ip of b") + tc3: remove the tunnel route. + Send packets to the route prefix dst. packets should not + be received at any ports with dst ip of b") ''' self.setup = setUp try: - logger.info("Remove the existing routes in the DUT.") - set_routes_in_dut(self.setup['duthost'], self.setup[encap_type]['dest_to_nh_map'], get_payload_version(encap_type), "DEL") - logger.info("Verify that the traffic is not coming back.") + Logger.info("Remove the existing routes in the DUT.") + ecmp_utils.set_routes_in_dut( + self.setup['duthost'], + self.setup[encap_type]['dest_to_nh_map'], + ecmp_utils.get_payload_version(encap_type), + "DEL") + Logger.info("Verify that the traffic is not coming back.") self.dump_self_info_and_run_ptf("tc3", encap_type, False) finally: - logger.info("Restore the routes in the DUT.") - set_routes_in_dut(self.setup['duthost'], self.setup[encap_type]['dest_to_nh_map'], get_payload_version(encap_type), "SET") + Logger.info("Restore the routes in the DUT.") + ecmp_utils.set_routes_in_dut( + self.setup['duthost'], + self.setup[encap_type]['dest_to_nh_map'], + ecmp_utils.get_payload_version(encap_type), + "SET", + bfd=self.setup['enable_bfd']) + class Test_VxLAN_ecmp_create(Test_VxLAN): + ''' + Class for all the ECMP (multiple nexthops per destination) + create testcases. + ''' def test_vxlan_configure_route1_ecmp_group_a(self, setUp, encap_type): ''' - tc4:create tunnel route 1 with two endpoints a = {a1, a2...}. send packets to the route 1's prefix dst. packets are received at either a1 or a2. + tc4:create tunnel route 1 with two endpoints a = {a1, a2...}. send + packets to the route 1's prefix dst. packets are received at either + a1 or a2. ''' self.setup = setUp - logger.info("Choose a vnet.") + Logger.info("Choose a vnet.") vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] - logger.info("Create a new list of endpoint(s).") + Logger.info("Create a new list of endpoint(s).") tc4_end_point_list = [] - for i in range(2): - tc4_end_point_list.append(get_ip_address(af=get_outer_layer_version(encap_type), netid=NEXTHOP_PREFIX)) + for _ in range(2): + tc4_end_point_list.append(ecmp_utils.get_ip_address( + af=ecmp_utils.get_outer_layer_version(encap_type), + netid=NEXTHOP_PREFIX)) + + Logger.info("Create a new destination") + tc4_new_dest = ecmp_utils.get_ip_address( + af=ecmp_utils.get_payload_version(encap_type), + netid=DESTINATION_PREFIX) + + Logger.info("Map the new destination and the new endpoint(s).") + self.setup[encap_type]['dest_to_nh_map'][vnet][tc4_new_dest] = \ + tc4_end_point_list + + Logger.info("Create a new config and Copy to the DUT.") + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc4_new_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + tc4_end_point_list, + "SET", + bfd=self.setup['enable_bfd']) + self.update_monitor_list( + self.setup['enable_bfd'], encap_type, tc4_end_point_list) + + Logger.info("Verify that the new config takes effect and run traffic.") - logger.info("Create a new destination") - tc4_new_dest = get_ip_address(af=get_payload_version(encap_type), netid=DESTINATION_PREFIX) + self.dump_self_info_and_run_ptf("tc4", encap_type, True) - logger.info("Map the new destination and the new endpoint(s).") - self.setup[encap_type]['dest_to_nh_map'][vnet][tc4_new_dest] = tc4_end_point_list + def test_vxlan_remove_ecmp_route1(self, setUp, encap_type): + ''' + Remove tunnel route 1. Send multiple packets (varying tuple) to the + route 1's prefix dst. + ''' + self.setup = setUp - logger.info("Create a new config and Copy to the DUT.") - tc4_config = '[\n' + create_single_route(vnet, tc4_new_dest, HOST_MASK[get_payload_version(encap_type)], tc4_end_point_list, "SET") + '\n]' - apply_config_in_swss(self.setup['duthost'], tc4_config, "vnet_route_tc4_"+encap_type) + Logger.info("Choose a vnet.") + vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] - logger.info("Verify that the new config takes effect and run traffic.") - self.dump_self_info_and_run_ptf("tc4", encap_type, True) + backup_dest = self.setup[encap_type]['dest_to_nh_map'][vnet].copy() + + Logger.info("Create a new list of endpoint(s).") + ecmp_route1_end_point_list = [] + for _ in range(2): + ecmp_route1_end_point_list.append( + ecmp_utils.get_ip_address( + af=ecmp_utils.get_outer_layer_version(encap_type), + netid=NEXTHOP_PREFIX)) + + Logger.info("Create a new destination") + ecmp_route1_new_dest = ecmp_utils.get_ip_address( + af=ecmp_utils.get_payload_version(encap_type), + netid=DESTINATION_PREFIX) + + Logger.info("Map the new destination and the new endpoint(s).") + self.setup[encap_type]['dest_to_nh_map'][vnet][ecmp_route1_new_dest] =\ + ecmp_route1_end_point_list + + Logger.info("Create a new config and Copy to the DUT.") + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + ecmp_route1_new_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + ecmp_route1_end_point_list, + "SET", + bfd=self.setup['enable_bfd']) + self.update_monitor_list( + self.setup['enable_bfd'], + encap_type, + ecmp_route1_end_point_list) + + Logger.info("Verify that the new config takes effect and run traffic.") + self.dump_self_info_and_run_ptf("tc5", encap_type, True) + + # Deleting Tunnel route 1 + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + ecmp_route1_new_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + ecmp_route1_end_point_list, + "DEL") + + self.setup[encap_type]['dest_to_nh_map'][vnet] =\ + {ecmp_route1_new_dest: ecmp_route1_end_point_list} + + Logger.info("Verify that the new config takes effect and run traffic.") + self.dump_self_info_and_run_ptf("tc5", encap_type, False) + + # Restoring dest_to_nh_map to old values + self.setup[encap_type]['dest_to_nh_map'][vnet] = backup_dest.copy() + self.dump_self_info_and_run_ptf("tc5", encap_type, True) def test_vxlan_configure_route1_ecmp_group_b(self, setUp, encap_type): ''' - tc5: set tunnel route 2 to endpoint group a = {a1, a2}. send packets to route 2"s prefix dst. packets are received at either a1 or a2 + tc5: set tunnel route 2 to endpoint group a = {a1, a2}. send + packets to route 2"s prefix dst. packets are received at either a1 + or a2 ''' self.setup = setUp self.setup_route2_ecmp_group_b(encap_type) - logger.info("Verify the configs work and traffic flows correctly.") + Logger.info("Verify the configs work and traffic flows correctly.") self.dump_self_info_and_run_ptf("tc5", encap_type, True) def setup_route2_ecmp_group_b(self, encap_type): + ''' + Function for handling the dependency of tc6 on tc5. This function + is essentially tc5. + ''' if self.setup[encap_type].get('tc5_dest', None): return - logger.info("Choose a vnet for testing.") + Logger.info("Choose a vnet for testing.") vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] - logger.info("Select an existing endpoint.") - tc5_end_point_list = self.setup[encap_type]['dest_to_nh_map'][vnet].values()[0] + Logger.info("Select an existing endpoint.") + tc5_end_point_list = \ + self.setup[encap_type]['dest_to_nh_map'][vnet].values()[0] + + Logger.info("Create a new destination to use.") + tc5_new_dest = ecmp_utils.get_ip_address( + af=ecmp_utils.get_payload_version(encap_type), + netid=DESTINATION_PREFIX) + + Logger.info("Map the new destination to the endpoint.") + self.setup[encap_type]['dest_to_nh_map'][vnet][tc5_new_dest] = \ + tc5_end_point_list + + Logger.info("Create the new config and apply to the DUT.") + + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc5_new_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + tc5_end_point_list, + "SET", + bfd=self.setup['enable_bfd']) + self.update_monitor_list( + self.setup['enable_bfd'], + encap_type, + tc5_end_point_list) - logger.info("Create a new destination to use.") - tc5_new_dest = get_ip_address(af=get_payload_version(encap_type), netid=DESTINATION_PREFIX) - - logger.info("Map the new destination to the endpoint.") - self.setup[encap_type]['dest_to_nh_map'][vnet][tc5_new_dest] = tc5_end_point_list - - logger.info("Create the new config and apply to the DUT.") - tc5_config = '[\n' + create_single_route(vnet, tc5_new_dest, HOST_MASK[get_payload_version(encap_type)], tc5_end_point_list, "SET") + '\n]' - apply_config_in_swss(self.setup['duthost'], tc5_config, "vnet_route_tc5_"+encap_type) self.setup[encap_type]['tc5_dest'] = tc5_new_dest def test_vxlan_configure_route2_ecmp_group_b(self, setUp, encap_type): ''' - tc6: set tunnel route 2 to endpoint group b = {b1, b2}. send packets to route 2"s prefix dst. packets are received at either b1 or b2. + tc6: set tunnel route 2 to endpoint group b = {b1, b2}. send + packets to route 2"s prefix dst. packets are received at either + b1 or b2. ''' self.setup = setUp self.setup_route2_ecmp_group_b(encap_type) - logger.info("Choose a vnet for testing.") + Logger.info("Choose a vnet for testing.") vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] - logger.info("Create a new list of endpoints.") + Logger.info("Create a new list of endpoints.") tc6_end_point_list = [] - for i in range(2): - tc6_end_point_list.append(get_ip_address(af=get_outer_layer_version(encap_type), netid=NEXTHOP_PREFIX)) + for _ in range(2): + tc6_end_point_list.append( + ecmp_utils.get_ip_address( + af=ecmp_utils.get_outer_layer_version(encap_type), + netid=NEXTHOP_PREFIX)) - logger.info("Choose one of the existing destinations.") + Logger.info("Choose one of the existing destinations.") tc6_new_dest = self.setup[encap_type]['tc5_dest'] - logger.info("Map the destination to the new endpoints.") - self.setup[encap_type]['dest_to_nh_map'][vnet][tc6_new_dest] = tc6_end_point_list - - logger.info("Create the config and apply on the DUT.") - tc6_config = '[\n' + create_single_route(vnet, tc6_new_dest, HOST_MASK[get_payload_version(encap_type)], tc6_end_point_list, "SET") + '\n]' - apply_config_in_swss(self.setup['duthost'], tc6_config, "vnet_route_tc6_"+encap_type) + Logger.info("Map the destination to the new endpoints.") + self.setup[encap_type]['dest_to_nh_map'][vnet][tc6_new_dest] = \ + tc6_end_point_list + + Logger.info("Create the config and apply on the DUT.") + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc6_new_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + tc6_end_point_list, + "SET", + bfd=self.setup['enable_bfd']) + self.update_monitor_list( + self.setup['enable_bfd'], + encap_type, + tc6_end_point_list) + Logger.info("Verify that the traffic works.") - logger.info("Verify that the traffic works.") self.dump_self_info_and_run_ptf("tc6", encap_type, True) -class Test_VxLAN_NHG_Modify(Test_VxLAN): + @pytest.mark.skipif( + "config.option.bfd is False", + reason="This test will be run only if '--bfd=True' is provided.") + def test_vxlan_bfd_health_state_change_a2down_a1up( + self, setUp, encap_type): + ''' + Set BFD state for a1' to UP and a2' to Down. Send multiple packets + (varying tuple) to the route 1's prefix dst. Packets are received + only at endpoint a1. Verify advertise table is present. + ''' + self.setup = setUp + + Logger.info("Choose a vnet.") + vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] + + Logger.info("Create a new list of endpoint(s).") + end_point_list = [] + for _ in range(2): + end_point_list.append( + ecmp_utils.get_ip_address( + af=ecmp_utils.get_outer_layer_version(encap_type), + netid=NEXTHOP_PREFIX)) + + Logger.info("Create a new destination") + tc4_new_dest = ecmp_utils.get_ip_address( + af=ecmp_utils.get_payload_version(encap_type), + netid=DESTINATION_PREFIX) + + Logger.info("Map the new destination and the new endpoint(s).") + self.setup[encap_type]['dest_to_nh_map'][vnet][tc4_new_dest] = \ + end_point_list + + Logger.info("Create a new config and Copy to the DUT.") + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc4_new_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + end_point_list, + "SET", + bfd=self.setup['enable_bfd']) + # Only a1 is up, bfd-responder will not respond to a2. + self.update_monitor_list( + self.setup['enable_bfd'], + encap_type, + end_point_list[0]) + self.update_down_list( + self.setup['enable_bfd'], + encap_type, + end_point_list[1]) + + Logger.info("Verify that the new config takes effect and run traffic.") + self.dump_self_info_and_run_ptf("tc_a2down_a1up", encap_type, True) + + @pytest.mark.skipif( + "config.option.bfd is False", + reason="This test will be run only if '--bfd=True' is provided.") + def test_vxlan_bfd_health_state_change_a1a2_down(self, setUp, encap_type): + ''' + Set BFD state for a1' to Down and a2' to Down. Send multiple + packets (varying tuple) to the route 1's prefix dst. Packets + are not received at any ports. Verify advertise table is removed. + ''' + self.setup = setUp + + Logger.info("Choose a vnet.") + vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] + + Logger.info("Create a new list of endpoint(s).") + end_point_list = [] + for _ in range(2): + end_point_list.append(ecmp_utils.get_ip_address( + af=ecmp_utils.get_outer_layer_version(encap_type), + netid=NEXTHOP_PREFIX)) + + Logger.info("Create a new destination") + tc4_new_dest = ecmp_utils.get_ip_address( + af=ecmp_utils.get_payload_version(encap_type), + netid=DESTINATION_PREFIX) + + Logger.info("Map the new destination and the new endpoint(s).") + self.setup[encap_type]['dest_to_nh_map'][vnet][tc4_new_dest] = \ + end_point_list + + Logger.info("Create a new config and Copy to the DUT.") + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc4_new_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + end_point_list, + "SET", + bfd=self.setup['enable_bfd']) + # No adding to the monitor_list. + self.update_down_list( + self.setup['enable_bfd'], + encap_type, + end_point_list) + + Logger.info("Verify that the new config takes effect and run traffic.") + self.dump_self_info_and_run_ptf( + "a1a2_down", + encap_type, + True, + packet_count=4) + + @pytest.mark.skipif( + "config.option.bfd is False", + reason="This test will be run only if '--bfd=True' is provided.") + def test_vxlan_bfd_health_state_change_a2up_a1down( + self, setUp, encap_type): + ''' + Set BFD state for a2' to UP. Send packets to the route 1's prefix + dst. Packets are received only at endpoint a2. Verify advertise + table is present + ''' + self.setup = setUp + + Logger.info("Choose a vnet.") + vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] + Logger.info("Create a new list of endpoint(s).") + end_point_list = [] + for _ in range(2): + end_point_list.append( + ecmp_utils.get_ip_address( + af=ecmp_utils.get_outer_layer_version(encap_type), + netid=NEXTHOP_PREFIX)) + + Logger.info("Create a new destination") + tc4_new_dest = ecmp_utils.get_ip_address( + af=ecmp_utils.get_payload_version(encap_type), + netid=DESTINATION_PREFIX) + + Logger.info("Map the new destination and the new endpoint(s).") + self.setup[encap_type]['dest_to_nh_map'][vnet][tc4_new_dest] = \ + end_point_list + + Logger.info("Create a new config and Copy to the DUT.") + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc4_new_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + end_point_list, + "SET", + bfd=self.setup['enable_bfd']) + # Only a2 is up, but a1 is down. + self.update_monitor_list( + self.setup['enable_bfd'], + encap_type, + end_point_list[1]) + self.update_down_list( + self.setup['enable_bfd'], + encap_type, + end_point_list[0]) + + Logger.info("Verify that the new config takes effect and run traffic.") + self.dump_self_info_and_run_ptf("a2up_a1down", encap_type, True) + + def test_vxlan_bfd_health_state_change_a1a2_up(self, setUp, encap_type): + ''' + Set BFD state for a1' & a2' to UP. Send multiple packets (varying + tuple) to the route 1's prefix dst. Packets are received at both + a1 and a2. Verify advertise table is present + ''' + self.setup = setUp + + Logger.info("Choose a vnet.") + vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] + + Logger.info("Create a new list of endpoint(s).") + end_point_list = [] + for _ in range(2): + end_point_list.append(ecmp_utils.get_ip_address( + af=ecmp_utils.get_outer_layer_version(encap_type), + netid=NEXTHOP_PREFIX)) + + Logger.info("Create a new destination") + tc4_new_dest = ecmp_utils.get_ip_address( + af=ecmp_utils.get_payload_version(encap_type), + netid=DESTINATION_PREFIX) + + Logger.info("Map the new destination and the new endpoint(s).") + self.setup[encap_type]['dest_to_nh_map'][vnet][tc4_new_dest] = \ + end_point_list + + Logger.info("Create a new config and Copy to the DUT.") + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc4_new_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + end_point_list, + "SET", + bfd=self.setup['enable_bfd']) + self.update_monitor_list( + self.setup['enable_bfd'], + encap_type, + end_point_list) + + Logger.info("Verify that the new config takes effect and run traffic.") + + self.dump_self_info_and_run_ptf("tc4", encap_type, True) + + +class Test_VxLAN_NHG_Modify(Test_VxLAN): + ''' + Class for all the next-hop group modification testcases. + ''' def setup_route2_single_endpoint(self, encap_type): + ''' + Function to handle dependency of tc9 on tc8. + ''' if self.setup[encap_type].get('tc8_dest', None): return - logger.info("Pick a vnet for testing.") + Logger.info("Pick a vnet for testing.") vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] - logger.info("Choose a route 2 destination and a new single endpoint for it.") + Logger.info( + "Choose a route 2 destination and a new single endpoint for it.") tc8_new_dest = self.setup[encap_type]['dest_to_nh_map'][vnet].keys()[0] - tc8_new_nh = get_ip_address(af=get_outer_layer_version(encap_type), netid=NEXTHOP_PREFIX) - logger.info("Using destinations: dest:{} => nh:{}".format(tc8_new_dest, tc8_new_nh)) - - logger.info("Map the destination and new endpoint.") - tc8_config = '[\n' + create_single_route(vnet, tc8_new_dest, HOST_MASK[get_payload_version(encap_type)], [tc8_new_nh], "SET") + '\n]' - self.setup[encap_type]['dest_to_nh_map'][vnet][tc8_new_dest] = [tc8_new_nh] - - logger.info("Apply the new config in the DUT and run traffic test.") - apply_config_in_swss(self.setup['duthost'], tc8_config, "vnet_route_tc8_"+encap_type) + tc8_new_nh = ecmp_utils.get_ip_address( + af=ecmp_utils.get_outer_layer_version(encap_type), + netid=NEXTHOP_PREFIX) + self.setup[encap_type]['dest_to_nh_map'][vnet][tc8_new_dest] = \ + [tc8_new_nh] + Logger.info( + "Using destinations: dest:%s => nh:%s", + tc8_new_dest, + tc8_new_nh) + + Logger.info("Map the destination and new endpoint.") + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc8_new_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + [tc8_new_nh], + "SET", + bfd=self.setup['enable_bfd']) + self.update_monitor_list( + self.setup['enable_bfd'], + encap_type, + tc8_new_nh) + + Logger.info("Apply the new config in the DUT and run traffic test.") self.setup[encap_type]['tc8_dest'] = tc8_new_dest def setup_route2_shared_endpoints(self, encap_type): + ''' + Function to handle dependency of tc10 on tc9 + ''' if self.setup[encap_type].get('tc9_dest', None): return self.setup_route2_single_endpoint(encap_type) - logger.info("Choose a vnet for testing.") + Logger.info("Choose a vnet for testing.") vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] - logger.info("Select 2 already existing destinations. They must have 2 different nexthops.") + Logger.info( + "Select 2 already existing destinations. " + "They must have 2 different nexthops.") tc9_new_dest1 = self.setup[encap_type]['tc8_dest'] nh1 = self.setup[encap_type]['dest_to_nh_map'][vnet][tc9_new_dest1][0] @@ -888,32 +1001,58 @@ def setup_route2_shared_endpoints(self, encap_type): nh2 = nh break if nh2: - logger.info("Using destinations: dest:{}, nexthops:{}, {}".format(tc9_new_dest1, nh1, nh2)) + Logger.info( + "Using destinations: dest:%s, nexthops:%s, %s", + tc9_new_dest1, + nh1, + nh2) else: - raise RuntimeError("Couldnot find different nexthop for this test. The current list: {}".format(self.setup[encap_type]['dest_to_nh_map'])) - - logger.info("Use the selected nexthops(tunnel endpoints). They are guaranteed to be different.") + raise RuntimeError( + "Couldnot find different nexthop for this test." + "The current list: {}".format( + self.setup[encap_type]['dest_to_nh_map'])) + + Logger.info( + "Use the selected nexthops(tunnel endpoints)." + "They are guaranteed to be different.") tc9_new_nhs = [nh1, nh2] - logger.info("Map the destination 1 to the combined list.") - self.setup[encap_type]['dest_to_nh_map'][vnet][tc9_new_dest1] = tc9_new_nhs - tc9_config = '[\n' + create_single_route(vnet, tc9_new_dest1, HOST_MASK[get_payload_version(encap_type)], tc9_new_nhs, "SET") + '\n]' + Logger.info("Map the destination 1 to the combined list.") + self.setup[encap_type]['dest_to_nh_map'][vnet][tc9_new_dest1] = \ + tc9_new_nhs + + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc9_new_dest1, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + tc9_new_nhs, + "SET", + bfd=self.setup['enable_bfd']) + self.update_monitor_list( + self.setup['enable_bfd'], + encap_type, + tc9_new_nhs) - logger.info("Apply the new config to the DUT and send traffic.") - apply_config_in_swss(self.setup['duthost'], tc9_config, "vnet_route_tc9_"+encap_type) self.setup[encap_type]['tc9_dest'] = tc9_new_dest1 def setup_route2_shared_different_endpoints(self, encap_type): + ''' + Function to handle dependency of tc9.2 on tc9 + ''' if self.setup[encap_type].get('tc9_dest', None): return self.setup_route2_single_endpoint(encap_type) - logger.info("Choose a vnet for testing.") + Logger.info("Choose a vnet for testing.") vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] - logger.info("Select 2 already existing destinations. They must have 2 different nexthops.") + Logger.info( + "Select 2 already existing destinations. " + "They must have 2 different nexthops.") tc9_new_dest1 = self.setup[encap_type]['tc8_dest'] - old_nh = self.setup[encap_type]['dest_to_nh_map'][vnet][tc9_new_dest1][0] + old_nh = \ + self.setup[encap_type]['dest_to_nh_map'][vnet][tc9_new_dest1][0] nh1 = None nh2 = None @@ -921,7 +1060,7 @@ def setup_route2_shared_different_endpoints(self, encap_type): nexthops = self.setup[encap_type]['dest_to_nh_map'][vnet][dest] for nh in nexthops: if nh == old_nh: - next + continue else: if not nh1: nh1 = nh @@ -930,69 +1069,111 @@ def setup_route2_shared_different_endpoints(self, encap_type): nh2 = nh break if nh2: - logger.info("Using destinations: dest:{}, nexthops:{}, {}".format(tc9_new_dest1, nh1, nh2)) + Logger.info( + "Using destinations: dest:%s, nexthops:%s, %s", + tc9_new_dest1, + nh1, + nh2) else: - raise RuntimeError("Couldnot find different nexthop for this test. The current list: {}".format(self.setup[encap_type]['dest_to_nh_map'])) - - logger.info("Use the selected nexthops(tunnel endpoints). They are guaranteed to be different.") + raise RuntimeError( + "Couldnot find different nexthop for this test." + "The current list: {}".format( + self.setup[encap_type]['dest_to_nh_map'])) + + Logger.info( + "Use the selected nexthops(tunnel endpoints)." + "They are guaranteed to be different.") tc9_new_nhs = [nh1, nh2] - logger.info("Map the destination 1 to the combined list.") - self.setup[encap_type]['dest_to_nh_map'][vnet][tc9_new_dest1] = tc9_new_nhs - tc9_config = '[\n' + create_single_route(vnet, tc9_new_dest1, HOST_MASK[get_payload_version(encap_type)], tc9_new_nhs, "SET") + '\n]' - - logger.info("Apply the new config to the DUT and send traffic.") - apply_config_in_swss(self.setup['duthost'], tc9_config, "vnet_route_tc9_"+encap_type) + Logger.info("Map the destination 1 to the combined list.") + self.setup[encap_type]['dest_to_nh_map'][vnet][tc9_new_dest1] = \ + tc9_new_nhs self.setup[encap_type]['tc9_dest'] = tc9_new_dest1 - + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc9_new_dest1, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + tc9_new_nhs, + "SET", + bfd=self.setup['enable_bfd']) + self.update_monitor_list( + self.setup['enable_bfd'], + encap_type, + tc9_new_nhs) def test_vxlan_remove_route2(self, setUp, encap_type): ''' - tc7:send packets to route 1's prefix dst. by removing route 2 from group a, no change expected to route 1. + tc7:send packets to route 1's prefix dst. by removing route 2 from + group a, no change expected to route 1. ''' self.setup = setUp - logger.info("Pick a vnet for testing.") + Logger.info("Pick a vnet for testing.") vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] - logger.info("Setup: Create two destinations with the same endpoint group.") + Logger.info( + "Setup: Create two destinations with the same endpoint group.") tc7_end_point_list = [] - for i in range(2): - tc7_end_point_list.append(get_ip_address(af=get_outer_layer_version(encap_type), netid=NEXTHOP_PREFIX)) + for _ in range(2): + tc7_end_point_list.append(ecmp_utils.get_ip_address( + af=ecmp_utils.get_outer_layer_version(encap_type), + netid=NEXTHOP_PREFIX)) tc7_destinations = [] + for _ in range(2): + tc7_destinations.append(ecmp_utils.get_ip_address( + af=ecmp_utils.get_payload_version(encap_type), + netid=DESTINATION_PREFIX)) + dest_nh_map = self.setup[encap_type]['dest_to_nh_map'] + Logger.info("Map the new destinations to the same endpoint list.") for i in range(2): - tc7_destinations.append(get_ip_address(af=get_payload_version(encap_type), netid=DESTINATION_PREFIX)) - - logger.info("Map the new destinations to the same endpoint list.") - for i in range(2): - self.setup[encap_type]['dest_to_nh_map'][vnet][tc7_destinations[i]] = tc7_end_point_list + dest_nh_map[vnet][tc7_destinations[i]] = \ + tc7_end_point_list - logger.info("Apply the setup configs to the DUT.") + Logger.info("Apply the setup configs to the DUT.") + payload_af = ecmp_utils.get_payload_version(encap_type) for i in range(2): - tc7_setup_config = '[\n' + create_single_route(vnet, tc7_destinations[i], HOST_MASK[get_payload_version(encap_type)], tc7_end_point_list, "SET") + '\n]' - apply_config_in_swss(self.setup['duthost'], tc7_setup_config, "vnet_route_tc7_"+encap_type) - - logger.info("Verify the setup works.") + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc7_destinations[i], + ecmp_utils.HOST_MASK[payload_af], + tc7_end_point_list, + "SET", + bfd=self.setup['enable_bfd']) + + self.update_monitor_list( + self.setup['enable_bfd'], + encap_type, + tc7_end_point_list) + Logger.info("Verify the setup works.") self.dump_self_info_and_run_ptf("tc7", encap_type, True) - logger.info("End of setup.") + Logger.info("End of setup.") - logger.info("Remove one of the routes.") - logger.info("Pick one out of the two TC7 destinations.") + Logger.info("Remove one of the routes.") + Logger.info("Pick one out of the two TC7 destinations.") tc7_removed_dest = tc7_destinations[0] - tc7_removed_endpoint = self.setup[encap_type]['dest_to_nh_map'][vnet][tc7_removed_dest] + tc7_removed_endpoint = \ + self.setup[encap_type]['dest_to_nh_map'][vnet][tc7_removed_dest] del self.setup[encap_type]['dest_to_nh_map'][vnet][tc7_removed_dest] - logger.info("Remove the chosen dest/endpoint from the DUT.") - tc7_config = '[\n' + create_single_route(vnet, tc7_removed_dest, HOST_MASK[get_payload_version(encap_type)], tc7_removed_endpoint, "DEL") + '\n]' - apply_config_in_swss(self.setup['duthost'], tc7_config, "vnet_route_tc7_"+encap_type) + Logger.info("Remove the chosen dest/endpoint from the DUT.") + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc7_removed_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + tc7_removed_endpoint, + "DEL") - logger.info("Verify the rest of the traffic still works.") + Logger.info("Verify the rest of the traffic still works.") self.dump_self_info_and_run_ptf("tc7", encap_type, True) def test_vxlan_route2_single_nh(self, setUp, encap_type): ''' - tc8: set tunnel route 2 to single endpoint b1. send packets to route 2's prefix dst. + tc8: set tunnel route 2 to single endpoint b1. + Send packets to route 2's prefix dst. ''' self.setup = setUp self.setup_route2_single_endpoint(encap_type) @@ -1000,7 +1181,9 @@ def test_vxlan_route2_single_nh(self, setUp, encap_type): def test_vxlan_route2_shared_nh(self, setUp, encap_type): ''' - tc9: set tunnel route 2 to shared endpoints a1 and b1. send packets to route 2's prefix dst. + tc9: set tunnel route 2 to shared endpoints a1 and b1. + Send packets to route 2's + prefix dst. ''' self.setup = setUp self.setup_route2_shared_endpoints(encap_type) @@ -1008,7 +1191,9 @@ def test_vxlan_route2_shared_nh(self, setUp, encap_type): def test_vxlan_route2_shared_different_nh(self, setUp, encap_type): ''' - tc9.2: set tunnel route 2 to 2 completely different shared(no-reuse) endpoints a1 and b1. send packets to route 2's prefix dst. + tc9.2: set tunnel route 2 to 2 completely different + shared(no-reuse) endpoints a1 and b1. send packets + to route 2's prefix dst. ''' self.setup = setUp self.setup_route2_shared_different_endpoints(encap_type) @@ -1020,98 +1205,169 @@ def test_vxlan_remove_ecmp_route2(self, setUp, encap_type): ''' self.setup = setUp self.setup_route2_shared_endpoints(encap_type) - logger.info("Backup the current route config.") - full_map = dict(self.setup[encap_type]['dest_to_nh_map']) + Logger.info("Backup the current route config.") + vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] + full_map = self.setup[encap_type]['dest_to_nh_map'][vnet].copy() + payload_af = ecmp_utils.get_payload_version(encap_type) - logger.info("This is to keep track if the selected route should be deleted in the end.") + Logger.info( + "This is to keep track if the selected route " + "should be deleted in the end.") del_needed = False try: - logger.info("Choose a vnet for testing.") - vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] + Logger.info("Choose a vnet for testing.") - logger.info("Choose a destination and its nhs to delete.") + Logger.info("Choose a destination and its nhs to delete.") tc10_dest = self.setup[encap_type]['tc9_dest'] - tc10_nhs = self.setup[encap_type]['dest_to_nh_map'][vnet][tc10_dest] - logger.info("Using destination: dest:{}, nh:{}".format(tc10_dest, tc10_nhs)) + tc10_nhs = \ + self.setup[encap_type]['dest_to_nh_map'][vnet][tc10_dest] + Logger.info( + "Using destination: dest:%s, nh:%s", + tc10_dest, + tc10_nhs) + + Logger.info("Delete the dest and nh in the DUT.") + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc10_dest, + ecmp_utils.HOST_MASK[payload_af], + tc10_nhs, + "DEL") - logger.info("Delete the dest and nh in the DUT.") - tc10_config = '[\n' + create_single_route(vnet, tc10_dest, HOST_MASK[get_payload_version(encap_type)], tc10_nhs, "DEL") + '\n]' - apply_config_in_swss(self.setup['duthost'], tc10_config, "vnet_route_tc10_"+encap_type) del_needed = True - logger.info("We should pass only the deleted entry to the ptf call, and expect encap to fail.") - logger.info("Clear out the mappings, and keep only the deleted dest and nhs.") + Logger.info( + "We should pass only the deleted entry to the ptf call," + "and expect encap to fail.") + Logger.info( + "Clear out the mappings, and keep only " + "the deleted dest and nhs.") self.setup[encap_type]['dest_to_nh_map'][vnet] = {} - self.setup[encap_type]['dest_to_nh_map'][vnet][tc10_dest] = tc10_nhs + self.setup[encap_type]['dest_to_nh_map'][vnet][tc10_dest] =\ + tc10_nhs - logger.info("The deleted route should fail to receive traffic.") + Logger.info("The deleted route should fail to receive traffic.") self.dump_self_info_and_run_ptf("tc10", encap_type, False) # all others should be working. # Housekeeping: - logger.info("Restore the mapping of dest->nhs.") - self.setup[encap_type]['dest_to_nh_map'] = dict(full_map) - logger.info("Remove the deleted entry alone.") + Logger.info("Restore the mapping of dest->nhs.") + self.setup[encap_type]['dest_to_nh_map'][vnet] = full_map.copy() + Logger.info("Remove the deleted entry alone.") del self.setup[encap_type]['dest_to_nh_map'][vnet][tc10_dest] del_needed = False - logger.info("Check the traffic is working in the other routes.") + Logger.info("Check the traffic is working in the other routes.") self.dump_self_info_and_run_ptf("tc10", encap_type, True) - except: - self.setup[encap_type]['dest_to_nh_map'] = dict(full_map) - logger.info("Remove the deleted entry alone.") + except BaseException: + self.setup[encap_type]['dest_to_nh_map'][vnet] = full_map.copy() + Logger.info("Remove the deleted entry alone.") if del_needed: del self.setup[encap_type]['dest_to_nh_map'][vnet][tc10_dest] raise + +@pytest.mark.skipif( + "config.option.include_long_tests is False", + reason="This test will be run only if " + "'--include_long_tests=True' is provided.") class Test_VxLAN_ecmp_random_hash(Test_VxLAN): + ''' + Class for testing different tcp ports for payload. + ''' def test_vxlan_random_hash(self, setUp, encap_type): ''' - tc11: set tunnel route 3 to endpoint group c = {c1, c2, c3}. ensure c1, c2, and c3 matches to underlay default route. send 1000 pkt with random hash to route 3's prefix dst. + tc11: set tunnel route 3 to endpoint group c = {c1, c2, c3}. + Ensure c1, c2, and c3 matches to underlay default route. + Send 1000 pkt with random hash to route 3's prefix dst. ''' self.setup = setUp - logger.info("Chose a vnet for testing.") + Logger.info("Chose a vnet for testing.") vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] - logger.info("Create a new destination and 3 nhs for it.") - tc11_new_dest = get_ip_address(af=get_payload_version(encap_type), netid=DESTINATION_PREFIX) + Logger.info("Create a new destination and 3 nhs for it.") + tc11_new_dest = ecmp_utils.get_ip_address( + af=ecmp_utils.get_payload_version(encap_type), + netid=DESTINATION_PREFIX) + tc11_new_nhs = [] - for i in range(3): - tc11_new_nhs.append(get_ip_address(af=get_outer_layer_version(encap_type), netid=NEXTHOP_PREFIX)) + for _ in range(3): + tc11_new_nhs.append(ecmp_utils.get_ip_address( + af=ecmp_utils.get_outer_layer_version(encap_type), + netid=NEXTHOP_PREFIX)) # the topology always provides the default routes for any ip address. # so it is already taken care of. - logger.info("Map the new dest and nhs.") - tc11_config = '[\n' + create_single_route(vnet, tc11_new_dest, HOST_MASK[get_payload_version(encap_type)], tc11_new_nhs, "SET") + '\n]' - self.setup[encap_type]['dest_to_nh_map'][vnet][tc11_new_dest] = tc11_new_nhs - - logger.info("Apply the config in the DUT and verify traffic. The random hash and ECMP check is already taken care of in the VxLAN PTF script.") - apply_config_in_swss(self.setup['duthost'], tc11_config, "vnet_route_tc11_"+encap_type) - self.dump_self_info_and_run_ptf("tc11", encap_type, True, packet_count=1000) - -@pytest.mark.usefixtures("ignore_route_sync_errlogs") + Logger.info("Map the new dest and nhs.") + self.setup[encap_type]['dest_to_nh_map'][vnet][tc11_new_dest] =\ + tc11_new_nhs + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + tc11_new_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + tc11_new_nhs, + "SET", + bfd=self.setup['enable_bfd']) + self.update_monitor_list( + self.setup['enable_bfd'], + encap_type, + tc11_new_nhs) + + Logger.info( + "Apply the config in the DUT and verify traffic. " + "The random hash and ECMP check is already taken care of in the " + "VxLAN PTF script.") + self.dump_self_info_and_run_ptf( + "tc11", + encap_type, + True, + packet_count=1000) + + +@pytest.mark.skipif( + "config.option.include_long_tests is False", + reason="This test will be run only if " + "'--include_long_tests=True' is provided.") class Test_VxLAN_underlay_ecmp(Test_VxLAN): + ''' + Class for all test cases that modify the underlay default route. + ''' @pytest.mark.parametrize("ecmp_path_count", [1, 2]) - def test_vxlan_modify_underlay_default(self, setUp, minigraph_facts, encap_type, ecmp_path_count): + def test_vxlan_modify_underlay_default( + self, setUp, minigraph_facts, encap_type, ecmp_path_count): ''' - tc12: modify the underlay default route nexthop/s. send packets to route 3's prefix dst. + tc12: modify the underlay default route nexthop/s. send packets to + route 3's prefix dst. ''' self.setup = setUp - # First step: pick one or two of the interfaces connected to t2, and bring them down. - # verify that the encap is still working, and ptf receives the traffic. - # Bring them back up. - # After that, bring down all the other t2 interfaces, other than the ones used in the first step. - # This will force a modification to the underlay default routes nexthops. + ''' + First step: pick one or two of the interfaces connected to t2, and + bring them down. verify that the encap is still working, and ptf + receives the traffic. Bring them back up. + After that, bring down all the other t2 interfaces, other than + the ones used in the first step. This will force a modification + to the underlay default routes nexthops. + ''' + + all_t2_intfs = list(ecmp_utils.get_portchannels_to_neighbors( + self.setup['duthost'], + "T2", + minigraph_facts)) - all_t2_intfs = list(get_portchannels_to_neighbors(self.setup['duthost'], "T2", minigraph_facts)) if not all_t2_intfs: - all_t2_intfs = get_ethernet_to_neighbors("T2", minigraph_facts) - logger.info("Dumping T2 link info: {}".format(all_t2_intfs)) + all_t2_intfs = ecmp_utils.get_ethernet_to_neighbors( + "T2", + minigraph_facts) + Logger.info("Dumping T2 link info: %s", all_t2_intfs) if not all_t2_intfs: - raise RuntimeError("No interface found connected to t2 neighbors. pls check the testbed, aborting.") + raise RuntimeError( + "No interface found connected to t2 neighbors. " + "pls check the testbed, aborting.") # Keep a copy of the internal housekeeping list of t2 ports. # This is the full list of DUT ports connected to T2 neighbors. @@ -1121,95 +1377,598 @@ def test_vxlan_modify_underlay_default(self, setUp, minigraph_facts, encap_type, # A distinction in this script between ports and interfaces: # Ports are physical (Ethernet) only. # Interfaces have IP address(Ethernet or PortChannel). - try: selected_intfs = [] # Choose some intfs based on the parameter ecmp_path_count. - # when ecmp_path_count == 1, it is non-ecmp. The switching happens between ecmp and non-ecmp. - # Otherwise, the switching happens within ecmp only. + # when ecmp_path_count == 1, it is non-ecmp. The switching + # happens between ecmp and non-ecmp. Otherwise, the switching + # happens within ecmp only. for i in range(ecmp_path_count): selected_intfs.append(all_t2_intfs[i]) for intf in selected_intfs: - self.setup['duthost'].shell("sudo config interface shutdown {}".format(intf)) - downed_ports = get_corresponding_ports(selected_intfs, minigraph_facts) - self.setup[encap_type]['t2_ports'] = list(set(all_t2_ports) - set(downed_ports)) - downed_bgp_neighbors = get_downed_bgp_neighbors(selected_intfs, minigraph_facts) - pytest_assert(wait_until(300, 30, 0, bgp_established, self.setup['duthost'], down_list=downed_bgp_neighbors), "BGP neighbors didn't come up after all interfaces have been brought up.") - self.dump_self_info_and_run_ptf("tc12", encap_type, True, packet_count=1000) - - logger.info("Reverse the action: bring up the selected_intfs and shutdown others.") + self.setup['duthost'].shell( + "sudo config interface shutdown {}".format(intf)) + downed_ports = ecmp_utils.get_corresponding_ports( + selected_intfs, + minigraph_facts) + self.setup[encap_type]['t2_ports'] = \ + list(set(all_t2_ports) - set(downed_ports)) + downed_bgp_neighbors = ecmp_utils.get_downed_bgp_neighbors( + selected_intfs, minigraph_facts) + pytest_assert( + wait_until( + 300, + 30, + 0, + ecmp_utils.bgp_established, + self.setup['duthost'], + down_list=downed_bgp_neighbors), + "BGP neighbors didn't come up after all " + "interfaces have been brought up.") + time.sleep(10) + self.dump_self_info_and_run_ptf( + "tc12", + encap_type, + True, + packet_count=1000) + + Logger.info( + "Reverse the action: bring up the selected_intfs" + " and shutdown others.") for intf in selected_intfs: - self.setup['duthost'].shell("sudo config interface startup {}".format(intf)) - logger.info("Shutdown other interfaces.") - remaining_interfaces = list(set(all_t2_intfs) - set(selected_intfs)) + self.setup['duthost'].shell( + "sudo config interface startup {}".format(intf)) + Logger.info("Shutdown other interfaces.") + remaining_interfaces = list( + set(all_t2_intfs) - set(selected_intfs)) for intf in remaining_interfaces: - self.setup['duthost'].shell("sudo config interface shutdown {}".format(intf)) - downed_bgp_neighbors = get_downed_bgp_neighbors(remaining_interfaces, minigraph_facts) - pytest_assert(wait_until(300, 30, 0, bgp_established, self.setup['duthost'], down_list=downed_bgp_neighbors), "BGP neighbors didn't come up after all interfaces have been brought up.") - self.setup[encap_type]['t2_ports'] = get_corresponding_ports(selected_intfs, minigraph_facts) - self.dump_self_info_and_run_ptf("tc12", encap_type, True, packet_count=1000) + self.setup['duthost'].shell( + "sudo config interface shutdown {}".format(intf)) + downed_bgp_neighbors = ecmp_utils.get_downed_bgp_neighbors( + remaining_interfaces, + minigraph_facts) + pytest_assert( + wait_until( + 300, + 30, + 0, + ecmp_utils.bgp_established, + self.setup['duthost'], + down_list=downed_bgp_neighbors), + "BGP neighbors didn't come up after all interfaces have been" + "brought up.") + self.setup[encap_type]['t2_ports'] = \ + ecmp_utils.get_corresponding_ports( + selected_intfs, + minigraph_facts) - logger.info("Recovery. Bring all up, and verify traffic works.") + ''' + Need to update the bfd_responder to listen only on the sub-set of + T2 ports that are active. If we still receive packets on the + downed ports, we have a problem! + ''' + ecmp_utils.update_monitor_file( + self.setup['ptfhost'], + self.setup['monitor_file'], + self.setup[encap_type]['t2_ports'], + list(self.setup['list_of_bfd_monitors'])) + time.sleep(10) + self.dump_self_info_and_run_ptf( + "tc12", + encap_type, + True, + packet_count=1000) + + Logger.info("Recovery. Bring all up, and verify traffic works.") for intf in all_t2_intfs: - self.setup['duthost'].shell("sudo config interface startup {}".format(intf)) - logger.info("Wait for all bgp is up.") - pytest_assert(wait_until(300, 30, 0, bgp_established, self.setup['duthost']), "BGP neighbors didn't come up after all interfaces have been brought up.") - logger.info("Verify traffic flows after recovery.") + self.setup['duthost'].shell( + "sudo config interface startup {}".format(intf)) + Logger.info("Wait for all bgp is up.") + pytest_assert( + wait_until( + 300, + 30, + 0, + ecmp_utils.bgp_established, + self.setup['duthost']), + "BGP neighbors didn't come up after " + "all interfaces have been brought up.") + Logger.info("Verify traffic flows after recovery.") self.setup[encap_type]['t2_ports'] = all_t2_ports - self.dump_self_info_and_run_ptf("tc12", encap_type, True, packet_count=1000) + ecmp_utils.update_monitor_file( + self.setup['ptfhost'], + self.setup['monitor_file'], + self.setup[encap_type]['t2_ports'], + list(self.setup['list_of_bfd_monitors'])) + time.sleep(10) + self.dump_self_info_and_run_ptf( + "tc12", + encap_type, + True, + packet_count=1000) except Exception: - # If anything goes wrong in the try block, atleast bring the intf back up. + # If anything goes wrong in the try block, atleast bring the intf + # back up. self.setup[encap_type]['t2_ports'] = all_t2_ports + ecmp_utils.update_monitor_file( + self.setup['ptfhost'], + self.setup['monitor_file'], + self.setup[encap_type]['t2_ports'], + list(self.setup['list_of_bfd_monitors'])) for intf in all_t2_intfs: - self.setup['duthost'].shell("sudo config interface startup {}".format(intf)) - pytest_assert(wait_until(300, 30, 0, bgp_established, self.setup['duthost']), "BGP neighbors didn't come up after all interfaces have been brought up.") + self.setup['duthost'].shell( + "sudo config interface startup {}".format(intf)) + pytest_assert( + wait_until( + 300, + 30, + 0, + ecmp_utils.bgp_established, + self.setup['duthost']), + "BGP neighbors didn't come up after all interfaces " + "have been brought up.") raise - def test_vxlan_remove_add_underlay_default(self, setUp, minigraph_facts, encap_type): + def test_vxlan_remove_add_underlay_default(self, + setUp, + minigraph_facts, + encap_type): ''' tc13: remove the underlay default route. tc14: add the underlay default route. ''' self.setup = setUp - - logger.info("Find all the underlay default routes' interfaces. This means all T2 interfaces.") - all_t2_intfs = list(get_portchannels_to_neighbors(self.setup['duthost'], "T2", minigraph_facts)) + Logger.info( + "Find all the underlay default routes' interfaces. This means all " + "T2 interfaces.") + all_t2_intfs = list(ecmp_utils.get_portchannels_to_neighbors( + self.setup['duthost'], + "T2", + minigraph_facts)) if not all_t2_intfs: - all_t2_intfs = get_ethernet_to_neighbors("T2", minigraph_facts) - logger.info("Dumping T2 link info: {}".format(all_t2_intfs)) + all_t2_intfs = ecmp_utils.get_ethernet_to_neighbors( + "T2", + minigraph_facts) + Logger.info("Dumping T2 link info: %s", all_t2_intfs) if not all_t2_intfs: - raise RuntimeError("No interface found connected to t2 neighbors. pls check the testbed, aborting.") - + raise RuntimeError( + "No interface found connected to t2 neighbors." + "Pls check the testbed, aborting.") try: - logger.info("Bring down the T2 interfaces.") + Logger.info("Bring down the T2 interfaces.") for intf in all_t2_intfs: - self.setup['duthost'].shell("sudo config interface shutdown {}".format(intf)) - downed_bgp_neighbors = get_downed_bgp_neighbors(all_t2_intfs, minigraph_facts) - pytest_assert(wait_until(300, 30, 0, bgp_established, self.setup['duthost'], down_list=downed_bgp_neighbors), - "BGP neighbors have not reached the required state after T2 intf are shutdown.") - - logger.info("Verify that traffic is not flowing through.") + self.setup['duthost'].shell( + "sudo config interface shutdown {}".format(intf)) + downed_bgp_neighbors = ecmp_utils.get_downed_bgp_neighbors( + all_t2_intfs, + minigraph_facts) + pytest_assert( + wait_until( + 300, + 30, + 0, + ecmp_utils.bgp_established, + self.setup['duthost'], + down_list=downed_bgp_neighbors), + "BGP neighbors have not reached the required state after " + "T2 intf are shutdown.") + Logger.info("Verify that traffic is not flowing through.") self.dump_self_info_and_run_ptf("tc13", encap_type, False) - ''' - tc14: Re-add the underlay default route. - ''' - - logger.info("Bring up the T2 interfaces.") + # tc14: Re-add the underlay default route. + Logger.info("Bring up the T2 interfaces.") for intf in all_t2_intfs: - self.setup['duthost'].shell("sudo config interface startup {}".format(intf)) + self.setup['duthost'].shell( + "sudo config interface startup {}".format(intf)) + Logger.info("Wait for all bgp is up.") + pytest_assert( + wait_until( + 300, + 30, + 0, + ecmp_utils.bgp_established, + self.setup['duthost']), + "BGP neighbors didn't come up after all interfaces" + " have been brought up.") + Logger.info("Verify the traffic is flowing through, again.") + self.dump_self_info_and_run_ptf( + "tc14", + encap_type, + True, + packet_count=1000) + except Exception: + Logger.info( + "If anything goes wrong in the try block," + " atleast bring the intf back up.") + for intf in all_t2_intfs: + self.setup['duthost'].shell( + "sudo config interface startup {}".format(intf)) + pytest_assert( + wait_until( + 300, + 30, + 0, + ecmp_utils.bgp_established, + self.setup['duthost']), + "BGP neighbors didn't come up after all" + " interfaces have been brought up.") + raise - logger.info("Wait for all bgp is up.") - pytest_assert(wait_until(300, 30, 0, bgp_established, self.setup['duthost']), "BGP neighbors didn't come up after all interfaces have been brought up.") + def test_underlay_specific_route(self, setUp, minigraph_facts, encap_type): + ''' + Create a more specific underlay route to c1. + Verify c1 packets are received only on the c1's nexthop interface + ''' + self.setup = setUp + vnet = self.setup[encap_type]['vnet_vni_map'].keys()[0] + endpoint_nhmap = self.setup[encap_type]['dest_to_nh_map'][vnet] + backup_t2_ports = self.setup[encap_type]['t2_ports'] + # Gathering all T2 Neighbors + all_t2_neighbors = ecmp_utils.get_all_bgp_neighbors( + minigraph_facts, + "T2") + + # Choosing a specific T2 Neighbor to add static route + t2_neighbor = all_t2_neighbors.keys()[0] + + # Gathering PTF indices corresponding to specific T2 Neighbor + ret_list = ecmp_utils.gather_ptf_indices_t2_neighbor( + minigraph_facts, + all_t2_neighbors, + t2_neighbor, + encap_type) + outer_layer_version = ecmp_utils.get_outer_layer_version(encap_type) + ''' + Addition & Modification of static routes - endpoint_nhmap will be + prefix to endpoint mapping. Static routes are added towards + endpoint with T2 VM's ip as nexthop + ''' + gateway = all_t2_neighbors[t2_neighbor][outer_layer_version].lower() + for _, nexthops in endpoint_nhmap.items(): + for nexthop in nexthops: + if outer_layer_version == "v6": + vtysh_config_commands = [] + vtysh_config_commands.append("ipv6 route {}/{} {}".format( + nexthop, + "64", + gateway)) + vtysh_config_commands.append("ipv6 route {}/{} {}".format( + nexthop, + "68", + gateway)) + self.setup['duthost'].copy( + content="\n".join(vtysh_config_commands), + dest="/tmp/specific_route_v6.txt") + self.setup['duthost'].command( + "docker cp /tmp/specific_route_v6.txt bgp:/") + self.setup['duthost'].command( + "vtysh -f /specific_route_v6.txt") + elif outer_layer_version == "v4": + static_route = [] + static_route.append( + "sudo config route add prefix {}/{} nexthop {}".format( + ".".join(nexthop.split(".")[:-1])+".0", "24", + gateway)) + static_route.append( + "sudo config route add prefix {}/{} nexthop {}".format( + nexthop, + ecmp_utils.HOST_MASK[outer_layer_version], + gateway)) + + self.setup['duthost'].shell_cmds(cmds=static_route) + self.setup[encap_type]['t2_ports'] = ret_list - logger.info("Verify the traffic is flowing through, again.") - self.dump_self_info_and_run_ptf("tc14", encap_type, True, packet_count=1000) + ''' + Traffic verification to see if specific route is preferred before + deletion of static route + ''' + self.dump_self_info_and_run_ptf( + "underlay_specific_route", + encap_type, + True) + # Deletion of all static routes + gateway = all_t2_neighbors[t2_neighbor][outer_layer_version].lower() + for _, nexthops in endpoint_nhmap.items(): + for nexthop in nexthops: + if ecmp_utils.get_outer_layer_version(encap_type) == "v6": + vtysh_config_commands = [] + vtysh_config_commands.append( + "no ipv6 route {}/{} {}".format( + nexthop, "64", gateway)) + vtysh_config_commands.append( + "no ipv6 route {}/{} {}".format( + nexthop, "68", gateway)) + self.setup['duthost'].copy( + content="\n".join(vtysh_config_commands), + dest="/tmp/specific_route_v6.txt") + self.setup['duthost'].command( + "docker cp /tmp/specific_route_v6.txt bgp:/") + self.setup['duthost'].command( + "vtysh -f /specific_route_v6.txt") + + elif ecmp_utils.get_outer_layer_version(encap_type) == "v4": + static_route = [] + static_route.append( + "sudo config route del prefix {}/{} nexthop {}".format( + ".".join( + nexthop.split(".")[:-1])+".0", "24", gateway)) + static_route.append( + "sudo config route del prefix {}/{} nexthop {}".format( + nexthop, + ecmp_utils.HOST_MASK[outer_layer_version], + gateway)) + + self.setup['duthost'].shell_cmds(cmds=static_route) + self.setup[encap_type]['t2_ports'] = backup_t2_ports + + Logger.info( + "Allow some time for recovery of default route" + " after deleting the specific route.") + time.sleep(10) - except Exception: - logger.info("If anything goes wrong in the try block, atleast bring the intf back up.") - for intf in all_t2_intfs: - self.setup['duthost'].shell("sudo config interface startup {}".format(intf)) - pytest_assert(wait_until(300, 30, 0, bgp_established, self.setup['duthost']), "BGP neighbors didn't come up after all interfaces have been brought up.") + ''' + Traffic verification to see if default route is preferred after + deletion of static route + ''' + self.dump_self_info_and_run_ptf( + "underlay_specific_route", + encap_type, + True) + + def test_underlay_portchannel_shutdown(self, + setUp, + minigraph_facts, + encap_type): + ''' + Bring down one of the port-channels. + Packets are equally recieved at c1, c2 or c3 + ''' + self.setup = setUp + + # Verification of traffic before shutting down port channel + self.dump_self_info_and_run_ptf("tc12", encap_type, True) + + # Gathering all portchannels + all_t2_portchannel_intfs = \ + list(ecmp_utils.get_portchannels_to_neighbors( + self.setup['duthost'], + "T2", + minigraph_facts)) + all_t2_portchannel_members = {} + for each_pc in all_t2_portchannel_intfs: + all_t2_portchannel_members[each_pc] =\ + minigraph_facts['minigraph_portchannels'][each_pc]['members'] + + selected_portchannel = all_t2_portchannel_members.keys()[0] + + try: + # Shutting down the ethernet interfaces + for intf in all_t2_portchannel_members[selected_portchannel]: + self.setup['duthost'].shell( + "sudo config interface shutdown {}".format(intf)) + + all_t2_ports = list(self.setup[encap_type]['t2_ports']) + downed_ports = ecmp_utils.get_corresponding_ports( + all_t2_portchannel_members[selected_portchannel], + minigraph_facts) + self.setup[encap_type]['t2_ports'] = \ + list(set(all_t2_ports) - set(downed_ports)) + + # Verification of traffic + ecmp_utils.update_monitor_file( + self.setup['ptfhost'], + self.setup['monitor_file'], + self.setup[encap_type]['t2_ports'], + list(self.setup['list_of_bfd_monitors'])) + time.sleep(10) + self.dump_self_info_and_run_ptf("tc12", encap_type, True) + + for intf in all_t2_portchannel_members[selected_portchannel]: + self.setup['duthost'].shell( + "sudo config interface startup {}".format(intf)) + self.setup[encap_type]['t2_ports'] = all_t2_ports + ecmp_utils.update_monitor_file( + self.setup['ptfhost'], + self.setup['monitor_file'], + self.setup[encap_type]['t2_ports'], + list(self.setup['list_of_bfd_monitors'])) + time.sleep(10) + self.dump_self_info_and_run_ptf("tc12", encap_type, True) + except BaseException: + for intf in all_t2_portchannel_members[selected_portchannel]: + self.setup['duthost'].shell( + "sudo config interface startup {}".format(intf)) + self.setup[encap_type]['t2_ports'] = all_t2_ports + ecmp_utils.update_monitor_file( + self.setup['ptfhost'], + self.setup['monitor_file'], + self.setup[encap_type]['t2_ports'], + list(self.setup['list_of_bfd_monitors'])) raise + + +@pytest.mark.skipif( + "config.option.include_long_tests is False", + reason="This test will be run only if" + "'--include_long_tests=True' is provided.") +class Test_VxLAN_entropy(Test_VxLAN): + ''' + Class for all test cases that modify the payload traffic + properties - tcp source port, destination port and source IP address. + ''' + def verify_entropy( + self, + encap_type, + random_sport=False, + random_dport=True, + random_src_ip=False, + tolerance=None): + ''' + Function to be reused by the entropy testcases. Sets up a couple of + endpoints on the top of the existing ones, and performs the traffic + test, with different payload variants. + ''' + + Logger.info("Choose a vnet.") + vnet = self.setup[encap_type]['dest_to_nh_map'].keys()[0] + Logger.info("Create a new list of endpoint(s).") + end_point_list = [] + for _ in range(2): + end_point_list.append(ecmp_utils.get_ip_address( + af=ecmp_utils.get_outer_layer_version(encap_type), + netid=NEXTHOP_PREFIX)) + Logger.info("Create a new destination") + new_dest = ecmp_utils.get_ip_address( + af=ecmp_utils.get_payload_version(encap_type), + netid=DESTINATION_PREFIX) + Logger.info("Map the new destination and the new endpoint(s).") + self.setup[encap_type]['dest_to_nh_map'][vnet][new_dest] = \ + end_point_list + Logger.info("Create a new config and Copy to the DUT.") + ecmp_utils.create_and_apply_config( + self.setup['duthost'], + vnet, + new_dest, + ecmp_utils.HOST_MASK[ecmp_utils.get_payload_version(encap_type)], + end_point_list, + "SET", + bfd=self.setup['enable_bfd']) + self.update_monitor_list( + self.setup['enable_bfd'], + encap_type, + end_point_list) + Logger.info("Verify that the new config takes effect and run traffic.") + self.dump_self_info_and_run_ptf( + "entropy", + encap_type, + True, + random_sport=random_sport, + random_dport=random_dport, + random_src_ip=random_src_ip, + packet_count=1000, + tolerance=tolerance) + + def test_verify_entropy(self, setUp, encap_type): + ''' + Verification of entropy - Create tunnel route 4 to endpoint group A. + Send packets (fixed tuple) to route 4's prefix dst + ''' + self.setup = setUp + self.verify_entropy( + encap_type, + random_dport=True, + random_sport=True, + random_src_ip=True, + tolerance=0.75) # More tolerance since this varies entropy a lot. + + def test_vxlan_random_dst_port(self, setUp, encap_type): + ''' + Verification of entropy - Change the udp dst port of original packet to + route 4's prefix dst + ''' + self.setup = setUp + self.verify_entropy(encap_type, tolerance=0.03) + + def test_vxlan_random_src_port(self, setUp, encap_type): + ''' + Verification of entropy - Change the udp src port of original packet + to route 4's prefix dst + ''' + self.setup = setUp + self.verify_entropy( + encap_type, + random_dport=False, + random_sport=True, + tolerance=0.03) + + def test_vxlan_varying_src_ip(self, setUp, encap_type): + ''' + Verification of entropy - Change the udp src ip of original packet to + route 4's prefix dst + ''' + self.setup = setUp + self.verify_entropy( + encap_type, + random_dport=False, + random_src_ip=True, + tolerance=0.03) + + +@pytest.mark.skipif( + "config.option.include_crm is False", + reason="This test will be run only if " + "'--include_crm=True' is provided.") +class Test_VxLAN_Crm(Test_VxLAN): + ''' + Class for all testcases that verify Critical Resource Monitoring + counters. + ''' + def test_crm_16k_routes(self, setUp, encap_type): + ''' + Verify that the CRM counter values for ipv4_route, ipv4_nexthop, + ipv6_route and ipv6_nexthop are updated as per the vxlan route + configs. + ''' + self.setup = setUp + vnet = self.setup[encap_type]['dest_to_nh_map'].keys()[0] + crm_output = \ + self.setup['duthost'].get_crm_resources()['main_resources'] + outer_layer_version = ecmp_utils.get_outer_layer_version(encap_type) + number_of_routes_configured = \ + len(self.setup[encap_type]['dest_to_nh_map'][vnet].keys()) + + # We need the count of unique endpoints. + dest_nh_map = self.setup[encap_type]['dest_to_nh_map'][vnet] + set_of_unique_endpoints = set() + for _, nexthops in dest_nh_map.items(): + set_of_unique_endpoints = \ + set_of_unique_endpoints | set(nexthops) + number_of_nexthops_configured = len(set_of_unique_endpoints) + + routes_used = \ + crm_output['ip{}_route'.format(outer_layer_version)]['used'] + nhs_used = \ + crm_output['ip{}_nexthop'.format(outer_layer_version)]['used'] + old_routes = \ + self.setup['crm']['ip{}_route'.format(outer_layer_version)]['used'] + old_nhs = self.setup['crm'][ + 'ip{}_nexthop'.format(outer_layer_version)]['used'] + + pytest_assert( + routes_used >= old_routes + 0.90 * number_of_routes_configured, + "CRM:ip{}_route usage didn't increase as needed".format( + outer_layer_version)) + pytest_assert( + nhs_used >= old_nhs + 0.90 * number_of_nexthops_configured, + "CRM:ip{}_nexthop usage didn't increase as needed".format( + outer_layer_version)) + + def test_crm_512_nexthop_groups(self, setUp, encap_type): + ''' + Verify that the CRM counter values for nexthop_group is updated as + per the vxlan route configs. + ''' + self.setup = setUp + Logger.info("Verifying encap_type:%s", encap_type) + crm_output = \ + self.setup['duthost'].get_crm_resources()['main_resources'] + pytest_assert( + crm_output['nexthop_group']['used'] >= + self.setup['crm']['nexthop_group']['used'] + 16000, + "CRM:nexthop_group usage didn't increase as needed") + + def test_crm_128_group_members(self, setUp, encap_type): + ''' + Verify that the CRM counter values for nexthop_group_member + is updated as per the vxlan route configs. + ''' + self.setup = setUp + Logger.info("Verifying encap_type:%s", encap_type) + crm_output = \ + self.setup['duthost'].get_crm_resources()['main_resources'] + pytest_assert( + crm_output['nexthop_group_member']['used'] >= + self.setup['crm']['nexthop_group_member']['used'] + 16000, + "CRM:nexthop_group_member usage didn't increase as needed") diff --git a/tests/vxlan/vxlan_ecmp_utils.py b/tests/vxlan/vxlan_ecmp_utils.py new file mode 100644 index 00000000000..47d2ef2fa97 --- /dev/null +++ b/tests/vxlan/vxlan_ecmp_utils.py @@ -0,0 +1,868 @@ +''' + The functions used by test_vxlan_ecmp.py. Since there are plans to + seperate the test script to multiple files, we need a common location + for these functions. + Usage: + from tests.vxlan.ecmp_utils import Ecmp_Utils + my_own_ecmp_utils = Ecmp_Utils() + my_own_ecmp_utils.create_vxlan_tunnel(...) +''' + +from sys import getsizeof +import re +import time +import logging +from ipaddress import ip_address, IPv4Address, IPv6Address + +Logger = logging.getLogger(__name__) + + +class Ecmp_Utils(object): + ''' + Collection of functions that are used by the VxLAN scripts. + ''' + Address_Count = 0 + + # Some of the self.Constants used in this script. + Constants = {} + + # Mapping the version to the python module. + IP_TYPE = { + 'v4': IPv4Address, + 'v6': IPv6Address + } + + # Starting prefixes to be used for the destinations and End points. + DESTINATION_PREFIX = 150 + NEXTHOP_PREFIX = 100 + + # Scale values for CRM test cases + NHS_PER_DESTINATION = 8 + NUMBER_OF_AVAILABLE_NEXTHOPS = 4000 + NUMBER_OF_ECMP_NHS = 128000 + + # This is the list of encapsulations that will be tested in this script. + # v6_in_v4 means: V6 payload is encapsulated inside v4 outer layer. + # This list is used in many locations in the script. + SUPPORTED_ENCAP_TYPES = ['v4_in_v4', 'v4_in_v6', 'v6_in_v4', 'v6_in_v6'] + + # This is the mask values to use for destination + # in the vnet routes. + HOST_MASK = {'v4': 32, 'v6': 128} + + def create_vxlan_tunnel(self, + duthost, + minigraph_data, + af, + tunnel_name=None, + src_ip=None): + ''' + Function to create a vxlan tunnel. The arguments: + duthost : The DUT ansible host object. + minigraph_data: minigraph facts from the dut host. + tunnel_name : A name for the Tunnel, default: tunnel_ + src_ip : Source ip address of the tunnel. It has to be a + local ip address in the DUT. Default: Loopback + ip address. + af : Address family : v4 or v6. + ''' + if tunnel_name is None: + tunnel_name = "tunnel_{}".format(af) + + if src_ip is None: + src_ip = self.get_dut_loopback_address(duthost, minigraph_data, af) + + config = '''{{ + "VXLAN_TUNNEL": {{ + "{}": {{ + "src_ip": "{}" + }} + }} + }}'''.format(tunnel_name, src_ip) + + self.apply_config_in_dut(duthost, config, name="vxlan_tunnel_" + af) + return tunnel_name + + def apply_config_in_dut(self, duthost, config, name="vxlan"): + ''' + The given json(config) will be copied to the DUT and loaded up. + ''' + if self.Constants['DEBUG']: + filename = "/tmp/" + name + ".json" + else: + filename = "/tmp/" + name + "-" + str(time.time()) + ".json" + duthost.copy(content=config, dest=filename) + duthost.shell("sudo config load {} -y".format(filename)) + time.sleep(1) + if not self.Constants['KEEP_TEMP_FILES']: + duthost.shell("rm {}".format(filename)) + + def get_dut_loopback_address(self, duthost, minigraph_data, af): + ''' + Returns the IP address of the Loopback interface in DUT, from + minigraph. + Arguments: + duthost : DUT Ansible Host object. + minigraph_data: Minigraph facts from the DUT. + af : Address Family(v4 or v6). + ''' + lo_ip = minigraph_data['minigraph_lo_interfaces'] + for intf in lo_ip: + if isinstance(ip_address(intf['addr']), self.IP_TYPE[af]): + return intf['addr'] + + raise RuntimeError( + "Couldnot find the {} loopback address" + "for the DUT:{} from minigraph.".format(af, duthost.hostname)) + + def select_required_interfaces( + self, duthost, number_of_required_interfaces, minigraph_data, af): + ''' + Pick the required number of interfaces to use for tests. + These interfaces will be selected based on if they are currently + running a established BGP. The interfaces will be picked from the T0 + facing side. + ''' + bgp_interfaces = self.get_all_interfaces_running_bgp( + duthost, + minigraph_data, + "T0") + + # Randomly pick the interface from the above list + list_of_bgp_ips = [] + for neigh_ip_address in bgp_interfaces: + if isinstance(ip_address(neigh_ip_address), self.IP_TYPE[af]): + list_of_bgp_ips.append(neigh_ip_address) + + ret_interface_list = [] + available_number = len(list_of_bgp_ips) + # Confirm there are enough interfaces (basicaly more than or equal + # to the number of vnets). + if available_number <= number_of_required_interfaces+1: + raise RuntimeError( + "There are not enough interfaces needed to perform the test. " + "We need atleast {} interfaces, but only {} are " + "available.".format( + number_of_required_interfaces+1, available_number)) + for index in range(number_of_required_interfaces): + neigh_ip_address = list_of_bgp_ips[index] + current_interface_name = bgp_interfaces[neigh_ip_address].keys()[0] + ret_interface_list.append(current_interface_name) + + if ret_interface_list: + return ret_interface_list + else: + raise RuntimeError( + "There is no Ethernet interface running BGP." + "Pls run this test on any T1 topology.") + + @classmethod + def get_portchannels_to_neighbors(cls, + duthost, + neighbor_type, + minigraph_data): + ''' + A function to get the list of portchannels connected to BGP + neighbors of given type(T0 or T2). It returns a list of + portchannels+minigraph_lag_facts_of_that portchannel. + Arguments: + duthost : DUT Ansible Host object + localhost : Localhost Ansible Host object. + neighbor_type: T0 or T2. + ''' + lag_facts = duthost.lag_facts(host=duthost.sonichost.mgmt_ip) + names = lag_facts['ansible_facts']['lag_facts']['names'] + lags = lag_facts['ansible_facts']['lag_facts']['lags'] + + return_list = {} + pattern = re.compile("{}$".format(neighbor_type)) + for pc_name in names: + port_struct = lags[pc_name]['po_config']['ports'] + if lags[pc_name]['po_intf_stat'] == "Up": + intf = port_struct.keys()[0] + neighbor = minigraph_data['minigraph_neighbors'][intf]['name'] + match = pattern.search(neighbor) + if match: + # We found an interface that has a given neighbor_type. + # Let us use this. + return_list[pc_name] = port_struct + + return return_list + + @classmethod + def get_ethernet_to_neighbors(cls, neighbor_type, minigraph_data): + ''' + A function to get the list of Ethernet interfaces connected to + BGP neighbors of given type(T0 or T2). It returns a list of ports. + Arguments: + duthost : DUT Ansible Host object + neighbor_type: T0 or T2. + ''' + + pattern = re.compile("{}$".format(neighbor_type)) + ret_list = [] + + for intf in minigraph_data['minigraph_neighbors']: + if pattern.search( + minigraph_data['minigraph_neighbors'][intf]['name']): + ret_list.append(intf) + + return ret_list + + def assign_intf_ip_address(self, selected_interfaces, af): + ''' + Calculate an ip address for the selected interfaces. It is just a + mapping. Nothing is configured. + ''' + intf_ip_map = {} + for intf in selected_interfaces: + address = self.get_ip_address( + af=af, hostid=self.Constants['DUT_HOSTID'], netid=201) + intf_ip_map[intf] = address + return intf_ip_map + + @classmethod + def get_all_interfaces_running_bgp(cls, + duthost, + minigraph_data, + neighbor_type): + ''' + Analyze the DUT for bgp and return the a structure that have BGP + neighbors. + ''' + bgp_neigh_list = duthost.bgp_facts()['ansible_facts']['bgp_neighbors'] + minigraph_ip_interfaces = minigraph_data['minigraph_interfaces'] +\ + minigraph_data['minigraph_portchannel_interfaces'] + peer_addr_map = {} + pattern = re.compile("{}$".format(neighbor_type)) + for index in minigraph_ip_interfaces: + peer_addr_map[index['peer_addr']] =\ + {index['attachto']: index['addr']} + + ret_list = {} + for index, entry in peer_addr_map.iteritems(): + if bgp_neigh_list[index]['state'] == 'established' and \ + pattern.search(bgp_neigh_list[index]['description']): + ret_list[index] = entry + + return ret_list + + def configure_vnet_neighbors(self, + duthost, + intf_to_ip_map, + minigraph_data, af): + ''' + setup the vnet neighbor ip addresses. + ''' + family = "IPv4" + if af == "v6": + family = "IPv6" + + return_dict = {} + + config_list = [] + for intf, addr in intf_to_ip_map.iteritems(): + # If the given address is "net.1", the return address is "net.101" + # THE ASSUMPTION HERE IS THAT THE DUT ADDRESSES ARE ENDING IN ".1". + # addr.decode is only in python2.7 + ptf_ip = str(ip_address(addr.decode())+100) + + if "Ethernet" in intf: + return_dict[intf] = ptf_ip + elif "PortChannel" in intf: + for member in self.get_ethernet_ports([intf], minigraph_data): + return_dict[member] = ptf_ip + + config_list.append('''"{}|{}": {{ + "family": "{}" + }}'''.format(intf, ptf_ip, family)) + + full_config = '''{ + "NEIGH": { + ''' + ",\n".join(config_list) + '''\n}\n}''' + + self.apply_config_in_dut(duthost, full_config, name="vnet_nbr_"+af) + return return_dict + + def create_vnets( + self, + duthost, + tunnel_name, + vnet_count=1, + scope=None, + vni_base=10000, + vnet_name_prefix="Vnet"): + ''' + Create the required number of vnets. + duthost : AnsibleHost data structure of the DUT. + tunnel_name : The VxLAN Tunnel name. + vnet_count : Number of vnets to configure. + scope : The value for "scope" argument in the config. + Only "default" is supported. Or it should not + be given at all. + vni_base : The starting number for VNI. + vnet_name_prefix : The prefix for the name of vnets. + ''' + return_dict = {} + scope_entry = "" + if scope: + scope_entry = '''"scope": "{}",\n'''.format(scope) + config_list = [] + for cnt in range(vnet_count): + name = vnet_name_prefix + "-" + str(cnt) + vni = vni_base+cnt + return_dict[name] = vni + config_list.append('''"{}": {{ + "vxlan_tunnel": "{}", + {}"vni": "{}", + "peer_list": "" + }}'''.format(name, tunnel_name, scope_entry, vni)) + + full_config = '{\n"VNET": {' + ",\n".join(config_list) + '\n}\n}' + + self.apply_config_in_dut(duthost, full_config, "vnets_"+tunnel_name) + return return_dict + + def setup_vnet_intf(self, selected_interfaces, vnet_list, minigraph_data): + ''' + Setup the interface(or in other words associate the interface to + a Vnet. This will remove the ip address from the interfaces. + + selected_interfaces : The list of interfaces we decided to use. + vnet_list : The list of vnets to use. The list of vnets + and interfaces should be of same length. + minigraph_data : The minigraph_facts data from DUT. + ''' + if len(selected_interfaces) != len(vnet_list): + raise RuntimeError( + "Different number of interfaces and vnets, not supported yet") + + ret_list = {} + intf_config_list = [] + po_config_list = [] + for count, intf in enumerate(selected_interfaces): + config = (''' + "{}": {{ + "vnet_name": "{}" + }} + '''.format(intf, vnet_list[count])) + + if "Ethernet" in intf: + intf_config_list.append(config) + ret_list[intf] = vnet_list[count] + elif "PortChannel" in intf: + po_config_list.append(config) + for member in self.get_ethernet_ports([intf], minigraph_data): + ret_list[member] = vnet_list[count] + + return ret_list + + def configure_vxlan_switch(self, duthost, vxlan_port=4789, dutmac=None): + ''' + Configure the VxLAN parameters for the DUT. + This step is completely optional. + + duthost: AnsibleHost structure of the DUT. + vxlan_port : The UDP port to be used for VxLAN traffic. + dutmac : The mac address to be configured in the DUT. + ''' + if dutmac is None: + dutmac = "aa:bb:cc:dd:ee:ff" + + switch_config = ''' + [ + {{ + "SWITCH_TABLE:switch": {{ + "vxlan_port": "{}", + "vxlan_router_mac": "{}" + }}, + "OP": "SET" + }} + ] + '''.format(vxlan_port, dutmac) + self.apply_config_in_swss(duthost, switch_config, "vnet_switch") + + def apply_config_in_swss(self, duthost, config, name="swss_"): + ''' + Apply the given config data in the SWSS container of the DUT. + duthost: AnsibleHost structure of the DUT. + config : The config to be applied in the swss container. + name : The name of the config file to be created in the DUT. + ''' + if self.Constants['DEBUG']: + filename = name + ".json" + else: + filename = name + "-" + str(time.time()) + ".json" + + duthost.copy(content=config, dest="/tmp/{}".format(filename)) + duthost.shell( + 'docker exec -i swss swssconfig /dev/stdin < /tmp/{}'.format( + filename)) + Logger.info("Wait for %s seconds for the config to take effect.", + 0.0005*getsizeof(config) + 1) + time.sleep(int(0.0005*getsizeof(config)) + 1) + if not self.Constants['KEEP_TEMP_FILES']: + duthost.shell("rm /tmp/{}".format(filename)) + + def get_list_of_nexthops(self, number, af, prefix=100): + ''' + Get a list of IP addresses to be used as nexthops. This creates a + pool of dummy nexthops. The other functions can use this pool to + assign nexthops to different destinations. + number : Number of addresses we need. + af : Address Family (v4 or v6). + prefix : The first octet to be used for the addresses. + ''' + nexthop_list = [] + for _ in range(number): + nexthop_list.append( + self.get_ip_address(af=af, netid=prefix, hostid=10)) + return nexthop_list + + def create_vnet_routes( + self, + duthost, + vnet_list, + dest_af, + nh_af, + nhs_per_destination=1, + number_of_available_nexthops=100, + number_of_ecmp_nhs=1000, + dest_net_prefix=150, + nexthop_prefix=100, + bfd=False): + ''' + This configures the VNET_TUNNEL_ROUTES structure. It precalculates + the required number of destinations based on the given + "number_of_ecmp_nhs" and the "nhs_per_destination". + + inputs: + number_of_available_nexthops : Total number of unique + NextHops available for use. + nhs_per_destination : Number of ECMP nexthops to use + per destination. + number_of_ecmp_nhs : Maximum number of all NextHops + put together(for all + destinations). + ''' + if number_of_available_nexthops < nhs_per_destination: + raise RuntimeError( + "The number of available nexthops ip addresses is not enough " + "to cover even one destination. Pls rerun with " + "total_number_of_endpoints(%s) > ecmp_nhs_per_destination(%s)", + number_of_available_nexthops, nhs_per_destination) + + available_nexthops = self.get_list_of_nexthops( + number=number_of_available_nexthops, + af=nh_af, prefix=nexthop_prefix) + + number_of_destinations = int(number_of_ecmp_nhs / nhs_per_destination) + no_of_dests_per_vnet = int(number_of_destinations / len(vnet_list)) + available_nexthop_count = 0 + dest_to_nh_map = {} + for vnet in vnet_list: + for _ in range(no_of_dests_per_vnet): + dest = self.get_ip_address(af=dest_af, netid=dest_net_prefix) + my_nhs = [] + for _ in range(nhs_per_destination): + my_nhs.append( + available_nexthops[ + available_nexthop_count % + number_of_available_nexthops]) + available_nexthop_count = available_nexthop_count + 1 + if available_nexthop_count > number_of_ecmp_nhs: + break + + try: + dest_to_nh_map[vnet] + except KeyError: + dest_to_nh_map[vnet] = {} + dest_to_nh_map[vnet][dest] = my_nhs + + self.set_routes_in_dut(duthost, + dest_to_nh_map, + dest_af, + "SET", + bfd=bfd) + return dest_to_nh_map + + @classmethod + def get_outer_layer_version(cls, encap_type): + ''' + Short function to get the outer layer address family from the + encap type. + ''' + match = re.search("in_(v[46])", encap_type) + if match: + return match.group(1) + else: + raise RuntimeError( + "Invalid format for encap_type:{}".format(encap_type)) + + @classmethod + def get_payload_version(cls, encap_type): + ''' + Short function to get the inner layer address family from the + encap type. + ''' + match = re.search("(v[46])_in_v", encap_type) + if match: + return match.group(1) + else: + raise RuntimeError( + "Invalid format for encap_type:{}".format(encap_type)) + + def create_and_apply_config(self, + duthost, + vnet, + dest, + mask, + nhs, + op, + bfd=False): + ''' + Create a single destinatoin->endpoint list mapping, and configure + it in the DUT. + duthost : AnsibleHost structure for the DUT. + vnet : Name of the Vnet. + dest : IP(v4/v6) address of the destination. + mask : Dest netmask length. + nhs : Nexthop list(v4/v6). + op : Operation to be done : SET or DEL. + + ''' + config = self.create_single_route(vnet, dest, mask, nhs, op, bfd=bfd) + str_config = '[\n' + config + '\n]' + self.apply_config_in_swss(duthost, str_config, op + "_vnet_route") + + @classmethod + def create_single_route(cls, vnet, dest, mask, nhs, op, bfd=False): + ''' + Create a single route entry for vnet, for the given dest, through + the endpoints:nhs, op:SET/DEL + ''' + if bfd: + config = '''{{ + "VNET_ROUTE_TUNNEL_TABLE:{}:{}/{}": {{ + "endpoint": "{}", + "endpoint_monitor": "{}" + }}, + "OP": "{}" + }}'''.format(vnet, dest, mask, ",".join(nhs), ",".join(nhs), op) + + else: + config = '''{{ + "VNET_ROUTE_TUNNEL_TABLE:{}:{}/{}": {{ + "endpoint": "{}" + }}, + "OP": "{}" + }}'''.format(vnet, dest, mask, ",".join(nhs), op) + + return config + + def get_ip_address(self, af, hostid=1, netid=100): + ''' + Calculate an ip address from the given arguments. + af : Address Family. + hostid : The last octet. + netid : The first octet. + ''' + third_octet = self.Address_Count % 255 + second_octet = (self.Address_Count / 255) % 255 + first_octet = netid + (self.Address_Count / 65025) + self.Address_Count = self.Address_Count + 1 + if af == 'v4': + return "{}.{}.{}.{}".format( + first_octet, second_octet, third_octet, hostid) + if af == 'v6': + # :0: gets removed in the IPv6 addresses. + # Adding "a" to octets, to avoid it. + return "fddd:a{}:a{}::a{}:{}".format( + first_octet, second_octet, third_octet, hostid) + + def set_routes_in_dut(self, + duthost, + dest_to_nh_map, + dest_af, + op, + bfd=False): + ''' + Configure Vnet routes in the DUT. + duthost : AnsibleHost structure for the DUT. + dest_to_nh_map : The full map of the destination->Nexthops + dictionary. + dest_af : Address family of the destionation. + op : Operation to be done: SET or DEL. + bfd : Enable BFD or not (True/False). + ''' + config_list = [] + for vnet in dest_to_nh_map: + for dest in dest_to_nh_map[vnet]: + config_list.append(self.create_single_route( + vnet, + dest, + self.HOST_MASK[dest_af], + dest_to_nh_map[vnet][dest], + op, + bfd=bfd)) + + full_config = '[' + "\n,".join(config_list) + '\n]' + self.apply_config_in_swss(duthost, full_config, op+"_routes") + + def get_t2_ports(self, duthost, minigraph_data): + ''' + In T1 topology, any port connected to the T2 BGP neighbors are + needed. In T0, any port connected to the T1 BGP neighbors are + needed. + ''' + portchannels_to_t2 = self.get_portchannels_to_neighbors( + duthost, + "T2", + minigraph_data) + list_of_interfaces = [] + if portchannels_to_t2: + for pc_name in portchannels_to_t2: + list_of_interfaces.extend(portchannels_to_t2[pc_name]) + else: + list_of_interfaces = self.get_ethernet_to_neighbors( + "T2", minigraph_data) + + ret_list = [] + for iface in list_of_interfaces: + ret_list.append(minigraph_data["minigraph_ptf_indices"][iface]) + return ret_list + + @classmethod + def bgp_established(cls, duthost, down_list=None): + ''' + Verify if the BGP state is as per our requirements. + The BGP neighbors that are listed in the down_list must be down, + and the rest should be up. If this condition is met, return True, + else False. + + duthost : AnsibleHost structure of the DUT. + down_list : The BGP neighbors that are expected to be down. + ''' + bgp_facts = duthost.bgp_facts()['ansible_facts'] + if down_list is None: + down_list = [] + for addr, value in bgp_facts['bgp_neighbors'].items(): + if value['state'] == 'established': + if addr in down_list: + # The neighbor is supposed to be down, and is actually up. + Logger.info( + "Neighbor %s is established, but should be down.", + addr) + return False + else: + # The neighbor is supposed to be up, and is actually up. + continue + else: + if addr in down_list: + # The neighbor is supposed to be down, and is actually + # down. + continue + else: + # The neighbor is supposed to be up, but is actually down. + Logger.info( + "Neighbor %s is not yet established, has state: %s", + addr, + value['state']) + return False + + # Now wait for the routes to be updated. + time.sleep(30) + return True + + @classmethod + def get_downed_bgp_neighbors(cls, shut_intf_list, minigraph_data): + ''' + Get the list of bgp neighbors that should be down, + based on the interfaces that are shutdown. + ''' + ret_list = [] + for intf in shut_intf_list: + for m_intf in minigraph_data['minigraph_portchannel_interfaces'] +\ + minigraph_data['minigraph_interfaces']: + if m_intf['attachto'] == intf: + ret_list.append(m_intf['peer_addr']) + return ret_list + + @classmethod + def get_all_bgp_neighbors(cls, minigraph_facts, role): + ''' + Get the list of BGP neighbors from the minigraph_facts. + minigraph_facts : Minigraph data from the DUT. + role : The role of the BGP neighbor. T0 or T2. + ''' + all_neighbors = {} + for element in minigraph_facts['minigraph_bgp']: + if role in element['name']: + try: + all_neighbors[element['name']] + except KeyError: + all_neighbors[element['name']] = {} + if ip_address(element['addr']).version == 4: + all_neighbors[element['name']].update( + {"v4": element['addr']}) + elif ip_address(element['addr']).version == 6: + all_neighbors[element['name']].update( + {"v6": element['addr']}) + return all_neighbors + + def get_corresponding_ports(self, shut_intf_list, minigraph_data): + ''' + This is for tests that shutdown some of the T2 ports. + This function will check which ports are to be ignored for the encap + packets coming back to the PTF. If the encap packet comes in any of + these ports, it is a bug. + ''' + eth_ifaces_list = [] + for intf in shut_intf_list: + if "Ethernet" in intf: + eth_ifaces_list.append(intf) + elif "PortChannel" in intf: + for port in self.get_ethernet_ports([intf], minigraph_data): + eth_ifaces_list.append(port) + return_list = [minigraph_data["minigraph_ptf_indices"][iface] + for iface in eth_ifaces_list] + return return_list + + def get_ethernet_ports(self, intf_list, minigraph_data): + ''' + The given interface list can be either Ethernet or Portchannel. + This function will return a flat list of Ethernet ports + corresponding to the given intf_list itself, or members of + Portchannels. + ''' + ret_list = [] + for intf in intf_list: + if "Ethernet" in intf: + ret_list.append(intf) + elif "PortChannel" in intf: + ret_list.extend( + minigraph_data['minigraph_portchannels'][intf]['members']) + + return ret_list + + def gather_ptf_indices_t2_neighbor( + self, + minigraph_facts, + all_t2_neighbors, + t2_neighbor, + encap_type): + ''' + Get the list of PTF port indices for the given list of + t2_neighbors. In T1 topology, every DUT port is mapped to a port + in the PTF. This function calculates the list of PTF ports that are + mapped to the given list of t2_neighbors. + minigraph_facts : Minigraph data from the Duthost. + all_t2_neighbors : All T2 neighbors of the DUT. + t2_neighbor : The T2 neighbor for which we need the PTF ports. + encap_type : Encap type(v4_in_v4/v4_in_v6/v6_in_v4/v6_in_v6) + ''' + # All T2 Neighbors VM's name to Neighbor IP Mapping + all_pcs = minigraph_facts['minigraph_portchannel_interfaces'] + # Neighbor IP to Portchannel interfaces mapping + pc_to_ip_map = {} + for each_pc in all_pcs: + pc_to_ip_map[each_pc['peer_addr']] = each_pc['attachto'] + # Finding the portchannel under shutdown T2 Neighbor + outer_af = self.get_outer_layer_version(encap_type) + required_pc = \ + pc_to_ip_map[all_t2_neighbors[t2_neighbor][outer_af].lower()] + # Finding ethernet interfaces under that specific portchannel + required_ethernet_interfaces = \ + minigraph_facts['minigraph_portchannels'][required_pc]['members'] + # Finding interfaces with PTF indices + ret_list = [] + for iface in required_ethernet_interfaces: + ret_list.append(minigraph_facts["minigraph_ptf_indices"][iface]) + return ret_list + + @classmethod + def start_bfd_responder(cls, ptfhost, dut_mac, dut_loop_ips, monitor_file): + ''' + Configure the supervisor in the PTF with BFD responder and start + the BFD responder. + ptfhost : AnsibleHost structure of the PTF container. + t2_ports : The list of T2 ports(The BFD responder can take any + port actually). + dut_mac : Mac address of the DUT. + dut_loop_ips : IPv4 and IPv6 addresses of the Loopback interface + in the DUT. + monitor_file : The file to be monitored by the BFD responder. + ''' + ptfhost.copy(dest=monitor_file, content="\n\n\n") + + extra_vars = { + "bfd_responder_args": + 'dut_mac=u"{}";dut_loop_ips={};monitor_file="{}"'.format( + dut_mac, + str(dut_loop_ips).replace('\'', '"'), + monitor_file)} + try: + ptfhost.command('supervisorctl stop bfd_responder') + except BaseException: + pass + + ptfhost.host.options["variable_manager"].extra_vars.update(extra_vars) + script_args = \ + '''dut_mac=u"{}";dut_loop_ips={};monitor_file="{}"'''.format( + dut_mac, str(dut_loop_ips).replace('\'', '"'), monitor_file) + supervisor_conf_content = ''' +[program:bfd_responder] +command=ptf --test-dir /root/ptftests bfd_responder.BFD_Responder''' +\ + ' --platform-dir /root/ptftests -t' + \ + ''' '{}' --relax --platform remote +process_name=bfd_responder +stdout_logfile=/tmp/bfd_responder.out.log +stderr_logfile=/tmp/bfd_responder.err.log +redirect_stderr=false +autostart=false +autorestart=true +startsecs=1 +numprocs=1 +'''.format(script_args) + ptfhost.copy( + content=supervisor_conf_content, + dest='/etc/supervisor/conf.d/bfd_responder.conf') + + ptfhost.command('supervisorctl reread') + ptfhost.command('supervisorctl update') + ptfhost.command('supervisorctl start bfd_responder') + + @classmethod + def stop_bfd_responder(cls, ptfhost): + ''' + Stop the BFD responder, and clean it up from the supervisor. + ''' + try: + ptfhost.command('supervisorctl stop bfd_responder') + except BaseException: + pass + ptfhost.command('supervisorctl remove bfd_responder') + + @classmethod + def update_monitor_file(cls, + ptfhost, + monitor_file, + intf_list, + ip_address_list): + ''' + Update the BFD responder's list of IP addresses and interfaces to + respond to. The bfd_responder will keep reading this file every + second and update itself. + ptfhost : AnsibleHost structure of the PTF container. + monitor_file : The monitor file of the bfd_responder. + intf_list : The list of interface indices in the PTF to work + with. + ip_address_list : The list of IP addresses from the DUT to + respond to. + ''' + ptfhost.copy( + dest=monitor_file, + content="{}\n{}\n".format( + ",".join(map(str, intf_list)), + ",".join(ip_address_list))) + time.sleep(3)