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 udp_args['sport_step'] = int(stream_cfg['udp_port_step'])
397 udp_args['sport_max'] = int(stream_cfg['udp_src_port_max'])
398 if stream_cfg['udp_dst_port']:
399 udp_args['dport'] = int(stream_cfg['udp_dst_port'])
400 udp_args['dport_step'] = int(stream_cfg['udp_port_step'])
401 udp_args['dport_max'] = int(stream_cfg['udp_dst_port_max'])
403 pkt_base /= IP(src=stream_cfg['ip_src_addr'], dst=stream_cfg['ip_dst_addr']) / \
404 UDP(dport=udp_args['dport'], sport=udp_args['sport'])
405 if stream_cfg['ip_src_static'] is True:
406 src_max_ip_value = stream_cfg['ip_src_addr']
408 src_max_ip_value = stream_cfg['ip_src_addr_max']
409 if stream_cfg['ip_addrs_step'] == 'random':
410 src_fv_ip = STLVmFlowVarRepeatableRandom(
412 min_value=stream_cfg['ip_src_addr'],
413 max_value=src_max_ip_value,
415 seed=random.randint(0, 32767),
416 limit=stream_cfg['ip_src_count'])
417 dst_fv_ip = STLVmFlowVarRepeatableRandom(
419 min_value=stream_cfg['ip_dst_addr'],
420 max_value=stream_cfg['ip_dst_addr_max'],
422 seed=random.randint(0, 32767),
423 limit=stream_cfg['ip_dst_count'])
425 src_fv_ip = STLVmFlowVar(
427 min_value=stream_cfg['ip_src_addr'],
428 max_value=src_max_ip_value,
431 step=stream_cfg['ip_addrs_step'])
432 dst_fv_ip = STLVmFlowVar(
434 min_value=stream_cfg['ip_dst_addr'],
435 max_value=stream_cfg['ip_dst_addr_max'],
438 step=stream_cfg['ip_addrs_step'])
440 if stream_cfg['udp_port_step'] == 'random':
441 src_fv_port = STLVmFlowVarRepeatableRandom(
443 min_value=udp_args['sport'],
444 max_value=udp_args['sport_max'],
446 seed=random.randint(0, 32767),
447 limit=udp_args['udp_src_count'])
448 dst_fv_port = STLVmFlowVarRepeatableRandom(
450 min_value=udp_args['dport'],
451 max_value=udp_args['dport_max'],
453 seed=random.randint(0, 32767),
454 limit=stream_cfg['udp_dst_count'])
456 src_fv_port = STLVmFlowVar(
458 min_value=udp_args['sport'],
459 max_value=udp_args['sport_max'],
462 step=udp_args['sport_step'])
463 dst_fv_port = STLVmFlowVar(
465 min_value=udp_args['dport'],
466 max_value=udp_args['dport_max'],
469 step=udp_args['dport_step'])
472 STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)),
474 STLVmWrFlowVar(fv_name="p_src", pkt_offset="UDP:{}.sport".format(encap_level)),
476 STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level)),
478 STLVmWrFlowVar(fv_name="p_dst", pkt_offset="UDP:{}.dport".format(encap_level)),
480 # Use HW Offload to calculate the outter IP/UDP packet
481 vm_param.append(STLVmFixChecksumHw(l3_offset="IP:0",
483 l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP))
484 # Use software to fix the inner IP/UDP payload for VxLAN packets
486 vm_param.append(STLVmFixIpv4(offset="IP:1"))
487 pad = max(0, frame_size - len(pkt_base)) * 'x'
489 return STLPktBuilder(pkt=pkt_base / pad,
490 vm=STLScVmRaw(vm_param, cache_size=int(self.config.cache_size)))
492 def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True,
494 """Create a list of streams corresponding to a given chain and stream config.
496 port: port where the streams originate (0 or 1)
497 chain_id: the chain to which the streams are associated to
498 stream_cfg: stream configuration
499 l2frame: L2 frame size (including 4-byte FCS) or 'IMIX'
500 latency: if True also create a latency stream
501 e2e: True if performing "end to end" connectivity check
504 pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
505 if self.config.no_flow_stats:
506 LOG.info("Traffic flow statistics are disabled.")
507 if l2frame == 'IMIX':
508 for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES):
509 pkt = self._create_pkt(stream_cfg, l2_frame_size)
510 if e2e or stream_cfg['mpls']:
511 streams.append(STLStream(packet=pkt,
512 mode=STLTXCont(pps=ratio)))
514 if stream_cfg['vxlan'] is True:
515 streams.append(STLStream(packet=pkt,
516 flow_stats=STLFlowStats(pg_id=pg_id,
518 if not self.config.no_flow_stats else None,
519 mode=STLTXCont(pps=ratio)))
521 streams.append(STLStream(packet=pkt,
522 flow_stats=STLFlowStats(pg_id=pg_id)
523 if not self.config.no_flow_stats else None,
524 mode=STLTXCont(pps=ratio)))
527 # for IMIX, the latency packets have the average IMIX packet size
528 pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE)
531 l2frame_size = int(l2frame)
532 pkt = self._create_pkt(stream_cfg, l2frame_size)
533 if e2e or stream_cfg['mpls']:
534 streams.append(STLStream(packet=pkt,
535 # Flow stats is disabled for MPLS now
536 # flow_stats=STLFlowStats(pg_id=pg_id),
539 if stream_cfg['vxlan'] is True:
540 streams.append(STLStream(packet=pkt,
541 flow_stats=STLFlowStats(pg_id=pg_id,
543 if not self.config.no_flow_stats else None,
546 streams.append(STLStream(packet=pkt,
547 flow_stats=STLFlowStats(pg_id=pg_id)
548 if not self.config.no_flow_stats else None,
550 # for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging
551 # without vlan, the min l2 frame size is 64
553 # This only applies to the latency stream
554 if latency and stream_cfg['vlan_tag'] and l2frame_size < 68:
555 pkt = self._create_pkt(stream_cfg, 68)
558 if self.config.no_latency_stats:
559 LOG.info("Latency flow statistics are disabled.")
560 if stream_cfg['vxlan'] is True:
561 streams.append(STLStream(packet=pkt,
562 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id,
564 if not self.config.no_latency_stats else None,
565 mode=STLTXCont(pps=self.LATENCY_PPS)))
567 streams.append(STLStream(packet=pkt,
568 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id)
569 if not self.config.no_latency_stats else None,
570 mode=STLTXCont(pps=self.LATENCY_PPS)))
574 def __connect(self, client):
577 def __connect_after_start(self):
578 # after start, Trex may take a bit of time to initialize
579 # so we need to retry a few times
580 for it in range(self.config.generic_retry_count):
583 self.client.connect()
585 except Exception as ex:
586 if it == (self.config.generic_retry_count - 1):
588 LOG.info("Retrying connection to TRex (%s)...", ex.msg)
591 """Connect to the TRex server."""
592 server_ip = self.generator_config.ip
593 LOG.info("Connecting to TRex (%s)...", server_ip)
595 # Connect to TRex server
596 self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port,
597 async_port=self.generator_config.zmq_pub_port)
599 self.__connect(self.client)
600 if server_ip == '127.0.0.1':
601 config_updated = self.__check_config()
602 if config_updated or self.config.restart:
604 except (TimeoutError, STLError) as e:
605 if server_ip == '127.0.0.1':
606 self.__start_local_server()
608 raise TrafficGeneratorException(e.message)
610 ports = list(self.generator_config.ports)
611 self.port_handle = ports
613 self.client.reset(ports)
614 # Read HW information from each port
615 # this returns an array of dict (1 per port)
617 Example of output for Intel XL710
618 [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True,
619 'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes',
620 u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']},
621 u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE',
622 u'description': u'Ethernet Controller XL710 for 40GbE QSFP+',
623 'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-',
624 u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match',
625 'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0',
626 'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off',
627 'layer_mode': 'Ethernet', u'numa': 0}, ...]
629 self.port_info = self.client.get_port_info(ports)
630 LOG.info('Connected to TRex')
631 for id, port in enumerate(self.port_info):
632 LOG.info(' Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s',
633 id, port['description'], port['speed'], port['src_mac'],
634 port['pci_addr'], port['driver'])
635 # Make sure the 2 ports have the same speed
636 if self.port_info[0]['speed'] != self.port_info[1]['speed']:
637 raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' %
638 (self.port_info[0]['speed'],
639 self.port_info[1]['speed']))
641 def __start_local_server(self):
643 LOG.info("Starting TRex ...")
644 self.__start_server()
645 self.__connect_after_start()
646 except (TimeoutError, STLError) as e:
647 LOG.error('Cannot connect to TRex')
648 LOG.error(traceback.format_exc())
649 logpath = '/tmp/trex.log'
650 if os.path.isfile(logpath):
651 # Wait for TRex to finish writing error message
653 for _ in range(self.config.generic_retry_count):
654 size = os.path.getsize(logpath)
655 if size == last_size:
656 # probably not writing anymore
660 with open(logpath, 'r') as f:
664 raise TrafficGeneratorException(message)
666 def __start_server(self):
667 server = TRexTrafficServer()
668 server.run_server(self.generator_config)
670 def __check_config(self):
671 server = TRexTrafficServer()
672 return server.check_config_updated(self.generator_config)
675 LOG.info("Restarting TRex ...")
677 # Wait for server stopped
678 for _ in range(self.config.generic_retry_count):
680 if not self.client.is_connected():
681 LOG.info("TRex is stopped...")
683 self.__start_local_server()
685 def __stop_server(self):
686 if self.generator_config.ip == '127.0.0.1':
687 ports = self.client.get_acquired_ports()
688 LOG.info('Release ports %s and stopping TRex...', ports)
691 self.client.release(ports=ports)
692 self.client.server_shutdown()
693 except STLError as e:
694 LOG.warning('Unable to stop TRex. Error: %s', e)
696 LOG.info('Using remote TRex. Unable to stop TRex')
698 def resolve_arp(self):
699 """Resolve all configured remote IP addresses.
701 return: None if ARP failed to resolve for all IP addresses
702 else a dict of list of dest macs indexed by port#
703 the dest macs in the list are indexed by the chain id
705 self.client.set_service_mode(ports=self.port_handle)
706 LOG.info('Polling ARP until successful...')
708 for port, device in zip(self.port_handle, self.generator_config.devices):
709 # there should be 1 stream config per chain
710 stream_configs = device.get_stream_configs()
711 chain_count = len(stream_configs)
712 ctx = self.client.create_service_ctx(port=port)
713 # all dest macs on this port indexed by chain ID
714 dst_macs = [None] * chain_count
716 # the index in the list is the chain id
717 if self.config.vxlan or self.config.mpls:
720 src_ip=device.vtep_src_ip,
721 dst_ip=device.vtep_dst_ip,
722 vlan=device.vtep_vlan)
723 for cfg in stream_configs
728 src_ip=cfg['ip_src_tg_gw'],
729 dst_ip=cfg['mac_discovery_gw'],
730 # will be None if no vlan tagging
731 vlan=cfg['vlan_tag'])
732 for cfg in stream_configs
735 for attempt in range(self.config.generic_retry_count):
739 LOG.error(traceback.format_exc())
743 for chain_id, mac in enumerate(dst_macs):
745 arp_record = arps[chain_id].get_record()
746 if arp_record.dst_mac:
747 dst_macs[chain_id] = arp_record.dst_mac
749 LOG.info(' ARP: port=%d chain=%d src IP=%s dst IP=%s -> MAC=%s',
752 arp_record.dst_ip, arp_record.dst_mac)
754 unresolved.append(arp_record.dst_ip)
755 if dst_macs_count == chain_count:
756 arp_dest_macs[port] = dst_macs
757 LOG.info('ARP resolved successfully for port %s', port)
761 LOG.info('Retrying ARP for: %s (retry %d/%d)',
762 unresolved, retry, self.config.generic_retry_count)
763 if retry < self.config.generic_retry_count:
764 time.sleep(self.config.generic_poll_sec)
766 LOG.error('ARP timed out for port %s (resolved %d out of %d)',
772 # if the capture from the TRex console was started before the arp request step,
773 # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
774 if not self.config.service_mode:
775 self.client.set_service_mode(ports=self.port_handle, enabled=False)
776 if len(arp_dest_macs) == len(self.port_handle):
780 def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency):
781 """Check if rate provided by user is above requirements. Applies only if latency is True."""
782 intf_speed = self.generator_config.intf_speed
788 r = utils.convert_rates(l2frame_size, rate, intf_speed)
789 total_rate += int(r['rate_pps'])
792 total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
793 # rate must be enough for latency stream and at least 1 pps for base stream per chain
794 required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult
795 result = utils.convert_rates(l2frame_size,
796 {'rate_pps': required_rate},
798 result['result'] = total_rate >= required_rate
801 return {'result': True}
803 def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
804 """Program all the streams in Trex server.
806 l2frame_size: L2 frame size or IMIX
807 rates: a list of 2 rates to run each direction
808 each rate is a dict like {'rate_pps': '10kpps'}
809 bidirectional: True if bidirectional
810 latency: True if latency measurement is needed
811 e2e: True if performing "end to end" connectivity check
813 r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
815 raise TrafficGeneratorException(
816 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.'
817 .format(pps=r['rate_pps'],
819 load=r['rate_percent']))
820 self.l2_frame_size = l2frame_size
821 # a dict of list of streams indexed by port#
822 # in case of fixed size, has self.chain_count * 2 * 2 streams
823 # (1 normal + 1 latency stream per direction per chain)
824 # for IMIX, has self.chain_count * 2 * 4 streams
825 # (3 normal + 1 latency stream per direction per chain)
827 for port in self.port_handle:
828 streamblock[port] = []
829 stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
830 self.rates = [utils.to_rate_str(rate) for rate in rates]
831 for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
832 streamblock[0].extend(self.generate_streams(self.port_handle[0],
838 if len(self.rates) > 1:
839 streamblock[1].extend(self.generate_streams(self.port_handle[1],
843 latency=bidirectional and latency,
846 for port in self.port_handle:
847 if self.config.vxlan:
848 self.client.set_port_attr(ports=port, vxlan_fs=[4789])
850 self.client.set_port_attr(ports=port, vxlan_fs=None)
851 self.client.add_streams(streamblock[port], ports=port)
852 LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
854 def clear_streamblock(self):
855 """Clear all streams from TRex."""
857 self.client.reset(self.port_handle)
858 LOG.info('Cleared all existing streams')
861 """Get stats from Trex."""
862 stats = self.client.get_stats()
863 return self.extract_stats(stats)
866 """Return the Trex local port MAC addresses.
868 return: a list of MAC addresses indexed by the port#
870 return [port['src_mac'] for port in self.port_info]
872 def get_port_speed_gbps(self):
873 """Return the Trex local port MAC addresses.
875 return: a list of speed in Gbps indexed by the port#
877 return [port['speed'] for port in self.port_info]
879 def clear_stats(self):
880 """Clear all stats in the traffic gneerator."""
882 self.client.clear_stats()
884 def start_traffic(self):
885 """Start generating traffic in all ports."""
886 for port, rate in zip(self.port_handle, self.rates):
887 self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True)
889 def stop_traffic(self):
890 """Stop generating traffic."""
891 self.client.stop(ports=self.port_handle)
893 def start_capture(self):
894 """Capture all packets on both ports that are unicast to us."""
897 # Need to filter out unwanted packets so we do not end up counting
898 # src MACs of frames that are not unicast to us
899 src_mac_list = self.get_macs()
900 bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
901 # ports must be set in service in order to enable capture
902 self.client.set_service_mode(ports=self.port_handle)
903 self.capture_id = self.client.start_capture \
904 (rx_ports=self.port_handle, bpf_filter=bpf_filter)
906 def fetch_capture_packets(self):
907 """Fetch capture packets in capture mode."""
909 self.packet_list = []
910 self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
911 output=self.packet_list)
913 def stop_capture(self):
914 """Stop capturing packets."""
916 self.client.stop_capture(capture_id=self.capture_id['id'])
917 self.capture_id = None
918 # if the capture from TRex console was started before the connectivity step,
919 # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
920 if not self.config.service_mode:
921 self.client.set_service_mode(ports=self.port_handle, enabled=False)
924 """Cleanup Trex driver."""
927 self.client.reset(self.port_handle)
928 self.client.disconnect()
930 # TRex does not like a reset while in disconnected state
933 def set_service_mode(self, enabled=True):
934 """Enable/disable the 'service_mode'."""
935 self.client.set_service_mode(ports=self.port_handle, enabled=enabled)