Skip to content

Commit

Permalink
Add test case for trap flow counter feature (#4456)
Browse files Browse the repository at this point in the history
What is the motivation for this PR?
Add test case for trap flow counter feature.

How did you do it?
This new test case is verifying the packet send number and packet rate. Test flow is like:
* Enable and clear trap flow counter
* Send packets via ptf
* Verify the send number equals to the counter value, and the send rate is close to counter value

How did you verify/test it?
Manually run the test

Any platform specific information?
Any platform that support trap flow counter
  • Loading branch information
Junchao-Mellanox committed Nov 16, 2021
1 parent 9d1e34c commit c630ea1
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 32 deletions.
119 changes: 95 additions & 24 deletions ansible/roles/test/files/ptftests/copp_tests.py
Expand Up @@ -55,6 +55,11 @@ def __init__(self):
self.myip = test_params.get('myip', None)
self.peerip = test_params.get('peerip', None)
self.default_server_send_rate_limit_pps = test_params.get('send_rate_limit', 2000)

# For counter test
self.expect_send_pkt_number = test_params.get('sent_pkt_number', None)
self.send_duration = test_params.get('send_duration', None)
self.is_counter_test = self.expect_send_pkt_number is not None and self.send_duration is not None

self.needPreSend = None

Expand Down Expand Up @@ -110,6 +115,9 @@ def copp_test(self, packet, send_intf, recv_intf):
'''
Pre-send some packets for a second to absorb the CBS capacity.
'''
if self.is_counter_test:
return self.copp_counter_test(packet, send_intf, recv_intf)

if self.needPreSend:
pre_send_count = 0
end_time = datetime.datetime.now() + datetime.timedelta(seconds=self.DEFAULT_PRE_SEND_INTERVAL_SEC)
Expand Down Expand Up @@ -178,6 +186,67 @@ def copp_test(self, packet, send_intf, recv_intf):

return send_count, recv_count, time_delta, time_delta_ms, tx_pps, rx_pps

def copp_counter_test(self, packet, send_intf, recv_intf):
pre_test_ptf_tx_counter = self.dataplane.get_counters(*send_intf)
pre_test_ptf_rx_counter = self.dataplane.get_counters(*recv_intf)
pre_test_nn_tx_counter = self.dataplane.get_nn_counters(*send_intf)
pre_test_nn_rx_counter = self.dataplane.get_nn_counters(*recv_intf)

send_count = 0
start_time = datetime.datetime.now()
send_window = float(self.send_duration) / float(self.expect_send_pkt_number)
while send_count < self.expect_send_pkt_number:
begin = time.time()
testutils.send_packet(self, send_intf, packet)
send_count += 1
elapse = time.time() - begin

# Depending on the server/platform combination it is possible for the server to
# overwhelm the DUT, so we add an artificial delay here to rate-limit the server.
if elapse > 0:
time.sleep(send_window - elapse)

end_time = datetime.datetime.now()
time.sleep(self.DEFAULT_RECEIVE_WAIT_TIME) # Wait a little bit for all the packets to make it through
recv_count = testutils.count_matched_packets(self, packet, recv_intf[1], recv_intf[0])

post_test_ptf_tx_counter = self.dataplane.get_counters(*send_intf)
post_test_ptf_rx_counter = self.dataplane.get_counters(*recv_intf)
post_test_nn_tx_counter = self.dataplane.get_nn_counters(*send_intf)
post_test_nn_rx_counter = self.dataplane.get_nn_counters(*recv_intf)

ptf_tx_count = int(post_test_ptf_tx_counter[1] - pre_test_ptf_tx_counter[1])
nn_tx_count = int(post_test_nn_tx_counter[1] - pre_test_nn_tx_counter[1])
ptf_rx_count = int(post_test_ptf_rx_counter[0] - pre_test_ptf_rx_counter[0])
nn_rx_count = int(post_test_nn_rx_counter[0] - pre_test_nn_rx_counter[0])

self.log("", True)
self.log("Counters before the test:", True)
self.log("If counter (0, n): %s" % str(pre_test_ptf_tx_counter), True)
self.log("NN counter (0, n): %s" % str(pre_test_nn_tx_counter), True)
self.log("If counter (1, n): %s" % str(pre_test_ptf_rx_counter), True)
self.log("NN counter (1, n): %s" % str(pre_test_nn_rx_counter), True)
self.log("", True)
self.log("Counters after the test:", True)
self.log("If counter (0, n): %s" % str(post_test_ptf_tx_counter), True)
self.log("NN counter (0, n): %s" % str(post_test_nn_tx_counter), True)
self.log("If counter (1, n): %s" % str(post_test_ptf_rx_counter), True)
self.log("NN counter (1, n): %s" % str(post_test_nn_rx_counter), True)
self.log("")
self.log("Sent through NN to local ptf_nn_agent: %d" % ptf_tx_count)
self.log("Sent through If to remote ptf_nn_agent: %d" % nn_tx_count)
self.log("Recv from If on remote ptf_nn_agent: %d" % ptf_rx_count)
self.log("Recv from NN on from remote ptf_nn_agent: %d" % nn_rx_count)

time_delta = end_time - start_time
self.log("Sent out %d packets in %ds" % (send_count, time_delta.seconds))
time_delta_ms = (time_delta.microseconds + time_delta.seconds * 10**6) / 1000
tx_pps = int(send_count / (float(time_delta_ms) / 1000))
rx_pps = int(recv_count / (float(time_delta_ms) / 1000))

return send_count, recv_count, time_delta, time_delta_ms, tx_pps, rx_pps


def contruct_packet(self, port_number):
raise NotImplementedError

Expand Down Expand Up @@ -214,21 +283,22 @@ def __init__(self):
self.needPreSend = False

def check_constraints(self, send_count, recv_count, time_delta_ms, rx_pps):
pkt_rx_limit = send_count * 0.90
if not self.is_counter_test:
pkt_rx_limit = send_count * 0.90

self.log("")
self.log("Checking constraints (NoPolicy):")
self.log(
"rx_pps (%d) > NO_POLICER_LIMIT (%d): %s" %
(int(rx_pps), int(self.NO_POLICER_LIMIT), str(rx_pps > self.NO_POLICER_LIMIT))
)
self.log(
"recv_count (%d) > pkt_rx_limit (%d): %s" %
(int(recv_count), int(pkt_rx_limit), str(recv_count > pkt_rx_limit))
)
self.log("")
self.log("Checking constraints (NoPolicy):")
self.log(
"rx_pps (%d) > NO_POLICER_LIMIT (%d): %s" %
(int(rx_pps), int(self.NO_POLICER_LIMIT), str(rx_pps > self.NO_POLICER_LIMIT))
)
self.log(
"recv_count (%d) > pkt_rx_limit (%d): %s" %
(int(recv_count), int(pkt_rx_limit), str(recv_count > pkt_rx_limit))
)

assert(rx_pps > self.NO_POLICER_LIMIT)
assert(recv_count > pkt_rx_limit)
assert(rx_pps > self.NO_POLICER_LIMIT)
assert(recv_count > pkt_rx_limit)


class PolicyTest(ControlPlaneBaseTest):
Expand All @@ -237,17 +307,18 @@ def __init__(self):
self.needPreSend = True

def check_constraints(self, send_count, recv_count, time_delta_ms, rx_pps):
self.log("")
self.log("Checking constraints (PolicyApplied):")
self.log(
"PPS_LIMIT_MIN (%d) <= rx_pps (%d) <= PPS_LIMIT_MAX (%d): %s" %
(int(self.PPS_LIMIT_MIN),
int(rx_pps),
int(self.PPS_LIMIT_MAX),
str(self.PPS_LIMIT_MIN <= rx_pps <= self.PPS_LIMIT_MAX))
)

assert(self.PPS_LIMIT_MIN <= rx_pps <= self.PPS_LIMIT_MAX)
if not self.is_counter_test:
self.log("")
self.log("Checking constraints (PolicyApplied):")
self.log(
"PPS_LIMIT_MIN (%d) <= rx_pps (%d) <= PPS_LIMIT_MAX (%d): %s" %
(int(self.PPS_LIMIT_MIN),
int(rx_pps),
int(self.PPS_LIMIT_MAX),
str(self.PPS_LIMIT_MIN <= rx_pps <= self.PPS_LIMIT_MAX))
)

assert(self.PPS_LIMIT_MIN <= rx_pps <= self.PPS_LIMIT_MAX)


# SONIC config contains policer CIR=600 for ARP
Expand Down
131 changes: 123 additions & 8 deletions tests/copp/test_copp.py
Expand Up @@ -23,12 +23,16 @@
import logging
import pytest
import json
import random
import threading
import time
from collections import namedtuple

from tests.copp import copp_utils
from tests.ptf_runner import ptf_runner
from tests.common import config_reload, constants
from tests.common.system_utils import docker
from tests.common.utilities import wait_until

# Module-level fixtures
from tests.common.fixtures.ptfhost_utils import copy_ptftests_directory # lgtm[py/unused-import]
Expand All @@ -53,6 +57,8 @@
_SUPPORTED_T2_TOPOS = ["t2"]
_TOR_ONLY_PROTOCOL = ["DHCP"]
_TEST_RATE_LIMIT = 600
_SEND_PACKET_NUMBER = 1500
_SEND_DURATION = 30


class TestCOPP(object):
Expand Down Expand Up @@ -97,6 +103,51 @@ def test_no_policer(self, protocol, duthosts, enum_rand_one_per_hwsku_frontend_h
copp_testbed,
dut_type)

@pytest.mark.parametrize("protocol", ["LACP",
"LLDP",
"UDLD",
"IP2ME"])
def test_counter(self, protocol, duthosts, rand_one_dut_hostname, ptfhost, copp_testbed, dut_type, counter_test):
duthost = duthosts[rand_one_dut_hostname]
trap_type = protocol.lower()

# wait until the trap counter is enabled
assert wait_until(10, 1, 0, _check_trap_counter_enabled, duthost, trap_type), 'counter is not created for {}'.format(trap_type)

# clean previous counter value
duthost.command('sonic-clear flowcnt-trap')

# start a thread to collect the max PPS value
actual_rate = []
t = threading.Thread(target=_collect_counter_rate, args=(duthost, trap_type, actual_rate))
t.start()

# init and start PTF
_copp_runner(duthost,
ptfhost,
protocol,
copp_testbed,
dut_type,
True)

# wait for thread finish
t.join()

# get final packet count from CLI
expect_rate = float(_SEND_PACKET_NUMBER / _SEND_DURATION)
actual_packet_number = None
cli_data = duthost.show_and_parse('show flowcnt-trap stats')
for line in cli_data:
if 'trap name' in line and line['trap name'] == trap_type:
actual_packet_number = int(line['packets'].replace(',', ''))
break

assert actual_packet_number == _SEND_PACKET_NUMBER, 'Trap {} expect send packet number: {}, but actual: {}'.format(trap_type, _SEND_PACKET_NUMBER, actual_packet_number)
assert len(actual_rate) == 1, 'Failed to collect PPS value for trap {}'.format(trap_type)
# Allow a 10 percent threshold for trap rate
assert (expect_rate * 0.9) < actual_rate[0] < (expect_rate * 1.1), 'Trap {} expect send packet rate: {}, but actual: {}'.format(trap_type, expect_rate, actual_rate)


@pytest.fixture(scope="class")
def dut_type(duthosts, enum_rand_one_per_hwsku_frontend_hostname):
duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname]
Expand Down Expand Up @@ -156,17 +207,33 @@ def ignore_expected_loganalyzer_exceptions(enum_rand_one_per_hwsku_frontend_host
if loganalyzer: # Skip if loganalyzer is disabled
loganalyzer[enum_rand_one_per_hwsku_frontend_hostname].ignore_regex.extend(ignoreRegex)

def _copp_runner(dut, ptf, protocol, test_params, dut_type):
@pytest.fixture(scope="function")
def counter_test(duthosts, rand_one_dut_hostname):
duthost = duthosts[rand_one_dut_hostname]
duthost.command('counterpoll flowcnt-trap enable')

yield

duthost.command('counterpoll flowcnt-trap disable')

def _copp_runner(dut, ptf, protocol, test_params, dut_type, counter_test=False):
"""
Configures and runs the PTF test cases.
"""

params = {"verbose": False,
"target_port": test_params.nn_target_port,
"myip": test_params.myip,
"peerip": test_params.peerip,
"send_rate_limit": test_params.send_rate_limit}

if not counter_test:
params = {"verbose": False,
"target_port": test_params.nn_target_port,
"myip": test_params.myip,
"peerip": test_params.peerip,
"send_rate_limit": test_params.send_rate_limit}
else:
params = {"verbose": False,
"target_port": test_params.nn_target_port,
"myip": test_params.myip,
"peerip": test_params.peerip,
"send_rate_limit": test_params.send_rate_limit,
"sent_pkt_number": _SEND_PACKET_NUMBER,
"send_duration": _SEND_DURATION}
dut_ip = dut.mgmt_ip
device_sockets = ["0-{}@tcp://127.0.0.1:10900".format(test_params.nn_target_port),
"1-{}@tcp://{}:10900".format(test_params.nn_target_port, dut_ip)]
Expand Down Expand Up @@ -328,3 +395,51 @@ def _teardown_multi_asic_proxy(dut, creds, test_params, tbinfo):
ns_ip = dut.shell("sudo ip -n {} -4 -o addr show eth0".format(test_params.nn_target_namespace) + " | awk '{print $4}' | cut -d'/' -f1")["stdout"]
dut.command("sudo iptables -t nat -D PREROUTING -p tcp --dport 10900 -j DNAT --to-destination {}".format(ns_ip))
dut.command("sudo ip -n {} rule delete from {} to {} pref 3 lookup default".format(test_params.nn_target_namespace, ns_ip, tbinfo["ptf_ip"]))

def _check_trap_counter_enabled(duthost, trap_type):
lines = duthost.command('show flowcnt-trap stats')['stdout']
return trap_type in lines

def _collect_counter_rate(duthost, trap_type, actual_rate):
rate_values = []
# Wait up to _SEND_DURATION + 5 seconds for PTF to stop sending packet,
# as it might take some time for PTF to initialize itself
max_wait = _SEND_DURATION + 5
packets = None
while max_wait > 0:
cli_data = duthost.show_and_parse('show flowcnt-trap stats')
for line in cli_data:
if 'trap name' in line and line['trap name'] == trap_type:
packets = line['packets']
if packets == 'N/A':
# Packets value is not available yet
logging.debug('Trap {} packets value is not available yet'.format(trap_type))
break

pps_value = line['pps']
if pps_value == 'N/A':
# PPS value is not available yet
logging.debug('Trap {} PPS value is not available yet'.format(trap_type))
break

packets = int(packets.replace(',', ''))
if packets == 0:
# PTF has not started yet
logging.debug('Trap {} packets value is still 0, PTF has not started yet'.format(trap_type))
break

logging.info('Trap {} current PPS value is {}, packets value is {}'.format(trap_type, pps_value, packets))
rate_values.append(float(pps_value[:-2]))
break
if packets == _SEND_PACKET_NUMBER:
# Enough packets are sent, stop
break
time.sleep(0.5)
max_wait -= 0.5

if rate_values:
# Calculate max PPS
max_pps = max(rate_values)
logging.info('Trap {} max PPS is {}'.format(trap_type, max_pps))
actual_rate.append(max_pps)

0 comments on commit c630ea1

Please sign in to comment.