1 # Copyright 2016 Cisco Systems, Inc. All rights reserved.
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # 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, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
14 """Driver module for TRex traffic generator."""
22 from itertools import count
23 from nfvbench.log import LOG
24 from nfvbench.traffic_server import TRexTrafficServer
25 from nfvbench.utils import cast_integer
26 from nfvbench.utils import timeout
27 from nfvbench.utils import TimeoutError
28 from traffic_base import AbstractTrafficGenerator
29 from traffic_base import TrafficGeneratorException
30 import traffic_utils as utils
31 from traffic_utils import IMIX_AVG_L2_FRAME_SIZE
32 from traffic_utils import IMIX_L2_SIZES
33 from traffic_utils import IMIX_RATIOS
35 # pylint: disable=import-error
36 from trex.common.services.trex_service_arp import ServiceARP
37 from trex.stl.api import bind_layers
38 from trex.stl.api import CTRexVmInsFixHwCs
39 from trex.stl.api import Dot1Q
40 from trex.stl.api import Ether
41 from trex.stl.api import FlagsField
42 from trex.stl.api import IP
43 from trex.stl.api import Packet
44 from trex.stl.api import STLClient
45 from trex.stl.api import STLError
46 from trex.stl.api import STLFlowLatencyStats
47 from trex.stl.api import STLFlowStats
48 from trex.stl.api import STLPktBuilder
49 from trex.stl.api import STLScVmRaw
50 from trex.stl.api import STLStream
51 from trex.stl.api import STLTXCont
52 from trex.stl.api import STLVmFixChecksumHw
53 from trex.stl.api import STLVmFlowVar
54 from trex.stl.api import STLVmFlowVarRepeatableRandom
55 from trex.stl.api import STLVmWrFlowVar
56 from trex.stl.api import ThreeBytesField
57 from trex.stl.api import UDP
58 from trex.stl.api import XByteField
61 # pylint: enable=import-error
66 _VXLAN_FLAGS = ['R' * 27] + ['I'] + ['R' * 5]
68 fields_desc = [FlagsField("flags", 0x08000000, 32, _VXLAN_FLAGS),
69 ThreeBytesField("vni", 0),
70 XByteField("reserved", 0x00)]
74 return self.sprintf("VXLAN (vni=%VXLAN.vni%)")
76 class TRex(AbstractTrafficGenerator):
77 """TRex traffic generator driver."""
80 CHAIN_PG_ID_MASK = 0x007F
81 PORT_PG_ID_MASK = 0x0080
82 LATENCY_PG_ID_MASK = 0x0100
84 def __init__(self, traffic_client):
86 AbstractTrafficGenerator.__init__(self, traffic_client)
90 self.chain_count = self.generator_config.service_chain_count
92 self.capture_id = None
95 def get_version(self):
96 """Get the Trex version."""
97 return self.client.get_server_version() if self.client else ''
99 def get_pg_id(self, port, chain_id):
100 """Calculate the packet group IDs to use for a given port/stream type/chain_id.
103 chain_id: identifies to which chain the pg_id is associated (0 to 255)
104 return: pg_id, lat_pg_id
106 We use a bit mask to set up the 3 fields:
107 0x007F: chain ID (8 bits for a max of 128 chains)
111 pg_id = port * TRex.PORT_PG_ID_MASK | chain_id
112 return pg_id, pg_id | TRex.LATENCY_PG_ID_MASK
114 def extract_stats(self, in_stats):
115 """Extract stats from dict returned by Trex API.
117 :param in_stats: dict as returned by TRex api
119 utils.nan_replace(in_stats)
120 # LOG.debug(in_stats)
123 # port_handles should have only 2 elements: [0, 1]
124 # so (1 - ph) will be the index for the far end port
125 for ph in self.port_handle:
127 far_end_stats = in_stats[1 - ph]
130 'total_pkts': cast_integer(stats['opackets']),
131 'total_pkt_bytes': cast_integer(stats['obytes']),
132 'pkt_rate': cast_integer(stats['tx_pps']),
133 'pkt_bit_rate': cast_integer(stats['tx_bps'])
136 'total_pkts': cast_integer(stats['ipackets']),
137 'total_pkt_bytes': cast_integer(stats['ibytes']),
138 'pkt_rate': cast_integer(stats['rx_pps']),
139 'pkt_bit_rate': cast_integer(stats['rx_bps']),
140 # how many pkts were dropped in RX direction
141 # need to take the tx counter on the far end port
142 'dropped_pkts': cast_integer(
143 far_end_stats['opackets'] - stats['ipackets'])
146 self.__combine_latencies(in_stats, result[ph]['rx'], ph)
148 total_tx_pkts = result[0]['tx']['total_pkts'] + result[1]['tx']['total_pkts']
149 result["total_tx_rate"] = cast_integer(total_tx_pkts / self.config.duration_sec)
150 result["flow_stats"] = in_stats["flow_stats"]
151 result["latency"] = in_stats["latency"]
154 def get_stream_stats(self, trex_stats, if_stats, latencies, chain_idx):
155 """Extract the aggregated stats for a given chain.
157 trex_stats: stats as returned by get_stats()
158 if_stats: a list of 2 interface stats to update (port 0 and 1)
159 latencies: a list of 2 Latency instances to update for this chain (port 0 and 1)
160 latencies[p] is the latency for packets sent on port p
161 if there are no latency streams, the Latency instances are not modified
162 chain_idx: chain index of the interface stats
164 The packet counts include normal and latency streams.
166 Trex returns flows stats as follows:
168 'flow_stats': {0: {'rx_bps': {0: 0, 1: 0, 'total': 0},
169 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
170 'rx_bytes': {0: nan, 1: nan, 'total': nan},
171 'rx_pkts': {0: 0, 1: 15001, 'total': 15001},
172 'rx_pps': {0: 0, 1: 0, 'total': 0},
173 'tx_bps': {0: 0, 1: 0, 'total': 0},
174 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
175 'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068},
176 'tx_pkts': {0: 15001, 1: 0, 'total': 15001},
177 'tx_pps': {0: 0, 1: 0, 'total': 0}},
178 1: {'rx_bps': {0: 0, 1: 0, 'total': 0},
179 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
180 'rx_bytes': {0: nan, 1: nan, 'total': nan},
181 'rx_pkts': {0: 0, 1: 15001, 'total': 15001},
182 'rx_pps': {0: 0, 1: 0, 'total': 0},
183 'tx_bps': {0: 0, 1: 0, 'total': 0},
184 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
185 'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068},
186 'tx_pkts': {0: 15001, 1: 0, 'total': 15001},
187 'tx_pps': {0: 0, 1: 0, 'total': 0}},
188 128: {'rx_bps': {0: 0, 1: 0, 'total': 0},
189 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
190 'rx_bytes': {0: nan, 1: nan, 'total': nan},
191 'rx_pkts': {0: 15001, 1: 0, 'total': 15001},
192 'rx_pps': {0: 0, 1: 0, 'total': 0},
193 'tx_bps': {0: 0, 1: 0, 'total': 0},
194 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
195 'tx_bytes': {0: 0, 1: 1020068, 'total': 1020068},
196 'tx_pkts': {0: 0, 1: 15001, 'total': 15001},
197 'tx_pps': {0: 0, 1: 0, 'total': 0}},etc...
199 the pg_id (0, 1, 128,...) is the key of the dict and is obtained using the
201 packet counters for a given stream sent on port p are reported as:
202 - tx_pkts[p] on port p
203 - rx_pkts[1-p] on the far end port
205 This is a tricky/critical counter transposition operation because
206 the results are grouped by port (not by stream):
207 tx_pkts_port(p=0) comes from pg_id(port=0, chain_idx)['tx_pkts'][0]
208 rx_pkts_port(p=0) comes from pg_id(port=1, chain_idx)['rx_pkts'][0]
209 tx_pkts_port(p=1) comes from pg_id(port=1, chain_idx)['tx_pkts'][1]
210 rx_pkts_port(p=1) comes from pg_id(port=0, chain_idx)['rx_pkts'][1]
212 or using a more generic formula:
213 tx_pkts_port(p) comes from pg_id(port=p, chain_idx)['tx_pkts'][p]
214 rx_pkts_port(p) comes from pg_id(port=1-p, chain_idx)['rx_pkts'][p]
216 the second formula is equivalent to
217 rx_pkts_port(1-p) comes from pg_id(port=p, chain_idx)['rx_pkts'][1-p]
219 If there are latency streams, those same counters need to be added in the same way
221 def get_latency(lval):
223 return int(round(lval))
229 for port in range(2):
230 pg_id, lat_pg_id = self.get_pg_id(port, chain_idx)
231 for pid in [pg_id, lat_pg_id]:
233 pg_stats = trex_stats['flow_stats'][pid]
234 if_stats[port].tx += pg_stats['tx_pkts'][port]
235 if_stats[1 - port].rx += pg_stats['rx_pkts'][1 - port]
239 lat = trex_stats['latency'][lat_pg_id]['latency']
240 # dropped_pkts += lat['err_cntrs']['dropped']
241 latencies[port].max_usec = get_latency(lat['total_max'])
242 if math.isnan(lat['total_min']):
243 latencies[port].min_usec = 0
244 latencies[port].avg_usec = 0
246 latencies[port].min_usec = get_latency(lat['total_min'])
247 latencies[port].avg_usec = get_latency(lat['average'])
251 def __combine_latencies(self, in_stats, results, port_handle):
252 """Traverse TRex result dictionary and combines chosen latency stats."""
255 total_min = float("inf")
256 for chain_id in range(self.chain_count):
258 _, lat_pg_id = self.get_pg_id(port_handle, chain_id)
259 lat = in_stats['latency'][lat_pg_id]['latency']
260 # dropped_pkts += lat['err_cntrs']['dropped']
261 total_max = max(lat['total_max'], total_max)
262 total_min = min(lat['total_min'], total_min)
263 average += lat['average']
266 if total_min == float("inf"):
268 results['min_delay_usec'] = total_min
269 results['max_delay_usec'] = total_max
270 results['avg_delay_usec'] = int(average / self.chain_count)
272 def _bind_vxlan(self):
273 bind_layers(UDP, VXLAN, dport=4789)
274 bind_layers(VXLAN, Ether)
276 def _create_pkt(self, stream_cfg, l2frame_size):
277 """Create a packet of given size.
279 l2frame_size: size of the L2 frame in bytes (including the 32-bit FCS)
281 # Trex will add the FCS field, so we need to remove 4 bytes from the l2 frame size
282 frame_size = int(l2frame_size) - 4
284 if stream_cfg['vxlan'] is True:
287 pkt_base = Ether(src=stream_cfg['vtep_src_mac'], dst=stream_cfg['vtep_dst_mac'])
288 if stream_cfg['vtep_vlan'] is not None:
289 pkt_base /= Dot1Q(vlan=stream_cfg['vtep_vlan'])
290 pkt_base /= IP(src=stream_cfg['vtep_src_ip'], dst=stream_cfg['vtep_dst_ip'])
291 pkt_base /= UDP(sport=random.randint(1337, 32767), dport=4789)
292 pkt_base /= VXLAN(vni=stream_cfg['net_vni'])
293 pkt_base /= Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
294 # need to randomize the outer header UDP src port based on flow
295 vxlan_udp_src_fv = STLVmFlowVar(
296 name="vxlan_udp_src",
301 vm_param = [vxlan_udp_src_fv,
302 STLVmWrFlowVar(fv_name="vxlan_udp_src", pkt_offset="UDP.sport")]
305 pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
307 if stream_cfg['vlan_tag'] is not None:
308 pkt_base /= Dot1Q(vlan=stream_cfg['vlan_tag'])
311 if stream_cfg['udp_src_port']:
312 udp_args['sport'] = int(stream_cfg['udp_src_port'])
313 if stream_cfg['udp_dst_port']:
314 udp_args['dport'] = int(stream_cfg['udp_dst_port'])
315 pkt_base /= IP() / UDP(**udp_args)
317 if stream_cfg['ip_addrs_step'] == 'random':
318 src_fv = STLVmFlowVarRepeatableRandom(
320 min_value=stream_cfg['ip_src_addr'],
321 max_value=stream_cfg['ip_src_addr_max'],
323 seed=random.randint(0, 32767),
324 limit=stream_cfg['ip_src_count'])
325 dst_fv = STLVmFlowVarRepeatableRandom(
327 min_value=stream_cfg['ip_dst_addr'],
328 max_value=stream_cfg['ip_dst_addr_max'],
330 seed=random.randint(0, 32767),
331 limit=stream_cfg['ip_dst_count'])
333 src_fv = STLVmFlowVar(
335 min_value=stream_cfg['ip_src_addr'],
336 max_value=stream_cfg['ip_src_addr'],
339 step=stream_cfg['ip_addrs_step'])
340 dst_fv = STLVmFlowVar(
342 min_value=stream_cfg['ip_dst_addr'],
343 max_value=stream_cfg['ip_dst_addr_max'],
346 step=stream_cfg['ip_addrs_step'])
350 STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)),
352 STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level)),
353 STLVmFixChecksumHw(l3_offset="IP:{}".format(encap_level),
354 l4_offset="UDP:{}".format(encap_level),
355 l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP)
357 pad = max(0, frame_size - len(pkt_base)) * 'x'
359 return STLPktBuilder(pkt=pkt_base / pad, vm=STLScVmRaw(vm_param))
361 def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True,
363 """Create a list of streams corresponding to a given chain and stream config.
365 port: port where the streams originate (0 or 1)
366 chain_id: the chain to which the streams are associated to
367 stream_cfg: stream configuration
368 l2frame: L2 frame size (including 4-byte FCS) or 'IMIX'
369 latency: if True also create a latency stream
370 e2e: True if performing "end to end" connectivity check
373 pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
374 if l2frame == 'IMIX':
375 for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES):
376 pkt = self._create_pkt(stream_cfg, l2_frame_size)
378 streams.append(STLStream(packet=pkt,
379 mode=STLTXCont(pps=ratio)))
381 if stream_cfg['vxlan'] is True:
382 streams.append(STLStream(packet=pkt,
383 flow_stats=STLFlowStats(pg_id=pg_id,
385 mode=STLTXCont(pps=ratio)))
387 streams.append(STLStream(packet=pkt,
388 flow_stats=STLFlowStats(pg_id=pg_id),
389 mode=STLTXCont(pps=ratio)))
392 # for IMIX, the latency packets have the average IMIX packet size
393 pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE)
396 l2frame_size = int(l2frame)
397 pkt = self._create_pkt(stream_cfg, l2frame_size)
399 streams.append(STLStream(packet=pkt,
402 if stream_cfg['vxlan'] is True:
403 streams.append(STLStream(packet=pkt,
404 flow_stats=STLFlowStats(pg_id=pg_id,
408 streams.append(STLStream(packet=pkt,
409 flow_stats=STLFlowStats(pg_id=pg_id),
411 # for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging
412 # without vlan, the min l2 frame size is 64
414 # This only applies to the latency stream
415 if latency and stream_cfg['vlan_tag'] and l2frame_size < 68:
416 pkt = self._create_pkt(stream_cfg, 68)
419 # TRex limitation: VXLAN skip is not supported for latency stream
420 streams.append(STLStream(packet=pkt,
421 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id),
422 mode=STLTXCont(pps=self.LATENCY_PPS)))
426 def __connect(self, client):
429 def __connect_after_start(self):
430 # after start, Trex may take a bit of time to initialize
431 # so we need to retry a few times
432 for it in xrange(self.config.generic_retry_count):
435 self.client.connect()
437 except Exception as ex:
438 if it == (self.config.generic_retry_count - 1):
440 LOG.info("Retrying connection to TRex (%s)...", ex.message)
443 """Connect to the TRex server."""
444 server_ip = self.generator_config.ip
445 LOG.info("Connecting to TRex (%s)...", server_ip)
447 # Connect to TRex server
448 self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port,
449 async_port=self.generator_config.zmq_pub_port)
451 self.__connect(self.client)
452 if server_ip == '127.0.0.1':
453 config_updated = self.__check_config()
454 if config_updated or self.config.restart:
456 except (TimeoutError, STLError) as e:
457 if server_ip == '127.0.0.1':
458 self.__start_local_server()
460 raise TrafficGeneratorException(e.message)
462 ports = list(self.generator_config.ports)
463 self.port_handle = ports
465 self.client.reset(ports)
466 # Read HW information from each port
467 # this returns an array of dict (1 per port)
469 Example of output for Intel XL710
470 [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True,
471 'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes',
472 u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']},
473 u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE',
474 u'description': u'Ethernet Controller XL710 for 40GbE QSFP+',
475 'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-',
476 u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match',
477 'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0',
478 'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off',
479 'layer_mode': 'Ethernet', u'numa': 0}, ...]
481 self.port_info = self.client.get_port_info(ports)
482 LOG.info('Connected to TRex')
483 for id, port in enumerate(self.port_info):
484 LOG.info(' Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s',
485 id, port['description'], port['speed'], port['src_mac'],
486 port['pci_addr'], port['driver'])
487 # Make sure the 2 ports have the same speed
488 if self.port_info[0]['speed'] != self.port_info[1]['speed']:
489 raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' %
490 (self.port_info[0]['speed'],
491 self.port_info[1]['speed']))
493 def __start_local_server(self):
495 LOG.info("Starting TRex ...")
496 self.__start_server()
497 self.__connect_after_start()
498 except (TimeoutError, STLError) as e:
499 LOG.error('Cannot connect to TRex')
500 LOG.error(traceback.format_exc())
501 logpath = '/tmp/trex.log'
502 if os.path.isfile(logpath):
503 # Wait for TRex to finish writing error message
505 for _ in xrange(self.config.generic_retry_count):
506 size = os.path.getsize(logpath)
507 if size == last_size:
508 # probably not writing anymore
512 with open(logpath, 'r') as f:
516 raise TrafficGeneratorException(message)
518 def __start_server(self):
519 server = TRexTrafficServer()
520 server.run_server(self.generator_config)
522 def __check_config(self):
523 server = TRexTrafficServer()
524 return server.check_config_updated(self.generator_config)
527 LOG.info("Restarting TRex ...")
529 # Wait for server stopped
530 for _ in xrange(self.config.generic_retry_count):
532 if not self.client.is_connected():
533 LOG.info("TRex is stopped...")
535 self.__start_local_server()
537 def __stop_server(self):
538 if self.generator_config.ip == '127.0.0.1':
539 ports = self.client.get_acquired_ports()
540 LOG.info('Release ports %s and stopping TRex...', ports)
543 self.client.release(ports=ports)
544 self.client.server_shutdown()
545 except STLError as e:
546 LOG.warn('Unable to stop TRex. Error: %s', e)
548 LOG.info('Using remote TRex. Unable to stop TRex')
550 def resolve_arp(self):
551 """Resolve all configured remote IP addresses.
553 return: None if ARP failed to resolve for all IP addresses
554 else a dict of list of dest macs indexed by port#
555 the dest macs in the list are indexed by the chain id
557 self.client.set_service_mode(ports=self.port_handle)
558 LOG.info('Polling ARP until successful...')
560 for port, device in zip(self.port_handle, self.generator_config.devices):
561 # there should be 1 stream config per chain
562 stream_configs = device.get_stream_configs()
563 chain_count = len(stream_configs)
564 ctx = self.client.create_service_ctx(port=port)
565 # all dest macs on this port indexed by chain ID
566 dst_macs = [None] * chain_count
568 # the index in the list is the chain id
569 if self.config.vxlan:
572 src_ip=device.vtep_src_ip,
573 dst_ip=device.vtep_dst_ip,
574 vlan=device.vtep_vlan)
575 for cfg in stream_configs
580 src_ip=cfg['ip_src_tg_gw'],
581 dst_ip=cfg['mac_discovery_gw'],
582 # will be None if no vlan tagging
583 vlan=cfg['vlan_tag'])
584 for cfg in stream_configs
587 for attempt in range(self.config.generic_retry_count):
591 LOG.error(traceback.format_exc())
595 for chain_id, mac in enumerate(dst_macs):
597 arp_record = arps[chain_id].get_record()
598 if arp_record.dst_mac:
599 dst_macs[chain_id] = arp_record.dst_mac
601 LOG.info(' ARP: port=%d chain=%d src IP=%s dst IP=%s -> MAC=%s',
604 arp_record.dst_ip, arp_record.dst_mac)
606 unresolved.append(arp_record.dst_ip)
607 if dst_macs_count == chain_count:
608 arp_dest_macs[port] = dst_macs
609 LOG.info('ARP resolved successfully for port %s', port)
613 LOG.info('Retrying ARP for: %s (retry %d/%d)',
614 unresolved, retry, self.config.generic_retry_count)
615 if retry < self.config.generic_retry_count:
616 time.sleep(self.config.generic_poll_sec)
618 LOG.error('ARP timed out for port %s (resolved %d out of %d)',
624 self.client.set_service_mode(ports=self.port_handle, enabled=False)
625 if len(arp_dest_macs) == len(self.port_handle):
629 def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency):
630 """Check if rate provided by user is above requirements. Applies only if latency is True."""
631 intf_speed = self.generator_config.intf_speed
637 r = utils.convert_rates(l2frame_size, rate, intf_speed)
638 total_rate += int(r['rate_pps'])
641 total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
642 # rate must be enough for latency stream and at least 1 pps for base stream per chain
643 required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult
644 result = utils.convert_rates(l2frame_size,
645 {'rate_pps': required_rate},
647 result['result'] = total_rate >= required_rate
650 return {'result': True}
652 def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
653 """Program all the streams in Trex server.
655 l2frame_size: L2 frame size or IMIX
656 rates: a list of 2 rates to run each direction
657 each rate is a dict like {'rate_pps': '10kpps'}
658 bidirectional: True if bidirectional
659 latency: True if latency measurement is needed
660 e2e: True if performing "end to end" connectivity check
662 r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
664 raise TrafficGeneratorException(
665 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.'
666 .format(pps=r['rate_pps'],
668 load=r['rate_percent']))
669 # a dict of list of streams indexed by port#
670 # in case of fixed size, has self.chain_count * 2 * 2 streams
671 # (1 normal + 1 latency stream per direction per chain)
672 # for IMIX, has self.chain_count * 2 * 4 streams
673 # (3 normal + 1 latency stream per direction per chain)
675 for port in self.port_handle:
676 streamblock[port] = []
677 stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
678 self.rates = [utils.to_rate_str(rate) for rate in rates]
679 for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
680 streamblock[0].extend(self.generate_streams(self.port_handle[0],
686 if len(self.rates) > 1:
687 streamblock[1].extend(self.generate_streams(self.port_handle[1],
691 latency=bidirectional and latency,
694 for port in self.port_handle:
695 if self.config.vxlan:
696 self.client.set_port_attr(ports=port, vxlan_fs=[4789])
698 self.client.set_port_attr(ports=port, vxlan_fs=None)
699 self.client.add_streams(streamblock[port], ports=port)
700 LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
702 def clear_streamblock(self):
703 """Clear all streams from TRex."""
705 self.client.reset(self.port_handle)
706 LOG.info('Cleared all existing streams')
709 """Get stats from Trex."""
710 stats = self.client.get_stats()
711 return self.extract_stats(stats)
714 """Return the Trex local port MAC addresses.
716 return: a list of MAC addresses indexed by the port#
718 return [port['src_mac'] for port in self.port_info]
720 def get_port_speed_gbps(self):
721 """Return the Trex local port MAC addresses.
723 return: a list of speed in Gbps indexed by the port#
725 return [port['speed'] for port in self.port_info]
727 def clear_stats(self):
728 """Clear all stats in the traffic gneerator."""
730 self.client.clear_stats()
732 def start_traffic(self):
733 """Start generating traffic in all ports."""
734 for port, rate in zip(self.port_handle, self.rates):
735 self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True)
737 def stop_traffic(self):
738 """Stop generating traffic."""
739 self.client.stop(ports=self.port_handle)
741 def start_capture(self):
742 """Capture all packets on both ports that are unicast to us."""
745 # Need to filter out unwanted packets so we do not end up counting
746 # src MACs of frames that are not unicast to us
747 src_mac_list = self.get_macs()
748 bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
749 # ports must be set in service in order to enable capture
750 self.client.set_service_mode(ports=self.port_handle)
751 self.capture_id = self.client.start_capture(rx_ports=self.port_handle,
752 bpf_filter=bpf_filter)
754 def fetch_capture_packets(self):
755 """Fetch capture packets in capture mode."""
757 self.packet_list = []
758 self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
759 output=self.packet_list)
761 def stop_capture(self):
762 """Stop capturing packets."""
764 self.client.stop_capture(capture_id=self.capture_id['id'])
765 self.capture_id = None
766 self.client.set_service_mode(ports=self.port_handle, enabled=False)
769 """Cleanup Trex driver."""
772 self.client.reset(self.port_handle)
773 self.client.disconnect()
775 # TRex does not like a reset while in disconnected state