Daniele Moro | 8088956 | 2021-09-08 10:09:26 +0200 | [diff] [blame] | 1 | from distutils.util import strtobool |
| 2 | from tests.USECASE.SegmentRouting.dependencies import scapy_helper |
| 3 | |
| 4 | |
| 5 | class 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 Tseng | dda7e32 | 2021-09-20 14:21:20 -0700 | [diff] [blame] | 70 | def resetFlows(self): |
| 71 | self.packets = {} |
| 72 | |
Daniele Moro | 8088956 | 2021-09-08 10:09:26 +0200 | [diff] [blame] | 73 | 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 |
| 78 | :return: |
| 79 | """ |
| 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"]) |
| 91 | self.trex_client.startAndWaitTraffic(duration=duration) |
| 92 | self.trex_client.disconnectTrexClient() |
| 93 | |
| 94 | def assertRxPackets(self, flow_name): |
| 95 | if not self.isFlowStats(flow_name): |
| 96 | main.log.info("No flow stats for flow {}".format(flow_name)) |
| 97 | expected_min_received = int( |
| 98 | self.traffic_flows[flow_name].get("expected_min_received", "1")) |
| 99 | flow_id = self.traffic_flows[flow_name]["flow_id"] |
| 100 | flow_stats = self.trex_client.getFlowStats(flow_id) |
| 101 | utilities.assert_equals( |
| 102 | expect=True, |
| 103 | actual=flow_stats.rx_packets >= expected_min_received, |
| 104 | onpass="Traffic Flow {}: Received traffic".format(flow_name), |
| 105 | onfail="Traffic Flow {}: No traffic received".format(flow_name)) |
| 106 | |
| 107 | def assertDroppedPacket(self, flow_name): |
| 108 | if not self.isFlowStats(flow_name): |
| 109 | main.log.info("No flow stats for flow {}".format(flow_name)) |
| 110 | expected_max_dropped = int( |
| 111 | self.traffic_flows[flow_name].get("expected_max_dropped", "0")) |
| 112 | latency_stats = self.__getLatencyStats(flow_name) |
| 113 | utilities.assert_equals( |
| 114 | expect=True, |
| 115 | actual=latency_stats.dropped <= expected_max_dropped, |
| 116 | onpass="Traffic Flow {}: {} packets dropped, below threshold ({})".format( |
| 117 | flow_name, latency_stats.dropped, |
| 118 | expected_max_dropped), |
| 119 | onfail="Traffic Flow {}: {} packets dropped, above threshold ({})".format( |
| 120 | flow_name, latency_stats.dropped, |
| 121 | expected_max_dropped)) |
| 122 | |
| 123 | def assertMaxLatency(self, flow_name): |
| 124 | if not self.isFlowStats(flow_name): |
| 125 | main.log.info("No flow stats for flow {}".format(flow_name)) |
| 126 | expected_max_latency = int( |
| 127 | self.traffic_flows[flow_name].get("expected_max_latency", "0")) |
| 128 | latency_stats = self.__getLatencyStats(flow_name) |
| 129 | utilities.assert_equals( |
| 130 | expect=True, |
| 131 | actual=latency_stats.total_max <= expected_max_latency, |
| 132 | onpass="Traffic Flow {}: Maximum latency below threshold".format( |
| 133 | flow_name), |
| 134 | onfail="Traffic Flow {}: Maximum latency is too high {}".format( |
| 135 | flow_name, latency_stats.total_max)) |
| 136 | |
| 137 | def assert99_9PercentileLatency(self, flow_name): |
| 138 | if not self.isFlowStats(flow_name): |
| 139 | main.log.info("No flow stats for flow {}".format(flow_name)) |
| 140 | expected_99_9_percentile_latency = int( |
| 141 | self.traffic_flows[flow_name].get( |
| 142 | "expected_99_9_percentile_latency", "0")) |
| 143 | latency_stats = self.__getLatencyStats(flow_name) |
| 144 | utilities.assert_equals( |
| 145 | expect=True, |
| 146 | actual=latency_stats.percentile_99_9 <= expected_99_9_percentile_latency, |
| 147 | onpass="Traffic Flow {}: 99.9th percentile latency below threshold".format( |
| 148 | flow_name), |
| 149 | onfail="Traffic Flow {}: 99.9th percentile latency is too high {}".format( |
| 150 | flow_name, latency_stats.percentile_99_9)) |
| 151 | |
| 152 | def logPortStats(self): |
| 153 | main.log.debug(self.port_stats) |
| 154 | for port in self.port_stats: |
| 155 | self.trex_client.logPortStats(port) |
| 156 | |
| 157 | def logFlowStats(self, flow_name): |
| 158 | if self.isFlowStats(flow_name): |
| 159 | flow_id = self.traffic_flows[flow_name]["flow_id"] |
| 160 | self.trex_client.logFlowStats(flow_id) |
| 161 | self.trex_client.logLatencyStats(flow_id) |
| 162 | |
| 163 | def isFlowStats(self, flow_name): |
| 164 | return self.traffic_flows[flow_name]["latency_stats"] |
| 165 | |
| 166 | def __getLatencyStats(self, flow_name): |
| 167 | flow_id = self.traffic_flows[flow_name]["flow_id"] |
| 168 | return self.trex_client.getLatencyStats(flow_id) |
| 169 | |
| 170 | @staticmethod |
| 171 | def __sanitizePacketConfig(packet): |
| 172 | if "gtp_teid" in packet.keys(): |
| 173 | packet["gtp_teid"] = int(packet["gtp_teid"]) |
| 174 | if "pktlen" in packet.keys(): |
| 175 | packet["pktlen"] = int(packet["pktlen"]) |
Daniele Moro | 04a62d1 | 2021-10-06 17:37:36 +0200 | [diff] [blame^] | 176 | if "udp_dport" in packet.keys(): |
| 177 | packet["udp_dport"] = int(packet["udp_dport"]) |
| 178 | if "udp_sport" in packet.keys(): |
| 179 | packet["udp_sport"] = int(packet["udp_sport"]) |
Daniele Moro | 8088956 | 2021-09-08 10:09:26 +0200 | [diff] [blame] | 180 | return packet |
| 181 | |
| 182 | @staticmethod |
| 183 | def __sanitizeFlowConfig(flow_config): |
| 184 | flow_config["trex_port"] = int(flow_config["trex_port"]) |
| 185 | flow_config["percentage"] = float( |
| 186 | flow_config["percentage"]) if "percentage" in flow_config else None |
| 187 | flow_config["l1_bps"] = float( |
| 188 | flow_config["l1_bps"]) if "l1_bps" in flow_config else None |
| 189 | flow_config["delay"] = int(flow_config.get("delay", 0)) |
| 190 | flow_config["flow_id"] = int( |
| 191 | flow_config["flow_id"]) if "flow_id" in flow_config else None |
| 192 | flow_config["latency_stats"] = bool( |
| 193 | strtobool(flow_config.get("latency_stats", "False"))) |