Skip to content

Commit c630ea1

Browse files
Add test case for trap flow counter feature (sonic-net#4456)
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
1 parent 9d1e34c commit c630ea1

File tree

2 files changed

+218
-32
lines changed

2 files changed

+218
-32
lines changed

ansible/roles/test/files/ptftests/copp_tests.py

+95-24
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ def __init__(self):
5555
self.myip = test_params.get('myip', None)
5656
self.peerip = test_params.get('peerip', None)
5757
self.default_server_send_rate_limit_pps = test_params.get('send_rate_limit', 2000)
58+
59+
# For counter test
60+
self.expect_send_pkt_number = test_params.get('sent_pkt_number', None)
61+
self.send_duration = test_params.get('send_duration', None)
62+
self.is_counter_test = self.expect_send_pkt_number is not None and self.send_duration is not None
5863

5964
self.needPreSend = None
6065

@@ -110,6 +115,9 @@ def copp_test(self, packet, send_intf, recv_intf):
110115
'''
111116
Pre-send some packets for a second to absorb the CBS capacity.
112117
'''
118+
if self.is_counter_test:
119+
return self.copp_counter_test(packet, send_intf, recv_intf)
120+
113121
if self.needPreSend:
114122
pre_send_count = 0
115123
end_time = datetime.datetime.now() + datetime.timedelta(seconds=self.DEFAULT_PRE_SEND_INTERVAL_SEC)
@@ -178,6 +186,67 @@ def copp_test(self, packet, send_intf, recv_intf):
178186

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

189+
def copp_counter_test(self, packet, send_intf, recv_intf):
190+
pre_test_ptf_tx_counter = self.dataplane.get_counters(*send_intf)
191+
pre_test_ptf_rx_counter = self.dataplane.get_counters(*recv_intf)
192+
pre_test_nn_tx_counter = self.dataplane.get_nn_counters(*send_intf)
193+
pre_test_nn_rx_counter = self.dataplane.get_nn_counters(*recv_intf)
194+
195+
send_count = 0
196+
start_time = datetime.datetime.now()
197+
send_window = float(self.send_duration) / float(self.expect_send_pkt_number)
198+
while send_count < self.expect_send_pkt_number:
199+
begin = time.time()
200+
testutils.send_packet(self, send_intf, packet)
201+
send_count += 1
202+
elapse = time.time() - begin
203+
204+
# Depending on the server/platform combination it is possible for the server to
205+
# overwhelm the DUT, so we add an artificial delay here to rate-limit the server.
206+
if elapse > 0:
207+
time.sleep(send_window - elapse)
208+
209+
end_time = datetime.datetime.now()
210+
time.sleep(self.DEFAULT_RECEIVE_WAIT_TIME) # Wait a little bit for all the packets to make it through
211+
recv_count = testutils.count_matched_packets(self, packet, recv_intf[1], recv_intf[0])
212+
213+
post_test_ptf_tx_counter = self.dataplane.get_counters(*send_intf)
214+
post_test_ptf_rx_counter = self.dataplane.get_counters(*recv_intf)
215+
post_test_nn_tx_counter = self.dataplane.get_nn_counters(*send_intf)
216+
post_test_nn_rx_counter = self.dataplane.get_nn_counters(*recv_intf)
217+
218+
ptf_tx_count = int(post_test_ptf_tx_counter[1] - pre_test_ptf_tx_counter[1])
219+
nn_tx_count = int(post_test_nn_tx_counter[1] - pre_test_nn_tx_counter[1])
220+
ptf_rx_count = int(post_test_ptf_rx_counter[0] - pre_test_ptf_rx_counter[0])
221+
nn_rx_count = int(post_test_nn_rx_counter[0] - pre_test_nn_rx_counter[0])
222+
223+
self.log("", True)
224+
self.log("Counters before the test:", True)
225+
self.log("If counter (0, n): %s" % str(pre_test_ptf_tx_counter), True)
226+
self.log("NN counter (0, n): %s" % str(pre_test_nn_tx_counter), True)
227+
self.log("If counter (1, n): %s" % str(pre_test_ptf_rx_counter), True)
228+
self.log("NN counter (1, n): %s" % str(pre_test_nn_rx_counter), True)
229+
self.log("", True)
230+
self.log("Counters after the test:", True)
231+
self.log("If counter (0, n): %s" % str(post_test_ptf_tx_counter), True)
232+
self.log("NN counter (0, n): %s" % str(post_test_nn_tx_counter), True)
233+
self.log("If counter (1, n): %s" % str(post_test_ptf_rx_counter), True)
234+
self.log("NN counter (1, n): %s" % str(post_test_nn_rx_counter), True)
235+
self.log("")
236+
self.log("Sent through NN to local ptf_nn_agent: %d" % ptf_tx_count)
237+
self.log("Sent through If to remote ptf_nn_agent: %d" % nn_tx_count)
238+
self.log("Recv from If on remote ptf_nn_agent: %d" % ptf_rx_count)
239+
self.log("Recv from NN on from remote ptf_nn_agent: %d" % nn_rx_count)
240+
241+
time_delta = end_time - start_time
242+
self.log("Sent out %d packets in %ds" % (send_count, time_delta.seconds))
243+
time_delta_ms = (time_delta.microseconds + time_delta.seconds * 10**6) / 1000
244+
tx_pps = int(send_count / (float(time_delta_ms) / 1000))
245+
rx_pps = int(recv_count / (float(time_delta_ms) / 1000))
246+
247+
return send_count, recv_count, time_delta, time_delta_ms, tx_pps, rx_pps
248+
249+
181250
def contruct_packet(self, port_number):
182251
raise NotImplementedError
183252

@@ -214,21 +283,22 @@ def __init__(self):
214283
self.needPreSend = False
215284

216285
def check_constraints(self, send_count, recv_count, time_delta_ms, rx_pps):
217-
pkt_rx_limit = send_count * 0.90
286+
if not self.is_counter_test:
287+
pkt_rx_limit = send_count * 0.90
218288

219-
self.log("")
220-
self.log("Checking constraints (NoPolicy):")
221-
self.log(
222-
"rx_pps (%d) > NO_POLICER_LIMIT (%d): %s" %
223-
(int(rx_pps), int(self.NO_POLICER_LIMIT), str(rx_pps > self.NO_POLICER_LIMIT))
224-
)
225-
self.log(
226-
"recv_count (%d) > pkt_rx_limit (%d): %s" %
227-
(int(recv_count), int(pkt_rx_limit), str(recv_count > pkt_rx_limit))
228-
)
289+
self.log("")
290+
self.log("Checking constraints (NoPolicy):")
291+
self.log(
292+
"rx_pps (%d) > NO_POLICER_LIMIT (%d): %s" %
293+
(int(rx_pps), int(self.NO_POLICER_LIMIT), str(rx_pps > self.NO_POLICER_LIMIT))
294+
)
295+
self.log(
296+
"recv_count (%d) > pkt_rx_limit (%d): %s" %
297+
(int(recv_count), int(pkt_rx_limit), str(recv_count > pkt_rx_limit))
298+
)
229299

230-
assert(rx_pps > self.NO_POLICER_LIMIT)
231-
assert(recv_count > pkt_rx_limit)
300+
assert(rx_pps > self.NO_POLICER_LIMIT)
301+
assert(recv_count > pkt_rx_limit)
232302

233303

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

239309
def check_constraints(self, send_count, recv_count, time_delta_ms, rx_pps):
240-
self.log("")
241-
self.log("Checking constraints (PolicyApplied):")
242-
self.log(
243-
"PPS_LIMIT_MIN (%d) <= rx_pps (%d) <= PPS_LIMIT_MAX (%d): %s" %
244-
(int(self.PPS_LIMIT_MIN),
245-
int(rx_pps),
246-
int(self.PPS_LIMIT_MAX),
247-
str(self.PPS_LIMIT_MIN <= rx_pps <= self.PPS_LIMIT_MAX))
248-
)
249-
250-
assert(self.PPS_LIMIT_MIN <= rx_pps <= self.PPS_LIMIT_MAX)
310+
if not self.is_counter_test:
311+
self.log("")
312+
self.log("Checking constraints (PolicyApplied):")
313+
self.log(
314+
"PPS_LIMIT_MIN (%d) <= rx_pps (%d) <= PPS_LIMIT_MAX (%d): %s" %
315+
(int(self.PPS_LIMIT_MIN),
316+
int(rx_pps),
317+
int(self.PPS_LIMIT_MAX),
318+
str(self.PPS_LIMIT_MIN <= rx_pps <= self.PPS_LIMIT_MAX))
319+
)
320+
321+
assert(self.PPS_LIMIT_MIN <= rx_pps <= self.PPS_LIMIT_MAX)
251322

252323

253324
# SONIC config contains policer CIR=600 for ARP

tests/copp/test_copp.py

+123-8
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,16 @@
2323
import logging
2424
import pytest
2525
import json
26+
import random
27+
import threading
28+
import time
2629
from collections import namedtuple
2730

2831
from tests.copp import copp_utils
2932
from tests.ptf_runner import ptf_runner
3033
from tests.common import config_reload, constants
3134
from tests.common.system_utils import docker
35+
from tests.common.utilities import wait_until
3236

3337
# Module-level fixtures
3438
from tests.common.fixtures.ptfhost_utils import copy_ptftests_directory # lgtm[py/unused-import]
@@ -53,6 +57,8 @@
5357
_SUPPORTED_T2_TOPOS = ["t2"]
5458
_TOR_ONLY_PROTOCOL = ["DHCP"]
5559
_TEST_RATE_LIMIT = 600
60+
_SEND_PACKET_NUMBER = 1500
61+
_SEND_DURATION = 30
5662

5763

5864
class TestCOPP(object):
@@ -97,6 +103,51 @@ def test_no_policer(self, protocol, duthosts, enum_rand_one_per_hwsku_frontend_h
97103
copp_testbed,
98104
dut_type)
99105

106+
@pytest.mark.parametrize("protocol", ["LACP",
107+
"LLDP",
108+
"UDLD",
109+
"IP2ME"])
110+
def test_counter(self, protocol, duthosts, rand_one_dut_hostname, ptfhost, copp_testbed, dut_type, counter_test):
111+
duthost = duthosts[rand_one_dut_hostname]
112+
trap_type = protocol.lower()
113+
114+
# wait until the trap counter is enabled
115+
assert wait_until(10, 1, 0, _check_trap_counter_enabled, duthost, trap_type), 'counter is not created for {}'.format(trap_type)
116+
117+
# clean previous counter value
118+
duthost.command('sonic-clear flowcnt-trap')
119+
120+
# start a thread to collect the max PPS value
121+
actual_rate = []
122+
t = threading.Thread(target=_collect_counter_rate, args=(duthost, trap_type, actual_rate))
123+
t.start()
124+
125+
# init and start PTF
126+
_copp_runner(duthost,
127+
ptfhost,
128+
protocol,
129+
copp_testbed,
130+
dut_type,
131+
True)
132+
133+
# wait for thread finish
134+
t.join()
135+
136+
# get final packet count from CLI
137+
expect_rate = float(_SEND_PACKET_NUMBER / _SEND_DURATION)
138+
actual_packet_number = None
139+
cli_data = duthost.show_and_parse('show flowcnt-trap stats')
140+
for line in cli_data:
141+
if 'trap name' in line and line['trap name'] == trap_type:
142+
actual_packet_number = int(line['packets'].replace(',', ''))
143+
break
144+
145+
assert actual_packet_number == _SEND_PACKET_NUMBER, 'Trap {} expect send packet number: {}, but actual: {}'.format(trap_type, _SEND_PACKET_NUMBER, actual_packet_number)
146+
assert len(actual_rate) == 1, 'Failed to collect PPS value for trap {}'.format(trap_type)
147+
# Allow a 10 percent threshold for trap rate
148+
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)
149+
150+
100151
@pytest.fixture(scope="class")
101152
def dut_type(duthosts, enum_rand_one_per_hwsku_frontend_hostname):
102153
duthost = duthosts[enum_rand_one_per_hwsku_frontend_hostname]
@@ -156,17 +207,33 @@ def ignore_expected_loganalyzer_exceptions(enum_rand_one_per_hwsku_frontend_host
156207
if loganalyzer: # Skip if loganalyzer is disabled
157208
loganalyzer[enum_rand_one_per_hwsku_frontend_hostname].ignore_regex.extend(ignoreRegex)
158209

159-
def _copp_runner(dut, ptf, protocol, test_params, dut_type):
210+
@pytest.fixture(scope="function")
211+
def counter_test(duthosts, rand_one_dut_hostname):
212+
duthost = duthosts[rand_one_dut_hostname]
213+
duthost.command('counterpoll flowcnt-trap enable')
214+
215+
yield
216+
217+
duthost.command('counterpoll flowcnt-trap disable')
218+
219+
def _copp_runner(dut, ptf, protocol, test_params, dut_type, counter_test=False):
160220
"""
161221
Configures and runs the PTF test cases.
162222
"""
163-
164-
params = {"verbose": False,
165-
"target_port": test_params.nn_target_port,
166-
"myip": test_params.myip,
167-
"peerip": test_params.peerip,
168-
"send_rate_limit": test_params.send_rate_limit}
169-
223+
if not counter_test:
224+
params = {"verbose": False,
225+
"target_port": test_params.nn_target_port,
226+
"myip": test_params.myip,
227+
"peerip": test_params.peerip,
228+
"send_rate_limit": test_params.send_rate_limit}
229+
else:
230+
params = {"verbose": False,
231+
"target_port": test_params.nn_target_port,
232+
"myip": test_params.myip,
233+
"peerip": test_params.peerip,
234+
"send_rate_limit": test_params.send_rate_limit,
235+
"sent_pkt_number": _SEND_PACKET_NUMBER,
236+
"send_duration": _SEND_DURATION}
170237
dut_ip = dut.mgmt_ip
171238
device_sockets = ["0-{}@tcp://127.0.0.1:10900".format(test_params.nn_target_port),
172239
"1-{}@tcp://{}:10900".format(test_params.nn_target_port, dut_ip)]
@@ -328,3 +395,51 @@ def _teardown_multi_asic_proxy(dut, creds, test_params, tbinfo):
328395
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"]
329396
dut.command("sudo iptables -t nat -D PREROUTING -p tcp --dport 10900 -j DNAT --to-destination {}".format(ns_ip))
330397
dut.command("sudo ip -n {} rule delete from {} to {} pref 3 lookup default".format(test_params.nn_target_namespace, ns_ip, tbinfo["ptf_ip"]))
398+
399+
def _check_trap_counter_enabled(duthost, trap_type):
400+
lines = duthost.command('show flowcnt-trap stats')['stdout']
401+
return trap_type in lines
402+
403+
def _collect_counter_rate(duthost, trap_type, actual_rate):
404+
rate_values = []
405+
# Wait up to _SEND_DURATION + 5 seconds for PTF to stop sending packet,
406+
# as it might take some time for PTF to initialize itself
407+
max_wait = _SEND_DURATION + 5
408+
packets = None
409+
while max_wait > 0:
410+
cli_data = duthost.show_and_parse('show flowcnt-trap stats')
411+
for line in cli_data:
412+
if 'trap name' in line and line['trap name'] == trap_type:
413+
packets = line['packets']
414+
if packets == 'N/A':
415+
# Packets value is not available yet
416+
logging.debug('Trap {} packets value is not available yet'.format(trap_type))
417+
break
418+
419+
pps_value = line['pps']
420+
if pps_value == 'N/A':
421+
# PPS value is not available yet
422+
logging.debug('Trap {} PPS value is not available yet'.format(trap_type))
423+
break
424+
425+
packets = int(packets.replace(',', ''))
426+
if packets == 0:
427+
# PTF has not started yet
428+
logging.debug('Trap {} packets value is still 0, PTF has not started yet'.format(trap_type))
429+
break
430+
431+
logging.info('Trap {} current PPS value is {}, packets value is {}'.format(trap_type, pps_value, packets))
432+
rate_values.append(float(pps_value[:-2]))
433+
break
434+
if packets == _SEND_PACKET_NUMBER:
435+
# Enough packets are sent, stop
436+
break
437+
time.sleep(0.5)
438+
max_wait -= 0.5
439+
440+
if rate_values:
441+
# Calculate max PPS
442+
max_pps = max(rate_values)
443+
logging.info('Trap {} max PPS is {}'.format(trap_type, max_pps))
444+
actual_rate.append(max_pps)
445+

0 commit comments

Comments
 (0)