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