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)
204 packet_1, packet_2 = Trex.create_packets(traffic, ports_info)
205 stream_1, stream_2, stream_1_lat, stream_2_lat = Trex.create_streams(packet_1, packet_2, traffic)
206 self._stlclient.add_streams(stream_1, ports=[0])
207 self._stlclient.add_streams(stream_2, ports=[1])
209 if stream_1_lat is not None:
210 self._stlclient.add_streams(stream_1_lat, ports=[0])
211 self._stlclient.add_streams(stream_2_lat, ports=[1])
213 self._stlclient.clear_stats()
214 self._stlclient.start(ports=[0, 1], force=True, duration=duration)
215 self._stlclient.wait_on_traffic(ports=[0, 1])
216 stats = self._stlclient.get_stats(sync_now=True)
220 def calculate_results(stats):
221 """Calculate results from Trex statistic
223 result = OrderedDict()
224 result[ResultsConstants.TX_RATE_FPS] = (
226 float(stats["total"]["tx_pps"])))
228 result[ResultsConstants.THROUGHPUT_RX_FPS] = (
230 float(stats["total"]["rx_pps"])))
232 result[ResultsConstants.TX_RATE_MBPS] = (
234 float(stats["total"]["tx_bps"] / 1000000)))
235 result[ResultsConstants.THROUGHPUT_RX_MBPS] = (
237 float(stats["total"]["rx_bps"] / 1000000)))
239 result[ResultsConstants.TX_RATE_PERCENT] = 'Unknown'
241 result[ResultsConstants.THROUGHPUT_RX_PERCENT] = 'Unknown'
242 if stats["total"]["opackets"]:
243 result[ResultsConstants.FRAME_LOSS_PERCENT] = (
245 float((stats["total"]["opackets"] - stats["total"]["ipackets"]) * 100 /
246 stats["total"]["opackets"])))
248 result[ResultsConstants.FRAME_LOSS_PERCENT] = 100
250 if settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS') > 0 and stats['latency']:
251 result[ResultsConstants.MIN_LATENCY_NS] = (
253 (float(min(stats["latency"][0]["latency"]["total_min"],
254 stats["latency"][1]["latency"]["total_min"])))))
256 result[ResultsConstants.MAX_LATENCY_NS] = (
258 (float(max(stats["latency"][0]["latency"]["total_max"],
259 stats["latency"][1]["latency"]["total_max"])))))
261 result[ResultsConstants.AVG_LATENCY_NS] = (
263 float((stats["latency"][0]["latency"]["average"]+
264 stats["latency"][1]["latency"]["average"])/2)))
267 result[ResultsConstants.MIN_LATENCY_NS] = 'Unknown'
268 result[ResultsConstants.MAX_LATENCY_NS] = 'Unknown'
269 result[ResultsConstants.AVG_LATENCY_NS] = 'Unknown'
272 def send_cont_traffic(self, traffic=None, duration=30):
273 """See ITrafficGenerator for description
275 self._logger.info("In Trex send_cont_traffic method")
278 self._params['traffic'] = self.traffic_defaults.copy()
280 self._params['traffic'] = merge_spec(
281 self._params['traffic'], traffic)
283 stats = self.generate_traffic(traffic, duration)
285 return self.calculate_results(stats)
287 def start_cont_traffic(self, traffic=None, duration=30):
288 raise NotImplementedError(
289 'Trex start cont traffic not implemented')
291 def stop_cont_traffic(self):
292 """See ITrafficGenerator for description
294 raise NotImplementedError(
295 'Trex stop_cont_traffic method not implemented')
297 def send_rfc2544_throughput(self, traffic=None, duration=60,
298 lossrate=0.0, tests=10):
299 """See ITrafficGenerator for description
301 self._logger.info("In Trex send_rfc2544_throughput method")
303 threshold = settings.getValue('TRAFFICGEN_TREX_RFC2544_TPUT_THRESHOLD')
307 stats_ok = _EMPTY_STATS
308 self._params['traffic'] = self.traffic_defaults.copy()
310 self._params['traffic'] = merge_spec(
311 self._params['traffic'], traffic)
312 new_params = copy.deepcopy(traffic)
313 stats = self.generate_traffic(traffic, duration)
314 right = traffic['frame_rate']
315 center = traffic['frame_rate']
317 # Loops until the preconfigured difference between frame rate
318 # of successful and unsuccessful iterations is reached
319 while (right - left) > threshold:
320 test_lossrate = ((stats["total"]["opackets"] - stats["total"]
321 ["ipackets"]) * 100) / stats["total"]["opackets"]
322 self._logger.debug("Iteration: %s, frame rate: %s, throughput_rx_fps: %s, frame_loss_percent: %s",
323 iteration, "{:.3f}".format(new_params['frame_rate']), stats['total']['rx_pps'],
324 "{:.3f}".format(test_lossrate))
325 if test_lossrate == 0.0 and new_params['frame_rate'] == traffic['frame_rate']:
326 stats_ok = copy.deepcopy(stats)
328 elif test_lossrate > lossrate:
330 center = (left+right) / 2
331 new_params = copy.deepcopy(traffic)
332 new_params['frame_rate'] = center
333 stats = self.generate_traffic(new_params, duration)
335 stats_ok = copy.deepcopy(stats)
337 center = (left+right) / 2
338 new_params = copy.deepcopy(traffic)
339 new_params['frame_rate'] = center
340 stats = self.generate_traffic(new_params, duration)
342 return self.calculate_results(stats_ok)
344 def start_rfc2544_throughput(self, traffic=None, tests=1, duration=60,
346 raise NotImplementedError(
347 'Trex start rfc2544 throughput not implemented')
349 def wait_rfc2544_throughput(self):
350 raise NotImplementedError(
351 'Trex wait rfc2544 throughput not implemented')
353 def send_burst_traffic(self, traffic=None, numpkts=100, duration=5):
354 raise NotImplementedError(
355 'Trex send burst traffic not implemented')
357 def send_rfc2544_back2back(self, traffic=None, tests=1, duration=30,
359 raise NotImplementedError(
360 'Trex send rfc2544 back2back not implemented')
362 def start_rfc2544_back2back(self, traffic=None, tests=1, duration=30,
364 raise NotImplementedError(
365 'Trex start rfc2544 back2back not implemented')
367 def wait_rfc2544_back2back(self):
368 raise NotImplementedError(
369 'Trex wait rfc2544 back2back not implemented')
371 if __name__ == "__main__":