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
25 from conf import settings
26 from conf import merge_spec
27 from core.results.results_constants import ResultsConstants
28 from tools.pkt_gen.trafficgen.trafficgen import ITrafficGenerator
29 # pylint: disable=wrong-import-position, import-error
30 sys.path.append(settings.getValue('PATHS')['trafficgen']['trex']['src']['path'])
31 from trex_stl_lib.api import *
34 'global': {'bw_per_core': 0.0,
44 'total': {'ibytes': 0.0,
59 class Trex(ITrafficGenerator):
60 """Trex Traffic generator wrapper."""
61 _logger = logging.getLogger(__name__)
64 """Trex class constructor."""
66 self._logger.info("In trex __init__ method")
68 self._trex_host_ip_addr = (
69 settings.getValue('TRAFFICGEN_TREX_HOST_IP_ADDR'))
70 self._trex_base_dir = (
71 settings.getValue('TRAFFICGEN_TREX_BASE_DIR'))
72 self._trex_user = settings.getValue('TRAFFICGEN_TREX_USER')
73 self._stlclient = None
76 '''Connect to Trex traffic generator
78 Verify that Trex is on the system indicated by
79 the configuration file
81 self._stlclient = STLClient()
82 self._logger.info("TREX: In Trex connect method...")
83 if self._trex_host_ip_addr:
84 cmd_ping = "ping -c1 " + self._trex_host_ip_addr
86 raise RuntimeError('TREX: Trex host not defined')
88 ping = subprocess.Popen(cmd_ping, shell=True, stderr=subprocess.PIPE)
89 output, error = ping.communicate()
92 self._logger.error(error)
93 self._logger.error(output)
94 raise RuntimeError('TREX: Cannot ping Trex host at ' + \
95 self._trex_host_ip_addr)
97 connect_trex = "ssh " + self._trex_user + \
98 "@" + self._trex_host_ip_addr
100 cmd_find_trex = connect_trex + " ls " + \
101 self._trex_base_dir + "t-rex-64"
104 find_trex = subprocess.Popen(cmd_find_trex,
106 stderr=subprocess.PIPE)
107 output, error = find_trex.communicate()
109 if find_trex.returncode:
110 self._logger.error(error)
111 self._logger.error(output)
113 'TREX: Cannot locate Trex program at %s within %s' \
114 % (self._trex_host_ip_addr, self._trex_base_dir))
116 self._stlclient = STLClient(username=self._trex_user, server=self._trex_host_ip_addr,
118 self._stlclient.connect()
119 self._logger.info("TREX: Trex host successfully found...")
121 def disconnect(self):
122 """Disconnect from the traffic generator.
124 As with :func:`connect`, this function is optional.
126 Where implemented, this function should raise an exception on
131 self._logger.info("TREX: In trex disconnect method")
132 self._stlclient.disconnect(stop_traffic=True, release_ports=True)
135 def create_packets(traffic, ports_info):
136 """Create base packet according to traffic specification.
137 If traffic haven't specified srcmac and dstmac fields
138 packet will be create with mac address of trex server.
140 mac_add = [li['hw_mac'] for li in ports_info]
142 if traffic and traffic['l2']['framesize'] > 0:
143 if traffic['l2']['dstmac'] == '00:00:00:00:00:00' and \
144 traffic['l2']['srcmac'] == '00:00:00:00:00:00':
145 base_pkt_a = Ether(src=mac_add[0], dst=mac_add[1])/ \
146 IP(proto=traffic['l3']['proto'], src=traffic['l3']['srcip'],
147 dst=traffic['l3']['dstip'])/ \
148 UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport'])
149 base_pkt_b = Ether(src=mac_add[1], dst=mac_add[0])/ \
150 IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'],
151 dst=traffic['l3']['srcip'])/ \
152 UDP(dport=traffic['l4']['srcport'], sport=traffic['l4']['dstport'])
154 base_pkt_a = Ether(src=traffic['l2']['srcmac'], dst=traffic['l2']['dstmac'])/ \
155 IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'],
156 dst=traffic['l3']['srcip'])/ \
157 UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport'])
159 base_pkt_b = Ether(src=traffic['l2']['dstmac'], dst=traffic['l2']['srcmac'])/ \
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 return (base_pkt_a, base_pkt_b)
167 def create_streams(base_pkt_a, base_pkt_b, traffic):
168 """Add the base packet to the streams. Erase FCS and add payload
169 according to traffic specification
173 frame_size = int(traffic['l2']['framesize'])
174 fsize_no_fcs = frame_size - 4
175 payload_a = max(0, fsize_no_fcs - len(base_pkt_a)) * 'x'
176 payload_b = max(0, fsize_no_fcs - len(base_pkt_b)) * 'x'
177 pkt_a = STLPktBuilder(pkt=base_pkt_a/payload_a)
178 pkt_b = STLPktBuilder(pkt=base_pkt_b/payload_b)
179 stream_1 = STLStream(packet=pkt_a,
181 mode=STLTXCont(percentage=traffic['frame_rate']))
182 stream_2 = STLStream(packet=pkt_b,
184 mode=STLTXCont(percentage=traffic['frame_rate']))
185 lat_pps = settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS')
187 stream_1_lat = STLStream(packet=pkt_a,
188 flow_stats=STLFlowLatencyStats(pg_id=0),
190 mode=STLTXCont(pps=lat_pps))
191 stream_2_lat = STLStream(packet=pkt_b,
192 flow_stats=STLFlowLatencyStats(pg_id=1),
194 mode=STLTXCont(pps=lat_pps))
196 return (stream_1, stream_2, stream_1_lat, stream_2_lat)
198 def generate_traffic(self, traffic, duration):
199 """The method that generate a stream
202 self._stlclient.reset(my_ports)
203 ports_info = self._stlclient.get_port_info(my_ports)
205 if settings.getValue('TRAFFICGEN_TREX_PROMISCUOUS'):
206 self._stlclient.set_port_attr(my_ports, promiscuous=True)
208 packet_1, packet_2 = Trex.create_packets(traffic, ports_info)
209 stream_1, stream_2, stream_1_lat, stream_2_lat = Trex.create_streams(packet_1, packet_2, traffic)
210 self._stlclient.add_streams(stream_1, ports=[0])
211 self._stlclient.add_streams(stream_2, ports=[1])
213 if stream_1_lat is not None:
214 self._stlclient.add_streams(stream_1_lat, ports=[0])
215 self._stlclient.add_streams(stream_2_lat, ports=[1])
217 self._stlclient.clear_stats()
218 self._stlclient.start(ports=[0, 1], force=True, duration=duration)
219 self._stlclient.wait_on_traffic(ports=[0, 1])
220 stats = self._stlclient.get_stats(sync_now=True)
224 def calculate_results(stats):
225 """Calculate results from Trex statistic
227 result = OrderedDict()
228 result[ResultsConstants.TX_RATE_FPS] = (
230 float(stats["total"]["tx_pps"])))
232 result[ResultsConstants.THROUGHPUT_RX_FPS] = (
234 float(stats["total"]["rx_pps"])))
236 result[ResultsConstants.TX_RATE_MBPS] = (
238 float(stats["total"]["tx_bps"] / 1000000)))
239 result[ResultsConstants.THROUGHPUT_RX_MBPS] = (
241 float(stats["total"]["rx_bps"] / 1000000)))
243 result[ResultsConstants.TX_RATE_PERCENT] = 'Unknown'
245 result[ResultsConstants.THROUGHPUT_RX_PERCENT] = 'Unknown'
246 if stats["total"]["opackets"]:
247 result[ResultsConstants.FRAME_LOSS_PERCENT] = (
249 float((stats["total"]["opackets"] - stats["total"]["ipackets"]) * 100 /
250 stats["total"]["opackets"])))
252 result[ResultsConstants.FRAME_LOSS_PERCENT] = 100
254 if settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS') > 0 and stats['latency']:
255 result[ResultsConstants.MIN_LATENCY_NS] = (
257 (float(min(stats["latency"][0]["latency"]["total_min"],
258 stats["latency"][1]["latency"]["total_min"])))))
260 result[ResultsConstants.MAX_LATENCY_NS] = (
262 (float(max(stats["latency"][0]["latency"]["total_max"],
263 stats["latency"][1]["latency"]["total_max"])))))
265 result[ResultsConstants.AVG_LATENCY_NS] = (
267 float((stats["latency"][0]["latency"]["average"]+
268 stats["latency"][1]["latency"]["average"])/2)))
271 result[ResultsConstants.MIN_LATENCY_NS] = 'Unknown'
272 result[ResultsConstants.MAX_LATENCY_NS] = 'Unknown'
273 result[ResultsConstants.AVG_LATENCY_NS] = 'Unknown'
276 def send_cont_traffic(self, traffic=None, duration=30):
277 """See ITrafficGenerator for description
279 self._logger.info("In Trex send_cont_traffic method")
282 self._params['traffic'] = self.traffic_defaults.copy()
284 self._params['traffic'] = merge_spec(
285 self._params['traffic'], traffic)
287 stats = self.generate_traffic(traffic, duration)
289 return self.calculate_results(stats)
291 def start_cont_traffic(self, traffic=None, duration=30):
292 raise NotImplementedError(
293 'Trex start cont traffic not implemented')
295 def stop_cont_traffic(self):
296 """See ITrafficGenerator for description
298 raise NotImplementedError(
299 'Trex stop_cont_traffic method not implemented')
301 def send_rfc2544_throughput(self, traffic=None, duration=60,
302 lossrate=0.0, tests=10):
303 """See ITrafficGenerator for description
305 self._logger.info("In Trex send_rfc2544_throughput method")
307 threshold = settings.getValue('TRAFFICGEN_TREX_RFC2544_TPUT_THRESHOLD')
311 stats_ok = _EMPTY_STATS
312 self._params['traffic'] = self.traffic_defaults.copy()
314 self._params['traffic'] = merge_spec(
315 self._params['traffic'], traffic)
316 new_params = copy.deepcopy(traffic)
317 stats = self.generate_traffic(traffic, duration)
318 right = traffic['frame_rate']
319 center = traffic['frame_rate']
321 # Loops until the preconfigured difference between frame rate
322 # of successful and unsuccessful iterations is reached
323 while (right - left) > threshold:
324 test_lossrate = ((stats["total"]["opackets"] - stats["total"]
325 ["ipackets"]) * 100) / stats["total"]["opackets"]
326 self._logger.debug("Iteration: %s, frame rate: %s, throughput_rx_fps: %s, frame_loss_percent: %s",
327 iteration, "{:.3f}".format(new_params['frame_rate']), stats['total']['rx_pps'],
328 "{:.3f}".format(test_lossrate))
329 if test_lossrate == 0.0 and new_params['frame_rate'] == traffic['frame_rate']:
330 stats_ok = copy.deepcopy(stats)
332 elif test_lossrate > lossrate:
334 center = (left+right) / 2
335 new_params = copy.deepcopy(traffic)
336 new_params['frame_rate'] = center
337 stats = self.generate_traffic(new_params, duration)
339 stats_ok = copy.deepcopy(stats)
341 center = (left+right) / 2
342 new_params = copy.deepcopy(traffic)
343 new_params['frame_rate'] = center
344 stats = self.generate_traffic(new_params, duration)
346 return self.calculate_results(stats_ok)
348 def start_rfc2544_throughput(self, traffic=None, tests=1, duration=60,
350 raise NotImplementedError(
351 'Trex start rfc2544 throughput not implemented')
353 def wait_rfc2544_throughput(self):
354 raise NotImplementedError(
355 'Trex wait rfc2544 throughput not implemented')
357 def send_burst_traffic(self, traffic=None, numpkts=100, duration=5):
358 raise NotImplementedError(
359 'Trex send burst traffic not implemented')
361 def send_rfc2544_back2back(self, traffic=None, tests=1, duration=30,
363 raise NotImplementedError(
364 'Trex send rfc2544 back2back not implemented')
366 def start_rfc2544_back2back(self, traffic=None, tests=1, duration=30,
368 raise NotImplementedError(
369 'Trex start rfc2544 back2back not implemented')
371 def wait_rfc2544_back2back(self):
372 raise NotImplementedError(
373 'Trex wait rfc2544 back2back not implemented')
375 if __name__ == "__main__":