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 # pylint: disable=import-error
24 from scapy.contrib.mpls import MPLS # flake8: noqa
25 # pylint: enable=import-error
26 from nfvbench.log import LOG
27 from nfvbench.traffic_server import TRexTrafficServer
28 from nfvbench.utils import cast_integer
29 from nfvbench.utils import timeout
30 from nfvbench.utils import TimeoutError
32 # pylint: disable=import-error
33 from trex.common.services.trex_service_arp import ServiceARP
34 from trex.stl.api import bind_layers
35 from trex.stl.api import CTRexVmInsFixHwCs
36 from trex.stl.api import Dot1Q
37 from trex.stl.api import Ether
38 from trex.stl.api import FlagsField
39 from trex.stl.api import IP
40 from trex.stl.api import Packet
41 from trex.stl.api import STLClient
42 from trex.stl.api import STLError
43 from trex.stl.api import STLFlowLatencyStats
44 from trex.stl.api import STLFlowStats
45 from trex.stl.api import STLPktBuilder
46 from trex.stl.api import STLScVmRaw
47 from trex.stl.api import STLStream
48 from trex.stl.api import STLTXCont
49 from trex.stl.api import STLVmFixChecksumHw
50 from trex.stl.api import STLVmFixIpv4
51 from trex.stl.api import STLVmFlowVar
52 from trex.stl.api import STLVmFlowVarRepeatableRandom
53 from trex.stl.api import STLVmWrFlowVar
54 from trex.stl.api import ThreeBytesField
55 from trex.stl.api import UDP
56 from trex.stl.api import XByteField
58 # pylint: enable=import-error
60 from .traffic_base import AbstractTrafficGenerator
61 from .traffic_base import TrafficGeneratorException
62 from . import traffic_utils as utils
63 from .traffic_utils import IMIX_AVG_L2_FRAME_SIZE
64 from .traffic_utils import IMIX_L2_SIZES
65 from .traffic_utils import IMIX_RATIOS
70 _VXLAN_FLAGS = ['R' * 27] + ['I'] + ['R' * 5]
72 fields_desc = [FlagsField("flags", 0x08000000, 32, _VXLAN_FLAGS),
73 ThreeBytesField("vni", 0),
74 XByteField("reserved", 0x00)]
78 return self.sprintf("VXLAN (vni=%VXLAN.vni%)")
80 class TRex(AbstractTrafficGenerator):
81 """TRex traffic generator driver."""
84 CHAIN_PG_ID_MASK = 0x007F
85 PORT_PG_ID_MASK = 0x0080
86 LATENCY_PG_ID_MASK = 0x0100
88 def __init__(self, traffic_client):
90 AbstractTrafficGenerator.__init__(self, traffic_client)
94 self.chain_count = self.generator_config.service_chain_count
96 self.capture_id = None
98 self.l2_frame_size = 0
100 def get_version(self):
101 """Get the Trex version."""
102 return self.client.get_server_version() if self.client else ''
104 def get_pg_id(self, port, chain_id):
105 """Calculate the packet group IDs to use for a given port/stream type/chain_id.
108 chain_id: identifies to which chain the pg_id is associated (0 to 255)
109 return: pg_id, lat_pg_id
111 We use a bit mask to set up the 3 fields:
112 0x007F: chain ID (8 bits for a max of 128 chains)
116 pg_id = port * TRex.PORT_PG_ID_MASK | chain_id
117 return pg_id, pg_id | TRex.LATENCY_PG_ID_MASK
119 def extract_stats(self, in_stats):
120 """Extract stats from dict returned by Trex API.
122 :param in_stats: dict as returned by TRex api
124 utils.nan_replace(in_stats)
125 # LOG.debug(in_stats)
128 # port_handles should have only 2 elements: [0, 1]
129 # so (1 - ph) will be the index for the far end port
130 for ph in self.port_handle:
132 far_end_stats = in_stats[1 - ph]
135 'total_pkts': cast_integer(stats['opackets']),
136 'total_pkt_bytes': cast_integer(stats['obytes']),
137 'pkt_rate': cast_integer(stats['tx_pps']),
138 'pkt_bit_rate': cast_integer(stats['tx_bps'])
141 'total_pkts': cast_integer(stats['ipackets']),
142 'total_pkt_bytes': cast_integer(stats['ibytes']),
143 'pkt_rate': cast_integer(stats['rx_pps']),
144 'pkt_bit_rate': cast_integer(stats['rx_bps']),
145 # how many pkts were dropped in RX direction
146 # need to take the tx counter on the far end port
147 'dropped_pkts': cast_integer(
148 far_end_stats['opackets'] - stats['ipackets'])
151 self.__combine_latencies(in_stats, result[ph]['rx'], ph)
153 total_tx_pkts = result[0]['tx']['total_pkts'] + result[1]['tx']['total_pkts']
154 result["total_tx_rate"] = cast_integer(total_tx_pkts / self.config.duration_sec)
155 # actual offered tx rate in bps
156 avg_packet_size = utils.get_average_packet_size(self.l2_frame_size)
157 total_tx_bps = utils.pps_to_bps(result["total_tx_rate"], avg_packet_size)
158 result['offered_tx_rate_bps'] = total_tx_bps
159 result["flow_stats"] = in_stats["flow_stats"]
160 result["latency"] = in_stats["latency"]
163 def get_stream_stats(self, trex_stats, if_stats, latencies, chain_idx):
164 """Extract the aggregated stats for a given chain.
166 trex_stats: stats as returned by get_stats()
167 if_stats: a list of 2 interface stats to update (port 0 and 1)
168 latencies: a list of 2 Latency instances to update for this chain (port 0 and 1)
169 latencies[p] is the latency for packets sent on port p
170 if there are no latency streams, the Latency instances are not modified
171 chain_idx: chain index of the interface stats
173 The packet counts include normal and latency streams.
175 Trex returns flows stats as follows:
177 'flow_stats': {0: {'rx_bps': {0: 0, 1: 0, 'total': 0},
178 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
179 'rx_bytes': {0: nan, 1: nan, 'total': nan},
180 'rx_pkts': {0: 0, 1: 15001, 'total': 15001},
181 'rx_pps': {0: 0, 1: 0, 'total': 0},
182 'tx_bps': {0: 0, 1: 0, 'total': 0},
183 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
184 'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068},
185 'tx_pkts': {0: 15001, 1: 0, 'total': 15001},
186 'tx_pps': {0: 0, 1: 0, 'total': 0}},
187 1: {'rx_bps': {0: 0, 1: 0, 'total': 0},
188 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
189 'rx_bytes': {0: nan, 1: nan, 'total': nan},
190 'rx_pkts': {0: 0, 1: 15001, 'total': 15001},
191 'rx_pps': {0: 0, 1: 0, 'total': 0},
192 'tx_bps': {0: 0, 1: 0, 'total': 0},
193 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
194 'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068},
195 'tx_pkts': {0: 15001, 1: 0, 'total': 15001},
196 'tx_pps': {0: 0, 1: 0, 'total': 0}},
197 128: {'rx_bps': {0: 0, 1: 0, 'total': 0},
198 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
199 'rx_bytes': {0: nan, 1: nan, 'total': nan},
200 'rx_pkts': {0: 15001, 1: 0, 'total': 15001},
201 'rx_pps': {0: 0, 1: 0, 'total': 0},
202 'tx_bps': {0: 0, 1: 0, 'total': 0},
203 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
204 'tx_bytes': {0: 0, 1: 1020068, 'total': 1020068},
205 'tx_pkts': {0: 0, 1: 15001, 'total': 15001},
206 'tx_pps': {0: 0, 1: 0, 'total': 0}},etc...
208 the pg_id (0, 1, 128,...) is the key of the dict and is obtained using the
210 packet counters for a given stream sent on port p are reported as:
211 - tx_pkts[p] on port p
212 - rx_pkts[1-p] on the far end port
214 This is a tricky/critical counter transposition operation because
215 the results are grouped by port (not by stream):
216 tx_pkts_port(p=0) comes from pg_id(port=0, chain_idx)['tx_pkts'][0]
217 rx_pkts_port(p=0) comes from pg_id(port=1, chain_idx)['rx_pkts'][0]
218 tx_pkts_port(p=1) comes from pg_id(port=1, chain_idx)['tx_pkts'][1]
219 rx_pkts_port(p=1) comes from pg_id(port=0, chain_idx)['rx_pkts'][1]
221 or using a more generic formula:
222 tx_pkts_port(p) comes from pg_id(port=p, chain_idx)['tx_pkts'][p]
223 rx_pkts_port(p) comes from pg_id(port=1-p, chain_idx)['rx_pkts'][p]
225 the second formula is equivalent to
226 rx_pkts_port(1-p) comes from pg_id(port=p, chain_idx)['rx_pkts'][1-p]
228 If there are latency streams, those same counters need to be added in the same way
230 def get_latency(lval):
232 return int(round(lval))
238 for port in range(2):
239 pg_id, lat_pg_id = self.get_pg_id(port, chain_idx)
240 for pid in [pg_id, lat_pg_id]:
242 pg_stats = trex_stats['flow_stats'][pid]
243 if_stats[port].tx += pg_stats['tx_pkts'][port]
244 if_stats[1 - port].rx += pg_stats['rx_pkts'][1 - port]
248 lat = trex_stats['latency'][lat_pg_id]['latency']
249 # dropped_pkts += lat['err_cntrs']['dropped']
250 latencies[port].max_usec = get_latency(lat['total_max'])
251 if math.isnan(lat['total_min']):
252 latencies[port].min_usec = 0
253 latencies[port].avg_usec = 0
255 latencies[port].min_usec = get_latency(lat['total_min'])
256 latencies[port].avg_usec = get_latency(lat['average'])
257 # pick up the HDR histogram if present (otherwise will raise KeyError)
258 latencies[port].hdrh = lat['hdrh']
262 def __combine_latencies(self, in_stats, results, port_handle):
263 """Traverse TRex result dictionary and combines chosen latency stats.
265 example of latency dict returned by trex (2 chains):
266 'latency': {256: {'err_cntrs': {'dropped': 0,
271 'latency': {'average': 26.5,
272 'hdrh': u'HISTFAAAAEx4nJNpmSgz...bFRgxi',
273 'histogram': {20: 303,
283 257: {'err_cntrs': {'dropped': 0,
288 'latency': {'average': 29.75,
289 'hdrh': u'HISTFAAAAEV4nJN...CALilDG0=',
290 'histogram': {20: 261,
299 384: {'err_cntrs': {'dropped': 0,
304 'latency': {'average': 18.0,
305 'hdrh': u'HISTFAAAADR4nJNpm...MjCwDDxAZG',
306 'histogram': {20: 987, 30: 14},
311 385: {'err_cntrs': {'dropped': 0,
316 'latency': {'average': 19.0,
317 'hdrh': u'HISTFAAAADR4nJNpm...NkYmJgDdagfK',
318 'histogram': {20: 989, 30: 11},
323 'global': {'bad_hdr': 0, 'old_flow': 0}},
327 total_min = float("inf")
328 for chain_id in range(self.chain_count):
330 _, lat_pg_id = self.get_pg_id(port_handle, chain_id)
331 lat = in_stats['latency'][lat_pg_id]['latency']
332 # dropped_pkts += lat['err_cntrs']['dropped']
333 total_max = max(lat['total_max'], total_max)
334 total_min = min(lat['total_min'], total_min)
335 average += lat['average']
338 if total_min == float("inf"):
340 results['min_delay_usec'] = total_min
341 results['max_delay_usec'] = total_max
342 results['avg_delay_usec'] = int(average / self.chain_count)
344 def _bind_vxlan(self):
345 bind_layers(UDP, VXLAN, dport=4789)
346 bind_layers(VXLAN, Ether)
348 def _create_pkt(self, stream_cfg, l2frame_size):
349 """Create a packet of given size.
351 l2frame_size: size of the L2 frame in bytes (including the 32-bit FCS)
353 # Trex will add the FCS field, so we need to remove 4 bytes from the l2 frame size
354 frame_size = int(l2frame_size) - 4
356 if stream_cfg['vxlan'] is True:
359 pkt_base = Ether(src=stream_cfg['vtep_src_mac'], dst=stream_cfg['vtep_dst_mac'])
360 if stream_cfg['vtep_vlan'] is not None:
361 pkt_base /= Dot1Q(vlan=stream_cfg['vtep_vlan'])
362 pkt_base /= IP(src=stream_cfg['vtep_src_ip'], dst=stream_cfg['vtep_dst_ip'])
363 pkt_base /= UDP(sport=random.randint(1337, 32767), dport=4789)
364 pkt_base /= VXLAN(vni=stream_cfg['net_vni'])
365 pkt_base /= Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
366 # need to randomize the outer header UDP src port based on flow
367 vxlan_udp_src_fv = STLVmFlowVar(
368 name="vxlan_udp_src",
373 vm_param = [vxlan_udp_src_fv,
374 STLVmWrFlowVar(fv_name="vxlan_udp_src", pkt_offset="UDP.sport")]
375 elif stream_cfg['mpls'] is True:
377 pkt_base = Ether(src=stream_cfg['vtep_src_mac'], dst=stream_cfg['vtep_dst_mac'])
378 if stream_cfg['vtep_vlan'] is not None:
379 pkt_base /= Dot1Q(vlan=stream_cfg['vtep_vlan'])
380 if stream_cfg['mpls_outer_label'] is not None:
381 pkt_base /= MPLS(label=stream_cfg['mpls_outer_label'], cos=1, s=0, ttl=255)
382 if stream_cfg['mpls_inner_label'] is not None:
383 pkt_base /= MPLS(label=stream_cfg['mpls_inner_label'], cos=1, s=1, ttl=255)
384 # Flow stats and MPLS labels randomization TBD
385 pkt_base /= Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
388 pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
390 if stream_cfg['vlan_tag'] is not None:
391 pkt_base /= Dot1Q(vlan=stream_cfg['vlan_tag'])
394 if stream_cfg['udp_src_port']:
395 udp_args['sport'] = int(stream_cfg['udp_src_port'])
396 if stream_cfg['udp_port_step'] == 'random':
399 step = stream_cfg['udp_port_step']
400 udp_args['sport_step'] = int(step)
401 udp_args['sport_max'] = int(stream_cfg['udp_src_port_max'])
402 if stream_cfg['udp_dst_port']:
403 udp_args['dport'] = int(stream_cfg['udp_dst_port'])
404 if stream_cfg['udp_port_step'] == 'random':
407 step = stream_cfg['udp_port_step']
408 udp_args['dport_step'] = int(step)
409 udp_args['dport_max'] = int(stream_cfg['udp_dst_port_max'])
411 pkt_base /= IP(src=stream_cfg['ip_src_addr'], dst=stream_cfg['ip_dst_addr']) / \
412 UDP(dport=udp_args['dport'], sport=udp_args['sport'])
413 if stream_cfg['ip_src_static'] is True:
414 src_max_ip_value = stream_cfg['ip_src_addr']
416 src_max_ip_value = stream_cfg['ip_src_addr_max']
417 if stream_cfg['ip_addrs_step'] == 'random':
418 src_fv_ip = STLVmFlowVarRepeatableRandom(
420 min_value=stream_cfg['ip_src_addr'],
421 max_value=src_max_ip_value,
423 seed=random.randint(0, 32767),
424 limit=stream_cfg['ip_src_count'])
425 dst_fv_ip = STLVmFlowVarRepeatableRandom(
427 min_value=stream_cfg['ip_dst_addr'],
428 max_value=stream_cfg['ip_dst_addr_max'],
430 seed=random.randint(0, 32767),
431 limit=stream_cfg['ip_dst_count'])
433 src_fv_ip = STLVmFlowVar(
435 min_value=stream_cfg['ip_src_addr'],
436 max_value=src_max_ip_value,
439 step=stream_cfg['ip_addrs_step'])
440 dst_fv_ip = STLVmFlowVar(
442 min_value=stream_cfg['ip_dst_addr'],
443 max_value=stream_cfg['ip_dst_addr_max'],
446 step=stream_cfg['ip_addrs_step'])
448 if stream_cfg['udp_port_step'] == 'random':
449 src_fv_port = STLVmFlowVarRepeatableRandom(
451 min_value=udp_args['sport'],
452 max_value=udp_args['sport_max'],
454 seed=random.randint(0, 32767),
455 limit=stream_cfg['udp_src_count'])
456 dst_fv_port = STLVmFlowVarRepeatableRandom(
458 min_value=udp_args['dport'],
459 max_value=udp_args['dport_max'],
461 seed=random.randint(0, 32767),
462 limit=stream_cfg['udp_dst_count'])
464 src_fv_port = STLVmFlowVar(
466 min_value=udp_args['sport'],
467 max_value=udp_args['sport_max'],
470 step=udp_args['sport_step'])
471 dst_fv_port = STLVmFlowVar(
473 min_value=udp_args['dport'],
474 max_value=udp_args['dport_max'],
477 step=udp_args['dport_step'])
480 STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)),
482 STLVmWrFlowVar(fv_name="p_src", pkt_offset="UDP:{}.sport".format(encap_level)),
484 STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level)),
486 STLVmWrFlowVar(fv_name="p_dst", pkt_offset="UDP:{}.dport".format(encap_level)),
488 # Use HW Offload to calculate the outter IP/UDP packet
489 vm_param.append(STLVmFixChecksumHw(l3_offset="IP:0",
491 l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP))
492 # Use software to fix the inner IP/UDP payload for VxLAN packets
494 vm_param.append(STLVmFixIpv4(offset="IP:1"))
495 pad = max(0, frame_size - len(pkt_base)) * 'x'
497 return STLPktBuilder(pkt=pkt_base / pad,
498 vm=STLScVmRaw(vm_param, cache_size=int(self.config.cache_size)))
500 def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True,
502 """Create a list of streams corresponding to a given chain and stream config.
504 port: port where the streams originate (0 or 1)
505 chain_id: the chain to which the streams are associated to
506 stream_cfg: stream configuration
507 l2frame: L2 frame size (including 4-byte FCS) or 'IMIX'
508 latency: if True also create a latency stream
509 e2e: True if performing "end to end" connectivity check
512 pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
513 if self.config.no_flow_stats:
514 LOG.info("Traffic flow statistics are disabled.")
515 if l2frame == 'IMIX':
516 for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES):
517 pkt = self._create_pkt(stream_cfg, l2_frame_size)
518 if e2e or stream_cfg['mpls']:
519 streams.append(STLStream(packet=pkt,
520 mode=STLTXCont(pps=ratio)))
522 if stream_cfg['vxlan'] is True:
523 streams.append(STLStream(packet=pkt,
524 flow_stats=STLFlowStats(pg_id=pg_id,
526 if not self.config.no_flow_stats else None,
527 mode=STLTXCont(pps=ratio)))
529 streams.append(STLStream(packet=pkt,
530 flow_stats=STLFlowStats(pg_id=pg_id)
531 if not self.config.no_flow_stats else None,
532 mode=STLTXCont(pps=ratio)))
535 # for IMIX, the latency packets have the average IMIX packet size
536 pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE)
539 l2frame_size = int(l2frame)
540 pkt = self._create_pkt(stream_cfg, l2frame_size)
541 if e2e or stream_cfg['mpls']:
542 streams.append(STLStream(packet=pkt,
543 # Flow stats is disabled for MPLS now
544 # flow_stats=STLFlowStats(pg_id=pg_id),
547 if stream_cfg['vxlan'] is True:
548 streams.append(STLStream(packet=pkt,
549 flow_stats=STLFlowStats(pg_id=pg_id,
551 if not self.config.no_flow_stats else None,
554 streams.append(STLStream(packet=pkt,
555 flow_stats=STLFlowStats(pg_id=pg_id)
556 if not self.config.no_flow_stats else None,
558 # for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging
559 # without vlan, the min l2 frame size is 64
561 # This only applies to the latency stream
562 if latency and stream_cfg['vlan_tag'] and l2frame_size < 68:
563 pkt = self._create_pkt(stream_cfg, 68)
566 if self.config.no_latency_stats:
567 LOG.info("Latency flow statistics are disabled.")
568 if stream_cfg['vxlan'] is True:
569 streams.append(STLStream(packet=pkt,
570 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id,
572 if not self.config.no_latency_stats else None,
573 mode=STLTXCont(pps=self.LATENCY_PPS)))
575 streams.append(STLStream(packet=pkt,
576 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id)
577 if not self.config.no_latency_stats else None,
578 mode=STLTXCont(pps=self.LATENCY_PPS)))
582 def __connect(self, client):
585 def __connect_after_start(self):
586 # after start, Trex may take a bit of time to initialize
587 # so we need to retry a few times
588 for it in range(self.config.generic_retry_count):
591 self.client.connect()
593 except Exception as ex:
594 if it == (self.config.generic_retry_count - 1):
596 LOG.info("Retrying connection to TRex (%s)...", ex.msg)
599 """Connect to the TRex server."""
600 server_ip = self.generator_config.ip
601 LOG.info("Connecting to TRex (%s)...", server_ip)
603 # Connect to TRex server
604 self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port,
605 async_port=self.generator_config.zmq_pub_port)
607 self.__connect(self.client)
608 if server_ip == '127.0.0.1':
609 config_updated = self.__check_config()
610 if config_updated or self.config.restart:
612 except (TimeoutError, STLError) as e:
613 if server_ip == '127.0.0.1':
614 self.__start_local_server()
616 raise TrafficGeneratorException(e.message)
618 ports = list(self.generator_config.ports)
619 self.port_handle = ports
621 self.client.reset(ports)
622 # Read HW information from each port
623 # this returns an array of dict (1 per port)
625 Example of output for Intel XL710
626 [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True,
627 'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes',
628 u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']},
629 u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE',
630 u'description': u'Ethernet Controller XL710 for 40GbE QSFP+',
631 'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-',
632 u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match',
633 'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0',
634 'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off',
635 'layer_mode': 'Ethernet', u'numa': 0}, ...]
637 self.port_info = self.client.get_port_info(ports)
638 LOG.info('Connected to TRex')
639 for id, port in enumerate(self.port_info):
640 LOG.info(' Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s',
641 id, port['description'], port['speed'], port['src_mac'],
642 port['pci_addr'], port['driver'])
643 # Make sure the 2 ports have the same speed
644 if self.port_info[0]['speed'] != self.port_info[1]['speed']:
645 raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' %
646 (self.port_info[0]['speed'],
647 self.port_info[1]['speed']))
649 def __start_local_server(self):
651 LOG.info("Starting TRex ...")
652 self.__start_server()
653 self.__connect_after_start()
654 except (TimeoutError, STLError) as e:
655 LOG.error('Cannot connect to TRex')
656 LOG.error(traceback.format_exc())
657 logpath = '/tmp/trex.log'
658 if os.path.isfile(logpath):
659 # Wait for TRex to finish writing error message
661 for _ in range(self.config.generic_retry_count):
662 size = os.path.getsize(logpath)
663 if size == last_size:
664 # probably not writing anymore
668 with open(logpath, 'r') as f:
672 raise TrafficGeneratorException(message)
674 def __start_server(self):
675 server = TRexTrafficServer()
676 server.run_server(self.generator_config)
678 def __check_config(self):
679 server = TRexTrafficServer()
680 return server.check_config_updated(self.generator_config)
683 LOG.info("Restarting TRex ...")
685 # Wait for server stopped
686 for _ in range(self.config.generic_retry_count):
688 if not self.client.is_connected():
689 LOG.info("TRex is stopped...")
691 self.__start_local_server()
693 def __stop_server(self):
694 if self.generator_config.ip == '127.0.0.1':
695 ports = self.client.get_acquired_ports()
696 LOG.info('Release ports %s and stopping TRex...', ports)
699 self.client.release(ports=ports)
700 self.client.server_shutdown()
701 except STLError as e:
702 LOG.warning('Unable to stop TRex. Error: %s', e)
704 LOG.info('Using remote TRex. Unable to stop TRex')
706 def resolve_arp(self):
707 """Resolve all configured remote IP addresses.
709 return: None if ARP failed to resolve for all IP addresses
710 else a dict of list of dest macs indexed by port#
711 the dest macs in the list are indexed by the chain id
713 self.client.set_service_mode(ports=self.port_handle)
714 LOG.info('Polling ARP until successful...')
716 for port, device in zip(self.port_handle, self.generator_config.devices):
717 # there should be 1 stream config per chain
718 stream_configs = device.get_stream_configs()
719 chain_count = len(stream_configs)
720 ctx = self.client.create_service_ctx(port=port)
721 # all dest macs on this port indexed by chain ID
722 dst_macs = [None] * chain_count
724 # the index in the list is the chain id
725 if self.config.vxlan or self.config.mpls:
728 src_ip=device.vtep_src_ip,
729 dst_ip=device.vtep_dst_ip,
730 vlan=device.vtep_vlan)
731 for cfg in stream_configs
736 src_ip=cfg['ip_src_tg_gw'],
737 dst_ip=cfg['mac_discovery_gw'],
738 # will be None if no vlan tagging
739 vlan=cfg['vlan_tag'])
740 for cfg in stream_configs
743 for attempt in range(self.config.generic_retry_count):
747 LOG.error(traceback.format_exc())
751 for chain_id, mac in enumerate(dst_macs):
753 arp_record = arps[chain_id].get_record()
754 if arp_record.dst_mac:
755 dst_macs[chain_id] = arp_record.dst_mac
757 LOG.info(' ARP: port=%d chain=%d src IP=%s dst IP=%s -> MAC=%s',
760 arp_record.dst_ip, arp_record.dst_mac)
762 unresolved.append(arp_record.dst_ip)
763 if dst_macs_count == chain_count:
764 arp_dest_macs[port] = dst_macs
765 LOG.info('ARP resolved successfully for port %s', port)
769 LOG.info('Retrying ARP for: %s (retry %d/%d)',
770 unresolved, retry, self.config.generic_retry_count)
771 if retry < self.config.generic_retry_count:
772 time.sleep(self.config.generic_poll_sec)
774 LOG.error('ARP timed out for port %s (resolved %d out of %d)',
780 # if the capture from the TRex console was started before the arp request step,
781 # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
782 if not self.config.service_mode:
783 self.client.set_service_mode(ports=self.port_handle, enabled=False)
784 if len(arp_dest_macs) == len(self.port_handle):
788 def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency):
789 """Check if rate provided by user is above requirements. Applies only if latency is True."""
790 intf_speed = self.generator_config.intf_speed
796 r = utils.convert_rates(l2frame_size, rate, intf_speed)
797 total_rate += int(r['rate_pps'])
800 total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
801 # rate must be enough for latency stream and at least 1 pps for base stream per chain
802 required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult
803 result = utils.convert_rates(l2frame_size,
804 {'rate_pps': required_rate},
806 result['result'] = total_rate >= required_rate
809 return {'result': True}
811 def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
812 """Program all the streams in Trex server.
814 l2frame_size: L2 frame size or IMIX
815 rates: a list of 2 rates to run each direction
816 each rate is a dict like {'rate_pps': '10kpps'}
817 bidirectional: True if bidirectional
818 latency: True if latency measurement is needed
819 e2e: True if performing "end to end" connectivity check
821 r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
823 raise TrafficGeneratorException(
824 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.'
825 .format(pps=r['rate_pps'],
827 load=r['rate_percent']))
828 self.l2_frame_size = l2frame_size
829 # a dict of list of streams indexed by port#
830 # in case of fixed size, has self.chain_count * 2 * 2 streams
831 # (1 normal + 1 latency stream per direction per chain)
832 # for IMIX, has self.chain_count * 2 * 4 streams
833 # (3 normal + 1 latency stream per direction per chain)
835 for port in self.port_handle:
836 streamblock[port] = []
837 stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
838 self.rates = [utils.to_rate_str(rate) for rate in rates]
839 for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
840 streamblock[0].extend(self.generate_streams(self.port_handle[0],
846 if len(self.rates) > 1:
847 streamblock[1].extend(self.generate_streams(self.port_handle[1],
851 latency=bidirectional and latency,
854 for port in self.port_handle:
855 if self.config.vxlan:
856 self.client.set_port_attr(ports=port, vxlan_fs=[4789])
858 self.client.set_port_attr(ports=port, vxlan_fs=None)
859 self.client.add_streams(streamblock[port], ports=port)
860 LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
862 def clear_streamblock(self):
863 """Clear all streams from TRex."""
865 self.client.reset(self.port_handle)
866 LOG.info('Cleared all existing streams')
869 """Get stats from Trex."""
870 stats = self.client.get_stats()
871 return self.extract_stats(stats)
874 """Return the Trex local port MAC addresses.
876 return: a list of MAC addresses indexed by the port#
878 return [port['src_mac'] for port in self.port_info]
880 def get_port_speed_gbps(self):
881 """Return the Trex local port MAC addresses.
883 return: a list of speed in Gbps indexed by the port#
885 return [port['speed'] for port in self.port_info]
887 def clear_stats(self):
888 """Clear all stats in the traffic gneerator."""
890 self.client.clear_stats()
892 def start_traffic(self):
893 """Start generating traffic in all ports."""
894 for port, rate in zip(self.port_handle, self.rates):
895 self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True)
897 def stop_traffic(self):
898 """Stop generating traffic."""
899 self.client.stop(ports=self.port_handle)
901 def start_capture(self):
902 """Capture all packets on both ports that are unicast to us."""
905 # Need to filter out unwanted packets so we do not end up counting
906 # src MACs of frames that are not unicast to us
907 src_mac_list = self.get_macs()
908 bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
909 # ports must be set in service in order to enable capture
910 self.client.set_service_mode(ports=self.port_handle)
911 self.capture_id = self.client.start_capture \
912 (rx_ports=self.port_handle, bpf_filter=bpf_filter)
914 def fetch_capture_packets(self):
915 """Fetch capture packets in capture mode."""
917 self.packet_list = []
918 self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
919 output=self.packet_list)
921 def stop_capture(self):
922 """Stop capturing packets."""
924 self.client.stop_capture(capture_id=self.capture_id['id'])
925 self.capture_id = None
926 # if the capture from TRex console was started before the connectivity step,
927 # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
928 if not self.config.service_mode:
929 self.client.set_service_mode(ports=self.port_handle, enabled=False)
932 """Cleanup Trex driver."""
935 self.client.reset(self.port_handle)
936 self.client.disconnect()
938 # TRex does not like a reset while in disconnected state
941 def set_service_mode(self, enabled=True):
942 """Enable/disable the 'service_mode'."""
943 self.client.set_service_mode(ports=self.port_handle, enabled=enabled)