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))
355 for encap in range(int(encap_level), -1, -1):
356 # Fixing the checksums for all encap levels
357 vm_param.append(STLVmFixChecksumHw(l3_offset="IP:{}".format(encap),
358 l4_offset="UDP:{}".format(encap),
359 l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP))
360 pad = max(0, frame_size - len(pkt_base)) * 'x'
362 return STLPktBuilder(pkt=pkt_base / pad, vm=STLScVmRaw(vm_param))
364 def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True,
366 """Create a list of streams corresponding to a given chain and stream config.
368 port: port where the streams originate (0 or 1)
369 chain_id: the chain to which the streams are associated to
370 stream_cfg: stream configuration
371 l2frame: L2 frame size (including 4-byte FCS) or 'IMIX'
372 latency: if True also create a latency stream
373 e2e: True if performing "end to end" connectivity check
376 pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
377 if l2frame == 'IMIX':
378 for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES):
379 pkt = self._create_pkt(stream_cfg, l2_frame_size)
381 streams.append(STLStream(packet=pkt,
382 mode=STLTXCont(pps=ratio)))
384 if stream_cfg['vxlan'] is True:
385 streams.append(STLStream(packet=pkt,
386 flow_stats=STLFlowStats(pg_id=pg_id,
388 mode=STLTXCont(pps=ratio)))
390 streams.append(STLStream(packet=pkt,
391 flow_stats=STLFlowStats(pg_id=pg_id),
392 mode=STLTXCont(pps=ratio)))
395 # for IMIX, the latency packets have the average IMIX packet size
396 pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE)
399 l2frame_size = int(l2frame)
400 pkt = self._create_pkt(stream_cfg, l2frame_size)
402 streams.append(STLStream(packet=pkt,
405 if stream_cfg['vxlan'] is True:
406 streams.append(STLStream(packet=pkt,
407 flow_stats=STLFlowStats(pg_id=pg_id,
411 streams.append(STLStream(packet=pkt,
412 flow_stats=STLFlowStats(pg_id=pg_id),
414 # for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging
415 # without vlan, the min l2 frame size is 64
417 # This only applies to the latency stream
418 if latency and stream_cfg['vlan_tag'] and l2frame_size < 68:
419 pkt = self._create_pkt(stream_cfg, 68)
422 # TRex limitation: VXLAN skip is not supported for latency stream
423 streams.append(STLStream(packet=pkt,
424 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id),
425 mode=STLTXCont(pps=self.LATENCY_PPS)))
429 def __connect(self, client):
432 def __connect_after_start(self):
433 # after start, Trex may take a bit of time to initialize
434 # so we need to retry a few times
435 for it in xrange(self.config.generic_retry_count):
438 self.client.connect()
440 except Exception as ex:
441 if it == (self.config.generic_retry_count - 1):
443 LOG.info("Retrying connection to TRex (%s)...", ex.message)
446 """Connect to the TRex server."""
447 server_ip = self.generator_config.ip
448 LOG.info("Connecting to TRex (%s)...", server_ip)
450 # Connect to TRex server
451 self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port,
452 async_port=self.generator_config.zmq_pub_port)
454 self.__connect(self.client)
455 if server_ip == '127.0.0.1':
456 config_updated = self.__check_config()
457 if config_updated or self.config.restart:
459 except (TimeoutError, STLError) as e:
460 if server_ip == '127.0.0.1':
461 self.__start_local_server()
463 raise TrafficGeneratorException(e.message)
465 ports = list(self.generator_config.ports)
466 self.port_handle = ports
468 self.client.reset(ports)
469 # Read HW information from each port
470 # this returns an array of dict (1 per port)
472 Example of output for Intel XL710
473 [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True,
474 'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes',
475 u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']},
476 u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE',
477 u'description': u'Ethernet Controller XL710 for 40GbE QSFP+',
478 'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-',
479 u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match',
480 'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0',
481 'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off',
482 'layer_mode': 'Ethernet', u'numa': 0}, ...]
484 self.port_info = self.client.get_port_info(ports)
485 LOG.info('Connected to TRex')
486 for id, port in enumerate(self.port_info):
487 LOG.info(' Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s',
488 id, port['description'], port['speed'], port['src_mac'],
489 port['pci_addr'], port['driver'])
490 # Make sure the 2 ports have the same speed
491 if self.port_info[0]['speed'] != self.port_info[1]['speed']:
492 raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' %
493 (self.port_info[0]['speed'],
494 self.port_info[1]['speed']))
496 def __start_local_server(self):
498 LOG.info("Starting TRex ...")
499 self.__start_server()
500 self.__connect_after_start()
501 except (TimeoutError, STLError) as e:
502 LOG.error('Cannot connect to TRex')
503 LOG.error(traceback.format_exc())
504 logpath = '/tmp/trex.log'
505 if os.path.isfile(logpath):
506 # Wait for TRex to finish writing error message
508 for _ in xrange(self.config.generic_retry_count):
509 size = os.path.getsize(logpath)
510 if size == last_size:
511 # probably not writing anymore
515 with open(logpath, 'r') as f:
519 raise TrafficGeneratorException(message)
521 def __start_server(self):
522 server = TRexTrafficServer()
523 server.run_server(self.generator_config)
525 def __check_config(self):
526 server = TRexTrafficServer()
527 return server.check_config_updated(self.generator_config)
530 LOG.info("Restarting TRex ...")
532 # Wait for server stopped
533 for _ in xrange(self.config.generic_retry_count):
535 if not self.client.is_connected():
536 LOG.info("TRex is stopped...")
538 self.__start_local_server()
540 def __stop_server(self):
541 if self.generator_config.ip == '127.0.0.1':
542 ports = self.client.get_acquired_ports()
543 LOG.info('Release ports %s and stopping TRex...', ports)
546 self.client.release(ports=ports)
547 self.client.server_shutdown()
548 except STLError as e:
549 LOG.warn('Unable to stop TRex. Error: %s', e)
551 LOG.info('Using remote TRex. Unable to stop TRex')
553 def resolve_arp(self):
554 """Resolve all configured remote IP addresses.
556 return: None if ARP failed to resolve for all IP addresses
557 else a dict of list of dest macs indexed by port#
558 the dest macs in the list are indexed by the chain id
560 self.client.set_service_mode(ports=self.port_handle)
561 LOG.info('Polling ARP until successful...')
563 for port, device in zip(self.port_handle, self.generator_config.devices):
564 # there should be 1 stream config per chain
565 stream_configs = device.get_stream_configs()
566 chain_count = len(stream_configs)
567 ctx = self.client.create_service_ctx(port=port)
568 # all dest macs on this port indexed by chain ID
569 dst_macs = [None] * chain_count
571 # the index in the list is the chain id
572 if self.config.vxlan:
575 src_ip=device.vtep_src_ip,
576 dst_ip=device.vtep_dst_ip,
577 vlan=device.vtep_vlan)
578 for cfg in stream_configs
583 src_ip=cfg['ip_src_tg_gw'],
584 dst_ip=cfg['mac_discovery_gw'],
585 # will be None if no vlan tagging
586 vlan=cfg['vlan_tag'])
587 for cfg in stream_configs
590 for attempt in range(self.config.generic_retry_count):
594 LOG.error(traceback.format_exc())
598 for chain_id, mac in enumerate(dst_macs):
600 arp_record = arps[chain_id].get_record()
601 if arp_record.dst_mac:
602 dst_macs[chain_id] = arp_record.dst_mac
604 LOG.info(' ARP: port=%d chain=%d src IP=%s dst IP=%s -> MAC=%s',
607 arp_record.dst_ip, arp_record.dst_mac)
609 unresolved.append(arp_record.dst_ip)
610 if dst_macs_count == chain_count:
611 arp_dest_macs[port] = dst_macs
612 LOG.info('ARP resolved successfully for port %s', port)
616 LOG.info('Retrying ARP for: %s (retry %d/%d)',
617 unresolved, retry, self.config.generic_retry_count)
618 if retry < self.config.generic_retry_count:
619 time.sleep(self.config.generic_poll_sec)
621 LOG.error('ARP timed out for port %s (resolved %d out of %d)',
627 self.client.set_service_mode(ports=self.port_handle, enabled=False)
628 if len(arp_dest_macs) == len(self.port_handle):
632 def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency):
633 """Check if rate provided by user is above requirements. Applies only if latency is True."""
634 intf_speed = self.generator_config.intf_speed
640 r = utils.convert_rates(l2frame_size, rate, intf_speed)
641 total_rate += int(r['rate_pps'])
644 total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
645 # rate must be enough for latency stream and at least 1 pps for base stream per chain
646 required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult
647 result = utils.convert_rates(l2frame_size,
648 {'rate_pps': required_rate},
650 result['result'] = total_rate >= required_rate
653 return {'result': True}
655 def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
656 """Program all the streams in Trex server.
658 l2frame_size: L2 frame size or IMIX
659 rates: a list of 2 rates to run each direction
660 each rate is a dict like {'rate_pps': '10kpps'}
661 bidirectional: True if bidirectional
662 latency: True if latency measurement is needed
663 e2e: True if performing "end to end" connectivity check
665 r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
667 raise TrafficGeneratorException(
668 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.'
669 .format(pps=r['rate_pps'],
671 load=r['rate_percent']))
672 # a dict of list of streams indexed by port#
673 # in case of fixed size, has self.chain_count * 2 * 2 streams
674 # (1 normal + 1 latency stream per direction per chain)
675 # for IMIX, has self.chain_count * 2 * 4 streams
676 # (3 normal + 1 latency stream per direction per chain)
678 for port in self.port_handle:
679 streamblock[port] = []
680 stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
681 self.rates = [utils.to_rate_str(rate) for rate in rates]
682 for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
683 streamblock[0].extend(self.generate_streams(self.port_handle[0],
689 if len(self.rates) > 1:
690 streamblock[1].extend(self.generate_streams(self.port_handle[1],
694 latency=bidirectional and latency,
697 for port in self.port_handle:
698 if self.config.vxlan:
699 self.client.set_port_attr(ports=port, vxlan_fs=[4789])
701 self.client.set_port_attr(ports=port, vxlan_fs=None)
702 self.client.add_streams(streamblock[port], ports=port)
703 LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
705 def clear_streamblock(self):
706 """Clear all streams from TRex."""
708 self.client.reset(self.port_handle)
709 LOG.info('Cleared all existing streams')
712 """Get stats from Trex."""
713 stats = self.client.get_stats()
714 return self.extract_stats(stats)
717 """Return the Trex local port MAC addresses.
719 return: a list of MAC addresses indexed by the port#
721 return [port['src_mac'] for port in self.port_info]
723 def get_port_speed_gbps(self):
724 """Return the Trex local port MAC addresses.
726 return: a list of speed in Gbps indexed by the port#
728 return [port['speed'] for port in self.port_info]
730 def clear_stats(self):
731 """Clear all stats in the traffic gneerator."""
733 self.client.clear_stats()
735 def start_traffic(self):
736 """Start generating traffic in all ports."""
737 for port, rate in zip(self.port_handle, self.rates):
738 self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True)
740 def stop_traffic(self):
741 """Stop generating traffic."""
742 self.client.stop(ports=self.port_handle)
744 def start_capture(self):
745 """Capture all packets on both ports that are unicast to us."""
748 # Need to filter out unwanted packets so we do not end up counting
749 # src MACs of frames that are not unicast to us
750 src_mac_list = self.get_macs()
751 bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
752 # ports must be set in service in order to enable capture
753 self.client.set_service_mode(ports=self.port_handle)
754 self.capture_id = self.client.start_capture(rx_ports=self.port_handle,
755 bpf_filter=bpf_filter)
757 def fetch_capture_packets(self):
758 """Fetch capture packets in capture mode."""
760 self.packet_list = []
761 self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
762 output=self.packet_list)
764 def stop_capture(self):
765 """Stop capturing packets."""
767 self.client.stop_capture(capture_id=self.capture_id['id'])
768 self.capture_id = None
769 self.client.set_service_mode(ports=self.port_handle, enabled=False)
772 """Cleanup Trex driver."""
775 self.client.reset(self.port_handle)
776 self.client.disconnect()
778 # TRex does not like a reset while in disconnected state