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 if stream_cfg['udp_dst_port']:
391 udp_args['dport'] = int(stream_cfg['udp_dst_port'])
392 pkt_base /= IP() / UDP(**udp_args)
394 if stream_cfg['ip_addrs_step'] == 'random':
395 src_fv = STLVmFlowVarRepeatableRandom(
397 min_value=stream_cfg['ip_src_addr'],
398 max_value=stream_cfg['ip_src_addr_max'],
400 seed=random.randint(0, 32767),
401 limit=stream_cfg['ip_src_count'])
402 dst_fv = STLVmFlowVarRepeatableRandom(
404 min_value=stream_cfg['ip_dst_addr'],
405 max_value=stream_cfg['ip_dst_addr_max'],
407 seed=random.randint(0, 32767),
408 limit=stream_cfg['ip_dst_count'])
410 src_fv = STLVmFlowVar(
412 min_value=stream_cfg['ip_src_addr'],
413 max_value=stream_cfg['ip_src_addr'],
416 step=stream_cfg['ip_addrs_step'])
417 dst_fv = STLVmFlowVar(
419 min_value=stream_cfg['ip_dst_addr'],
420 max_value=stream_cfg['ip_dst_addr_max'],
423 step=stream_cfg['ip_addrs_step'])
427 STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)),
429 STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level))
432 for encap in range(int(encap_level), -1, -1):
433 # Fixing the checksums for all encap levels
434 vm_param.append(STLVmFixChecksumHw(l3_offset="IP:{}".format(encap),
435 l4_offset="UDP:{}".format(encap),
436 l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP))
437 pad = max(0, frame_size - len(pkt_base)) * 'x'
439 return STLPktBuilder(pkt=pkt_base / pad,
440 vm=STLScVmRaw(vm_param, cache_size=int(self.config.cache_size)))
442 def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True,
444 """Create a list of streams corresponding to a given chain and stream config.
446 port: port where the streams originate (0 or 1)
447 chain_id: the chain to which the streams are associated to
448 stream_cfg: stream configuration
449 l2frame: L2 frame size (including 4-byte FCS) or 'IMIX'
450 latency: if True also create a latency stream
451 e2e: True if performing "end to end" connectivity check
454 pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
455 if self.config.no_flow_stats:
456 LOG.info("Traffic flow statistics are disabled.")
457 if l2frame == 'IMIX':
458 for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES):
459 pkt = self._create_pkt(stream_cfg, l2_frame_size)
460 if e2e or stream_cfg['mpls']:
461 streams.append(STLStream(packet=pkt,
462 mode=STLTXCont(pps=ratio)))
464 if stream_cfg['vxlan'] is True:
465 streams.append(STLStream(packet=pkt,
466 flow_stats=STLFlowStats(pg_id=pg_id,
468 if not self.config.no_flow_stats else None,
469 mode=STLTXCont(pps=ratio)))
471 streams.append(STLStream(packet=pkt,
472 flow_stats=STLFlowStats(pg_id=pg_id)
473 if not self.config.no_flow_stats else None,
474 mode=STLTXCont(pps=ratio)))
477 # for IMIX, the latency packets have the average IMIX packet size
478 pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE)
481 l2frame_size = int(l2frame)
482 pkt = self._create_pkt(stream_cfg, l2frame_size)
483 if e2e or stream_cfg['mpls']:
484 streams.append(STLStream(packet=pkt,
485 # Flow stats is disabled for MPLS now
486 # flow_stats=STLFlowStats(pg_id=pg_id),
489 if stream_cfg['vxlan'] is True:
490 streams.append(STLStream(packet=pkt,
491 flow_stats=STLFlowStats(pg_id=pg_id,
493 if not self.config.no_flow_stats else None,
496 streams.append(STLStream(packet=pkt,
497 flow_stats=STLFlowStats(pg_id=pg_id)
498 if not self.config.no_flow_stats else None,
500 # for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging
501 # without vlan, the min l2 frame size is 64
503 # This only applies to the latency stream
504 if latency and stream_cfg['vlan_tag'] and l2frame_size < 68:
505 pkt = self._create_pkt(stream_cfg, 68)
508 if self.config.no_latency_stats:
509 LOG.info("Latency flow statistics are disabled.")
510 if stream_cfg['vxlan'] is True:
511 streams.append(STLStream(packet=pkt,
512 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id,
514 if not self.config.no_latency_stats else None,
515 mode=STLTXCont(pps=self.LATENCY_PPS)))
517 streams.append(STLStream(packet=pkt,
518 flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id)
519 if not self.config.no_latency_stats else None,
520 mode=STLTXCont(pps=self.LATENCY_PPS)))
524 def __connect(self, client):
527 def __connect_after_start(self):
528 # after start, Trex may take a bit of time to initialize
529 # so we need to retry a few times
530 for it in range(self.config.generic_retry_count):
533 self.client.connect()
535 except Exception as ex:
536 if it == (self.config.generic_retry_count - 1):
538 LOG.info("Retrying connection to TRex (%s)...", ex.msg)
541 """Connect to the TRex server."""
542 server_ip = self.generator_config.ip
543 LOG.info("Connecting to TRex (%s)...", server_ip)
545 # Connect to TRex server
546 self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port,
547 async_port=self.generator_config.zmq_pub_port)
549 self.__connect(self.client)
550 if server_ip == '127.0.0.1':
551 config_updated = self.__check_config()
552 if config_updated or self.config.restart:
554 except (TimeoutError, STLError) as e:
555 if server_ip == '127.0.0.1':
556 self.__start_local_server()
558 raise TrafficGeneratorException(e.message)
560 ports = list(self.generator_config.ports)
561 self.port_handle = ports
563 self.client.reset(ports)
564 # Read HW information from each port
565 # this returns an array of dict (1 per port)
567 Example of output for Intel XL710
568 [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True,
569 'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes',
570 u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']},
571 u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE',
572 u'description': u'Ethernet Controller XL710 for 40GbE QSFP+',
573 'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-',
574 u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match',
575 'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0',
576 'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off',
577 'layer_mode': 'Ethernet', u'numa': 0}, ...]
579 self.port_info = self.client.get_port_info(ports)
580 LOG.info('Connected to TRex')
581 for id, port in enumerate(self.port_info):
582 LOG.info(' Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s',
583 id, port['description'], port['speed'], port['src_mac'],
584 port['pci_addr'], port['driver'])
585 # Make sure the 2 ports have the same speed
586 if self.port_info[0]['speed'] != self.port_info[1]['speed']:
587 raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' %
588 (self.port_info[0]['speed'],
589 self.port_info[1]['speed']))
591 def __start_local_server(self):
593 LOG.info("Starting TRex ...")
594 self.__start_server()
595 self.__connect_after_start()
596 except (TimeoutError, STLError) as e:
597 LOG.error('Cannot connect to TRex')
598 LOG.error(traceback.format_exc())
599 logpath = '/tmp/trex.log'
600 if os.path.isfile(logpath):
601 # Wait for TRex to finish writing error message
603 for _ in range(self.config.generic_retry_count):
604 size = os.path.getsize(logpath)
605 if size == last_size:
606 # probably not writing anymore
610 with open(logpath, 'r') as f:
614 raise TrafficGeneratorException(message)
616 def __start_server(self):
617 server = TRexTrafficServer()
618 server.run_server(self.generator_config)
620 def __check_config(self):
621 server = TRexTrafficServer()
622 return server.check_config_updated(self.generator_config)
625 LOG.info("Restarting TRex ...")
627 # Wait for server stopped
628 for _ in range(self.config.generic_retry_count):
630 if not self.client.is_connected():
631 LOG.info("TRex is stopped...")
633 self.__start_local_server()
635 def __stop_server(self):
636 if self.generator_config.ip == '127.0.0.1':
637 ports = self.client.get_acquired_ports()
638 LOG.info('Release ports %s and stopping TRex...', ports)
641 self.client.release(ports=ports)
642 self.client.server_shutdown()
643 except STLError as e:
644 LOG.warning('Unable to stop TRex. Error: %s', e)
646 LOG.info('Using remote TRex. Unable to stop TRex')
648 def resolve_arp(self):
649 """Resolve all configured remote IP addresses.
651 return: None if ARP failed to resolve for all IP addresses
652 else a dict of list of dest macs indexed by port#
653 the dest macs in the list are indexed by the chain id
655 self.client.set_service_mode(ports=self.port_handle)
656 LOG.info('Polling ARP until successful...')
658 for port, device in zip(self.port_handle, self.generator_config.devices):
659 # there should be 1 stream config per chain
660 stream_configs = device.get_stream_configs()
661 chain_count = len(stream_configs)
662 ctx = self.client.create_service_ctx(port=port)
663 # all dest macs on this port indexed by chain ID
664 dst_macs = [None] * chain_count
666 # the index in the list is the chain id
667 if self.config.vxlan or self.config.mpls:
670 src_ip=device.vtep_src_ip,
671 dst_ip=device.vtep_dst_ip,
672 vlan=device.vtep_vlan)
673 for cfg in stream_configs
678 src_ip=cfg['ip_src_tg_gw'],
679 dst_ip=cfg['mac_discovery_gw'],
680 # will be None if no vlan tagging
681 vlan=cfg['vlan_tag'])
682 for cfg in stream_configs
685 for attempt in range(self.config.generic_retry_count):
689 LOG.error(traceback.format_exc())
693 for chain_id, mac in enumerate(dst_macs):
695 arp_record = arps[chain_id].get_record()
696 if arp_record.dst_mac:
697 dst_macs[chain_id] = arp_record.dst_mac
699 LOG.info(' ARP: port=%d chain=%d src IP=%s dst IP=%s -> MAC=%s',
702 arp_record.dst_ip, arp_record.dst_mac)
704 unresolved.append(arp_record.dst_ip)
705 if dst_macs_count == chain_count:
706 arp_dest_macs[port] = dst_macs
707 LOG.info('ARP resolved successfully for port %s', port)
711 LOG.info('Retrying ARP for: %s (retry %d/%d)',
712 unresolved, retry, self.config.generic_retry_count)
713 if retry < self.config.generic_retry_count:
714 time.sleep(self.config.generic_poll_sec)
716 LOG.error('ARP timed out for port %s (resolved %d out of %d)',
722 # if the capture from the TRex console was started before the arp request step,
723 # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
724 if not self.config.service_mode:
725 self.client.set_service_mode(ports=self.port_handle, enabled=False)
726 if len(arp_dest_macs) == len(self.port_handle):
730 def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency):
731 """Check if rate provided by user is above requirements. Applies only if latency is True."""
732 intf_speed = self.generator_config.intf_speed
738 r = utils.convert_rates(l2frame_size, rate, intf_speed)
739 total_rate += int(r['rate_pps'])
742 total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
743 # rate must be enough for latency stream and at least 1 pps for base stream per chain
744 required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult
745 result = utils.convert_rates(l2frame_size,
746 {'rate_pps': required_rate},
748 result['result'] = total_rate >= required_rate
751 return {'result': True}
753 def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
754 """Program all the streams in Trex server.
756 l2frame_size: L2 frame size or IMIX
757 rates: a list of 2 rates to run each direction
758 each rate is a dict like {'rate_pps': '10kpps'}
759 bidirectional: True if bidirectional
760 latency: True if latency measurement is needed
761 e2e: True if performing "end to end" connectivity check
763 r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
765 raise TrafficGeneratorException(
766 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.'
767 .format(pps=r['rate_pps'],
769 load=r['rate_percent']))
770 # a dict of list of streams indexed by port#
771 # in case of fixed size, has self.chain_count * 2 * 2 streams
772 # (1 normal + 1 latency stream per direction per chain)
773 # for IMIX, has self.chain_count * 2 * 4 streams
774 # (3 normal + 1 latency stream per direction per chain)
776 for port in self.port_handle:
777 streamblock[port] = []
778 stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
779 self.rates = [utils.to_rate_str(rate) for rate in rates]
780 for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
781 streamblock[0].extend(self.generate_streams(self.port_handle[0],
787 if len(self.rates) > 1:
788 streamblock[1].extend(self.generate_streams(self.port_handle[1],
792 latency=bidirectional and latency,
795 for port in self.port_handle:
796 if self.config.vxlan:
797 self.client.set_port_attr(ports=port, vxlan_fs=[4789])
799 self.client.set_port_attr(ports=port, vxlan_fs=None)
800 self.client.add_streams(streamblock[port], ports=port)
801 LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
803 def clear_streamblock(self):
804 """Clear all streams from TRex."""
806 self.client.reset(self.port_handle)
807 LOG.info('Cleared all existing streams')
810 """Get stats from Trex."""
811 stats = self.client.get_stats()
812 return self.extract_stats(stats)
815 """Return the Trex local port MAC addresses.
817 return: a list of MAC addresses indexed by the port#
819 return [port['src_mac'] for port in self.port_info]
821 def get_port_speed_gbps(self):
822 """Return the Trex local port MAC addresses.
824 return: a list of speed in Gbps indexed by the port#
826 return [port['speed'] for port in self.port_info]
828 def clear_stats(self):
829 """Clear all stats in the traffic gneerator."""
831 self.client.clear_stats()
833 def start_traffic(self):
834 """Start generating traffic in all ports."""
835 for port, rate in zip(self.port_handle, self.rates):
836 self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True)
838 def stop_traffic(self):
839 """Stop generating traffic."""
840 self.client.stop(ports=self.port_handle)
842 def start_capture(self):
843 """Capture all packets on both ports that are unicast to us."""
846 # Need to filter out unwanted packets so we do not end up counting
847 # src MACs of frames that are not unicast to us
848 src_mac_list = self.get_macs()
849 bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
850 # ports must be set in service in order to enable capture
851 self.client.set_service_mode(ports=self.port_handle)
852 self.capture_id = self.client.start_capture(rx_ports=self.port_handle,
853 bpf_filter=bpf_filter)
855 def fetch_capture_packets(self):
856 """Fetch capture packets in capture mode."""
858 self.packet_list = []
859 self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
860 output=self.packet_list)
862 def stop_capture(self):
863 """Stop capturing packets."""
865 self.client.stop_capture(capture_id=self.capture_id['id'])
866 self.capture_id = None
867 # if the capture from TRex console was started before the connectivity step,
868 # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
869 if not self.config.service_mode:
870 self.client.set_service_mode(ports=self.port_handle, enabled=False)
873 """Cleanup Trex driver."""
876 self.client.reset(self.port_handle)
877 self.client.disconnect()
879 # TRex does not like a reset while in disconnected state
882 def set_service_mode(self, enabled=True):
883 """Enable/disable the 'service_mode'."""
884 self.client.set_service_mode(ports=self.port_handle, enabled=enabled)