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
30 # pylint: disable=wrong-import-position, import-error
31 sys.path.append(settings.getValue('PATHS')['trafficgen']['trex']['src']['path'])
32 from trex_stl_lib.api import *
35 'global': {'bw_per_core': 0.0,
45 'total': {'ibytes': 0.0,
60 class Trex(ITrafficGenerator):
61 """Trex Traffic generator wrapper."""
62 _logger = logging.getLogger(__name__)
65 """Trex class constructor."""
67 self._logger.info("In trex __init__ method")
69 self._trex_host_ip_addr = (
70 settings.getValue('TRAFFICGEN_TREX_HOST_IP_ADDR'))
71 self._trex_base_dir = (
72 settings.getValue('TRAFFICGEN_TREX_BASE_DIR'))
73 self._trex_user = settings.getValue('TRAFFICGEN_TREX_USER')
74 self._stlclient = None
77 '''Connect to Trex traffic generator
79 Verify that Trex is on the system indicated by
80 the configuration file
82 self._stlclient = STLClient()
83 self._logger.info("TREX: In Trex connect method...")
84 if self._trex_host_ip_addr:
85 cmd_ping = "ping -c1 " + self._trex_host_ip_addr
87 raise RuntimeError('TREX: Trex host not defined')
89 ping = subprocess.Popen(cmd_ping, shell=True, stderr=subprocess.PIPE)
90 output, error = ping.communicate()
93 self._logger.error(error)
94 self._logger.error(output)
95 raise RuntimeError('TREX: Cannot ping Trex host at ' + \
96 self._trex_host_ip_addr)
98 connect_trex = "ssh " + self._trex_user + \
99 "@" + self._trex_host_ip_addr
101 cmd_find_trex = connect_trex + " ls " + \
102 self._trex_base_dir + "t-rex-64"
105 find_trex = subprocess.Popen(cmd_find_trex,
107 stderr=subprocess.PIPE)
108 output, error = find_trex.communicate()
110 if find_trex.returncode:
111 self._logger.error(error)
112 self._logger.error(output)
114 'TREX: Cannot locate Trex program at %s within %s' \
115 % (self._trex_host_ip_addr, self._trex_base_dir))
117 self._stlclient = STLClient(username=self._trex_user, server=self._trex_host_ip_addr,
119 self._stlclient.connect()
120 self._logger.info("TREX: Trex host successfully found...")
122 def disconnect(self):
123 """Disconnect from the traffic generator.
125 As with :func:`connect`, this function is optional.
127 Where implemented, this function should raise an exception on
132 self._logger.info("TREX: In trex disconnect method")
133 self._stlclient.disconnect(stop_traffic=True, release_ports=True)
136 def create_packets(traffic, ports_info):
137 """Create base packet according to traffic specification.
138 If traffic haven't specified srcmac and dstmac fields
139 packet will be create with mac address of trex server.
141 mac_add = [li['hw_mac'] for li in ports_info]
143 if traffic and traffic['l2']['framesize'] > 0:
144 if traffic['l2']['dstmac'] == '00:00:00:00:00:00' and \
145 traffic['l2']['srcmac'] == '00:00:00:00:00:00':
146 base_pkt_a = Ether(src=mac_add[0], dst=mac_add[1])/ \
147 IP(proto=traffic['l3']['proto'], src=traffic['l3']['srcip'],
148 dst=traffic['l3']['dstip'])/ \
149 UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport'])
150 base_pkt_b = Ether(src=mac_add[1], dst=mac_add[0])/ \
151 IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'],
152 dst=traffic['l3']['srcip'])/ \
153 UDP(dport=traffic['l4']['srcport'], sport=traffic['l4']['dstport'])
155 base_pkt_a = Ether(src=traffic['l2']['srcmac'], dst=traffic['l2']['dstmac'])/ \
156 IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'],
157 dst=traffic['l3']['srcip'])/ \
158 UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport'])
160 base_pkt_b = Ether(src=traffic['l2']['dstmac'], dst=traffic['l2']['srcmac'])/ \
161 IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'],
162 dst=traffic['l3']['srcip'])/ \
163 UDP(dport=traffic['l4']['srcport'], sport=traffic['l4']['dstport'])
165 return (base_pkt_a, base_pkt_b)
168 def create_streams(base_pkt_a, base_pkt_b, traffic):
169 """Add the base packet to the streams. Erase FCS and add payload
170 according to traffic specification
174 frame_size = int(traffic['l2']['framesize'])
175 fsize_no_fcs = frame_size - 4
176 payload_a = max(0, fsize_no_fcs - len(base_pkt_a)) * 'x'
177 payload_b = max(0, fsize_no_fcs - len(base_pkt_b)) * 'x'
179 # Multistream configuration, increments source values only
180 ms_mod = list() # mod list for incrementing values to be populated based on layer
181 if traffic['multistream'] > 1:
182 if traffic['stream_type'].upper() == 'L2':
183 for _ in [base_pkt_a, base_pkt_b]:
184 ms_mod += [STLVmFlowVar(name="mac_start", min_value=0,
185 max_value=traffic['multistream'] - 1, size=4, op="inc"),
186 STLVmWrFlowVar(fv_name="mac_start", pkt_offset=7)]
187 elif traffic['stream_type'].upper() == 'L3':
188 ip_src = {"start": int(netaddr.IPAddress(traffic['l3']['srcip'])),
189 "end": int(netaddr.IPAddress(traffic['l3']['srcip'])) + traffic['multistream'] - 1}
190 ip_dst = {"start": int(netaddr.IPAddress(traffic['l3']['dstip'])),
191 "end": int(netaddr.IPAddress(traffic['l3']['dstip'])) + traffic['multistream'] - 1}
192 for ip_address in [ip_src, ip_dst]:
193 ms_mod += [STLVmFlowVar(name="ip_src", min_value=ip_address['start'],
194 max_value=ip_address['end'], size=4, op="inc"),
195 STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP.src")]
196 elif traffic['stream_type'].upper() == 'L4':
197 for udpport in [traffic['l4']['srcport'], traffic['l4']['dstport']]:
198 if udpport + (traffic['multistream'] - 1) > 65535:
200 # find the max/min port number based on the loop around of 65535 to 0 if needed
201 minimum_value = 65535 - (traffic['multistream'] -1)
202 maximum_value = 65535
204 start_port, minimum_value = udpport, udpport
205 maximum_value = start_port + (traffic['multistream'] - 1)
206 ms_mod += [STLVmFlowVar(name="port_src", init_value=start_port,
207 min_value=minimum_value, max_value=maximum_value,
209 STLVmWrFlowVar(fv_name="port_src", pkt_offset="UDP.sport"),]
211 if ms_mod: # multistream detected
212 pkt_a = STLPktBuilder(pkt=base_pkt_a/payload_a, vm=[ms_mod[0], ms_mod[1]])
213 pkt_b = STLPktBuilder(pkt=base_pkt_b/payload_b, vm=[ms_mod[2], ms_mod[3]])
215 pkt_a = STLPktBuilder(pkt=base_pkt_a / payload_a)
216 pkt_b = STLPktBuilder(pkt=base_pkt_b / payload_b)
218 stream_1 = STLStream(packet=pkt_a,
220 mode=STLTXCont(percentage=traffic['frame_rate']))
221 stream_2 = STLStream(packet=pkt_b,
223 mode=STLTXCont(percentage=traffic['frame_rate']))
224 lat_pps = settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS')
226 stream_1_lat = STLStream(packet=pkt_a,
227 flow_stats=STLFlowLatencyStats(pg_id=0),
229 mode=STLTXCont(pps=lat_pps))
230 stream_2_lat = STLStream(packet=pkt_b,
231 flow_stats=STLFlowLatencyStats(pg_id=1),
233 mode=STLTXCont(pps=lat_pps))
235 return (stream_1, stream_2, stream_1_lat, stream_2_lat)
237 def generate_traffic(self, traffic, duration):
238 """The method that generate a stream
241 self._stlclient.reset(my_ports)
242 ports_info = self._stlclient.get_port_info(my_ports)
244 if settings.getValue('TRAFFICGEN_TREX_PROMISCUOUS'):
245 self._stlclient.set_port_attr(my_ports, promiscuous=True)
247 packet_1, packet_2 = Trex.create_packets(traffic, ports_info)
248 stream_1, stream_2, stream_1_lat, stream_2_lat = Trex.create_streams(packet_1, packet_2, traffic)
249 self._stlclient.add_streams(stream_1, ports=[0])
250 self._stlclient.add_streams(stream_2, ports=[1])
252 if stream_1_lat is not None:
253 self._stlclient.add_streams(stream_1_lat, ports=[0])
254 self._stlclient.add_streams(stream_2_lat, ports=[1])
256 self._stlclient.clear_stats()
257 self._stlclient.start(ports=[0, 1], force=True, duration=duration)
258 self._stlclient.wait_on_traffic(ports=[0, 1])
259 stats = self._stlclient.get_stats(sync_now=True)
263 def calculate_results(stats):
264 """Calculate results from Trex statistic
266 result = OrderedDict()
267 result[ResultsConstants.TX_RATE_FPS] = (
269 float(stats["total"]["tx_pps"])))
271 result[ResultsConstants.THROUGHPUT_RX_FPS] = (
273 float(stats["total"]["rx_pps"])))
275 result[ResultsConstants.TX_RATE_MBPS] = (
277 float(stats["total"]["tx_bps"] / 1000000)))
278 result[ResultsConstants.THROUGHPUT_RX_MBPS] = (
280 float(stats["total"]["rx_bps"] / 1000000)))
282 result[ResultsConstants.TX_RATE_PERCENT] = 'Unknown'
284 result[ResultsConstants.THROUGHPUT_RX_PERCENT] = 'Unknown'
285 if stats["total"]["opackets"]:
286 result[ResultsConstants.FRAME_LOSS_PERCENT] = (
288 float((stats["total"]["opackets"] - stats["total"]["ipackets"]) * 100 /
289 stats["total"]["opackets"])))
291 result[ResultsConstants.FRAME_LOSS_PERCENT] = 100
293 if settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS') > 0 and stats['latency']:
294 result[ResultsConstants.MIN_LATENCY_NS] = (
296 (float(min(stats["latency"][0]["latency"]["total_min"],
297 stats["latency"][1]["latency"]["total_min"])))))
299 result[ResultsConstants.MAX_LATENCY_NS] = (
301 (float(max(stats["latency"][0]["latency"]["total_max"],
302 stats["latency"][1]["latency"]["total_max"])))))
304 result[ResultsConstants.AVG_LATENCY_NS] = (
306 float((stats["latency"][0]["latency"]["average"]+
307 stats["latency"][1]["latency"]["average"])/2)))
310 result[ResultsConstants.MIN_LATENCY_NS] = 'Unknown'
311 result[ResultsConstants.MAX_LATENCY_NS] = 'Unknown'
312 result[ResultsConstants.AVG_LATENCY_NS] = 'Unknown'
315 def send_cont_traffic(self, traffic=None, duration=30):
316 """See ITrafficGenerator for description
318 self._logger.info("In Trex send_cont_traffic method")
321 self._params['traffic'] = self.traffic_defaults.copy()
323 self._params['traffic'] = merge_spec(
324 self._params['traffic'], traffic)
326 stats = self.generate_traffic(traffic, duration)
328 return self.calculate_results(stats)
330 def start_cont_traffic(self, traffic=None, duration=30):
331 raise NotImplementedError(
332 'Trex start cont traffic not implemented')
334 def stop_cont_traffic(self):
335 """See ITrafficGenerator for description
337 raise NotImplementedError(
338 'Trex stop_cont_traffic method not implemented')
340 def send_rfc2544_throughput(self, traffic=None, duration=60,
341 lossrate=0.0, tests=10):
342 """See ITrafficGenerator for description
344 self._logger.info("In Trex send_rfc2544_throughput method")
346 threshold = settings.getValue('TRAFFICGEN_TREX_RFC2544_TPUT_THRESHOLD')
350 stats_ok = _EMPTY_STATS
351 self._params['traffic'] = self.traffic_defaults.copy()
353 self._params['traffic'] = merge_spec(
354 self._params['traffic'], traffic)
355 new_params = copy.deepcopy(traffic)
356 stats = self.generate_traffic(traffic, duration)
357 right = traffic['frame_rate']
358 center = traffic['frame_rate']
360 # Loops until the preconfigured difference between frame rate
361 # of successful and unsuccessful iterations is reached
362 while (right - left) > threshold:
363 test_lossrate = ((stats["total"]["opackets"] - stats["total"]
364 ["ipackets"]) * 100) / stats["total"]["opackets"]
365 self._logger.debug("Iteration: %s, frame rate: %s, throughput_rx_fps: %s, frame_loss_percent: %s",
366 iteration, "{:.3f}".format(new_params['frame_rate']), stats['total']['rx_pps'],
367 "{:.3f}".format(test_lossrate))
368 if test_lossrate == 0.0 and new_params['frame_rate'] == traffic['frame_rate']:
369 stats_ok = copy.deepcopy(stats)
371 elif test_lossrate > lossrate:
373 center = (left+right) / 2
374 new_params = copy.deepcopy(traffic)
375 new_params['frame_rate'] = center
376 stats = self.generate_traffic(new_params, duration)
378 stats_ok = copy.deepcopy(stats)
380 center = (left+right) / 2
381 new_params = copy.deepcopy(traffic)
382 new_params['frame_rate'] = center
383 stats = self.generate_traffic(new_params, duration)
385 return self.calculate_results(stats_ok)
387 def start_rfc2544_throughput(self, traffic=None, tests=1, duration=60,
389 raise NotImplementedError(
390 'Trex start rfc2544 throughput not implemented')
392 def wait_rfc2544_throughput(self):
393 raise NotImplementedError(
394 'Trex wait rfc2544 throughput not implemented')
396 def send_burst_traffic(self, traffic=None, numpkts=100, duration=5):
397 raise NotImplementedError(
398 'Trex send burst traffic not implemented')
400 def send_rfc2544_back2back(self, traffic=None, tests=1, duration=30,
402 raise NotImplementedError(
403 'Trex send rfc2544 back2back not implemented')
405 def start_rfc2544_back2back(self, traffic=None, tests=1, duration=30,
407 raise NotImplementedError(
408 'Trex start rfc2544 back2back not implemented')
410 def wait_rfc2544_back2back(self):
411 raise NotImplementedError(
412 'Trex wait rfc2544 back2back not implemented')
414 if __name__ == "__main__":