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)),
473 STLVmFixChecksumHw(l3_offset="IP:{}".format(encap_level),
474 l4_offset="UDP:{}".format(encap_level),
475 l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP)
477 pad = max(0, frame_size - len(pkt_base)) * 'x'
479 return STLPktBuilder(pkt=pkt_base / pad,
480 vm=STLScVmRaw(vm_param, cache_size=int(self.config.cache_size)))
482 def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True,
484 """Create a list of streams corresponding to a given chain and stream config.
486 port: port where the streams originate (0 or 1)
487 chain_id: the chain to which the streams are associated to
488 stream_cfg: stream configuration
489 l2frame: L2 frame size (including 4-byte FCS) or 'IMIX'
490 latency: if True also create a latency stream
491 e2e: True if performing "end to end" connectivity check
494 pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
495 if self.config.no_flow_stats:
496 LOG.info("Traffic flow statistics are disabled.")
497 if l2frame == 'IMIX':
498 for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES):
499 pkt = self._create_pkt(stream_cfg, l2_frame_size)
500 if e2e or stream_cfg['mpls']:
501 streams.append(STLStream(packet=pkt,
502 mode=STLTXCont(pps=ratio)))
504 if stream_cfg['vxlan'] is True:
505 streams.append(STLStream(packet=pkt,
506 flow_stats=STLFlowStats(pg_id=pg_id,
508 if not self.config.no_flow_stats else None,
509 mode=STLTXCont(pps=ratio)))
511 streams.append(STLStream(packet=pkt,
512 flow_stats=STLFlowStats(pg_id=pg_id)
513 if not self.config.no_flow_stats else None,
514 mode=STLTXCont(pps=ratio)))
517 # for IMIX, the latency packets have the average IMIX packet size
518 pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE)
521 l2frame_size = int(l2frame)
522 pkt = self._create_pkt(stream_cfg, l2frame_size)
523 if e2e or stream_cfg['mpls']:
524 streams.append(STLStream(packet=pkt,
525 # Flow stats is disabled for MPLS now
526 # flow_stats=STLFlowStats(pg_id=pg_id),
529 if stream_cfg['vxlan'] is True:
530 streams.append(STLStream(packet=pkt,
531 flow_stats=STLFlowStats(pg_id=pg_id,
533 if not self.config.no_flow_stats else None,
536 streams.append(STLStream(packet=pkt,
537 flow_stats=STLFlowStats(pg_id=pg_id)
538 if not self.config.no_flow_stats else None,
540 # for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging
541 # without vlan, the min l2 frame size is 64
543 # This only applies to the latency stream
544 if latency and stream_cfg['vlan_tag'] and l2frame_size < 68:
545 pkt = self._create_pkt(stream_cfg, 68)
548 if self.config.no_latency_stats:
549 LOG.info("Latency flow statistics are disabled.")
550 if stream_cfg['vxlan'] is True:
551 streams.append(STLStream(packet=pkt,
552 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id,
554 if not self.config.no_latency_stats else None,
555 mode=STLTXCont(pps=self.LATENCY_PPS)))
557 streams.append(STLStream(packet=pkt,
558 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id)
559 if not self.config.no_latency_stats else None,
560 mode=STLTXCont(pps=self.LATENCY_PPS)))
564 def __connect(self, client):
567 def __connect_after_start(self):
568 # after start, Trex may take a bit of time to initialize
569 # so we need to retry a few times
570 for it in range(self.config.generic_retry_count):
573 self.client.connect()
575 except Exception as ex:
576 if it == (self.config.generic_retry_count - 1):
578 LOG.info("Retrying connection to TRex (%s)...", ex.msg)
581 """Connect to the TRex server."""
582 server_ip = self.generator_config.ip
583 LOG.info("Connecting to TRex (%s)...", server_ip)
585 # Connect to TRex server
586 self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port,
587 async_port=self.generator_config.zmq_pub_port)
589 self.__connect(self.client)
590 if server_ip == '127.0.0.1':
591 config_updated = self.__check_config()
592 if config_updated or self.config.restart:
594 except (TimeoutError, STLError) as e:
595 if server_ip == '127.0.0.1':
596 self.__start_local_server()
598 raise TrafficGeneratorException(e.message)
600 ports = list(self.generator_config.ports)
601 self.port_handle = ports
603 self.client.reset(ports)
604 # Read HW information from each port
605 # this returns an array of dict (1 per port)
607 Example of output for Intel XL710
608 [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True,
609 'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes',
610 u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']},
611 u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE',
612 u'description': u'Ethernet Controller XL710 for 40GbE QSFP+',
613 'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-',
614 u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match',
615 'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0',
616 'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off',
617 'layer_mode': 'Ethernet', u'numa': 0}, ...]
619 self.port_info = self.client.get_port_info(ports)
620 LOG.info('Connected to TRex')
621 for id, port in enumerate(self.port_info):
622 LOG.info(' Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s',
623 id, port['description'], port['speed'], port['src_mac'],
624 port['pci_addr'], port['driver'])
625 # Make sure the 2 ports have the same speed
626 if self.port_info[0]['speed'] != self.port_info[1]['speed']:
627 raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' %
628 (self.port_info[0]['speed'],
629 self.port_info[1]['speed']))
631 def __start_local_server(self):
633 LOG.info("Starting TRex ...")
634 self.__start_server()
635 self.__connect_after_start()
636 except (TimeoutError, STLError) as e:
637 LOG.error('Cannot connect to TRex')
638 LOG.error(traceback.format_exc())
639 logpath = '/tmp/trex.log'
640 if os.path.isfile(logpath):
641 # Wait for TRex to finish writing error message
643 for _ in range(self.config.generic_retry_count):
644 size = os.path.getsize(logpath)
645 if size == last_size:
646 # probably not writing anymore
650 with open(logpath, 'r') as f:
654 raise TrafficGeneratorException(message)
656 def __start_server(self):
657 server = TRexTrafficServer()
658 server.run_server(self.generator_config)
660 def __check_config(self):
661 server = TRexTrafficServer()
662 return server.check_config_updated(self.generator_config)
665 LOG.info("Restarting TRex ...")
667 # Wait for server stopped
668 for _ in range(self.config.generic_retry_count):
670 if not self.client.is_connected():
671 LOG.info("TRex is stopped...")
673 self.__start_local_server()
675 def __stop_server(self):
676 if self.generator_config.ip == '127.0.0.1':
677 ports = self.client.get_acquired_ports()
678 LOG.info('Release ports %s and stopping TRex...', ports)
681 self.client.release(ports=ports)
682 self.client.server_shutdown()
683 except STLError as e:
684 LOG.warning('Unable to stop TRex. Error: %s', e)
686 LOG.info('Using remote TRex. Unable to stop TRex')
688 def resolve_arp(self):
689 """Resolve all configured remote IP addresses.
691 return: None if ARP failed to resolve for all IP addresses
692 else a dict of list of dest macs indexed by port#
693 the dest macs in the list are indexed by the chain id
695 self.client.set_service_mode(ports=self.port_handle)
696 LOG.info('Polling ARP until successful...')
698 for port, device in zip(self.port_handle, self.generator_config.devices):
699 # there should be 1 stream config per chain
700 stream_configs = device.get_stream_configs()
701 chain_count = len(stream_configs)
702 ctx = self.client.create_service_ctx(port=port)
703 # all dest macs on this port indexed by chain ID
704 dst_macs = [None] * chain_count
706 # the index in the list is the chain id
707 if self.config.vxlan or self.config.mpls:
710 src_ip=device.vtep_src_ip,
711 dst_ip=device.vtep_dst_ip,
712 vlan=device.vtep_vlan)
713 for cfg in stream_configs
718 src_ip=cfg['ip_src_tg_gw'],
719 dst_ip=cfg['mac_discovery_gw'],
720 # will be None if no vlan tagging
721 vlan=cfg['vlan_tag'])
722 for cfg in stream_configs
725 for attempt in range(self.config.generic_retry_count):
729 LOG.error(traceback.format_exc())
733 for chain_id, mac in enumerate(dst_macs):
735 arp_record = arps[chain_id].get_record()
736 if arp_record.dst_mac:
737 dst_macs[chain_id] = arp_record.dst_mac
739 LOG.info(' ARP: port=%d chain=%d src IP=%s dst IP=%s -> MAC=%s',
742 arp_record.dst_ip, arp_record.dst_mac)
744 unresolved.append(arp_record.dst_ip)
745 if dst_macs_count == chain_count:
746 arp_dest_macs[port] = dst_macs
747 LOG.info('ARP resolved successfully for port %s', port)
751 LOG.info('Retrying ARP for: %s (retry %d/%d)',
752 unresolved, retry, self.config.generic_retry_count)
753 if retry < self.config.generic_retry_count:
754 time.sleep(self.config.generic_poll_sec)
756 LOG.error('ARP timed out for port %s (resolved %d out of %d)',
762 # if the capture from the TRex console was started before the arp request step,
763 # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
764 if not self.config.service_mode:
765 self.client.set_service_mode(ports=self.port_handle, enabled=False)
766 if len(arp_dest_macs) == len(self.port_handle):
770 def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency):
771 """Check if rate provided by user is above requirements. Applies only if latency is True."""
772 intf_speed = self.generator_config.intf_speed
778 r = utils.convert_rates(l2frame_size, rate, intf_speed)
779 total_rate += int(r['rate_pps'])
782 total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
783 # rate must be enough for latency stream and at least 1 pps for base stream per chain
784 required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult
785 result = utils.convert_rates(l2frame_size,
786 {'rate_pps': required_rate},
788 result['result'] = total_rate >= required_rate
791 return {'result': True}
793 def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
794 """Program all the streams in Trex server.
796 l2frame_size: L2 frame size or IMIX
797 rates: a list of 2 rates to run each direction
798 each rate is a dict like {'rate_pps': '10kpps'}
799 bidirectional: True if bidirectional
800 latency: True if latency measurement is needed
801 e2e: True if performing "end to end" connectivity check
803 r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
805 raise TrafficGeneratorException(
806 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.'
807 .format(pps=r['rate_pps'],
809 load=r['rate_percent']))
810 # a dict of list of streams indexed by port#
811 # in case of fixed size, has self.chain_count * 2 * 2 streams
812 # (1 normal + 1 latency stream per direction per chain)
813 # for IMIX, has self.chain_count * 2 * 4 streams
814 # (3 normal + 1 latency stream per direction per chain)
816 for port in self.port_handle:
817 streamblock[port] = []
818 stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
819 self.rates = [utils.to_rate_str(rate) for rate in rates]
820 for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
821 streamblock[0].extend(self.generate_streams(self.port_handle[0],
827 if len(self.rates) > 1:
828 streamblock[1].extend(self.generate_streams(self.port_handle[1],
832 latency=bidirectional and latency,
835 for port in self.port_handle:
836 if self.config.vxlan:
837 self.client.set_port_attr(ports=port, vxlan_fs=[4789])
839 self.client.set_port_attr(ports=port, vxlan_fs=None)
840 self.client.add_streams(streamblock[port], ports=port)
841 LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
843 def clear_streamblock(self):
844 """Clear all streams from TRex."""
846 self.client.reset(self.port_handle)
847 LOG.info('Cleared all existing streams')
850 """Get stats from Trex."""
851 stats = self.client.get_stats()
852 return self.extract_stats(stats)
855 """Return the Trex local port MAC addresses.
857 return: a list of MAC addresses indexed by the port#
859 return [port['src_mac'] for port in self.port_info]
861 def get_port_speed_gbps(self):
862 """Return the Trex local port MAC addresses.
864 return: a list of speed in Gbps indexed by the port#
866 return [port['speed'] for port in self.port_info]
868 def clear_stats(self):
869 """Clear all stats in the traffic gneerator."""
871 self.client.clear_stats()
873 def start_traffic(self):
874 """Start generating traffic in all ports."""
875 for port, rate in zip(self.port_handle, self.rates):
876 self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True)
878 def stop_traffic(self):
879 """Stop generating traffic."""
880 self.client.stop(ports=self.port_handle)
882 def start_capture(self):
883 """Capture all packets on both ports that are unicast to us."""
886 # Need to filter out unwanted packets so we do not end up counting
887 # src MACs of frames that are not unicast to us
888 src_mac_list = self.get_macs()
889 bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
890 # ports must be set in service in order to enable capture
891 self.client.set_service_mode(ports=self.port_handle)
892 self.capture_id = self.client.start_capture \
893 (rx_ports=self.port_handle, bpf_filter=bpf_filter)
895 def fetch_capture_packets(self):
896 """Fetch capture packets in capture mode."""
898 self.packet_list = []
899 self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
900 output=self.packet_list)
902 def stop_capture(self):
903 """Stop capturing packets."""
905 self.client.stop_capture(capture_id=self.capture_id['id'])
906 self.capture_id = None
907 # if the capture from TRex console was started before the connectivity step,
908 # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
909 if not self.config.service_mode:
910 self.client.set_service_mode(ports=self.port_handle, enabled=False)
913 """Cleanup Trex driver."""
916 self.client.reset(self.port_handle)
917 self.client.disconnect()
919 # TRex does not like a reset while in disconnected state
922 def set_service_mode(self, enabled=True):
923 """Enable/disable the 'service_mode'."""
924 self.client.set_service_mode(ports=self.port_handle, enabled=enabled)