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 STLVmFlowVar
51 from trex.stl.api import STLVmFlowVarRepeatableRandom
52 from trex.stl.api import STLVmWrFlowVar
53 from trex.stl.api import ThreeBytesField
54 from trex.stl.api import UDP
55 from trex.stl.api import XByteField
57 # pylint: enable=import-error
59 from .traffic_base import AbstractTrafficGenerator
60 from .traffic_base import TrafficGeneratorException
61 from . import traffic_utils as utils
62 from .traffic_utils import IMIX_AVG_L2_FRAME_SIZE
63 from .traffic_utils import IMIX_L2_SIZES
64 from .traffic_utils import IMIX_RATIOS
69 _VXLAN_FLAGS = ['R' * 27] + ['I'] + ['R' * 5]
71 fields_desc = [FlagsField("flags", 0x08000000, 32, _VXLAN_FLAGS),
72 ThreeBytesField("vni", 0),
73 XByteField("reserved", 0x00)]
77 return self.sprintf("VXLAN (vni=%VXLAN.vni%)")
79 class TRex(AbstractTrafficGenerator):
80 """TRex traffic generator driver."""
83 CHAIN_PG_ID_MASK = 0x007F
84 PORT_PG_ID_MASK = 0x0080
85 LATENCY_PG_ID_MASK = 0x0100
87 def __init__(self, traffic_client):
89 AbstractTrafficGenerator.__init__(self, traffic_client)
93 self.chain_count = self.generator_config.service_chain_count
95 self.capture_id = None
98 def get_version(self):
99 """Get the Trex version."""
100 return self.client.get_server_version() if self.client else ''
102 def get_pg_id(self, port, chain_id):
103 """Calculate the packet group IDs to use for a given port/stream type/chain_id.
106 chain_id: identifies to which chain the pg_id is associated (0 to 255)
107 return: pg_id, lat_pg_id
109 We use a bit mask to set up the 3 fields:
110 0x007F: chain ID (8 bits for a max of 128 chains)
114 pg_id = port * TRex.PORT_PG_ID_MASK | chain_id
115 return pg_id, pg_id | TRex.LATENCY_PG_ID_MASK
117 def extract_stats(self, in_stats):
118 """Extract stats from dict returned by Trex API.
120 :param in_stats: dict as returned by TRex api
122 utils.nan_replace(in_stats)
123 # LOG.debug(in_stats)
126 # port_handles should have only 2 elements: [0, 1]
127 # so (1 - ph) will be the index for the far end port
128 for ph in self.port_handle:
130 far_end_stats = in_stats[1 - ph]
133 'total_pkts': cast_integer(stats['opackets']),
134 'total_pkt_bytes': cast_integer(stats['obytes']),
135 'pkt_rate': cast_integer(stats['tx_pps']),
136 'pkt_bit_rate': cast_integer(stats['tx_bps'])
139 'total_pkts': cast_integer(stats['ipackets']),
140 'total_pkt_bytes': cast_integer(stats['ibytes']),
141 'pkt_rate': cast_integer(stats['rx_pps']),
142 'pkt_bit_rate': cast_integer(stats['rx_bps']),
143 # how many pkts were dropped in RX direction
144 # need to take the tx counter on the far end port
145 'dropped_pkts': cast_integer(
146 far_end_stats['opackets'] - stats['ipackets'])
149 self.__combine_latencies(in_stats, result[ph]['rx'], ph)
151 total_tx_pkts = result[0]['tx']['total_pkts'] + result[1]['tx']['total_pkts']
152 result["total_tx_rate"] = cast_integer(total_tx_pkts / self.config.duration_sec)
153 result["flow_stats"] = in_stats["flow_stats"]
154 result["latency"] = in_stats["latency"]
157 def get_stream_stats(self, trex_stats, if_stats, latencies, chain_idx):
158 """Extract the aggregated stats for a given chain.
160 trex_stats: stats as returned by get_stats()
161 if_stats: a list of 2 interface stats to update (port 0 and 1)
162 latencies: a list of 2 Latency instances to update for this chain (port 0 and 1)
163 latencies[p] is the latency for packets sent on port p
164 if there are no latency streams, the Latency instances are not modified
165 chain_idx: chain index of the interface stats
167 The packet counts include normal and latency streams.
169 Trex returns flows stats as follows:
171 'flow_stats': {0: {'rx_bps': {0: 0, 1: 0, 'total': 0},
172 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
173 'rx_bytes': {0: nan, 1: nan, 'total': nan},
174 'rx_pkts': {0: 0, 1: 15001, 'total': 15001},
175 'rx_pps': {0: 0, 1: 0, 'total': 0},
176 'tx_bps': {0: 0, 1: 0, 'total': 0},
177 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
178 'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068},
179 'tx_pkts': {0: 15001, 1: 0, 'total': 15001},
180 'tx_pps': {0: 0, 1: 0, 'total': 0}},
181 1: {'rx_bps': {0: 0, 1: 0, 'total': 0},
182 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
183 'rx_bytes': {0: nan, 1: nan, 'total': nan},
184 'rx_pkts': {0: 0, 1: 15001, 'total': 15001},
185 'rx_pps': {0: 0, 1: 0, 'total': 0},
186 'tx_bps': {0: 0, 1: 0, 'total': 0},
187 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
188 'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068},
189 'tx_pkts': {0: 15001, 1: 0, 'total': 15001},
190 'tx_pps': {0: 0, 1: 0, 'total': 0}},
191 128: {'rx_bps': {0: 0, 1: 0, 'total': 0},
192 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
193 'rx_bytes': {0: nan, 1: nan, 'total': nan},
194 'rx_pkts': {0: 15001, 1: 0, 'total': 15001},
195 'rx_pps': {0: 0, 1: 0, 'total': 0},
196 'tx_bps': {0: 0, 1: 0, 'total': 0},
197 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
198 'tx_bytes': {0: 0, 1: 1020068, 'total': 1020068},
199 'tx_pkts': {0: 0, 1: 15001, 'total': 15001},
200 'tx_pps': {0: 0, 1: 0, 'total': 0}},etc...
202 the pg_id (0, 1, 128,...) is the key of the dict and is obtained using the
204 packet counters for a given stream sent on port p are reported as:
205 - tx_pkts[p] on port p
206 - rx_pkts[1-p] on the far end port
208 This is a tricky/critical counter transposition operation because
209 the results are grouped by port (not by stream):
210 tx_pkts_port(p=0) comes from pg_id(port=0, chain_idx)['tx_pkts'][0]
211 rx_pkts_port(p=0) comes from pg_id(port=1, chain_idx)['rx_pkts'][0]
212 tx_pkts_port(p=1) comes from pg_id(port=1, chain_idx)['tx_pkts'][1]
213 rx_pkts_port(p=1) comes from pg_id(port=0, chain_idx)['rx_pkts'][1]
215 or using a more generic formula:
216 tx_pkts_port(p) comes from pg_id(port=p, chain_idx)['tx_pkts'][p]
217 rx_pkts_port(p) comes from pg_id(port=1-p, chain_idx)['rx_pkts'][p]
219 the second formula is equivalent to
220 rx_pkts_port(1-p) comes from pg_id(port=p, chain_idx)['rx_pkts'][1-p]
222 If there are latency streams, those same counters need to be added in the same way
224 def get_latency(lval):
226 return int(round(lval))
232 for port in range(2):
233 pg_id, lat_pg_id = self.get_pg_id(port, chain_idx)
234 for pid in [pg_id, lat_pg_id]:
236 pg_stats = trex_stats['flow_stats'][pid]
237 if_stats[port].tx += pg_stats['tx_pkts'][port]
238 if_stats[1 - port].rx += pg_stats['rx_pkts'][1 - port]
242 lat = trex_stats['latency'][lat_pg_id]['latency']
243 # dropped_pkts += lat['err_cntrs']['dropped']
244 latencies[port].max_usec = get_latency(lat['total_max'])
245 if math.isnan(lat['total_min']):
246 latencies[port].min_usec = 0
247 latencies[port].avg_usec = 0
249 latencies[port].min_usec = get_latency(lat['total_min'])
250 latencies[port].avg_usec = get_latency(lat['average'])
251 # pick up the HDR histogram if present (otherwise will raise KeyError)
252 latencies[port].hdrh = lat['hdrh']
256 def __combine_latencies(self, in_stats, results, port_handle):
257 """Traverse TRex result dictionary and combines chosen latency stats.
259 example of latency dict returned by trex (2 chains):
260 'latency': {256: {'err_cntrs': {'dropped': 0,
265 'latency': {'average': 26.5,
266 'hdrh': u'HISTFAAAAEx4nJNpmSgz...bFRgxi',
267 'histogram': {20: 303,
277 257: {'err_cntrs': {'dropped': 0,
282 'latency': {'average': 29.75,
283 'hdrh': u'HISTFAAAAEV4nJN...CALilDG0=',
284 'histogram': {20: 261,
293 384: {'err_cntrs': {'dropped': 0,
298 'latency': {'average': 18.0,
299 'hdrh': u'HISTFAAAADR4nJNpm...MjCwDDxAZG',
300 'histogram': {20: 987, 30: 14},
305 385: {'err_cntrs': {'dropped': 0,
310 'latency': {'average': 19.0,
311 'hdrh': u'HISTFAAAADR4nJNpm...NkYmJgDdagfK',
312 'histogram': {20: 989, 30: 11},
317 'global': {'bad_hdr': 0, 'old_flow': 0}},
321 total_min = float("inf")
322 for chain_id in range(self.chain_count):
324 _, lat_pg_id = self.get_pg_id(port_handle, chain_id)
325 lat = in_stats['latency'][lat_pg_id]['latency']
326 # dropped_pkts += lat['err_cntrs']['dropped']
327 total_max = max(lat['total_max'], total_max)
328 total_min = min(lat['total_min'], total_min)
329 average += lat['average']
332 if total_min == float("inf"):
334 results['min_delay_usec'] = total_min
335 results['max_delay_usec'] = total_max
336 results['avg_delay_usec'] = int(average / self.chain_count)
338 def _bind_vxlan(self):
339 bind_layers(UDP, VXLAN, dport=4789)
340 bind_layers(VXLAN, Ether)
342 def _create_pkt(self, stream_cfg, l2frame_size):
343 """Create a packet of given size.
345 l2frame_size: size of the L2 frame in bytes (including the 32-bit FCS)
347 # Trex will add the FCS field, so we need to remove 4 bytes from the l2 frame size
348 frame_size = int(l2frame_size) - 4
350 if stream_cfg['vxlan'] is True:
353 pkt_base = Ether(src=stream_cfg['vtep_src_mac'], dst=stream_cfg['vtep_dst_mac'])
354 if stream_cfg['vtep_vlan'] is not None:
355 pkt_base /= Dot1Q(vlan=stream_cfg['vtep_vlan'])
356 pkt_base /= IP(src=stream_cfg['vtep_src_ip'], dst=stream_cfg['vtep_dst_ip'])
357 pkt_base /= UDP(sport=random.randint(1337, 32767), dport=4789)
358 pkt_base /= VXLAN(vni=stream_cfg['net_vni'])
359 pkt_base /= Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
360 # need to randomize the outer header UDP src port based on flow
361 vxlan_udp_src_fv = STLVmFlowVar(
362 name="vxlan_udp_src",
367 vm_param = [vxlan_udp_src_fv,
368 STLVmWrFlowVar(fv_name="vxlan_udp_src", pkt_offset="UDP.sport")]
369 elif stream_cfg['mpls'] is True:
371 pkt_base = Ether(src=stream_cfg['vtep_src_mac'], dst=stream_cfg['vtep_dst_mac'])
372 if stream_cfg['vtep_vlan'] is not None:
373 pkt_base /= Dot1Q(vlan=stream_cfg['vtep_vlan'])
374 if stream_cfg['mpls_outer_label'] is not None:
375 pkt_base /= MPLS(label=stream_cfg['mpls_outer_label'], cos=1, s=0, ttl=255)
376 if stream_cfg['mpls_inner_label'] is not None:
377 pkt_base /= MPLS(label=stream_cfg['mpls_inner_label'], cos=1, s=1, ttl=255)
378 # Flow stats and MPLS labels randomization TBD
379 pkt_base /= Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
382 pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
384 if stream_cfg['vlan_tag'] is not None:
385 pkt_base /= Dot1Q(vlan=stream_cfg['vlan_tag'])
388 if stream_cfg['udp_src_port']:
389 udp_args['sport'] = int(stream_cfg['udp_src_port'])
390 udp_args['sport_step'] = int(stream_cfg['udp_port_step'])
391 udp_args['sport_max'] = int(stream_cfg['udp_src_port_max'])
392 if stream_cfg['udp_dst_port']:
393 udp_args['dport'] = int(stream_cfg['udp_dst_port'])
394 udp_args['dport_step'] = int(stream_cfg['udp_port_step'])
395 udp_args['dport_max'] = int(stream_cfg['udp_dst_port_max'])
397 pkt_base /= IP(src=stream_cfg['ip_src_addr'], dst=stream_cfg['ip_dst_addr']) / \
398 UDP(dport=udp_args['dport'], sport=udp_args['sport'])
399 if stream_cfg['ip_src_static'] is True:
400 src_max_ip_value = stream_cfg['ip_src_addr']
402 src_max_ip_value = stream_cfg['ip_src_addr_max']
403 if stream_cfg['ip_addrs_step'] == 'random':
404 src_fv_ip = STLVmFlowVarRepeatableRandom(
406 min_value=stream_cfg['ip_src_addr'],
407 max_value=src_max_ip_value,
409 seed=random.randint(0, 32767),
410 limit=stream_cfg['ip_src_count'])
411 dst_fv_ip = STLVmFlowVarRepeatableRandom(
413 min_value=stream_cfg['ip_dst_addr'],
414 max_value=stream_cfg['ip_dst_addr_max'],
416 seed=random.randint(0, 32767),
417 limit=stream_cfg['ip_dst_count'])
419 src_fv_ip = STLVmFlowVar(
421 min_value=stream_cfg['ip_src_addr'],
422 max_value=src_max_ip_value,
425 step=stream_cfg['ip_addrs_step'])
426 dst_fv_ip = STLVmFlowVar(
428 min_value=stream_cfg['ip_dst_addr'],
429 max_value=stream_cfg['ip_dst_addr_max'],
432 step=stream_cfg['ip_addrs_step'])
434 if stream_cfg['udp_port_step'] == 'random':
435 src_fv_port = STLVmFlowVarRepeatableRandom(
437 min_value=udp_args['sport'],
438 max_value=udp_args['sport_max'],
440 seed=random.randint(0, 32767),
441 limit=udp_args['udp_src_count'])
442 dst_fv_port = STLVmFlowVarRepeatableRandom(
444 min_value=udp_args['dport'],
445 max_value=udp_args['dport_max'],
447 seed=random.randint(0, 32767),
448 limit=stream_cfg['udp_dst_count'])
450 src_fv_port = STLVmFlowVar(
452 min_value=udp_args['sport'],
453 max_value=udp_args['sport_max'],
456 step=udp_args['sport_step'])
457 dst_fv_port = STLVmFlowVar(
459 min_value=udp_args['dport'],
460 max_value=udp_args['dport_max'],
463 step=udp_args['dport_step'])
466 STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)),
468 STLVmWrFlowVar(fv_name="p_src", pkt_offset="UDP:{}.sport".format(encap_level)),
470 STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level)),
472 STLVmWrFlowVar(fv_name="p_dst", pkt_offset="UDP:{}.dport".format(encap_level)),
474 for encap in range(int(encap_level), -1, -1):
475 vm_param.append(STLVmFixChecksumHw(l3_offset="IP:{}".format(encap),
476 l4_offset="UDP:{}".format(encap),
477 l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP))
478 pad = max(0, frame_size - len(pkt_base)) * 'x'
480 return STLPktBuilder(pkt=pkt_base / pad,
481 vm=STLScVmRaw(vm_param, cache_size=int(self.config.cache_size)))
483 def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True,
485 """Create a list of streams corresponding to a given chain and stream config.
487 port: port where the streams originate (0 or 1)
488 chain_id: the chain to which the streams are associated to
489 stream_cfg: stream configuration
490 l2frame: L2 frame size (including 4-byte FCS) or 'IMIX'
491 latency: if True also create a latency stream
492 e2e: True if performing "end to end" connectivity check
495 pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
496 if self.config.no_flow_stats:
497 LOG.info("Traffic flow statistics are disabled.")
498 if l2frame == 'IMIX':
499 for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES):
500 pkt = self._create_pkt(stream_cfg, l2_frame_size)
501 if e2e or stream_cfg['mpls']:
502 streams.append(STLStream(packet=pkt,
503 mode=STLTXCont(pps=ratio)))
505 if stream_cfg['vxlan'] is True:
506 streams.append(STLStream(packet=pkt,
507 flow_stats=STLFlowStats(pg_id=pg_id,
509 if not self.config.no_flow_stats else None,
510 mode=STLTXCont(pps=ratio)))
512 streams.append(STLStream(packet=pkt,
513 flow_stats=STLFlowStats(pg_id=pg_id)
514 if not self.config.no_flow_stats else None,
515 mode=STLTXCont(pps=ratio)))
518 # for IMIX, the latency packets have the average IMIX packet size
519 pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE)
522 l2frame_size = int(l2frame)
523 pkt = self._create_pkt(stream_cfg, l2frame_size)
524 if e2e or stream_cfg['mpls']:
525 streams.append(STLStream(packet=pkt,
526 # Flow stats is disabled for MPLS now
527 # flow_stats=STLFlowStats(pg_id=pg_id),
530 if stream_cfg['vxlan'] is True:
531 streams.append(STLStream(packet=pkt,
532 flow_stats=STLFlowStats(pg_id=pg_id,
534 if not self.config.no_flow_stats else None,
537 streams.append(STLStream(packet=pkt,
538 flow_stats=STLFlowStats(pg_id=pg_id)
539 if not self.config.no_flow_stats else None,
541 # for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging
542 # without vlan, the min l2 frame size is 64
544 # This only applies to the latency stream
545 if latency and stream_cfg['vlan_tag'] and l2frame_size < 68:
546 pkt = self._create_pkt(stream_cfg, 68)
549 if self.config.no_latency_stats:
550 LOG.info("Latency flow statistics are disabled.")
551 if stream_cfg['vxlan'] is True:
552 streams.append(STLStream(packet=pkt,
553 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id,
555 if not self.config.no_latency_stats else None,
556 mode=STLTXCont(pps=self.LATENCY_PPS)))
558 streams.append(STLStream(packet=pkt,
559 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id)
560 if not self.config.no_latency_stats else None,
561 mode=STLTXCont(pps=self.LATENCY_PPS)))
565 def __connect(self, client):
568 def __connect_after_start(self):
569 # after start, Trex may take a bit of time to initialize
570 # so we need to retry a few times
571 for it in range(self.config.generic_retry_count):
574 self.client.connect()
576 except Exception as ex:
577 if it == (self.config.generic_retry_count - 1):
579 LOG.info("Retrying connection to TRex (%s)...", ex.msg)
582 """Connect to the TRex server."""
583 server_ip = self.generator_config.ip
584 LOG.info("Connecting to TRex (%s)...", server_ip)
586 # Connect to TRex server
587 self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port,
588 async_port=self.generator_config.zmq_pub_port)
590 self.__connect(self.client)
591 if server_ip == '127.0.0.1':
592 config_updated = self.__check_config()
593 if config_updated or self.config.restart:
595 except (TimeoutError, STLError) as e:
596 if server_ip == '127.0.0.1':
597 self.__start_local_server()
599 raise TrafficGeneratorException(e.message)
601 ports = list(self.generator_config.ports)
602 self.port_handle = ports
604 self.client.reset(ports)
605 # Read HW information from each port
606 # this returns an array of dict (1 per port)
608 Example of output for Intel XL710
609 [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True,
610 'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes',
611 u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']},
612 u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE',
613 u'description': u'Ethernet Controller XL710 for 40GbE QSFP+',
614 'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-',
615 u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match',
616 'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0',
617 'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off',
618 'layer_mode': 'Ethernet', u'numa': 0}, ...]
620 self.port_info = self.client.get_port_info(ports)
621 LOG.info('Connected to TRex')
622 for id, port in enumerate(self.port_info):
623 LOG.info(' Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s',
624 id, port['description'], port['speed'], port['src_mac'],
625 port['pci_addr'], port['driver'])
626 # Make sure the 2 ports have the same speed
627 if self.port_info[0]['speed'] != self.port_info[1]['speed']:
628 raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' %
629 (self.port_info[0]['speed'],
630 self.port_info[1]['speed']))
632 def __start_local_server(self):
634 LOG.info("Starting TRex ...")
635 self.__start_server()
636 self.__connect_after_start()
637 except (TimeoutError, STLError) as e:
638 LOG.error('Cannot connect to TRex')
639 LOG.error(traceback.format_exc())
640 logpath = '/tmp/trex.log'
641 if os.path.isfile(logpath):
642 # Wait for TRex to finish writing error message
644 for _ in range(self.config.generic_retry_count):
645 size = os.path.getsize(logpath)
646 if size == last_size:
647 # probably not writing anymore
651 with open(logpath, 'r') as f:
655 raise TrafficGeneratorException(message)
657 def __start_server(self):
658 server = TRexTrafficServer()
659 server.run_server(self.generator_config)
661 def __check_config(self):
662 server = TRexTrafficServer()
663 return server.check_config_updated(self.generator_config)
666 LOG.info("Restarting TRex ...")
668 # Wait for server stopped
669 for _ in range(self.config.generic_retry_count):
671 if not self.client.is_connected():
672 LOG.info("TRex is stopped...")
674 self.__start_local_server()
676 def __stop_server(self):
677 if self.generator_config.ip == '127.0.0.1':
678 ports = self.client.get_acquired_ports()
679 LOG.info('Release ports %s and stopping TRex...', ports)
682 self.client.release(ports=ports)
683 self.client.server_shutdown()
684 except STLError as e:
685 LOG.warning('Unable to stop TRex. Error: %s', e)
687 LOG.info('Using remote TRex. Unable to stop TRex')
689 def resolve_arp(self):
690 """Resolve all configured remote IP addresses.
692 return: None if ARP failed to resolve for all IP addresses
693 else a dict of list of dest macs indexed by port#
694 the dest macs in the list are indexed by the chain id
696 self.client.set_service_mode(ports=self.port_handle)
697 LOG.info('Polling ARP until successful...')
699 for port, device in zip(self.port_handle, self.generator_config.devices):
700 # there should be 1 stream config per chain
701 stream_configs = device.get_stream_configs()
702 chain_count = len(stream_configs)
703 ctx = self.client.create_service_ctx(port=port)
704 # all dest macs on this port indexed by chain ID
705 dst_macs = [None] * chain_count
707 # the index in the list is the chain id
708 if self.config.vxlan or self.config.mpls:
711 src_ip=device.vtep_src_ip,
712 dst_ip=device.vtep_dst_ip,
713 vlan=device.vtep_vlan)
714 for cfg in stream_configs
719 src_ip=cfg['ip_src_tg_gw'],
720 dst_ip=cfg['mac_discovery_gw'],
721 # will be None if no vlan tagging
722 vlan=cfg['vlan_tag'])
723 for cfg in stream_configs
726 for attempt in range(self.config.generic_retry_count):
730 LOG.error(traceback.format_exc())
734 for chain_id, mac in enumerate(dst_macs):
736 arp_record = arps[chain_id].get_record()
737 if arp_record.dst_mac:
738 dst_macs[chain_id] = arp_record.dst_mac
740 LOG.info(' ARP: port=%d chain=%d src IP=%s dst IP=%s -> MAC=%s',
743 arp_record.dst_ip, arp_record.dst_mac)
745 unresolved.append(arp_record.dst_ip)
746 if dst_macs_count == chain_count:
747 arp_dest_macs[port] = dst_macs
748 LOG.info('ARP resolved successfully for port %s', port)
752 LOG.info('Retrying ARP for: %s (retry %d/%d)',
753 unresolved, retry, self.config.generic_retry_count)
754 if retry < self.config.generic_retry_count:
755 time.sleep(self.config.generic_poll_sec)
757 LOG.error('ARP timed out for port %s (resolved %d out of %d)',
763 # if the capture from the TRex console was started before the arp request step,
764 # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
765 if not self.config.service_mode:
766 self.client.set_service_mode(ports=self.port_handle, enabled=False)
767 if len(arp_dest_macs) == len(self.port_handle):
771 def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency):
772 """Check if rate provided by user is above requirements. Applies only if latency is True."""
773 intf_speed = self.generator_config.intf_speed
779 r = utils.convert_rates(l2frame_size, rate, intf_speed)
780 total_rate += int(r['rate_pps'])
783 total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
784 # rate must be enough for latency stream and at least 1 pps for base stream per chain
785 required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult
786 result = utils.convert_rates(l2frame_size,
787 {'rate_pps': required_rate},
789 result['result'] = total_rate >= required_rate
792 return {'result': True}
794 def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
795 """Program all the streams in Trex server.
797 l2frame_size: L2 frame size or IMIX
798 rates: a list of 2 rates to run each direction
799 each rate is a dict like {'rate_pps': '10kpps'}
800 bidirectional: True if bidirectional
801 latency: True if latency measurement is needed
802 e2e: True if performing "end to end" connectivity check
804 r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
806 raise TrafficGeneratorException(
807 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.'
808 .format(pps=r['rate_pps'],
810 load=r['rate_percent']))
811 # a dict of list of streams indexed by port#
812 # in case of fixed size, has self.chain_count * 2 * 2 streams
813 # (1 normal + 1 latency stream per direction per chain)
814 # for IMIX, has self.chain_count * 2 * 4 streams
815 # (3 normal + 1 latency stream per direction per chain)
817 for port in self.port_handle:
818 streamblock[port] = []
819 stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
820 self.rates = [utils.to_rate_str(rate) for rate in rates]
821 for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
822 streamblock[0].extend(self.generate_streams(self.port_handle[0],
828 if len(self.rates) > 1:
829 streamblock[1].extend(self.generate_streams(self.port_handle[1],
833 latency=bidirectional and latency,
836 for port in self.port_handle:
837 if self.config.vxlan:
838 self.client.set_port_attr(ports=port, vxlan_fs=[4789])
840 self.client.set_port_attr(ports=port, vxlan_fs=None)
841 self.client.add_streams(streamblock[port], ports=port)
842 LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
844 def clear_streamblock(self):
845 """Clear all streams from TRex."""
847 self.client.reset(self.port_handle)
848 LOG.info('Cleared all existing streams')
851 """Get stats from Trex."""
852 stats = self.client.get_stats()
853 return self.extract_stats(stats)
856 """Return the Trex local port MAC addresses.
858 return: a list of MAC addresses indexed by the port#
860 return [port['src_mac'] for port in self.port_info]
862 def get_port_speed_gbps(self):
863 """Return the Trex local port MAC addresses.
865 return: a list of speed in Gbps indexed by the port#
867 return [port['speed'] for port in self.port_info]
869 def clear_stats(self):
870 """Clear all stats in the traffic gneerator."""
872 self.client.clear_stats()
874 def start_traffic(self):
875 """Start generating traffic in all ports."""
876 for port, rate in zip(self.port_handle, self.rates):
877 self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True)
879 def stop_traffic(self):
880 """Stop generating traffic."""
881 self.client.stop(ports=self.port_handle)
883 def start_capture(self):
884 """Capture all packets on both ports that are unicast to us."""
887 # Need to filter out unwanted packets so we do not end up counting
888 # src MACs of frames that are not unicast to us
889 src_mac_list = self.get_macs()
890 bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
891 # ports must be set in service in order to enable capture
892 self.client.set_service_mode(ports=self.port_handle)
893 self.capture_id = self.client.start_capture \
894 (rx_ports=self.port_handle, bpf_filter=bpf_filter)
896 def fetch_capture_packets(self):
897 """Fetch capture packets in capture mode."""
899 self.packet_list = []
900 self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
901 output=self.packet_list)
903 def stop_capture(self):
904 """Stop capturing packets."""
906 self.client.stop_capture(capture_id=self.capture_id['id'])
907 self.capture_id = None
908 # if the capture from TRex console was started before the connectivity step,
909 # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
910 if not self.config.service_mode:
911 self.client.set_service_mode(ports=self.port_handle, enabled=False)
914 """Cleanup Trex driver."""
917 self.client.reset(self.port_handle)
918 self.client.disconnect()
920 # TRex does not like a reset while in disconnected state
923 def set_service_mode(self, enabled=True):
924 """Enable/disable the 'service_mode'."""
925 self.client.set_service_mode(ports=self.port_handle, enabled=enabled)