NFVBENCH-135 Enhance VxLAN UDP src port entropy
[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             STLVmFixChecksumHw(l3_offset="IP:{}".format(encap_level),
354                                l4_offset="UDP:{}".format(encap_level),
355                                l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP)
356         ])
357         pad = max(0, frame_size - len(pkt_base)) * 'x'
358
359         return STLPktBuilder(pkt=pkt_base / pad, vm=STLScVmRaw(vm_param))
360
361     def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True,
362                          e2e=False):
363         """Create a list of streams corresponding to a given chain and stream config.
364
365         port: port where the streams originate (0 or 1)
366         chain_id: the chain to which the streams are associated to
367         stream_cfg: stream configuration
368         l2frame: L2 frame size (including 4-byte FCS) or 'IMIX'
369         latency: if True also create a latency stream
370         e2e: True if performing "end to end" connectivity check
371         """
372         streams = []
373         pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
374         if l2frame == 'IMIX':
375             for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES):
376                 pkt = self._create_pkt(stream_cfg, l2_frame_size)
377                 if e2e:
378                     streams.append(STLStream(packet=pkt,
379                                              mode=STLTXCont(pps=ratio)))
380                 else:
381                     if stream_cfg['vxlan'] is True:
382                         streams.append(STLStream(packet=pkt,
383                                                  flow_stats=STLFlowStats(pg_id=pg_id,
384                                                                          vxlan=True),
385                                                  mode=STLTXCont(pps=ratio)))
386                     else:
387                         streams.append(STLStream(packet=pkt,
388                                                  flow_stats=STLFlowStats(pg_id=pg_id),
389                                                  mode=STLTXCont(pps=ratio)))
390
391             if latency:
392                 # for IMIX, the latency packets have the average IMIX packet size
393                 pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE)
394
395         else:
396             l2frame_size = int(l2frame)
397             pkt = self._create_pkt(stream_cfg, l2frame_size)
398             if e2e:
399                 streams.append(STLStream(packet=pkt,
400                                          mode=STLTXCont()))
401             else:
402                 if stream_cfg['vxlan'] is True:
403                     streams.append(STLStream(packet=pkt,
404                                              flow_stats=STLFlowStats(pg_id=pg_id,
405                                                                      vxlan=True),
406                                              mode=STLTXCont()))
407                 else:
408                     streams.append(STLStream(packet=pkt,
409                                              flow_stats=STLFlowStats(pg_id=pg_id),
410                                              mode=STLTXCont()))
411             # for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging
412             # without vlan, the min l2 frame size is 64
413             # with vlan it is 68
414             # This only applies to the latency stream
415             if latency and stream_cfg['vlan_tag'] and l2frame_size < 68:
416                 pkt = self._create_pkt(stream_cfg, 68)
417
418         if latency:
419             # TRex limitation: VXLAN skip is not supported for latency stream
420             streams.append(STLStream(packet=pkt,
421                                      flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id),
422                                      mode=STLTXCont(pps=self.LATENCY_PPS)))
423         return streams
424
425     @timeout(5)
426     def __connect(self, client):
427         client.connect()
428
429     def __connect_after_start(self):
430         # after start, Trex may take a bit of time to initialize
431         # so we need to retry a few times
432         for it in xrange(self.config.generic_retry_count):
433             try:
434                 time.sleep(1)
435                 self.client.connect()
436                 break
437             except Exception as ex:
438                 if it == (self.config.generic_retry_count - 1):
439                     raise
440                 LOG.info("Retrying connection to TRex (%s)...", ex.message)
441
442     def connect(self):
443         """Connect to the TRex server."""
444         server_ip = self.generator_config.ip
445         LOG.info("Connecting to TRex (%s)...", server_ip)
446
447         # Connect to TRex server
448         self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port,
449                                 async_port=self.generator_config.zmq_pub_port)
450         try:
451             self.__connect(self.client)
452             if server_ip == '127.0.0.1':
453                 config_updated = self.__check_config()
454                 if config_updated or self.config.restart:
455                     self.__restart()
456         except (TimeoutError, STLError) as e:
457             if server_ip == '127.0.0.1':
458                 self.__start_local_server()
459             else:
460                 raise TrafficGeneratorException(e.message)
461
462         ports = list(self.generator_config.ports)
463         self.port_handle = ports
464         # Prepare the ports
465         self.client.reset(ports)
466         # Read HW information from each port
467         # this returns an array of dict (1 per port)
468         """
469         Example of output for Intel XL710
470         [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True,
471           'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes',
472           u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']},
473           u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE',
474           u'description': u'Ethernet Controller XL710 for 40GbE QSFP+',
475           'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-',
476           u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match',
477           'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0',
478           'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off',
479           'layer_mode': 'Ethernet', u'numa': 0}, ...]
480         """
481         self.port_info = self.client.get_port_info(ports)
482         LOG.info('Connected to TRex')
483         for id, port in enumerate(self.port_info):
484             LOG.info('   Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s',
485                      id, port['description'], port['speed'], port['src_mac'],
486                      port['pci_addr'], port['driver'])
487         # Make sure the 2 ports have the same speed
488         if self.port_info[0]['speed'] != self.port_info[1]['speed']:
489             raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' %
490                                             (self.port_info[0]['speed'],
491                                              self.port_info[1]['speed']))
492
493     def __start_local_server(self):
494         try:
495             LOG.info("Starting TRex ...")
496             self.__start_server()
497             self.__connect_after_start()
498         except (TimeoutError, STLError) as e:
499             LOG.error('Cannot connect to TRex')
500             LOG.error(traceback.format_exc())
501             logpath = '/tmp/trex.log'
502             if os.path.isfile(logpath):
503                 # Wait for TRex to finish writing error message
504                 last_size = 0
505                 for _ in xrange(self.config.generic_retry_count):
506                     size = os.path.getsize(logpath)
507                     if size == last_size:
508                         # probably not writing anymore
509                         break
510                     last_size = size
511                     time.sleep(1)
512                 with open(logpath, 'r') as f:
513                     message = f.read()
514             else:
515                 message = e.message
516             raise TrafficGeneratorException(message)
517
518     def __start_server(self):
519         server = TRexTrafficServer()
520         server.run_server(self.generator_config)
521
522     def __check_config(self):
523         server = TRexTrafficServer()
524         return server.check_config_updated(self.generator_config)
525
526     def __restart(self):
527         LOG.info("Restarting TRex ...")
528         self.__stop_server()
529         # Wait for server stopped
530         for _ in xrange(self.config.generic_retry_count):
531             time.sleep(1)
532             if not self.client.is_connected():
533                 LOG.info("TRex is stopped...")
534                 break
535         self.__start_local_server()
536
537     def __stop_server(self):
538         if self.generator_config.ip == '127.0.0.1':
539             ports = self.client.get_acquired_ports()
540             LOG.info('Release ports %s and stopping TRex...', ports)
541             try:
542                 if ports:
543                     self.client.release(ports=ports)
544                 self.client.server_shutdown()
545             except STLError as e:
546                 LOG.warn('Unable to stop TRex. Error: %s', e)
547         else:
548             LOG.info('Using remote TRex. Unable to stop TRex')
549
550     def resolve_arp(self):
551         """Resolve all configured remote IP addresses.
552
553         return: None if ARP failed to resolve for all IP addresses
554                 else a dict of list of dest macs indexed by port#
555                 the dest macs in the list are indexed by the chain id
556         """
557         self.client.set_service_mode(ports=self.port_handle)
558         LOG.info('Polling ARP until successful...')
559         arp_dest_macs = {}
560         for port, device in zip(self.port_handle, self.generator_config.devices):
561             # there should be 1 stream config per chain
562             stream_configs = device.get_stream_configs()
563             chain_count = len(stream_configs)
564             ctx = self.client.create_service_ctx(port=port)
565             # all dest macs on this port indexed by chain ID
566             dst_macs = [None] * chain_count
567             dst_macs_count = 0
568             # the index in the list is the chain id
569             if self.config.vxlan:
570                 arps = [
571                     ServiceARP(ctx,
572                                src_ip=device.vtep_src_ip,
573                                dst_ip=device.vtep_dst_ip,
574                                vlan=device.vtep_vlan)
575                     for cfg in stream_configs
576                 ]
577             else:
578                 arps = [
579                     ServiceARP(ctx,
580                                src_ip=cfg['ip_src_tg_gw'],
581                                dst_ip=cfg['mac_discovery_gw'],
582                                # will be None if no vlan tagging
583                                vlan=cfg['vlan_tag'])
584                     for cfg in stream_configs
585                 ]
586
587             for attempt in range(self.config.generic_retry_count):
588                 try:
589                     ctx.run(arps)
590                 except STLError:
591                     LOG.error(traceback.format_exc())
592                     continue
593
594                 unresolved = []
595                 for chain_id, mac in enumerate(dst_macs):
596                     if not mac:
597                         arp_record = arps[chain_id].get_record()
598                         if arp_record.dst_mac:
599                             dst_macs[chain_id] = arp_record.dst_mac
600                             dst_macs_count += 1
601                             LOG.info('   ARP: port=%d chain=%d src IP=%s dst IP=%s -> MAC=%s',
602                                      port, chain_id,
603                                      arp_record.src_ip,
604                                      arp_record.dst_ip, arp_record.dst_mac)
605                         else:
606                             unresolved.append(arp_record.dst_ip)
607                 if dst_macs_count == chain_count:
608                     arp_dest_macs[port] = dst_macs
609                     LOG.info('ARP resolved successfully for port %s', port)
610                     break
611                 else:
612                     retry = attempt + 1
613                     LOG.info('Retrying ARP for: %s (retry %d/%d)',
614                              unresolved, retry, self.config.generic_retry_count)
615                     if retry < self.config.generic_retry_count:
616                         time.sleep(self.config.generic_poll_sec)
617             else:
618                 LOG.error('ARP timed out for port %s (resolved %d out of %d)',
619                           port,
620                           dst_macs_count,
621                           chain_count)
622                 break
623
624         self.client.set_service_mode(ports=self.port_handle, enabled=False)
625         if len(arp_dest_macs) == len(self.port_handle):
626             return arp_dest_macs
627         return None
628
629     def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency):
630         """Check if rate provided by user is above requirements. Applies only if latency is True."""
631         intf_speed = self.generator_config.intf_speed
632         if latency:
633             if bidirectional:
634                 mult = 2
635                 total_rate = 0
636                 for rate in rates:
637                     r = utils.convert_rates(l2frame_size, rate, intf_speed)
638                     total_rate += int(r['rate_pps'])
639             else:
640                 mult = 1
641                 total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
642             # rate must be enough for latency stream and at least 1 pps for base stream per chain
643             required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult
644             result = utils.convert_rates(l2frame_size,
645                                          {'rate_pps': required_rate},
646                                          intf_speed * mult)
647             result['result'] = total_rate >= required_rate
648             return result
649
650         return {'result': True}
651
652     def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
653         """Program all the streams in Trex server.
654
655         l2frame_size: L2 frame size or IMIX
656         rates: a list of 2 rates to run each direction
657                each rate is a dict like {'rate_pps': '10kpps'}
658         bidirectional: True if bidirectional
659         latency: True if latency measurement is needed
660         e2e: True if performing "end to end" connectivity check
661         """
662         r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
663         if not r['result']:
664             raise TrafficGeneratorException(
665                 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.'
666                 .format(pps=r['rate_pps'],
667                         bps=r['rate_bps'],
668                         load=r['rate_percent']))
669         # a dict of list of streams indexed by port#
670         # in case of fixed size, has self.chain_count * 2 * 2 streams
671         # (1 normal + 1 latency stream per direction per chain)
672         # for IMIX, has self.chain_count * 2 * 4 streams
673         # (3 normal + 1 latency stream per direction per chain)
674         streamblock = {}
675         for port in self.port_handle:
676             streamblock[port] = []
677         stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
678         self.rates = [utils.to_rate_str(rate) for rate in rates]
679         for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
680             streamblock[0].extend(self.generate_streams(self.port_handle[0],
681                                                         chain_id,
682                                                         fwd_stream_cfg,
683                                                         l2frame_size,
684                                                         latency=latency,
685                                                         e2e=e2e))
686             if len(self.rates) > 1:
687                 streamblock[1].extend(self.generate_streams(self.port_handle[1],
688                                                             chain_id,
689                                                             rev_stream_cfg,
690                                                             l2frame_size,
691                                                             latency=bidirectional and latency,
692                                                             e2e=e2e))
693
694         for port in self.port_handle:
695             if self.config.vxlan:
696                 self.client.set_port_attr(ports=port, vxlan_fs=[4789])
697             else:
698                 self.client.set_port_attr(ports=port, vxlan_fs=None)
699             self.client.add_streams(streamblock[port], ports=port)
700             LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
701
702     def clear_streamblock(self):
703         """Clear all streams from TRex."""
704         self.rates = []
705         self.client.reset(self.port_handle)
706         LOG.info('Cleared all existing streams')
707
708     def get_stats(self):
709         """Get stats from Trex."""
710         stats = self.client.get_stats()
711         return self.extract_stats(stats)
712
713     def get_macs(self):
714         """Return the Trex local port MAC addresses.
715
716         return: a list of MAC addresses indexed by the port#
717         """
718         return [port['src_mac'] for port in self.port_info]
719
720     def get_port_speed_gbps(self):
721         """Return the Trex local port MAC addresses.
722
723         return: a list of speed in Gbps indexed by the port#
724         """
725         return [port['speed'] for port in self.port_info]
726
727     def clear_stats(self):
728         """Clear all stats in the traffic gneerator."""
729         if self.port_handle:
730             self.client.clear_stats()
731
732     def start_traffic(self):
733         """Start generating traffic in all ports."""
734         for port, rate in zip(self.port_handle, self.rates):
735             self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True)
736
737     def stop_traffic(self):
738         """Stop generating traffic."""
739         self.client.stop(ports=self.port_handle)
740
741     def start_capture(self):
742         """Capture all packets on both ports that are unicast to us."""
743         if self.capture_id:
744             self.stop_capture()
745         # Need to filter out unwanted packets so we do not end up counting
746         # src MACs of frames that are not unicast to us
747         src_mac_list = self.get_macs()
748         bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
749         # ports must be set in service in order to enable capture
750         self.client.set_service_mode(ports=self.port_handle)
751         self.capture_id = self.client.start_capture(rx_ports=self.port_handle,
752                                                     bpf_filter=bpf_filter)
753
754     def fetch_capture_packets(self):
755         """Fetch capture packets in capture mode."""
756         if self.capture_id:
757             self.packet_list = []
758             self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
759                                               output=self.packet_list)
760
761     def stop_capture(self):
762         """Stop capturing packets."""
763         if self.capture_id:
764             self.client.stop_capture(capture_id=self.capture_id['id'])
765             self.capture_id = None
766             self.client.set_service_mode(ports=self.port_handle, enabled=False)
767
768     def cleanup(self):
769         """Cleanup Trex driver."""
770         if self.client:
771             try:
772                 self.client.reset(self.port_handle)
773                 self.client.disconnect()
774             except STLError:
775                 # TRex does not like a reset while in disconnected state
776                 pass