blob: 03a534ac5220abde6203af212af15f47a6093c7b [file] [log] [blame]
Daniele Moro80889562021-09-08 10:09:26 +02001from distutils.util import strtobool
2from tests.USECASE.SegmentRouting.dependencies import scapy_helper
3
4
5class Trex:
6 """
7 Utility that manages interaction with TRex server via TRexDriver component
8 Example params:
9 <TREX>
10 <port_stats>0,1</port_stats>
11 <flows>
12 <RT_FROM_UE>
13 <name>Real Time</name>
14 <l1_bps>40000000</l1_bps>
15 <trex_port>0</trex_port>
16 <packet>
17 <pktlen>1400</pktlen>
18 <ip_src>10.240.0.2</ip_src>
19 <ip_dst>10.32.11.101</ip_dst>
20 <eth_src>3C:EC:EF:3E:0B:A0</eth_src>
21 <eth_dst>00:00:0A:4C:1C:46</eth_dst>
22 <gtp_teid>200</gtp_teid>
23 <s1u_addr>10.32.11.126</s1u_addr>
24 <enb_addr>10.32.11.100</enb_addr>
25 </packet>
26 <latency_stats>true</latency_stats>
27 <flow_id>10</flow_id> <!-- Mandatory when latency_stats=true -->
28 <delay>50000</delay> <!-- wait 50 ms till start to let queues fill up -->
29 <expected_min_received>1</expected_min_received>
30 <expected_max_dropped>0</expected_max_dropped>
31 <expected_max_latency>1500</expected_max_latency>
32 <expected_99_9_percentile_latency>100</expected_99_9_percentile_latency>
33 </RT_FROM_UE>
34 </flows>
35 <TREX>
36 """
37
38 def __init__(self):
39 self.trex_client = None
40 self.traffic_flows = {}
41 self.port_stats = []
42 self.packets = {} # Per-flow dictionary of packets
43
44 def setup(self, trex_client):
45 self.trex_client = trex_client
46 self.traffic_flows = main.params["TREX"]["flows"]
47 if "port_stats" in main.params["TREX"] and \
48 main.params["TREX"].get("port_stats") is not '':
49 self.port_stats = [int(p) for p in
50 main.params["TREX"].get("port_stats").split(",")]
51 self.trex_client.setupTrex(main.configPath)
52
53 def teardown(self):
54 self.trex_client.stopTrexServer()
55
56 def createFlow(self, flow_name):
57 if flow_name not in self.traffic_flows:
58 main.log.error("CFG flow not present in params")
59 return False
60 self.traffic_flows[flow_name]["packet"] = Trex.__sanitizePacketConfig(
61 self.traffic_flows[flow_name]["packet"])
62 if "gtp_teid" in self.traffic_flows[flow_name]["packet"]:
63 # packets must be GTP encapped
64 self.packets[flow_name] = scapy_helper.simple_gtp_udp_packet(
65 **self.traffic_flows[flow_name]["packet"])
66 else:
67 self.packets[flow_name] = scapy_helper.simple_udp_packet(
68 **self.traffic_flows[flow_name]["packet"])
69
Yi Tsengdda7e322021-09-20 14:21:20 -070070 def resetFlows(self):
71 self.packets = {}
72
Daniele Moro80889562021-09-08 10:09:26 +020073 def sendAndReceiveTraffic(self, duration):
74 """
75 Connect the client, create the flows in trex (with packets created with
76 createFlow, send and receive the traffic, and disconnect the client.
77 :param duration: traffic duration
Daniele Morof811f9f2021-09-21 19:07:52 +020078 :return: port statistics collected while running the test
Daniele Moro80889562021-09-08 10:09:26 +020079 """
80 self.trex_client.connectTrexClient()
81 for flow_name, packet in self.packets.items():
82 flow_config = self.traffic_flows[flow_name]
83 Trex.__sanitizeFlowConfig(flow_config)
84 self.trex_client.addStream(pkt=packet,
85 trex_port=flow_config["trex_port"],
86 l1_bps=flow_config["l1_bps"],
87 percentage=flow_config["percentage"],
88 delay=flow_config["delay"],
89 flow_id=flow_config["flow_id"],
90 flow_stats=flow_config["latency_stats"])
Daniele Morof811f9f2021-09-21 19:07:52 +020091 result = self.trex_client.startAndWaitTraffic(duration=duration,
92 ports=self.port_stats)
Daniele Moro80889562021-09-08 10:09:26 +020093 self.trex_client.disconnectTrexClient()
Daniele Morof811f9f2021-09-21 19:07:52 +020094 return result
95
96 def verifyCongestion(self, live_stats, multiplier=1):
97 """
98 Verify and assert that the test was able to generate congestion by
99 checking that average TX traffic is greater than average RX traffic from
100 stats collected during the test.
101
102 :param live_stats: Stats collected during tests
103 :param multiplier: Multiplier for RX traffic in case we encap/decap traffic
104 :return:
105 """
106 avg_tx = sum(
107 [sum(v["tx_bps"]) / len(v["tx_bps"])
108 for (k, v) in live_stats.items() if k != "duration"]
109 )
110 avg_rx = sum(
111 [sum(v["rx_bps"]) / len(v["rx_bps"])
112 for (k, v) in live_stats.items() if k != "duration"]
113 )
114
115 utilities.assert_equals(
116 expect=True,
117 actual=avg_tx > avg_rx * multiplier,
118 onpass="Congestion created: AVG TX ({}) > AVG RX ({})".format(
119 avg_tx, avg_rx),
120 onfail="NO Congestion: AVG TX ({}) <= AVG RX ({})".format(
121 avg_tx, avg_rx)
122 )
Daniele Moro80889562021-09-08 10:09:26 +0200123
Daniele Morob8404e82022-02-25 00:17:28 +0100124 def assertRxRate(self, flow_name, duration, delta=0.05):
125 if not self.isFlowStats(flow_name):
126 main.log.info("No flow stats for flow {}".format(flow_name))
127 utilities.assert_equals(
128 expect=True,
129 actual=False,
130 onpass="",
131 onfail="No Flow stats for requested flow: {}".format(flow_name))
132 return
133 expected_rx_rate_bps = int(
134 self.traffic_flows[flow_name].get("expected_rx_bps", "0"))
135 flow_label = self.traffic_flows[flow_name].get("name", flow_name)
136 flow_id = self.traffic_flows[flow_name]["flow_id"]
137 flow_stats = self.trex_client.getFlowStats(flow_id)
138 actual_rx_rate_bps = (flow_stats.rx_bytes * 8) / duration
139 rates_within_delta = abs((actual_rx_rate_bps/expected_rx_rate_bps) - 1) <= delta
140 utilities.assert_equals(
141 expect=True,
142 actual=rates_within_delta,
143 onpass="Traffic Flow {}: Expected rate ({}) within delta ({}) to actual rate ({})".format(
144 flow_label, expected_rx_rate_bps, delta, actual_rx_rate_bps),
145 onfail="Traffic Flow {}: Expected rate ({}) outside delta ({}) to actual rate ({})".format(
146 flow_label, expected_rx_rate_bps, delta, actual_rx_rate_bps)
147 )
148
Daniele Moro80889562021-09-08 10:09:26 +0200149 def assertRxPackets(self, flow_name):
150 if not self.isFlowStats(flow_name):
151 main.log.info("No flow stats for flow {}".format(flow_name))
152 expected_min_received = int(
153 self.traffic_flows[flow_name].get("expected_min_received", "1"))
Daniele Morob8404e82022-02-25 00:17:28 +0100154 flow_label = self.traffic_flows[flow_name].get("name", flow_name)
Daniele Moro80889562021-09-08 10:09:26 +0200155 flow_id = self.traffic_flows[flow_name]["flow_id"]
156 flow_stats = self.trex_client.getFlowStats(flow_id)
157 utilities.assert_equals(
158 expect=True,
159 actual=flow_stats.rx_packets >= expected_min_received,
Daniele Morob8404e82022-02-25 00:17:28 +0100160 onpass="Traffic Flow {}: Received traffic".format(flow_label),
161 onfail="Traffic Flow {}: No traffic received".format(flow_label))
Daniele Moro80889562021-09-08 10:09:26 +0200162
163 def assertDroppedPacket(self, flow_name):
164 if not self.isFlowStats(flow_name):
165 main.log.info("No flow stats for flow {}".format(flow_name))
166 expected_max_dropped = int(
167 self.traffic_flows[flow_name].get("expected_max_dropped", "0"))
Daniele Morob8404e82022-02-25 00:17:28 +0100168 flow_label = self.traffic_flows[flow_name].get("name", flow_name)
169 flow_id = self.traffic_flows[flow_name]["flow_id"]
170 flow_stats = self.trex_client.getFlowStats(flow_id)
171 actual_dropped = flow_stats.tx_packets - flow_stats.rx_packets
Daniele Moro80889562021-09-08 10:09:26 +0200172 utilities.assert_equals(
173 expect=True,
Daniele Morob8404e82022-02-25 00:17:28 +0100174 actual=actual_dropped <= expected_max_dropped,
175 onpass="Traffic Flow {}: {} packets dropped, below threshold={}".format(
176 flow_label, actual_dropped, expected_max_dropped
177 ),
178 onfail="Traffic Flow {}: {} packets dropped, above threshold={}".format(
179 flow_label, actual_dropped, expected_max_dropped
180 )
181 )
Daniele Moro80889562021-09-08 10:09:26 +0200182
183 def assertMaxLatency(self, flow_name):
184 if not self.isFlowStats(flow_name):
185 main.log.info("No flow stats for flow {}".format(flow_name))
186 expected_max_latency = int(
187 self.traffic_flows[flow_name].get("expected_max_latency", "0"))
Daniele Morob8404e82022-02-25 00:17:28 +0100188 flow_label = self.traffic_flows[flow_name].get("name", flow_name)
Daniele Moro80889562021-09-08 10:09:26 +0200189 latency_stats = self.__getLatencyStats(flow_name)
190 utilities.assert_equals(
191 expect=True,
192 actual=latency_stats.total_max <= expected_max_latency,
193 onpass="Traffic Flow {}: Maximum latency below threshold".format(
Daniele Morob8404e82022-02-25 00:17:28 +0100194 flow_label),
Daniele Moro80889562021-09-08 10:09:26 +0200195 onfail="Traffic Flow {}: Maximum latency is too high {}".format(
Daniele Morob8404e82022-02-25 00:17:28 +0100196 flow_label, latency_stats.total_max))
Daniele Moro80889562021-09-08 10:09:26 +0200197
198 def assert99_9PercentileLatency(self, flow_name):
199 if not self.isFlowStats(flow_name):
200 main.log.info("No flow stats for flow {}".format(flow_name))
Daniele Morof811f9f2021-09-21 19:07:52 +0200201 return
202 if not "expected_99_9_percentile_latency" in self.traffic_flows[flow_name].keys():
203 main.log.info("No 99.9th percentile parameter for test")
204 return
Daniele Moro80889562021-09-08 10:09:26 +0200205 expected_99_9_percentile_latency = int(
206 self.traffic_flows[flow_name].get(
207 "expected_99_9_percentile_latency", "0"))
Daniele Morob8404e82022-02-25 00:17:28 +0100208 flow_label = self.traffic_flows[flow_name].get("name", flow_name)
Daniele Moro80889562021-09-08 10:09:26 +0200209 latency_stats = self.__getLatencyStats(flow_name)
210 utilities.assert_equals(
211 expect=True,
212 actual=latency_stats.percentile_99_9 <= expected_99_9_percentile_latency,
213 onpass="Traffic Flow {}: 99.9th percentile latency below threshold".format(
Daniele Morob8404e82022-02-25 00:17:28 +0100214 flow_label),
Daniele Moro80889562021-09-08 10:09:26 +0200215 onfail="Traffic Flow {}: 99.9th percentile latency is too high {}".format(
Daniele Morob8404e82022-02-25 00:17:28 +0100216 flow_label, latency_stats.percentile_99_9))
Daniele Moro80889562021-09-08 10:09:26 +0200217
Daniele Morof811f9f2021-09-21 19:07:52 +0200218 def assert90PercentileLatency(self, flow_name):
219 if not self.isFlowStats(flow_name):
220 main.log.info("No flow stats for flow {}".format(flow_name))
221 return
222 if not "expected_90_percentile_latency" in self.traffic_flows[flow_name].keys():
223 main.log.info("No 90th percentile parameter for test")
224 return
225 expected_90_percentile_latency = int(
226 self.traffic_flows[flow_name].get(
227 "expected_90_percentile_latency", "0"))
Daniele Morob8404e82022-02-25 00:17:28 +0100228 flow_label = self.traffic_flows[flow_name].get("name", flow_name)
Daniele Morof811f9f2021-09-21 19:07:52 +0200229 latency_stats = self.__getLatencyStats(flow_name)
230 utilities.assert_equals(
231 expect=True,
232 actual=latency_stats.percentile_90 <= expected_90_percentile_latency,
233 onpass="Traffic Flow {}: 90th percentile latency below threshold".format(
Daniele Morob8404e82022-02-25 00:17:28 +0100234 flow_label),
Daniele Morof811f9f2021-09-21 19:07:52 +0200235 onfail="Traffic Flow {}: 90th percentile latency is too high {}".format(
Daniele Morob8404e82022-02-25 00:17:28 +0100236 flow_label, latency_stats.percentile_90))
Daniele Morof811f9f2021-09-21 19:07:52 +0200237
Daniele Moro80889562021-09-08 10:09:26 +0200238 def logPortStats(self):
239 main.log.debug(self.port_stats)
240 for port in self.port_stats:
241 self.trex_client.logPortStats(port)
242
243 def logFlowStats(self, flow_name):
244 if self.isFlowStats(flow_name):
245 flow_id = self.traffic_flows[flow_name]["flow_id"]
246 self.trex_client.logFlowStats(flow_id)
247 self.trex_client.logLatencyStats(flow_id)
248
249 def isFlowStats(self, flow_name):
250 return self.traffic_flows[flow_name]["latency_stats"]
251
252 def __getLatencyStats(self, flow_name):
253 flow_id = self.traffic_flows[flow_name]["flow_id"]
254 return self.trex_client.getLatencyStats(flow_id)
255
256 @staticmethod
257 def __sanitizePacketConfig(packet):
258 if "gtp_teid" in packet.keys():
259 packet["gtp_teid"] = int(packet["gtp_teid"])
260 if "pktlen" in packet.keys():
261 packet["pktlen"] = int(packet["pktlen"])
Daniele Moro04a62d12021-10-06 17:37:36 +0200262 if "udp_dport" in packet.keys():
263 packet["udp_dport"] = int(packet["udp_dport"])
264 if "udp_sport" in packet.keys():
265 packet["udp_sport"] = int(packet["udp_sport"])
Daniele Moro80889562021-09-08 10:09:26 +0200266 return packet
267
268 @staticmethod
269 def __sanitizeFlowConfig(flow_config):
270 flow_config["trex_port"] = int(flow_config["trex_port"])
271 flow_config["percentage"] = float(
272 flow_config["percentage"]) if "percentage" in flow_config else None
273 flow_config["l1_bps"] = float(
274 flow_config["l1_bps"]) if "l1_bps" in flow_config else None
275 flow_config["delay"] = int(flow_config.get("delay", 0))
276 flow_config["flow_id"] = int(
277 flow_config["flow_id"]) if "flow_id" in flow_config else None
278 flow_config["latency_stats"] = bool(
279 strtobool(flow_config.get("latency_stats", "False")))