[SDFAB-988] QER rate limiting tests
Change-Id: I4c542a5c9a122c0595b36e5e96d2b093682cfc7c
diff --git a/TestON/drivers/common/api/controller/trexclientdriver.py b/TestON/drivers/common/api/controller/trexclientdriver.py
index de65f3a..bdbc303 100644
--- a/TestON/drivers/common/api/controller/trexclientdriver.py
+++ b/TestON/drivers/common/api/controller/trexclientdriver.py
@@ -8,6 +8,7 @@
"""
import time
import os
+import copy
import sys
import importlib
import collections
@@ -92,6 +93,7 @@
self.trex_client = None
self.trex_daemon_client = None
self.trex_library_python_path = None
+ self.gen_traffic_per_port = {}
super(TrexClientDriver, self).__init__()
def connect(self, **connectargs):
@@ -172,8 +174,7 @@
# incoming packets if the destination mac is not the port mac address.
self.trex_client.set_port_attr(self.trex_client.get_all_ports(),
promiscuous=True)
- # Reset the used sender ports
- self.all_sender_port = set()
+ self.gen_traffic_per_port = {}
self.stats = None
return True
@@ -227,7 +228,9 @@
percentage=percentage,
l1_bps=l1_bps)
self.trex_client.add_streams(traffic_stream, ports=trex_port)
- self.all_sender_port.add(trex_port)
+ gen_traffic = self.gen_traffic_per_port.get(trex_port, 0)
+ gen_traffic += l1_bps
+ self.gen_traffic_per_port[trex_port] = gen_traffic
return True
def startAndWaitTraffic(self, duration=10, ports=[]):
@@ -242,16 +245,18 @@
main.log.error(
"Cannot start traffic, first connect the TRex client")
return False
- main.log.info("Start sending traffic for %d seconds" % duration)
- self.trex_client.start(list(self.all_sender_port), mult="1",
+ # Reset stats from previous run
+ self.stats = None
+ main.step("Sending traffic for %d seconds" % duration)
+ self.trex_client.start(self.gen_traffic_per_port.keys(), mult="1",
duration=duration)
main.log.info("Waiting until all traffic is sent..")
- result = self.__monitor_port_stats(ports)
- self.trex_client.wait_on_traffic(ports=list(self.all_sender_port),
+ result = self.__monitor_port_stats({p: self.gen_traffic_per_port.get(p, None) for p in ports})
+ self.trex_client.wait_on_traffic(ports=self.gen_traffic_per_port.keys(),
rx_delay_ms=100)
main.log.info("...traffic sent!")
# Reset sender port so we can run other tests with the same TRex client
- self.all_sender_port = set()
+ self.gen_traffic_per_port = {}
main.log.info("Getting stats")
self.stats = self.trex_client.get_stats()
return result
@@ -357,43 +362,53 @@
M = 1000 * K
G = 1000 * M
- def __monitor_port_stats(self, ports, time_interval=1):
+ def __monitor_port_stats(self, target_tx_per_port, num_samples=4,
+ ramp_up_timeout=5, time_interval=1, min_tx_bps_margin=0.95):
"""
- List some port stats continuously while traffic is active
+ List some port stats continuously while traffic is active and verify that
+ the generated amount traffic is the expected one
- :param ports: List of ports ids to monitor
+ :param target_tx_per_port: Traffic to be generated per port
:param time_interval: Interval between read
+ :param num_samples: Number of samples of statistics from each monitored ports
+ :param ramp_up_timeout: how many seconds to wait before TRex can reach the target TX rate
:return: Statistics read while traffic is active, or empty result if no
- ports provided.
+ target_tx_per_port provided.
"""
+
+ ports = target_tx_per_port.keys()
+ local_gen_traffic_per_port = copy.deepcopy(target_tx_per_port)
results = {
port_id: {"rx_bps": [], "tx_bps": [], "rx_pps": [], "tx_pps": []}
for port_id in ports
}
results["duration"] = []
- if not ports:
+ if len(ports) == 0:
return results
+ start_time = time.time()
prev = {
port_id: {
"opackets": 0,
"ipackets": 0,
"obytes": 0,
"ibytes": 0,
- "time": time.time(),
+ "time": start_time,
}
for port_id in ports
}
- s_time = time.time()
+ time.sleep(time_interval)
while self.trex_client.is_traffic_active():
stats = self.trex_client.get_stats(ports=ports)
+ sample_time = time.time()
+ elapsed = sample_time - start_time
if not stats:
break
main.log.debug(
- "\nTRAFFIC RUNNING {:.2f} SEC".format(time.time() - s_time))
+ "\nTRAFFIC RUNNING {:.2f} SEC".format(elapsed))
main.log.debug(
"{:^4} | {:<10} | {:<10} | {:<10} | {:<10} |".format(
"Port", "RX bps", "TX bps", "RX pps", "TX pps"
@@ -402,21 +417,21 @@
main.log.debug(
"----------------------------------------------------------")
- for port in ports:
- opackets = stats[port]["opackets"]
- ipackets = stats[port]["ipackets"]
- obytes = stats[port]["obytes"]
- ibytes = stats[port]["ibytes"]
- time_diff = time.time() - prev[port]["time"]
+ for (tx_port, target_tx_rate) in local_gen_traffic_per_port.items():
+ opackets = stats[tx_port]["opackets"]
+ ipackets = stats[tx_port]["ipackets"]
+ obytes = stats[tx_port]["obytes"]
+ ibytes = stats[tx_port]["ibytes"]
+ time_diff = sample_time - prev[tx_port]["time"]
- rx_bps = 8 * (ibytes - prev[port]["ibytes"]) / time_diff
- tx_bps = 8 * (obytes - prev[port]["obytes"]) / time_diff
- rx_pps = ipackets - prev[port]["ipackets"] / time_diff
- tx_pps = opackets - prev[port]["opackets"] / time_diff
+ rx_bps = 8 * (ibytes - prev[tx_port]["ibytes"]) / time_diff
+ tx_bps = 8 * (obytes - prev[tx_port]["obytes"]) / time_diff
+ rx_pps = ipackets - prev[tx_port]["ipackets"] / time_diff
+ tx_pps = opackets - prev[tx_port]["opackets"] / time_diff
main.log.debug(
"{:^4} | {:<10} | {:<10} | {:<10} | {:<10} |".format(
- port,
+ tx_port,
TrexClientDriver.__to_readable(rx_bps, "bps"),
TrexClientDriver.__to_readable(tx_bps, "bps"),
TrexClientDriver.__to_readable(rx_pps, "pps"),
@@ -424,21 +439,56 @@
)
)
- results["duration"].append(time.time() - s_time)
- results[port]["rx_bps"].append(rx_bps)
- results[port]["tx_bps"].append(tx_bps)
- results[port]["rx_pps"].append(rx_pps)
- results[port]["tx_pps"].append(tx_pps)
+ results["duration"].append(sample_time - start_time)
+ results[tx_port]["rx_bps"].append(rx_bps)
+ results[tx_port]["tx_bps"].append(tx_bps)
+ results[tx_port]["rx_pps"].append(rx_pps)
+ results[tx_port]["tx_pps"].append(tx_pps)
- prev[port]["opackets"] = opackets
- prev[port]["ipackets"] = ipackets
- prev[port]["obytes"] = obytes
- prev[port]["ibytes"] = ibytes
- prev[port]["time"] = time.time()
+ prev[tx_port]["opackets"] = opackets
+ prev[tx_port]["ipackets"] = ipackets
+ prev[tx_port]["obytes"] = obytes
+ prev[tx_port]["ibytes"] = ibytes
+ prev[tx_port]["time"] = sample_time
+
+ if target_tx_rate is not None:
+ if tx_bps < (target_tx_rate * min_tx_bps_margin):
+ if elapsed > ramp_up_timeout:
+ self.trex_client.stop(ports=ports)
+ utilities.assert_equal(
+ expect=True, actual=False,
+ onpass="Should never reach this",
+ onfail="TX port ({}) did not reach or sustain min sending rate ({})".format(
+ tx_port, target_tx_rate)
+ )
+ return {}
+ else:
+ results[tx_port]["rx_bps"].pop()
+ results[tx_port]["tx_bps"].pop()
+ results[tx_port]["rx_pps"].pop()
+ results[tx_port]["tx_pps"].pop()
+
+ if len(results[tx_port]["tx_bps"]) == num_samples:
+ # Stop monitoring ports for which we have enough samples
+ del local_gen_traffic_per_port[tx_port]
+
+ if len(local_gen_traffic_per_port) == 0:
+ # Enough samples for all ports
+ utilities.assert_equal(
+ expect=True, actual=True,
+ onpass="Enough samples have been generated",
+ onfail="Should never reach this"
+ )
+ return results
time.sleep(time_interval)
main.log.debug("")
+ utilities.assert_equal(
+ expect=True, actual=True,
+ onpass="Traffic sent correctly",
+ onfail="Should never reach this"
+ )
return results
@staticmethod
diff --git a/TestON/drivers/common/cli/p4runtimeclidriver.py b/TestON/drivers/common/cli/p4runtimeclidriver.py
index 9f371dc..b2b1756 100644
--- a/TestON/drivers/common/cli/p4runtimeclidriver.py
+++ b/TestON/drivers/common/cli/p4runtimeclidriver.py
@@ -234,6 +234,46 @@
main.log.exception(self.name + ": Uncaught exception!")
main.cleanAndExit()
+ def modifyMeterEntry(self, meterEntry=None, debug=True):
+ """
+ Modify a meter entry with either the given meter entry or use the saved
+ meter entry in the variable 'me'.
+
+ Example of a valid tableEntry string:
+ me = meter_entry["FabricIngress.upf.app_meter"]; me.cir = 1; me.cburst=1; me.pir=1; me.pburst=1; # nopep8
+
+ :param meterEntry: the string meter entry, if None it uses the meter
+ entry saved in the 'me' variable
+ :param debug: True to enable debug logging, False otherwise
+ :return: main.TRUE or main.FALSE on error
+ """
+ try:
+ main.log.debug(self.name + ": Pushing Meter Entry")
+ if debug:
+ self.handle.sendline("me")
+ self.handle.expect(self.p4rtShPrompt)
+ pushCmd = ""
+ if meterEntry:
+ pushCmd = meterEntry + ";"
+ pushCmd += "me.modify()"
+ response = self.__clearSendAndExpect(pushCmd)
+ if "Traceback" in response or "Error" in response or "INVALID_ARGUMENT" in response:
+ # TODO: other possibile errors?
+ # NameError...
+ main.log.error(
+ self.name + ": Error in modifying meter entry: " + response)
+ return main.FALSE
+ return main.TRUE
+ except pexpect.TIMEOUT:
+ main.log.exception(self.name + ": Command timed out")
+ return main.FALSE
+ except pexpect.EOF:
+ main.log.exception(self.name + ": connection closed.")
+ main.cleanAndExit()
+ except Exception:
+ main.log.exception(self.name + ": Uncaught exception!")
+ main.cleanAndExit()
+
def buildP4RtTableEntry(self, tableName, actionName, actionParams={},
matchFields={}, priority=0):
"""
@@ -250,7 +290,8 @@
try:
main.log.debug("%s: Building P4RT Table Entry "
"(table=%s, match=%s, priority=%s, action=%s, params=%s)" % (
- self.name, tableName, matchFields, priority, actionName, actionParams))
+ self.name, tableName, matchFields, priority,
+ actionName, actionParams))
cmd = 'te = table_entry["%s"](action="%s"); ' % (
tableName, actionName)
@@ -302,6 +343,48 @@
main.log.exception(self.name + ": Uncaught exception!")
main.cleanAndExit()
+ def buildP4RtMeterEntry(self, meterName, index, cir=None, cburst=None, pir=None,
+ pburst=None):
+ # TODO: improve error checking
+ try:
+ main.log.debug(
+ "%s: Building P4RT Meter Entry (meter=%s, index=%d, cir=%s, "
+ "cburst=%s, pir=%s, pburst=%s)" % (
+ self.name, meterName, index, str(cir), str(cburst), str(pir), str(pburst)
+ )
+ )
+ cmd = 'me = meter_entry["%s"]; ' % meterName
+ cmd += 'me.index=%d; ' % index
+ if cir is not None:
+ cmd += 'me.cir=%d; ' % cir
+ if cburst is not None:
+ cmd += 'me.cburst=%d; ' % cburst
+ if pir is not None:
+ cmd += 'me.pir=%d; ' % pir
+ if pburst is not None:
+ cmd += 'me.pburst=%d; ' % pburst
+
+ response = self.__clearSendAndExpect(cmd)
+ if "meter" in response and "does not exist" in response:
+ main.log.error("Unknown meter: " + response)
+ return main.FALSE
+ if "UNIMPLEMENTED" in response:
+ main.log.error("Error in creating the meter entry: " + response)
+ return main.FALSE
+ if "Traceback" in response:
+ main.log.error("Error in creating the meter entry: " + response)
+ return main.FALSE
+ return main.TRUE
+ except pexpect.TIMEOUT:
+ main.log.exception(self.name + ": Command timed out")
+ return main.FALSE
+ except pexpect.EOF:
+ main.log.exception(self.name + ": connection closed.")
+ main.cleanAndExit()
+ except Exception:
+ main.log.exception(self.name + ": Uncaught exception!")
+ main.cleanAndExit()
+
def readNumberTableEntries(self, tableName):
"""
Read table entries and return the number of entries present in a table.
@@ -310,7 +393,8 @@
:return: Number of entries,
"""
try:
- main.log.debug(self.name + ": Reading table entries from " + tableName)
+ main.log.debug(
+ self.name + ": Reading table entries from " + tableName)
cmd = 'table_entry["%s"].read(lambda te: print(te))' % tableName
response = self.__clearSendAndExpect(cmd, clearBufferAfter=True)
# Every table entries starts with "table_id: [P4RT obj ID] ("[tableName]")"