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'])
248 # pick up the HDR histogram if present (otherwise will raise KeyError)
249 latencies[port].hdrh = lat['hdrh']
253 def __combine_latencies(self, in_stats, results, port_handle):
254 """Traverse TRex result dictionary and combines chosen latency stats.
256 example of latency dict returned by trex (2 chains):
257 'latency': {256: {'err_cntrs': {'dropped': 0,
262 'latency': {'average': 26.5,
263 'hdrh': u'HISTFAAAAEx4nJNpmSgz...bFRgxi',
264 'histogram': {20: 303,
274 257: {'err_cntrs': {'dropped': 0,
279 'latency': {'average': 29.75,
280 'hdrh': u'HISTFAAAAEV4nJN...CALilDG0=',
281 'histogram': {20: 261,
290 384: {'err_cntrs': {'dropped': 0,
295 'latency': {'average': 18.0,
296 'hdrh': u'HISTFAAAADR4nJNpm...MjCwDDxAZG',
297 'histogram': {20: 987, 30: 14},
302 385: {'err_cntrs': {'dropped': 0,
307 'latency': {'average': 19.0,
308 'hdrh': u'HISTFAAAADR4nJNpm...NkYmJgDdagfK',
309 'histogram': {20: 989, 30: 11},
314 'global': {'bad_hdr': 0, 'old_flow': 0}},
318 total_min = float("inf")
319 for chain_id in range(self.chain_count):
321 _, lat_pg_id = self.get_pg_id(port_handle, chain_id)
322 lat = in_stats['latency'][lat_pg_id]['latency']
323 # dropped_pkts += lat['err_cntrs']['dropped']
324 total_max = max(lat['total_max'], total_max)
325 total_min = min(lat['total_min'], total_min)
326 average += lat['average']
329 if total_min == float("inf"):
331 results['min_delay_usec'] = total_min
332 results['max_delay_usec'] = total_max
333 results['avg_delay_usec'] = int(average / self.chain_count)
335 def _bind_vxlan(self):
336 bind_layers(UDP, VXLAN, dport=4789)
337 bind_layers(VXLAN, Ether)
339 def _create_pkt(self, stream_cfg, l2frame_size):
340 """Create a packet of given size.
342 l2frame_size: size of the L2 frame in bytes (including the 32-bit FCS)
344 # Trex will add the FCS field, so we need to remove 4 bytes from the l2 frame size
345 frame_size = int(l2frame_size) - 4
347 if stream_cfg['vxlan'] is True:
350 pkt_base = Ether(src=stream_cfg['vtep_src_mac'], dst=stream_cfg['vtep_dst_mac'])
351 if stream_cfg['vtep_vlan'] is not None:
352 pkt_base /= Dot1Q(vlan=stream_cfg['vtep_vlan'])
353 pkt_base /= IP(src=stream_cfg['vtep_src_ip'], dst=stream_cfg['vtep_dst_ip'])
354 pkt_base /= UDP(sport=random.randint(1337, 32767), dport=4789)
355 pkt_base /= VXLAN(vni=stream_cfg['net_vni'])
356 pkt_base /= Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
357 # need to randomize the outer header UDP src port based on flow
358 vxlan_udp_src_fv = STLVmFlowVar(
359 name="vxlan_udp_src",
364 vm_param = [vxlan_udp_src_fv,
365 STLVmWrFlowVar(fv_name="vxlan_udp_src", pkt_offset="UDP.sport")]
368 pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
370 if stream_cfg['vlan_tag'] is not None:
371 pkt_base /= Dot1Q(vlan=stream_cfg['vlan_tag'])
374 if stream_cfg['udp_src_port']:
375 udp_args['sport'] = int(stream_cfg['udp_src_port'])
376 if stream_cfg['udp_dst_port']:
377 udp_args['dport'] = int(stream_cfg['udp_dst_port'])
378 pkt_base /= IP() / UDP(**udp_args)
380 if stream_cfg['ip_addrs_step'] == 'random':
381 src_fv = STLVmFlowVarRepeatableRandom(
383 min_value=stream_cfg['ip_src_addr'],
384 max_value=stream_cfg['ip_src_addr_max'],
386 seed=random.randint(0, 32767),
387 limit=stream_cfg['ip_src_count'])
388 dst_fv = STLVmFlowVarRepeatableRandom(
390 min_value=stream_cfg['ip_dst_addr'],
391 max_value=stream_cfg['ip_dst_addr_max'],
393 seed=random.randint(0, 32767),
394 limit=stream_cfg['ip_dst_count'])
396 src_fv = STLVmFlowVar(
398 min_value=stream_cfg['ip_src_addr'],
399 max_value=stream_cfg['ip_src_addr'],
402 step=stream_cfg['ip_addrs_step'])
403 dst_fv = STLVmFlowVar(
405 min_value=stream_cfg['ip_dst_addr'],
406 max_value=stream_cfg['ip_dst_addr_max'],
409 step=stream_cfg['ip_addrs_step'])
413 STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)),
415 STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level))
418 for encap in range(int(encap_level), -1, -1):
419 # Fixing the checksums for all encap levels
420 vm_param.append(STLVmFixChecksumHw(l3_offset="IP:{}".format(encap),
421 l4_offset="UDP:{}".format(encap),
422 l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP))
423 pad = max(0, frame_size - len(pkt_base)) * 'x'
425 return STLPktBuilder(pkt=pkt_base / pad,
426 vm=STLScVmRaw(vm_param, cache_size=int(self.config.cache_size)))
428 def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True,
430 """Create a list of streams corresponding to a given chain and stream config.
432 port: port where the streams originate (0 or 1)
433 chain_id: the chain to which the streams are associated to
434 stream_cfg: stream configuration
435 l2frame: L2 frame size (including 4-byte FCS) or 'IMIX'
436 latency: if True also create a latency stream
437 e2e: True if performing "end to end" connectivity check
440 pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
441 if self.config.no_flow_stats:
442 LOG.info("Traffic flow statistics are disabled.")
443 if l2frame == 'IMIX':
444 for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES):
445 pkt = self._create_pkt(stream_cfg, l2_frame_size)
447 streams.append(STLStream(packet=pkt,
448 mode=STLTXCont(pps=ratio)))
450 if stream_cfg['vxlan'] is True:
451 streams.append(STLStream(packet=pkt,
452 flow_stats=STLFlowStats(pg_id=pg_id,
454 if not self.config.no_flow_stats else None,
455 mode=STLTXCont(pps=ratio)))
457 streams.append(STLStream(packet=pkt,
458 flow_stats=STLFlowStats(pg_id=pg_id)
459 if not self.config.no_flow_stats else None,
460 mode=STLTXCont(pps=ratio)))
463 # for IMIX, the latency packets have the average IMIX packet size
464 pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE)
467 l2frame_size = int(l2frame)
468 pkt = self._create_pkt(stream_cfg, l2frame_size)
470 streams.append(STLStream(packet=pkt,
473 if stream_cfg['vxlan'] is True:
474 streams.append(STLStream(packet=pkt,
475 flow_stats=STLFlowStats(pg_id=pg_id,
477 if not self.config.no_flow_stats else None,
480 streams.append(STLStream(packet=pkt,
481 flow_stats=STLFlowStats(pg_id=pg_id)
482 if not self.config.no_flow_stats else None,
484 # for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging
485 # without vlan, the min l2 frame size is 64
487 # This only applies to the latency stream
488 if latency and stream_cfg['vlan_tag'] and l2frame_size < 68:
489 pkt = self._create_pkt(stream_cfg, 68)
492 if self.config.no_latency_stats:
493 LOG.info("Latency flow statistics are disabled.")
494 if stream_cfg['vxlan'] is True:
495 streams.append(STLStream(packet=pkt,
496 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id,
498 if not self.config.no_latency_stats else None,
499 mode=STLTXCont(pps=self.LATENCY_PPS)))
501 streams.append(STLStream(packet=pkt,
502 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id)
503 if not self.config.no_latency_stats else None,
504 mode=STLTXCont(pps=self.LATENCY_PPS)))
508 def __connect(self, client):
511 def __connect_after_start(self):
512 # after start, Trex may take a bit of time to initialize
513 # so we need to retry a few times
514 for it in xrange(self.config.generic_retry_count):
517 self.client.connect()
519 except Exception as ex:
520 if it == (self.config.generic_retry_count - 1):
522 LOG.info("Retrying connection to TRex (%s)...", ex.message)
525 """Connect to the TRex server."""
526 server_ip = self.generator_config.ip
527 LOG.info("Connecting to TRex (%s)...", server_ip)
529 # Connect to TRex server
530 self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port,
531 async_port=self.generator_config.zmq_pub_port)
533 self.__connect(self.client)
534 if server_ip == '127.0.0.1':
535 config_updated = self.__check_config()
536 if config_updated or self.config.restart:
538 except (TimeoutError, STLError) as e:
539 if server_ip == '127.0.0.1':
540 self.__start_local_server()
542 raise TrafficGeneratorException(e.message)
544 ports = list(self.generator_config.ports)
545 self.port_handle = ports
547 self.client.reset(ports)
548 # Read HW information from each port
549 # this returns an array of dict (1 per port)
551 Example of output for Intel XL710
552 [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True,
553 'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes',
554 u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']},
555 u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE',
556 u'description': u'Ethernet Controller XL710 for 40GbE QSFP+',
557 'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-',
558 u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match',
559 'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0',
560 'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off',
561 'layer_mode': 'Ethernet', u'numa': 0}, ...]
563 self.port_info = self.client.get_port_info(ports)
564 LOG.info('Connected to TRex')
565 for id, port in enumerate(self.port_info):
566 LOG.info(' Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s',
567 id, port['description'], port['speed'], port['src_mac'],
568 port['pci_addr'], port['driver'])
569 # Make sure the 2 ports have the same speed
570 if self.port_info[0]['speed'] != self.port_info[1]['speed']:
571 raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' %
572 (self.port_info[0]['speed'],
573 self.port_info[1]['speed']))
575 def __start_local_server(self):
577 LOG.info("Starting TRex ...")
578 self.__start_server()
579 self.__connect_after_start()
580 except (TimeoutError, STLError) as e:
581 LOG.error('Cannot connect to TRex')
582 LOG.error(traceback.format_exc())
583 logpath = '/tmp/trex.log'
584 if os.path.isfile(logpath):
585 # Wait for TRex to finish writing error message
587 for _ in xrange(self.config.generic_retry_count):
588 size = os.path.getsize(logpath)
589 if size == last_size:
590 # probably not writing anymore
594 with open(logpath, 'r') as f:
598 raise TrafficGeneratorException(message)
600 def __start_server(self):
601 server = TRexTrafficServer()
602 server.run_server(self.generator_config)
604 def __check_config(self):
605 server = TRexTrafficServer()
606 return server.check_config_updated(self.generator_config)
609 LOG.info("Restarting TRex ...")
611 # Wait for server stopped
612 for _ in xrange(self.config.generic_retry_count):
614 if not self.client.is_connected():
615 LOG.info("TRex is stopped...")
617 self.__start_local_server()
619 def __stop_server(self):
620 if self.generator_config.ip == '127.0.0.1':
621 ports = self.client.get_acquired_ports()
622 LOG.info('Release ports %s and stopping TRex...', ports)
625 self.client.release(ports=ports)
626 self.client.server_shutdown()
627 except STLError as e:
628 LOG.warn('Unable to stop TRex. Error: %s', e)
630 LOG.info('Using remote TRex. Unable to stop TRex')
632 def resolve_arp(self):
633 """Resolve all configured remote IP addresses.
635 return: None if ARP failed to resolve for all IP addresses
636 else a dict of list of dest macs indexed by port#
637 the dest macs in the list are indexed by the chain id
639 self.client.set_service_mode(ports=self.port_handle)
640 LOG.info('Polling ARP until successful...')
642 for port, device in zip(self.port_handle, self.generator_config.devices):
643 # there should be 1 stream config per chain
644 stream_configs = device.get_stream_configs()
645 chain_count = len(stream_configs)
646 ctx = self.client.create_service_ctx(port=port)
647 # all dest macs on this port indexed by chain ID
648 dst_macs = [None] * chain_count
650 # the index in the list is the chain id
651 if self.config.vxlan:
654 src_ip=device.vtep_src_ip,
655 dst_ip=device.vtep_dst_ip,
656 vlan=device.vtep_vlan)
657 for cfg in stream_configs
662 src_ip=cfg['ip_src_tg_gw'],
663 dst_ip=cfg['mac_discovery_gw'],
664 # will be None if no vlan tagging
665 vlan=cfg['vlan_tag'])
666 for cfg in stream_configs
669 for attempt in range(self.config.generic_retry_count):
673 LOG.error(traceback.format_exc())
677 for chain_id, mac in enumerate(dst_macs):
679 arp_record = arps[chain_id].get_record()
680 if arp_record.dst_mac:
681 dst_macs[chain_id] = arp_record.dst_mac
683 LOG.info(' ARP: port=%d chain=%d src IP=%s dst IP=%s -> MAC=%s',
686 arp_record.dst_ip, arp_record.dst_mac)
688 unresolved.append(arp_record.dst_ip)
689 if dst_macs_count == chain_count:
690 arp_dest_macs[port] = dst_macs
691 LOG.info('ARP resolved successfully for port %s', port)
695 LOG.info('Retrying ARP for: %s (retry %d/%d)',
696 unresolved, retry, self.config.generic_retry_count)
697 if retry < self.config.generic_retry_count:
698 time.sleep(self.config.generic_poll_sec)
700 LOG.error('ARP timed out for port %s (resolved %d out of %d)',
706 # if the capture from the TRex console was started before the arp request step,
707 # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
708 if not self.config.service_mode:
709 self.client.set_service_mode(ports=self.port_handle, enabled=False)
710 if len(arp_dest_macs) == len(self.port_handle):
714 def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency):
715 """Check if rate provided by user is above requirements. Applies only if latency is True."""
716 intf_speed = self.generator_config.intf_speed
722 r = utils.convert_rates(l2frame_size, rate, intf_speed)
723 total_rate += int(r['rate_pps'])
726 total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
727 # rate must be enough for latency stream and at least 1 pps for base stream per chain
728 required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult
729 result = utils.convert_rates(l2frame_size,
730 {'rate_pps': required_rate},
732 result['result'] = total_rate >= required_rate
735 return {'result': True}
737 def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
738 """Program all the streams in Trex server.
740 l2frame_size: L2 frame size or IMIX
741 rates: a list of 2 rates to run each direction
742 each rate is a dict like {'rate_pps': '10kpps'}
743 bidirectional: True if bidirectional
744 latency: True if latency measurement is needed
745 e2e: True if performing "end to end" connectivity check
747 r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
749 raise TrafficGeneratorException(
750 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.'
751 .format(pps=r['rate_pps'],
753 load=r['rate_percent']))
754 # a dict of list of streams indexed by port#
755 # in case of fixed size, has self.chain_count * 2 * 2 streams
756 # (1 normal + 1 latency stream per direction per chain)
757 # for IMIX, has self.chain_count * 2 * 4 streams
758 # (3 normal + 1 latency stream per direction per chain)
760 for port in self.port_handle:
761 streamblock[port] = []
762 stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
763 self.rates = [utils.to_rate_str(rate) for rate in rates]
764 for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
765 streamblock[0].extend(self.generate_streams(self.port_handle[0],
771 if len(self.rates) > 1:
772 streamblock[1].extend(self.generate_streams(self.port_handle[1],
776 latency=bidirectional and latency,
779 for port in self.port_handle:
780 if self.config.vxlan:
781 self.client.set_port_attr(ports=port, vxlan_fs=[4789])
783 self.client.set_port_attr(ports=port, vxlan_fs=None)
784 self.client.add_streams(streamblock[port], ports=port)
785 LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
787 def clear_streamblock(self):
788 """Clear all streams from TRex."""
790 self.client.reset(self.port_handle)
791 LOG.info('Cleared all existing streams')
794 """Get stats from Trex."""
795 stats = self.client.get_stats()
796 return self.extract_stats(stats)
799 """Return the Trex local port MAC addresses.
801 return: a list of MAC addresses indexed by the port#
803 return [port['src_mac'] for port in self.port_info]
805 def get_port_speed_gbps(self):
806 """Return the Trex local port MAC addresses.
808 return: a list of speed in Gbps indexed by the port#
810 return [port['speed'] for port in self.port_info]
812 def clear_stats(self):
813 """Clear all stats in the traffic gneerator."""
815 self.client.clear_stats()
817 def start_traffic(self):
818 """Start generating traffic in all ports."""
819 for port, rate in zip(self.port_handle, self.rates):
820 self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True)
822 def stop_traffic(self):
823 """Stop generating traffic."""
824 self.client.stop(ports=self.port_handle)
826 def start_capture(self):
827 """Capture all packets on both ports that are unicast to us."""
830 # Need to filter out unwanted packets so we do not end up counting
831 # src MACs of frames that are not unicast to us
832 src_mac_list = self.get_macs()
833 bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
834 # ports must be set in service in order to enable capture
835 self.client.set_service_mode(ports=self.port_handle)
836 self.capture_id = self.client.start_capture(rx_ports=self.port_handle,
837 bpf_filter=bpf_filter)
839 def fetch_capture_packets(self):
840 """Fetch capture packets in capture mode."""
842 self.packet_list = []
843 self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
844 output=self.packet_list)
846 def stop_capture(self):
847 """Stop capturing packets."""
849 self.client.stop_capture(capture_id=self.capture_id['id'])
850 self.capture_id = None
851 # if the capture from TRex console was started before the connectivity step,
852 # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
853 if not self.config.service_mode:
854 self.client.set_service_mode(ports=self.port_handle, enabled=False)
857 """Cleanup Trex driver."""
860 self.client.reset(self.port_handle)
861 self.client.disconnect()
863 # TRex does not like a reset while in disconnected state
866 def set_service_mode(self, enabled=True):
867 """Enable/disable the 'service_mode'."""
868 self.client.set_service_mode(ports=self.port_handle, enabled=enabled)