Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 66 additions & 33 deletions ansible/roles/test/files/ptftests/vxlan_traffic.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# 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; \
# -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

Expand All @@ -17,26 +17,33 @@
import ptf.packet as scapy
from ptf.base_tests import BaseTest
from ptf import config
from ptf.testutils import *
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.mask import Mask
import datetime
import subprocess
import ipaddress
from pprint import pprint
import logging
from ipaddress import ip_address
import random

VARS = {}
VARS['tcp_sport'] = 1234
VARS['tcp_dport'] = 5000

logger = logging.getLogger(__name__)

# Some constants used in this code
TEST_ECN = False
MIN_PACKET_COUNT = 4
MINIMUM_PACKETS_FOR_ECMP_VALIDATION = 300
TEST_ECN = True

def get_incremental_value(key):

global VARS
VARS[key] = VARS[key] + 1
# 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():
Expand All @@ -59,6 +66,12 @@ def setUp(self):
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']
# 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)
self.packet_count = MIN_PACKET_COUNT

self.random_mac = "00:aa:bb:cc:dd:ee"
self.ptf_mac_addrs = read_ptf_macs()
Expand Down Expand Up @@ -122,6 +135,32 @@ def read_ptf_macs(self):

return addrs

def verify_all_addresses_used_equally(self, nhs, returned_ip_addresses):
'''
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 #1 : All addresses have been used.
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))
for nh_address in returned_ip_addresses.keys():
logger.info(" {} : {}".format(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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rraghav-cisco please clarify what this 1% value is based on. Unfortunately I was not able to find it in feature requirement docs

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:
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]))

def test_encap(self, ptf_port, vni, ptf_addr, destination, nhs, test_ecn=False, vlan=0):
rv = True
try:
Expand All @@ -132,22 +171,21 @@ def test_encap(self, ptf_port, vni, ptf_addr, destination, nhs, test_ecn=False,
else:
tagged = False


options = {'ip_tos' : 0}
options_v6 = {'ipv6_tc' : 0}
options = {'ip_ecn' : 0}
options_v6 = {'ipv6_ecn' : 0}
if test_ecn:
options = {'ip_tos' : random.randint(0, 3)}
options_v6 = {'ipv6_tos' : random.randint(0, 3)}
ecn = random.randint(0, 3)
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 = {}
check_ecmp = False
for host_address in nhs:
check_ecmp = True
# This will ensure that every nh is used atleast once.
for i in range(4):
for i in range(self.packet_count):
tcp_sport = get_incremental_value('tcp_sport')
tcp_dport = 5000
valid_combination = True
if isinstance(ip_address(destination), ipaddress.IPv4Address) and isinstance(ip_address(ptf_addr), ipaddress.IPv4Address):
pkt_opts = {
Expand All @@ -159,7 +197,7 @@ def test_encap(self, ptf_port, vni, ptf_addr, destination, nhs, test_ecn=False,
"ip_id":105,
"ip_ttl":64,
"tcp_sport":tcp_sport,
"tcp_dport":tcp_dport}
"tcp_dport":VARS['tcp_dport']}
pkt_opts.update(options)
pkt = simple_tcp_packet(**pkt_opts)
pkt_opts['ip_ttl'] = 63
Expand All @@ -174,16 +212,14 @@ def test_encap(self, ptf_port, vni, ptf_addr, destination, nhs, test_ecn=False,
"ipv6_src":ptf_addr,
"ipv6_hlim":64,
"tcp_sport":tcp_sport,
"tcp_dport":tcp_dport}
"tcp_dport":VARS['tcp_dport']}
pkt_opts.update(options_v6)
pkt = simple_tcpv6_packet(**pkt_opts)
pkt_opts['ipv6_hlim'] = 63
pkt_opts['eth_dst'] = self.dut_mac
pkt_opts['eth_src'] = self.dut_mac
exp_pkt = simple_tcpv6_packet(**pkt_opts)
else:
valid_combination = False
print("Unusable combination:src:{} and dst:{}".format(src, destination))
udp_sport = 1234 # Use entropy_hash(pkt), it will be ignored in the test later.
udp_dport = self.vxlan_port
if isinstance(ip_address(host_address), ipaddress.IPv4Address):
Expand All @@ -198,8 +234,9 @@ def test_encap(self, ptf_port, vni, ptf_addr, destination, nhs, test_ecn=False,
udp_dport=udp_dport,
with_udp_chksum=False,
vxlan_vni=vni,
inner_frame=exp_pkt)
encap_pkt[IP].flags = 0x2
inner_frame=exp_pkt,
**options)
encap_pkt[scapy.IP].flags = 0x2
elif isinstance(ip_address(host_address), ipaddress.IPv6Address):
encap_pkt = simple_vxlanv6_packet(
eth_src=self.dut_mac,
Expand All @@ -210,8 +247,9 @@ def test_encap(self, ptf_port, vni, ptf_addr, destination, nhs, test_ecn=False,
udp_dport=udp_dport,
with_udp_chksum=False,
vxlan_vni=vni,
inner_frame=exp_pkt)
send_packet(self, ptf_port, str(pkt), count=2)
inner_frame=exp_pkt,
**options_v6)
send_packet(self, ptf_port, str(pkt))

masked_exp_pkt = Mask(encap_pkt)
masked_exp_pkt.set_do_not_care_scapy(scapy.Ether, "src")
Expand All @@ -227,11 +265,11 @@ def test_encap(self, ptf_port, vni, ptf_addr, destination, nhs, test_ecn=False,
masked_exp_pkt.set_do_not_care_scapy(scapy.UDP, "sport")
masked_exp_pkt.set_do_not_care_scapy(scapy.UDP, "chksum")

logging.info("Sending packet from port " + str(ptf_port) + " to " + destination)
logger.info("Sending packet from port " + str(ptf_port) + " to " + destination)

if self.expect_encap_success:
status, received_pkt = verify_packet_any_port(self, masked_exp_pkt, self.t2_ports)
scapy_pkt = Ether(received_pkt)
_, 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), ipaddress.IPv6Address):
dest_ip = scapy_pkt['IPv6'].dst
Expand All @@ -244,20 +282,15 @@ def test_encap(self, ptf_port, vni, ptf_addr, destination, nhs, test_ecn=False,

else:
check_ecmp = False
print ("Verifying no packet")
logger.info("Verifying no packet")
verify_no_packet_any(self, masked_exp_pkt, self.t2_ports)

# Verify ECMP:
if check_ecmp:
if set(nhs) - set(returned_ip_addresses.keys()) == set([]):
print ("Each address has been used")
else:
raise RuntimeError('''ECMP might have failed for:{}, we expected every ip address in the nexthop group({} of them)
to be used, but only {} are used:\nUsed addresses:{}\nUnused Addresses:{}'''.format(destination,
len(nhs), len(returned_ip_addresses.keys()),
returned_ip_addresses.keys(), set(nhs)-set(returned_ip_addresses.keys())))
self.verify_all_addresses_used_equally(nhs, returned_ip_addresses)

pkt.load = '0' * 60 + str(len(self.packets))
self.packets.append((ptf_port, str(pkt).encode("base64")))

finally:
print
logger.info("")
Original file line number Diff line number Diff line change
Expand Up @@ -343,12 +343,7 @@ system_health/test_system_health.py::test_service_checker_with_process_exit:
#######################################
vxlan/test_vxlan_ecmp.py:
skip:
reason: "VxLAN ECMP test is not yet supported on multi-ASIC platform"
reason: "VxLAN ECMP test is not yet supported on multi-ASIC platform. Also this test can only run on 4600c and 8102."
conditions:
- "is_multi_asic==True"
- "(is_multi_asic==True) or (platform not in ['x86_64-mlnx_msn4600c-r0', 'x86_64-8102_64h_o-r0'])"

vxlan/test_vxlan_ecmp.py::Test_VxLAN_route_tests::test_vxlan_single_endpoint:
skip:
reason: "This test can only run on 4600c and 8102"
conditions:
- "platform not in ['x86_64-mlnx_msn4600c-r0', 'x86_64-8102_64h_o-r0']"
Loading