2.0 beta NFVBENCH-91 Allow multi-chaining with separate edge networks
[nfvbench.git] / nfvbench / traffic_gen / trex.py
1 # Copyright 2016 Cisco Systems, Inc.  All rights reserved.
2 #
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
6 #
7 #         http://www.apache.org/licenses/LICENSE-2.0
8 #
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
13 #    under the License.
14
15 import os
16 import random
17 import time
18 import traceback
19
20 from itertools import count
21 from nfvbench.log import LOG
22 from nfvbench.specs import ChainType
23 from nfvbench.traffic_server import TRexTrafficServer
24 from nfvbench.utils import cast_integer
25 from nfvbench.utils import timeout
26 from nfvbench.utils import TimeoutError
27 from traffic_base import AbstractTrafficGenerator
28 from traffic_base import TrafficGeneratorException
29 import traffic_utils as utils
30
31 # pylint: disable=import-error
32 from trex_stl_lib.api import CTRexVmInsFixHwCs
33 from trex_stl_lib.api import Dot1Q
34 from trex_stl_lib.api import Ether
35 from trex_stl_lib.api import IP
36 from trex_stl_lib.api import STLClient
37 from trex_stl_lib.api import STLError
38 from trex_stl_lib.api import STLFlowLatencyStats
39 from trex_stl_lib.api import STLFlowStats
40 from trex_stl_lib.api import STLPktBuilder
41 from trex_stl_lib.api import STLScVmRaw
42 from trex_stl_lib.api import STLStream
43 from trex_stl_lib.api import STLTXCont
44 from trex_stl_lib.api import STLVmFixChecksumHw
45 from trex_stl_lib.api import STLVmFlowVar
46 from trex_stl_lib.api import STLVmFlowVarRepetableRandom
47 from trex_stl_lib.api import STLVmWrFlowVar
48 from trex_stl_lib.api import UDP
49 from trex_stl_lib.services.trex_stl_service_arp import STLServiceARP
50
51
52 # pylint: enable=import-error
53
54
55 class TRex(AbstractTrafficGenerator):
56     """TRex traffic generator driver."""
57
58     LATENCY_PPS = 1000
59     CHAIN_PG_ID_MASK = 0x007F
60     PORT_PG_ID_MASK = 0x0080
61     LATENCY_PG_ID_MASK = 0x0100
62
63     def __init__(self, traffic_client):
64         AbstractTrafficGenerator.__init__(self, traffic_client)
65         self.client = None
66         self.id = count()
67         self.port_handle = []
68         self.chain_count = self.generator_config.service_chain_count
69         self.rates = []
70         # A dict of list of dest macs indexed by port#
71         # the dest macs in the list are indexed by the chain id
72         self.arps = {}
73         self.capture_id = None
74         self.packet_list = []
75
76     def get_version(self):
77         """Get the Trex version."""
78         return self.client.get_server_version()
79
80     def get_pg_id(self, port, chain_id):
81         """Calculate the packet group IDs to use for a given port/stream type/chain_id.
82
83         port: 0 or 1
84         chain_id: identifies to which chain the pg_id is associated (0 to 255)
85         return: pg_id, lat_pg_id
86
87         We use a bit mask to set up the 3 fields:
88         0x007F: chain ID (8 bits for a max of 128 chains)
89         0x0080: port bit
90         0x0100: latency bit
91         """
92         pg_id = port * TRex.PORT_PG_ID_MASK | chain_id
93         return pg_id, pg_id | TRex.LATENCY_PG_ID_MASK
94
95     def extract_stats(self, in_stats):
96         """Extract stats from dict returned by Trex API.
97
98         :param in_stats: dict as returned by TRex api
99         """
100         utils.nan_replace(in_stats)
101         # LOG.debug(in_stats)
102
103         result = {}
104         # port_handles should have only 2 elements: [0, 1]
105         # so (1 - ph) will be the index for the far end port
106         for ph in self.port_handle:
107             stats = in_stats[ph]
108             far_end_stats = in_stats[1 - ph]
109             result[ph] = {
110                 'tx': {
111                     'total_pkts': cast_integer(stats['opackets']),
112                     'total_pkt_bytes': cast_integer(stats['obytes']),
113                     'pkt_rate': cast_integer(stats['tx_pps']),
114                     'pkt_bit_rate': cast_integer(stats['tx_bps'])
115                 },
116                 'rx': {
117                     'total_pkts': cast_integer(stats['ipackets']),
118                     'total_pkt_bytes': cast_integer(stats['ibytes']),
119                     'pkt_rate': cast_integer(stats['rx_pps']),
120                     'pkt_bit_rate': cast_integer(stats['rx_bps']),
121                     # how many pkts were dropped in RX direction
122                     # need to take the tx counter on the far end port
123                     'dropped_pkts': cast_integer(
124                         far_end_stats['opackets'] - stats['ipackets'])
125                 }
126             }
127             self.__combine_latencies(in_stats, result[ph]['rx'], ph)
128
129         total_tx_pkts = result[0]['tx']['total_pkts'] + result[1]['tx']['total_pkts']
130         result["total_tx_rate"] = cast_integer(total_tx_pkts / self.config.duration_sec)
131         result["flow_stats"] = in_stats["flow_stats"]
132         result["latency"] = in_stats["latency"]
133         return result
134
135     def get_stream_stats(self, trex_stats, if_stats, latencies, chain_idx):
136         """Extract the aggregated stats for a given chain.
137
138         trex_stats: stats as returned by get_stats()
139         if_stats: a list of 2 interface stats to update (port 0 and 1)
140         latencies: a list of 2 Latency instances to update for this chain (port 0 and 1)
141                    latencies[p] is the latency for packets sent on port p
142                    if there are no latency streams, the Latency instances are not modified
143         chain_idx: chain index of the interface stats
144
145         The packet counts include normal and latency streams.
146
147         Trex returns flows stats as follows:
148
149         'flow_stats': {0: {'rx_bps': {0: 0, 1: 0, 'total': 0},
150                    'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
151                    'rx_bytes': {0: nan, 1: nan, 'total': nan},
152                    'rx_pkts': {0: 0, 1: 15001, 'total': 15001},
153                    'rx_pps': {0: 0, 1: 0, 'total': 0},
154                    'tx_bps': {0: 0, 1: 0, 'total': 0},
155                    'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
156                    'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068},
157                    'tx_pkts': {0: 15001, 1: 0, 'total': 15001},
158                    'tx_pps': {0: 0, 1: 0, 'total': 0}},
159                1: {'rx_bps': {0: 0, 1: 0, 'total': 0},
160                    'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
161                    'rx_bytes': {0: nan, 1: nan, 'total': nan},
162                    'rx_pkts': {0: 0, 1: 15001, 'total': 15001},
163                    'rx_pps': {0: 0, 1: 0, 'total': 0},
164                    'tx_bps': {0: 0, 1: 0, 'total': 0},
165                    'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
166                    'tx_bytes': {0: 1020068, 1: 0, 'total': 1020068},
167                    'tx_pkts': {0: 15001, 1: 0, 'total': 15001},
168                    'tx_pps': {0: 0, 1: 0, 'total': 0}},
169                 128: {'rx_bps': {0: 0, 1: 0, 'total': 0},
170                 'rx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
171                 'rx_bytes': {0: nan, 1: nan, 'total': nan},
172                 'rx_pkts': {0: 15001, 1: 0, 'total': 15001},
173                 'rx_pps': {0: 0, 1: 0, 'total': 0},
174                 'tx_bps': {0: 0, 1: 0, 'total': 0},
175                 'tx_bps_l1': {0: 0.0, 1: 0.0, 'total': 0.0},
176                 'tx_bytes': {0: 0, 1: 1020068, 'total': 1020068},
177                 'tx_pkts': {0: 0, 1: 15001, 'total': 15001},
178                 'tx_pps': {0: 0, 1: 0, 'total': 0}},etc...
179
180         the pg_id (0, 1, 128,...) is the key of the dict and is obtained using the
181         get_pg_id() method.
182         packet counters for a given stream sent on port p are reported as:
183         - tx_pkts[p] on port p
184         - rx_pkts[1-p] on the far end port
185
186         This is a tricky/critical counter transposition operation because
187         the results are grouped by port (not by stream):
188         tx_pkts_port(p=0) comes from pg_id(port=0, chain_idx)['tx_pkts'][0]
189         rx_pkts_port(p=0) comes from pg_id(port=1, chain_idx)['rx_pkts'][0]
190         tx_pkts_port(p=1) comes from pg_id(port=1, chain_idx)['tx_pkts'][1]
191         rx_pkts_port(p=1) comes from pg_id(port=0, chain_idx)['rx_pkts'][1]
192
193         or using a more generic formula:
194         tx_pkts_port(p) comes from pg_id(port=p, chain_idx)['tx_pkts'][p]
195         rx_pkts_port(p) comes from pg_id(port=1-p, chain_idx)['rx_pkts'][p]
196
197         the second formula is equivalent to
198         rx_pkts_port(1-p) comes from pg_id(port=p, chain_idx)['rx_pkts'][1-p]
199
200         If there are latency streams, those same counters need to be added in the same way
201         """
202         for ifs in if_stats:
203             ifs.tx = ifs.rx = 0
204         for port in range(2):
205             pg_id, lat_pg_id = self.get_pg_id(port, chain_idx)
206             for pid in [pg_id, lat_pg_id]:
207                 try:
208                     pg_stats = trex_stats['flow_stats'][pid]
209                     if_stats[port].tx += pg_stats['tx_pkts'][port]
210                     if_stats[1 - port].rx += pg_stats['rx_pkts'][1 - port]
211                 except KeyError:
212                     pass
213             try:
214                 lat = trex_stats['latency'][lat_pg_id]['latency']
215                 # dropped_pkts += lat['err_cntrs']['dropped']
216                 latencies[port].max_usec = int(round(lat['total_max']))
217                 latencies[port].min_usec = int(round(lat['total_min']))
218                 latencies[port].avg_usec = int(round(lat['average']))
219             except KeyError:
220                 pass
221
222     def __combine_latencies(self, in_stats, results, port_handle):
223         """Traverse TRex result dictionary and combines chosen latency stats."""
224         total_max = 0
225         average = 0
226         total_min = float("inf")
227         for chain_id in range(self.chain_count):
228             try:
229                 _, lat_pg_id = self.get_pg_id(port_handle, chain_id)
230                 lat = in_stats['latency'][lat_pg_id]['latency']
231                 # dropped_pkts += lat['err_cntrs']['dropped']
232                 total_max = max(lat['total_max'], total_max)
233                 total_min = min(lat['total_min'], total_min)
234                 average += lat['average']
235             except KeyError:
236                 pass
237         if total_min == float("inf"):
238             total_min = 0
239         results['min_delay_usec'] = total_min
240         results['max_delay_usec'] = total_max
241         results['avg_delay_usec'] = int(average / self.chain_count)
242
243     def _create_pkt(self, stream_cfg, l2frame_size):
244         pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
245         if stream_cfg['vlan_tag'] is not None:
246             # 50 = 14 (Ethernet II) + 4 (Vlan tag) + 4 (CRC Checksum) + 20 (IPv4) + 8 (UDP)
247             pkt_base /= Dot1Q(vlan=stream_cfg['vlan_tag'])
248             l2payload_size = int(l2frame_size) - 50
249         else:
250             # 46 = 14 (Ethernet II) + 4 (CRC Checksum) + 20 (IPv4) + 8 (UDP)
251             l2payload_size = int(l2frame_size) - 46
252         payload = 'x' * l2payload_size
253         udp_args = {}
254         if stream_cfg['udp_src_port']:
255             udp_args['sport'] = int(stream_cfg['udp_src_port'])
256         if stream_cfg['udp_dst_port']:
257             udp_args['dport'] = int(stream_cfg['udp_dst_port'])
258         pkt_base /= IP() / UDP(**udp_args)
259
260         if stream_cfg['ip_addrs_step'] == 'random':
261             src_fv = STLVmFlowVarRepetableRandom(
262                 name="ip_src",
263                 min_value=stream_cfg['ip_src_addr'],
264                 max_value=stream_cfg['ip_src_addr_max'],
265                 size=4,
266                 seed=random.randint(0, 32767),
267                 limit=stream_cfg['ip_src_count'])
268             dst_fv = STLVmFlowVarRepetableRandom(
269                 name="ip_dst",
270                 min_value=stream_cfg['ip_dst_addr'],
271                 max_value=stream_cfg['ip_dst_addr_max'],
272                 size=4,
273                 seed=random.randint(0, 32767),
274                 limit=stream_cfg['ip_dst_count'])
275         else:
276             src_fv = STLVmFlowVar(
277                 name="ip_src",
278                 min_value=stream_cfg['ip_src_addr'],
279                 max_value=stream_cfg['ip_src_addr'],
280                 size=4,
281                 op="inc",
282                 step=stream_cfg['ip_addrs_step'])
283             dst_fv = STLVmFlowVar(
284                 name="ip_dst",
285                 min_value=stream_cfg['ip_dst_addr'],
286                 max_value=stream_cfg['ip_dst_addr_max'],
287                 size=4,
288                 op="inc",
289                 step=stream_cfg['ip_addrs_step'])
290
291         vm_param = [
292             src_fv,
293             STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP.src"),
294             dst_fv,
295             STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP.dst"),
296             STLVmFixChecksumHw(l3_offset="IP",
297                                l4_offset="UDP",
298                                l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP)
299         ]
300
301         return STLPktBuilder(pkt=pkt_base / payload, vm=STLScVmRaw(vm_param))
302
303     def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True):
304         """Create a list of streams corresponding to a given chain and stream config.
305
306         port: port where the streams originate (0 or 1)
307         chain_id: the chain to which the streams are associated to
308         stream_cfg: stream configuration
309         l2frame: L2 frame size
310         latency: if True also create a latency stream
311         """
312         streams = []
313         pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
314         if l2frame == 'IMIX':
315             min_size = 64 if stream_cfg['vlan_tag'] is None else 68
316             self.adjust_imix_min_size(min_size)
317             for ratio, l2_frame_size in zip(self.imix_ratios, self.imix_l2_sizes):
318                 pkt = self._create_pkt(stream_cfg, l2_frame_size)
319                 streams.append(STLStream(packet=pkt,
320                                          flow_stats=STLFlowStats(pg_id=pg_id),
321                                          mode=STLTXCont(pps=ratio)))
322
323             if latency:
324                 # for IMIX, the latency packets have the average IMIX packet size
325                 pkt = self._create_pkt(stream_cfg, self.imix_avg_l2_size)
326
327         else:
328             pkt = self._create_pkt(stream_cfg, l2frame)
329             streams.append(STLStream(packet=pkt,
330                                      flow_stats=STLFlowStats(pg_id=pg_id),
331                                      mode=STLTXCont()))
332
333         if latency:
334             streams.append(STLStream(packet=pkt,
335                                      flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id),
336                                      mode=STLTXCont(pps=self.LATENCY_PPS)))
337         return streams
338
339     @timeout(5)
340     def __connect(self, client):
341         client.connect()
342
343     def __connect_after_start(self):
344         # after start, Trex may take a bit of time to initialize
345         # so we need to retry a few times
346         for it in xrange(self.config.generic_retry_count):
347             try:
348                 time.sleep(1)
349                 self.client.connect()
350                 break
351             except Exception as ex:
352                 if it == (self.config.generic_retry_count - 1):
353                     raise
354                 LOG.info("Retrying connection to TRex (%s)...", ex.message)
355
356     def connect(self):
357         """Connect to the TRex server."""
358         server_ip = self.generator_config.ip
359         LOG.info("Connecting to TRex (%s)...", server_ip)
360
361         # Connect to TRex server
362         self.client = STLClient(server=server_ip)
363         try:
364             self.__connect(self.client)
365         except (TimeoutError, STLError) as e:
366             if server_ip == '127.0.0.1':
367                 try:
368                     self.__start_server()
369                     self.__connect_after_start()
370                 except (TimeoutError, STLError) as e:
371                     LOG.error('Cannot connect to TRex')
372                     LOG.error(traceback.format_exc())
373                     logpath = '/tmp/trex.log'
374                     if os.path.isfile(logpath):
375                         # Wait for TRex to finish writing error message
376                         last_size = 0
377                         for _ in xrange(self.config.generic_retry_count):
378                             size = os.path.getsize(logpath)
379                             if size == last_size:
380                                 # probably not writing anymore
381                                 break
382                             last_size = size
383                             time.sleep(1)
384                         with open(logpath, 'r') as f:
385                             message = f.read()
386                     else:
387                         message = e.message
388                     raise TrafficGeneratorException(message)
389             else:
390                 raise TrafficGeneratorException(e.message)
391
392         ports = list(self.generator_config.ports)
393         self.port_handle = ports
394         # Prepare the ports
395         self.client.reset(ports)
396         # Read HW information from each port
397         # this returns an array of dict (1 per port)
398         """
399         Example of output for Intel XL710
400         [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True,
401           'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes',
402           u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']},
403           u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE',
404           u'description': u'Ethernet Controller XL710 for 40GbE QSFP+',
405           'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-',
406           u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match',
407           'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0',
408           'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off',
409           'layer_mode': 'Ethernet', u'numa': 0}, ...]
410         """
411         self.port_info = self.client.get_port_info(ports)
412         LOG.info('Connected to TRex')
413         for id, port in enumerate(self.port_info):
414             LOG.info('   Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s',
415                      id, port['description'], port['speed'], port['src_mac'],
416                      port['pci_addr'], port['driver'])
417         # Make sure the 2 ports have the same speed
418         if self.port_info[0]['speed'] != self.port_info[1]['speed']:
419             raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' %
420                                             (self.port_info[0]['speed'],
421                                              self.port_info[1]['speed']))
422
423     def set_mode(self):
424         if self.config.service_chain == ChainType.EXT and not self.config.no_arp:
425             self.__set_l3_mode()
426         else:
427             self.__set_l2_mode()
428
429     def __set_l3_mode(self):
430         self.client.set_service_mode(ports=self.port_handle, enabled=True)
431         for port, device in zip(self.port_handle, self.generator_config.devices):
432             try:
433                 self.client.set_l3_mode(port=port,
434                                         src_ipv4=device.tg_gateway_ip,
435                                         dst_ipv4=device.dst.gateway_ip,
436                                         vlan=device.vlan_tag if device.vlan_tagging else None)
437             except STLError:
438                 # TRex tries to resolve ARP already, doesn't have to be successful yet
439                 continue
440         self.client.set_service_mode(ports=self.port_handle, enabled=False)
441
442     def __set_l2_mode(self):
443         self.client.set_service_mode(ports=self.port_handle, enabled=True)
444         for port, device in zip(self.port_handle, self.generator_config.devices):
445             for cfg in device.get_stream_configs():
446                 self.client.set_l2_mode(port=port, dst_mac=cfg['mac_dst'])
447         self.client.set_service_mode(ports=self.port_handle, enabled=False)
448
449     def __start_server(self):
450         server = TRexTrafficServer()
451         server.run_server(self.generator_config)
452
453     def resolve_arp(self):
454         """Resolve all configured remote IP addresses.
455
456         return: True if ARP resolved successfully
457         """
458         self.client.set_service_mode(ports=self.port_handle)
459         LOG.info('Polling ARP until successful...')
460         arps = {}
461         for port, device in zip(self.port_handle, self.generator_config.devices):
462             # there should be 1 stream config per chain
463             stream_configs = device.get_stream_configs()
464             chain_count = len(stream_configs)
465             ctx = self.client.create_service_ctx(port=port)
466             # all dest macs on this port indexed by chain ID
467             dst_macs = [None] * chain_count
468             dst_macs_count = 0
469             # the index in the list is the chain id
470             arps = [
471                 STLServiceARP(ctx,
472                               src_ip=cfg['ip_src_tg_gw'],
473                               dst_ip=cfg['mac_discovery_gw'],
474                               vlan=device.vlan_tag if device.vlan_tagging else None)
475                 for cfg in stream_configs()
476             ]
477
478             for attempt in range(self.config.generic_retry_count):
479                 try:
480                     ctx.run(arps)
481                 except STLError:
482                     LOG.error(traceback.format_exc())
483                     continue
484
485                 unresolved = []
486                 for chain_id, mac in enumerate(dst_macs):
487                     if not mac:
488                         arp_record = arps[chain_id].get_record()
489                         if arp_record.dest_mac:
490                             dst_macs[chain_id] = arp_record.dst_mac
491                             dst_macs_count += 1
492                             LOG.info('   ARP: port=%d chain=%d IP=%s -> MAC=%s',
493                                      port, chain_id,
494                                      arp_record.dst_ip, arp_record.dst_mac)
495                         else:
496                             unresolved.append(arp_record.dst_ip)
497                 if dst_macs_count == chain_count:
498                     arps[port] = dst_macs
499                     LOG.info('ARP resolved successfully for port %s', port)
500                     break
501                 else:
502                     retry = attempt + 1
503                     LOG.info('Retrying ARP for: %s (retry %d/%d)',
504                              unresolved, retry, self.config.generic_retry_count)
505                     if retry < self.config.generic_retry_count:
506                         time.sleep(self.config.generic_poll_sec)
507             else:
508                 LOG.error('ARP timed out for port %s (resolved %d out of %d)',
509                           port,
510                           dst_macs_count,
511                           chain_count)
512                 break
513
514         self.client.set_service_mode(ports=self.port_handle, enabled=False)
515         if len(arps) == len(self.port_handle):
516             self.arps = arps
517             return True
518         return False
519
520     def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency):
521         """Check if rate provided by user is above requirements. Applies only if latency is True."""
522         intf_speed = self.generator_config.intf_speed
523         if latency:
524             if bidirectional:
525                 mult = 2
526                 total_rate = 0
527                 for rate in rates:
528                     r = utils.convert_rates(l2frame_size, rate, intf_speed)
529                     total_rate += int(r['rate_pps'])
530             else:
531                 mult = 1
532                 total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
533             # rate must be enough for latency stream and at least 1 pps for base stream per chain
534             required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult
535             result = utils.convert_rates(l2frame_size,
536                                          {'rate_pps': required_rate},
537                                          intf_speed * mult)
538             result['result'] = total_rate >= required_rate
539             return result
540
541         return {'result': True}
542
543     def create_traffic(self, l2frame_size, rates, bidirectional, latency=True):
544         """Program all the streams in Trex server.
545
546         l2frame_size: L2 frame size or IMIX
547         rates: a list of 2 rates to run each direction
548                each rate is a dict like {'rate_pps': '10kpps'}
549         bidirectional: True if bidirectional
550         latency: True if latency measurement is needed
551         """
552         r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
553         if not r['result']:
554             raise TrafficGeneratorException(
555                 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.'
556                 .format(pps=r['rate_pps'],
557                         bps=r['rate_bps'],
558                         load=r['rate_percent']))
559         # a dict of list of streams indexed by port#
560         # in case of fixed size, has self.chain_count * 2 * 2 streams
561         # (1 normal + 1 latency stream per direction per chain)
562         # for IMIX, has self.chain_count * 2 * 4 streams
563         # (3 normal + 1 latency stream per direction per chain)
564         streamblock = {}
565         for port in self.port_handle:
566             streamblock[port] = []
567         stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
568         self.rates = [utils.to_rate_str(rate) for rate in rates]
569         for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
570             if self.arps:
571                 # in case of external chain with ARP, fill in the proper dest MAC
572                 # based on the 2 ARP replies for each chain
573                 fwd_stream_cfg['mac_dst'] = self.arps[self.port_handle[0]][chain_id]
574                 rev_stream_cfg['mac_dst'] = self.arps[self.port_handle[1]][chain_id]
575
576             streamblock[0].extend(self.generate_streams(self.port_handle[0],
577                                                         chain_id,
578                                                         fwd_stream_cfg,
579                                                         l2frame_size,
580                                                         latency=latency))
581             if len(self.rates) > 1:
582                 streamblock[1].extend(self.generate_streams(self.port_handle[1],
583                                                             chain_id,
584                                                             rev_stream_cfg,
585                                                             l2frame_size,
586                                                             latency=bidirectional and latency))
587
588         for port in self.port_handle:
589             self.client.add_streams(streamblock[port], ports=port)
590             LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
591
592     def clear_streamblock(self):
593         """Clear all streams from TRex."""
594         self.rates = []
595         self.client.reset(self.port_handle)
596         LOG.info('Cleared all existing streams')
597
598     def get_stats(self):
599         """Get stats from Trex."""
600         stats = self.client.get_stats()
601         return self.extract_stats(stats)
602
603     def get_macs(self):
604         """Return the Trex local port MAC addresses.
605
606         return: a list of MAC addresses indexed by the port#
607         """
608         return [port['src_mac'] for port in self.port_info]
609
610     def get_port_speed_gbps(self):
611         """Return the Trex local port MAC addresses.
612
613         return: a list of speed in Gbps indexed by the port#
614         """
615         return [port['speed'] for port in self.port_info]
616
617     def get_dest_macs(self):
618         """Return the dest MAC for all chains for both ports for the current traffic setup.
619
620         return: a list of MAC addresses indexed by the port# [[m00, m01...], [m10, m11...]]
621
622         If ARP are used, resolve_arp() must be called prior to calling this method.
623         """
624         # if ARP was used, return the dest MACs resolved by ARP
625         if self.arps:
626             return [self.arps[port] for port in self.port_handle]
627         # no ARP, use the dest MACs as configured in the devices
628         return [d.dest_macs for d in self.generator_config.devices]
629
630     def clear_stats(self):
631         """Clear all stats in the traffic gneerator."""
632         if self.port_handle:
633             self.client.clear_stats()
634
635     def start_traffic(self):
636         """Start generating traffic in all ports."""
637         for port, rate in zip(self.port_handle, self.rates):
638             self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True)
639
640     def stop_traffic(self):
641         """Stop generating traffic."""
642         self.client.stop(ports=self.port_handle)
643
644     def start_capture(self):
645         """Capture all packets on both ports that are unicast to us."""
646         if self.capture_id:
647             self.stop_capture()
648         # Need to filter out unwanted packets so we do not end up counting
649         # src MACs of frames that are not unicast to us
650         src_mac_list = self.get_macs()
651         bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
652         # ports must be set in service in order to enable capture
653         self.client.set_service_mode(ports=self.port_handle)
654         self.capture_id = self.client.start_capture(rx_ports=self.port_handle,
655                                                     bpf_filter=bpf_filter)
656
657     def fetch_capture_packets(self):
658         """Fetch capture packets in capture mode."""
659         if self.capture_id:
660             self.packet_list = []
661             self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
662                                               output=self.packet_list)
663
664     def stop_capture(self):
665         """Stop capturing packets."""
666         if self.capture_id:
667             self.client.stop_capture(capture_id=self.capture_id['id'])
668             self.capture_id = None
669             self.client.set_service_mode(ports=self.port_handle, enabled=False)
670
671     def cleanup(self):
672         """Cleanup Trex driver."""
673         if self.client:
674             try:
675                 self.client.reset(self.port_handle)
676                 self.client.disconnect()
677             except STLError:
678                 # TRex does not like a reset while in disconnected state
679                 pass