1 # Copyright 2017 Martin Goldammer, OPNFV, Red Hat Inc.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
16 Trex Traffic Generator Model
18 # pylint: disable=undefined-variable
22 from collections import OrderedDict
23 # pylint: disable=unused-import
26 from conf import settings
27 from conf import merge_spec
28 from core.results.results_constants import ResultsConstants
29 from tools.pkt_gen.trafficgen.trafficgen import ITrafficGenerator
31 # pylint: disable=wrong-import-position, import-error
32 sys.path.append(settings.getValue('PATHS')['trafficgen']['Trex']['src']['path'])
33 from trex_stl_lib.api import *
35 # VSPERF performs detection of T-Rex api during testcase initialization. So if
36 # T-Rex is requsted and API is not available it will fail before this code
38 # This code can be reached in case that --list-trafficgens is called, but T-Rex
39 # api is not installed. In this case we can ignore an exception, becuase T-Rex
40 # import won't be used.
44 'global': {'bw_per_core': 0.0,
54 'total': {'ibytes': 0.0,
69 class Trex(ITrafficGenerator):
70 """Trex Traffic generator wrapper."""
71 _logger = logging.getLogger(__name__)
74 """Trex class constructor."""
76 self._logger.info("In trex __init__ method")
78 self._trex_host_ip_addr = (
79 settings.getValue('TRAFFICGEN_TREX_HOST_IP_ADDR'))
80 self._trex_base_dir = (
81 settings.getValue('TRAFFICGEN_TREX_BASE_DIR'))
82 self._trex_user = settings.getValue('TRAFFICGEN_TREX_USER')
83 self._stlclient = None
86 '''Connect to Trex traffic generator
88 Verify that Trex is on the system indicated by
89 the configuration file
91 self._stlclient = STLClient()
92 self._logger.info("TREX: In Trex connect method...")
93 if self._trex_host_ip_addr:
94 cmd_ping = "ping -c1 " + self._trex_host_ip_addr
96 raise RuntimeError('TREX: Trex host not defined')
98 ping = subprocess.Popen(cmd_ping, shell=True, stderr=subprocess.PIPE)
99 output, error = ping.communicate()
102 self._logger.error(error)
103 self._logger.error(output)
104 raise RuntimeError('TREX: Cannot ping Trex host at ' + \
105 self._trex_host_ip_addr)
107 connect_trex = "ssh " + self._trex_user + \
108 "@" + self._trex_host_ip_addr
110 cmd_find_trex = connect_trex + " ls " + \
111 self._trex_base_dir + "t-rex-64"
114 find_trex = subprocess.Popen(cmd_find_trex,
116 stderr=subprocess.PIPE)
117 output, error = find_trex.communicate()
119 if find_trex.returncode:
120 self._logger.error(error)
121 self._logger.error(output)
123 'TREX: Cannot locate Trex program at %s within %s' \
124 % (self._trex_host_ip_addr, self._trex_base_dir))
126 self._stlclient = STLClient(username=self._trex_user, server=self._trex_host_ip_addr,
128 self._stlclient.connect()
129 self._logger.info("TREX: Trex host successfully found...")
131 def disconnect(self):
132 """Disconnect from the traffic generator.
134 As with :func:`connect`, this function is optional.
136 Where implemented, this function should raise an exception on
141 self._logger.info("TREX: In trex disconnect method")
142 self._stlclient.disconnect(stop_traffic=True, release_ports=True)
145 def create_packets(traffic, ports_info):
146 """Create base packet according to traffic specification.
147 If traffic haven't specified srcmac and dstmac fields
148 packet will be create with mac address of trex server.
150 mac_add = [li['hw_mac'] for li in ports_info]
152 if traffic and traffic['l2']['framesize'] > 0:
153 if traffic['l2']['dstmac'] == '00:00:00:00:00:00' and \
154 traffic['l2']['srcmac'] == '00:00:00:00:00:00':
155 base_pkt_a = Ether(src=mac_add[0], dst=mac_add[1])/ \
156 IP(proto=traffic['l3']['proto'], src=traffic['l3']['srcip'],
157 dst=traffic['l3']['dstip'])/ \
158 UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport'])
159 base_pkt_b = Ether(src=mac_add[1], dst=mac_add[0])/ \
160 IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'],
161 dst=traffic['l3']['srcip'])/ \
162 UDP(dport=traffic['l4']['srcport'], sport=traffic['l4']['dstport'])
164 base_pkt_a = Ether(src=traffic['l2']['srcmac'], dst=traffic['l2']['dstmac'])/ \
165 IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'],
166 dst=traffic['l3']['srcip'])/ \
167 UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport'])
169 base_pkt_b = Ether(src=traffic['l2']['dstmac'], dst=traffic['l2']['srcmac'])/ \
170 IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'],
171 dst=traffic['l3']['srcip'])/ \
172 UDP(dport=traffic['l4']['srcport'], sport=traffic['l4']['dstport'])
174 return (base_pkt_a, base_pkt_b)
177 def create_streams(base_pkt_a, base_pkt_b, traffic):
178 """Add the base packet to the streams. Erase FCS and add payload
179 according to traffic specification
183 frame_size = int(traffic['l2']['framesize'])
184 fsize_no_fcs = frame_size - 4
185 payload_a = max(0, fsize_no_fcs - len(base_pkt_a)) * 'x'
186 payload_b = max(0, fsize_no_fcs - len(base_pkt_b)) * 'x'
188 # Multistream configuration, increments source values only
189 ms_mod = list() # mod list for incrementing values to be populated based on layer
190 if traffic['multistream'] > 1:
191 if traffic['stream_type'].upper() == 'L2':
192 for _ in [base_pkt_a, base_pkt_b]:
193 ms_mod += [STLVmFlowVar(name="mac_start", min_value=0,
194 max_value=traffic['multistream'] - 1, size=4, op="inc"),
195 STLVmWrFlowVar(fv_name="mac_start", pkt_offset=7)]
196 elif traffic['stream_type'].upper() == 'L3':
197 ip_src = {"start": int(netaddr.IPAddress(traffic['l3']['srcip'])),
198 "end": int(netaddr.IPAddress(traffic['l3']['srcip'])) + traffic['multistream'] - 1}
199 ip_dst = {"start": int(netaddr.IPAddress(traffic['l3']['dstip'])),
200 "end": int(netaddr.IPAddress(traffic['l3']['dstip'])) + traffic['multistream'] - 1}
201 for ip_address in [ip_src, ip_dst]:
202 ms_mod += [STLVmFlowVar(name="ip_src", min_value=ip_address['start'],
203 max_value=ip_address['end'], size=4, op="inc"),
204 STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP.src")]
205 elif traffic['stream_type'].upper() == 'L4':
206 for udpport in [traffic['l4']['srcport'], traffic['l4']['dstport']]:
207 if udpport + (traffic['multistream'] - 1) > 65535:
209 # find the max/min port number based on the loop around of 65535 to 0 if needed
210 minimum_value = 65535 - (traffic['multistream'] -1)
211 maximum_value = 65535
213 start_port, minimum_value = udpport, udpport
214 maximum_value = start_port + (traffic['multistream'] - 1)
215 ms_mod += [STLVmFlowVar(name="port_src", init_value=start_port,
216 min_value=minimum_value, max_value=maximum_value,
218 STLVmWrFlowVar(fv_name="port_src", pkt_offset="UDP.sport"),]
220 if ms_mod: # multistream detected
221 pkt_a = STLPktBuilder(pkt=base_pkt_a/payload_a, vm=[ms_mod[0], ms_mod[1]])
222 pkt_b = STLPktBuilder(pkt=base_pkt_b/payload_b, vm=[ms_mod[2], ms_mod[3]])
224 pkt_a = STLPktBuilder(pkt=base_pkt_a / payload_a)
225 pkt_b = STLPktBuilder(pkt=base_pkt_b / payload_b)
227 stream_1 = STLStream(packet=pkt_a,
229 mode=STLTXCont(percentage=traffic['frame_rate']))
230 stream_2 = STLStream(packet=pkt_b,
232 mode=STLTXCont(percentage=traffic['frame_rate']))
233 lat_pps = settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS')
235 stream_1_lat = STLStream(packet=pkt_a,
236 flow_stats=STLFlowLatencyStats(pg_id=0),
238 mode=STLTXCont(pps=lat_pps))
239 stream_2_lat = STLStream(packet=pkt_b,
240 flow_stats=STLFlowLatencyStats(pg_id=1),
242 mode=STLTXCont(pps=lat_pps))
244 return (stream_1, stream_2, stream_1_lat, stream_2_lat)
246 def generate_traffic(self, traffic, duration):
247 """The method that generate a stream
250 self._stlclient.reset(my_ports)
251 ports_info = self._stlclient.get_port_info(my_ports)
253 if settings.getValue('TRAFFICGEN_TREX_PROMISCUOUS'):
254 self._stlclient.set_port_attr(my_ports, promiscuous=True)
256 packet_1, packet_2 = Trex.create_packets(traffic, ports_info)
257 stream_1, stream_2, stream_1_lat, stream_2_lat = Trex.create_streams(packet_1, packet_2, traffic)
258 self._stlclient.add_streams(stream_1, ports=[0])
259 self._stlclient.add_streams(stream_2, ports=[1])
261 if stream_1_lat is not None:
262 self._stlclient.add_streams(stream_1_lat, ports=[0])
263 self._stlclient.add_streams(stream_2_lat, ports=[1])
265 self._stlclient.clear_stats()
266 self._stlclient.start(ports=[0, 1], force=True, duration=duration)
267 self._stlclient.wait_on_traffic(ports=[0, 1])
268 stats = self._stlclient.get_stats(sync_now=True)
272 def calculate_results(stats):
273 """Calculate results from Trex statistic
275 result = OrderedDict()
276 result[ResultsConstants.TX_RATE_FPS] = (
278 float(stats["total"]["tx_pps"])))
280 result[ResultsConstants.THROUGHPUT_RX_FPS] = (
282 float(stats["total"]["rx_pps"])))
284 result[ResultsConstants.TX_RATE_MBPS] = (
286 float(stats["total"]["tx_bps"] / 1000000)))
287 result[ResultsConstants.THROUGHPUT_RX_MBPS] = (
289 float(stats["total"]["rx_bps"] / 1000000)))
291 result[ResultsConstants.TX_RATE_PERCENT] = 'Unknown'
293 result[ResultsConstants.THROUGHPUT_RX_PERCENT] = 'Unknown'
294 if stats["total"]["opackets"]:
295 result[ResultsConstants.FRAME_LOSS_PERCENT] = (
297 float((stats["total"]["opackets"] - stats["total"]["ipackets"]) * 100 /
298 stats["total"]["opackets"])))
300 result[ResultsConstants.FRAME_LOSS_PERCENT] = 100
302 if settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS') > 0 and stats['latency']:
303 result[ResultsConstants.MIN_LATENCY_NS] = (
305 (float(min(stats["latency"][0]["latency"]["total_min"],
306 stats["latency"][1]["latency"]["total_min"])))))
308 result[ResultsConstants.MAX_LATENCY_NS] = (
310 (float(max(stats["latency"][0]["latency"]["total_max"],
311 stats["latency"][1]["latency"]["total_max"])))))
313 result[ResultsConstants.AVG_LATENCY_NS] = (
315 float((stats["latency"][0]["latency"]["average"]+
316 stats["latency"][1]["latency"]["average"])/2)))
319 result[ResultsConstants.MIN_LATENCY_NS] = 'Unknown'
320 result[ResultsConstants.MAX_LATENCY_NS] = 'Unknown'
321 result[ResultsConstants.AVG_LATENCY_NS] = 'Unknown'
324 def send_cont_traffic(self, traffic=None, duration=30):
325 """See ITrafficGenerator for description
327 self._logger.info("In Trex send_cont_traffic method")
330 self._params['traffic'] = self.traffic_defaults.copy()
332 self._params['traffic'] = merge_spec(
333 self._params['traffic'], traffic)
335 stats = self.generate_traffic(traffic, duration)
337 return self.calculate_results(stats)
339 def start_cont_traffic(self, traffic=None, duration=30):
340 raise NotImplementedError(
341 'Trex start cont traffic not implemented')
343 def stop_cont_traffic(self):
344 """See ITrafficGenerator for description
346 raise NotImplementedError(
347 'Trex stop_cont_traffic method not implemented')
349 def send_rfc2544_throughput(self, traffic=None, tests=1, duration=60,
351 """See ITrafficGenerator for description
353 self._logger.info("In Trex send_rfc2544_throughput method")
355 threshold = settings.getValue('TRAFFICGEN_TREX_RFC2544_TPUT_THRESHOLD')
359 stats_ok = _EMPTY_STATS
360 self._params['traffic'] = self.traffic_defaults.copy()
362 self._params['traffic'] = merge_spec(
363 self._params['traffic'], traffic)
364 new_params = copy.deepcopy(traffic)
365 stats = self.generate_traffic(traffic, duration)
366 right = traffic['frame_rate']
367 center = traffic['frame_rate']
369 # Loops until the preconfigured difference between frame rate
370 # of successful and unsuccessful iterations is reached
371 while (right - left) > threshold:
372 test_lossrate = ((stats["total"]["opackets"] - stats["total"]
373 ["ipackets"]) * 100) / stats["total"]["opackets"]
374 self._logger.debug("Iteration: %s, frame rate: %s, throughput_rx_fps: %s, frame_loss_percent: %s",
375 iteration, "{:.3f}".format(new_params['frame_rate']), stats['total']['rx_pps'],
376 "{:.3f}".format(test_lossrate))
377 if test_lossrate == 0.0 and new_params['frame_rate'] == traffic['frame_rate']:
378 stats_ok = copy.deepcopy(stats)
380 elif test_lossrate > lossrate:
382 center = (left+right) / 2
383 new_params = copy.deepcopy(traffic)
384 new_params['frame_rate'] = center
385 stats = self.generate_traffic(new_params, duration)
387 stats_ok = copy.deepcopy(stats)
389 center = (left+right) / 2
390 new_params = copy.deepcopy(traffic)
391 new_params['frame_rate'] = center
392 stats = self.generate_traffic(new_params, duration)
394 return self.calculate_results(stats_ok)
396 def start_rfc2544_throughput(self, traffic=None, tests=1, duration=60,
398 raise NotImplementedError(
399 'Trex start rfc2544 throughput not implemented')
401 def wait_rfc2544_throughput(self):
402 raise NotImplementedError(
403 'Trex wait rfc2544 throughput not implemented')
405 def send_burst_traffic(self, traffic=None, numpkts=100, duration=5):
406 raise NotImplementedError(
407 'Trex send burst traffic not implemented')
409 def send_rfc2544_back2back(self, traffic=None, tests=1, duration=30,
411 raise NotImplementedError(
412 'Trex send rfc2544 back2back not implemented')
414 def start_rfc2544_back2back(self, traffic=None, tests=1, duration=30,
416 raise NotImplementedError(
417 'Trex start rfc2544 back2back not implemented')
419 def wait_rfc2544_back2back(self):
420 raise NotImplementedError(
421 'Trex wait rfc2544 back2back not implemented')
423 if __name__ == "__main__":