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