Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test case for trap flow counter feature #4456

Merged
merged 2 commits into from
Nov 16, 2021
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
119 changes: 95 additions & 24 deletions ansible/roles/test/files/ptftests/copp_tests.py
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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)