NFVBENCH-155 Add options to disable extra stats, latency stats and latency streams
[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                 # pick up the HDR histogram if present (otherwise will raise KeyError)
249                 latencies[port].hdrh = lat['hdrh']
250             except KeyError:
251                 pass
252
253     def __combine_latencies(self, in_stats, results, port_handle):
254         """Traverse TRex result dictionary and combines chosen latency stats.
255
256           example of latency dict returned by trex (2 chains):
257          'latency': {256: {'err_cntrs': {'dropped': 0,
258                                  'dup': 0,
259                                  'out_of_order': 0,
260                                  'seq_too_high': 0,
261                                  'seq_too_low': 0},
262                             'latency': {'average': 26.5,
263                                         'hdrh': u'HISTFAAAAEx4nJNpmSgz...bFRgxi',
264                                         'histogram': {20: 303,
265                                                         30: 320,
266                                                         40: 300,
267                                                         50: 73,
268                                                         60: 4,
269                                                         70: 1},
270                                         'jitter': 14,
271                                         'last_max': 63,
272                                         'total_max': 63,
273                                         'total_min': 20}},
274                     257: {'err_cntrs': {'dropped': 0,
275                                         'dup': 0,
276                                         'out_of_order': 0,
277                                         'seq_too_high': 0,
278                                         'seq_too_low': 0},
279                             'latency': {'average': 29.75,
280                                         'hdrh': u'HISTFAAAAEV4nJN...CALilDG0=',
281                                         'histogram': {20: 261,
282                                                         30: 431,
283                                                         40: 3,
284                                                         50: 80,
285                                                         60: 225},
286                                         'jitter': 23,
287                                         'last_max': 67,
288                                         'total_max': 67,
289                                         'total_min': 20}},
290                     384: {'err_cntrs': {'dropped': 0,
291                                         'dup': 0,
292                                         'out_of_order': 0,
293                                         'seq_too_high': 0,
294                                         'seq_too_low': 0},
295                             'latency': {'average': 18.0,
296                                         'hdrh': u'HISTFAAAADR4nJNpm...MjCwDDxAZG',
297                                         'histogram': {20: 987, 30: 14},
298                                         'jitter': 0,
299                                         'last_max': 34,
300                                         'total_max': 34,
301                                         'total_min': 20}},
302                     385: {'err_cntrs': {'dropped': 0,
303                                     'dup': 0,
304                                     'out_of_order': 0,
305                                     'seq_too_high': 0,
306                                     'seq_too_low': 0},
307                             'latency': {'average': 19.0,
308                                         'hdrh': u'HISTFAAAADR4nJNpm...NkYmJgDdagfK',
309                                         'histogram': {20: 989, 30: 11},
310                                         'jitter': 0,
311                                         'last_max': 38,
312                                         'total_max': 38,
313                                         'total_min': 20}},
314                     'global': {'bad_hdr': 0, 'old_flow': 0}},
315         """
316         total_max = 0
317         average = 0
318         total_min = float("inf")
319         for chain_id in range(self.chain_count):
320             try:
321                 _, lat_pg_id = self.get_pg_id(port_handle, chain_id)
322                 lat = in_stats['latency'][lat_pg_id]['latency']
323                 # dropped_pkts += lat['err_cntrs']['dropped']
324                 total_max = max(lat['total_max'], total_max)
325                 total_min = min(lat['total_min'], total_min)
326                 average += lat['average']
327             except KeyError:
328                 pass
329         if total_min == float("inf"):
330             total_min = 0
331         results['min_delay_usec'] = total_min
332         results['max_delay_usec'] = total_max
333         results['avg_delay_usec'] = int(average / self.chain_count)
334
335     def _bind_vxlan(self):
336         bind_layers(UDP, VXLAN, dport=4789)
337         bind_layers(VXLAN, Ether)
338
339     def _create_pkt(self, stream_cfg, l2frame_size):
340         """Create a packet of given size.
341
342         l2frame_size: size of the L2 frame in bytes (including the 32-bit FCS)
343         """
344         # Trex will add the FCS field, so we need to remove 4 bytes from the l2 frame size
345         frame_size = int(l2frame_size) - 4
346         vm_param = []
347         if stream_cfg['vxlan'] is True:
348             self._bind_vxlan()
349             encap_level = '1'
350             pkt_base = Ether(src=stream_cfg['vtep_src_mac'], dst=stream_cfg['vtep_dst_mac'])
351             if stream_cfg['vtep_vlan'] is not None:
352                 pkt_base /= Dot1Q(vlan=stream_cfg['vtep_vlan'])
353             pkt_base /= IP(src=stream_cfg['vtep_src_ip'], dst=stream_cfg['vtep_dst_ip'])
354             pkt_base /= UDP(sport=random.randint(1337, 32767), dport=4789)
355             pkt_base /= VXLAN(vni=stream_cfg['net_vni'])
356             pkt_base /= Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
357             # need to randomize the outer header UDP src port based on flow
358             vxlan_udp_src_fv = STLVmFlowVar(
359                 name="vxlan_udp_src",
360                 min_value=1337,
361                 max_value=32767,
362                 size=2,
363                 op="random")
364             vm_param = [vxlan_udp_src_fv,
365                         STLVmWrFlowVar(fv_name="vxlan_udp_src", pkt_offset="UDP.sport")]
366         else:
367             encap_level = '0'
368             pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst'])
369
370         if stream_cfg['vlan_tag'] is not None:
371             pkt_base /= Dot1Q(vlan=stream_cfg['vlan_tag'])
372
373         udp_args = {}
374         if stream_cfg['udp_src_port']:
375             udp_args['sport'] = int(stream_cfg['udp_src_port'])
376         if stream_cfg['udp_dst_port']:
377             udp_args['dport'] = int(stream_cfg['udp_dst_port'])
378         pkt_base /= IP() / UDP(**udp_args)
379
380         if stream_cfg['ip_addrs_step'] == 'random':
381             src_fv = STLVmFlowVarRepeatableRandom(
382                 name="ip_src",
383                 min_value=stream_cfg['ip_src_addr'],
384                 max_value=stream_cfg['ip_src_addr_max'],
385                 size=4,
386                 seed=random.randint(0, 32767),
387                 limit=stream_cfg['ip_src_count'])
388             dst_fv = STLVmFlowVarRepeatableRandom(
389                 name="ip_dst",
390                 min_value=stream_cfg['ip_dst_addr'],
391                 max_value=stream_cfg['ip_dst_addr_max'],
392                 size=4,
393                 seed=random.randint(0, 32767),
394                 limit=stream_cfg['ip_dst_count'])
395         else:
396             src_fv = STLVmFlowVar(
397                 name="ip_src",
398                 min_value=stream_cfg['ip_src_addr'],
399                 max_value=stream_cfg['ip_src_addr'],
400                 size=4,
401                 op="inc",
402                 step=stream_cfg['ip_addrs_step'])
403             dst_fv = STLVmFlowVar(
404                 name="ip_dst",
405                 min_value=stream_cfg['ip_dst_addr'],
406                 max_value=stream_cfg['ip_dst_addr_max'],
407                 size=4,
408                 op="inc",
409                 step=stream_cfg['ip_addrs_step'])
410
411         vm_param.extend([
412             src_fv,
413             STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)),
414             dst_fv,
415             STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level))
416         ])
417
418         for encap in range(int(encap_level), -1, -1):
419             # Fixing the checksums for all encap levels
420             vm_param.append(STLVmFixChecksumHw(l3_offset="IP:{}".format(encap),
421                                                l4_offset="UDP:{}".format(encap),
422                                                l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP))
423         pad = max(0, frame_size - len(pkt_base)) * 'x'
424
425         return STLPktBuilder(pkt=pkt_base / pad,
426                              vm=STLScVmRaw(vm_param, cache_size=int(self.config.cache_size)))
427
428     def generate_streams(self, port, chain_id, stream_cfg, l2frame, latency=True,
429                          e2e=False):
430         """Create a list of streams corresponding to a given chain and stream config.
431
432         port: port where the streams originate (0 or 1)
433         chain_id: the chain to which the streams are associated to
434         stream_cfg: stream configuration
435         l2frame: L2 frame size (including 4-byte FCS) or 'IMIX'
436         latency: if True also create a latency stream
437         e2e: True if performing "end to end" connectivity check
438         """
439         streams = []
440         pg_id, lat_pg_id = self.get_pg_id(port, chain_id)
441         if self.config.no_flow_stats:
442             LOG.info("Traffic flow statistics are disabled.")
443         if l2frame == 'IMIX':
444             for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES):
445                 pkt = self._create_pkt(stream_cfg, l2_frame_size)
446                 if e2e:
447                     streams.append(STLStream(packet=pkt,
448                                              mode=STLTXCont(pps=ratio)))
449                 else:
450                     if stream_cfg['vxlan'] is True:
451                         streams.append(STLStream(packet=pkt,
452                                                  flow_stats=STLFlowStats(pg_id=pg_id,
453                                                                          vxlan=True)
454                                                  if not self.config.no_flow_stats else None,
455                                                  mode=STLTXCont(pps=ratio)))
456                     else:
457                         streams.append(STLStream(packet=pkt,
458                                                  flow_stats=STLFlowStats(pg_id=pg_id)
459                                                  if not self.config.no_flow_stats else None,
460                                                  mode=STLTXCont(pps=ratio)))
461
462             if latency:
463                 # for IMIX, the latency packets have the average IMIX packet size
464                 pkt = self._create_pkt(stream_cfg, IMIX_AVG_L2_FRAME_SIZE)
465
466         else:
467             l2frame_size = int(l2frame)
468             pkt = self._create_pkt(stream_cfg, l2frame_size)
469             if e2e:
470                 streams.append(STLStream(packet=pkt,
471                                          mode=STLTXCont()))
472             else:
473                 if stream_cfg['vxlan'] is True:
474                     streams.append(STLStream(packet=pkt,
475                                              flow_stats=STLFlowStats(pg_id=pg_id,
476                                                                      vxlan=True)
477                                              if not self.config.no_flow_stats else None,
478                                              mode=STLTXCont()))
479                 else:
480                     streams.append(STLStream(packet=pkt,
481                                              flow_stats=STLFlowStats(pg_id=pg_id)
482                                              if not self.config.no_flow_stats else None,
483                                              mode=STLTXCont()))
484             # for the latency stream, the minimum payload is 16 bytes even in case of vlan tagging
485             # without vlan, the min l2 frame size is 64
486             # with vlan it is 68
487             # This only applies to the latency stream
488             if latency and stream_cfg['vlan_tag'] and l2frame_size < 68:
489                 pkt = self._create_pkt(stream_cfg, 68)
490
491         if latency:
492             if self.config.no_latency_stats:
493                 LOG.info("Latency flow statistics are disabled.")
494             if stream_cfg['vxlan'] is True:
495                 streams.append(STLStream(packet=pkt,
496                                          flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id,
497                                                                         vxlan=True)
498                                          if not self.config.no_latency_stats else None,
499                                          mode=STLTXCont(pps=self.LATENCY_PPS)))
500             else:
501                 streams.append(STLStream(packet=pkt,
502                                          flow_stats=STLFlowLatencyStats(pg_id=lat_pg_id)
503                                          if not self.config.no_latency_stats else None,
504                                          mode=STLTXCont(pps=self.LATENCY_PPS)))
505         return streams
506
507     @timeout(5)
508     def __connect(self, client):
509         client.connect()
510
511     def __connect_after_start(self):
512         # after start, Trex may take a bit of time to initialize
513         # so we need to retry a few times
514         for it in xrange(self.config.generic_retry_count):
515             try:
516                 time.sleep(1)
517                 self.client.connect()
518                 break
519             except Exception as ex:
520                 if it == (self.config.generic_retry_count - 1):
521                     raise
522                 LOG.info("Retrying connection to TRex (%s)...", ex.message)
523
524     def connect(self):
525         """Connect to the TRex server."""
526         server_ip = self.generator_config.ip
527         LOG.info("Connecting to TRex (%s)...", server_ip)
528
529         # Connect to TRex server
530         self.client = STLClient(server=server_ip, sync_port=self.generator_config.zmq_rpc_port,
531                                 async_port=self.generator_config.zmq_pub_port)
532         try:
533             self.__connect(self.client)
534             if server_ip == '127.0.0.1':
535                 config_updated = self.__check_config()
536                 if config_updated or self.config.restart:
537                     self.__restart()
538         except (TimeoutError, STLError) as e:
539             if server_ip == '127.0.0.1':
540                 self.__start_local_server()
541             else:
542                 raise TrafficGeneratorException(e.message)
543
544         ports = list(self.generator_config.ports)
545         self.port_handle = ports
546         # Prepare the ports
547         self.client.reset(ports)
548         # Read HW information from each port
549         # this returns an array of dict (1 per port)
550         """
551         Example of output for Intel XL710
552         [{'arp': '-', 'src_ipv4': '-', u'supp_speeds': [40000], u'is_link_supported': True,
553           'grat_arp': 'off', 'speed': 40, u'index': 0, 'link_change_supported': 'yes',
554           u'rx': {u'counters': 127, u'caps': [u'flow_stats', u'latency']},
555           u'is_virtual': 'no', 'prom': 'off', 'src_mac': u'3c:fd:fe:a8:24:48', 'status': 'IDLE',
556           u'description': u'Ethernet Controller XL710 for 40GbE QSFP+',
557           'dest': u'fa:16:3e:3c:63:04', u'is_fc_supported': False, 'vlan': '-',
558           u'driver': u'net_i40e', 'led_change_supported': 'yes', 'rx_filter_mode': 'hardware match',
559           'fc': 'none', 'link': 'UP', u'hw_mac': u'3c:fd:fe:a8:24:48', u'pci_addr': u'0000:5e:00.0',
560           'mult': 'off', 'fc_supported': 'no', u'is_led_supported': True, 'rx_queue': 'off',
561           'layer_mode': 'Ethernet', u'numa': 0}, ...]
562         """
563         self.port_info = self.client.get_port_info(ports)
564         LOG.info('Connected to TRex')
565         for id, port in enumerate(self.port_info):
566             LOG.info('   Port %d: %s speed=%dGbps mac=%s pci=%s driver=%s',
567                      id, port['description'], port['speed'], port['src_mac'],
568                      port['pci_addr'], port['driver'])
569         # Make sure the 2 ports have the same speed
570         if self.port_info[0]['speed'] != self.port_info[1]['speed']:
571             raise TrafficGeneratorException('Traffic generator ports speed mismatch: %d/%d Gbps' %
572                                             (self.port_info[0]['speed'],
573                                              self.port_info[1]['speed']))
574
575     def __start_local_server(self):
576         try:
577             LOG.info("Starting TRex ...")
578             self.__start_server()
579             self.__connect_after_start()
580         except (TimeoutError, STLError) as e:
581             LOG.error('Cannot connect to TRex')
582             LOG.error(traceback.format_exc())
583             logpath = '/tmp/trex.log'
584             if os.path.isfile(logpath):
585                 # Wait for TRex to finish writing error message
586                 last_size = 0
587                 for _ in xrange(self.config.generic_retry_count):
588                     size = os.path.getsize(logpath)
589                     if size == last_size:
590                         # probably not writing anymore
591                         break
592                     last_size = size
593                     time.sleep(1)
594                 with open(logpath, 'r') as f:
595                     message = f.read()
596             else:
597                 message = e.message
598             raise TrafficGeneratorException(message)
599
600     def __start_server(self):
601         server = TRexTrafficServer()
602         server.run_server(self.generator_config)
603
604     def __check_config(self):
605         server = TRexTrafficServer()
606         return server.check_config_updated(self.generator_config)
607
608     def __restart(self):
609         LOG.info("Restarting TRex ...")
610         self.__stop_server()
611         # Wait for server stopped
612         for _ in xrange(self.config.generic_retry_count):
613             time.sleep(1)
614             if not self.client.is_connected():
615                 LOG.info("TRex is stopped...")
616                 break
617         self.__start_local_server()
618
619     def __stop_server(self):
620         if self.generator_config.ip == '127.0.0.1':
621             ports = self.client.get_acquired_ports()
622             LOG.info('Release ports %s and stopping TRex...', ports)
623             try:
624                 if ports:
625                     self.client.release(ports=ports)
626                 self.client.server_shutdown()
627             except STLError as e:
628                 LOG.warn('Unable to stop TRex. Error: %s', e)
629         else:
630             LOG.info('Using remote TRex. Unable to stop TRex')
631
632     def resolve_arp(self):
633         """Resolve all configured remote IP addresses.
634
635         return: None if ARP failed to resolve for all IP addresses
636                 else a dict of list of dest macs indexed by port#
637                 the dest macs in the list are indexed by the chain id
638         """
639         self.client.set_service_mode(ports=self.port_handle)
640         LOG.info('Polling ARP until successful...')
641         arp_dest_macs = {}
642         for port, device in zip(self.port_handle, self.generator_config.devices):
643             # there should be 1 stream config per chain
644             stream_configs = device.get_stream_configs()
645             chain_count = len(stream_configs)
646             ctx = self.client.create_service_ctx(port=port)
647             # all dest macs on this port indexed by chain ID
648             dst_macs = [None] * chain_count
649             dst_macs_count = 0
650             # the index in the list is the chain id
651             if self.config.vxlan:
652                 arps = [
653                     ServiceARP(ctx,
654                                src_ip=device.vtep_src_ip,
655                                dst_ip=device.vtep_dst_ip,
656                                vlan=device.vtep_vlan)
657                     for cfg in stream_configs
658                 ]
659             else:
660                 arps = [
661                     ServiceARP(ctx,
662                                src_ip=cfg['ip_src_tg_gw'],
663                                dst_ip=cfg['mac_discovery_gw'],
664                                # will be None if no vlan tagging
665                                vlan=cfg['vlan_tag'])
666                     for cfg in stream_configs
667                 ]
668
669             for attempt in range(self.config.generic_retry_count):
670                 try:
671                     ctx.run(arps)
672                 except STLError:
673                     LOG.error(traceback.format_exc())
674                     continue
675
676                 unresolved = []
677                 for chain_id, mac in enumerate(dst_macs):
678                     if not mac:
679                         arp_record = arps[chain_id].get_record()
680                         if arp_record.dst_mac:
681                             dst_macs[chain_id] = arp_record.dst_mac
682                             dst_macs_count += 1
683                             LOG.info('   ARP: port=%d chain=%d src IP=%s dst IP=%s -> MAC=%s',
684                                      port, chain_id,
685                                      arp_record.src_ip,
686                                      arp_record.dst_ip, arp_record.dst_mac)
687                         else:
688                             unresolved.append(arp_record.dst_ip)
689                 if dst_macs_count == chain_count:
690                     arp_dest_macs[port] = dst_macs
691                     LOG.info('ARP resolved successfully for port %s', port)
692                     break
693                 else:
694                     retry = attempt + 1
695                     LOG.info('Retrying ARP for: %s (retry %d/%d)',
696                              unresolved, retry, self.config.generic_retry_count)
697                     if retry < self.config.generic_retry_count:
698                         time.sleep(self.config.generic_poll_sec)
699             else:
700                 LOG.error('ARP timed out for port %s (resolved %d out of %d)',
701                           port,
702                           dst_macs_count,
703                           chain_count)
704                 break
705
706         # if the capture from the TRex console was started before the arp request step,
707         # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
708         if not self.config.service_mode:
709             self.client.set_service_mode(ports=self.port_handle, enabled=False)
710         if len(arp_dest_macs) == len(self.port_handle):
711             return arp_dest_macs
712         return None
713
714     def __is_rate_enough(self, l2frame_size, rates, bidirectional, latency):
715         """Check if rate provided by user is above requirements. Applies only if latency is True."""
716         intf_speed = self.generator_config.intf_speed
717         if latency:
718             if bidirectional:
719                 mult = 2
720                 total_rate = 0
721                 for rate in rates:
722                     r = utils.convert_rates(l2frame_size, rate, intf_speed)
723                     total_rate += int(r['rate_pps'])
724             else:
725                 mult = 1
726                 total_rate = utils.convert_rates(l2frame_size, rates[0], intf_speed)
727             # rate must be enough for latency stream and at least 1 pps for base stream per chain
728             required_rate = (self.LATENCY_PPS + 1) * self.config.service_chain_count * mult
729             result = utils.convert_rates(l2frame_size,
730                                          {'rate_pps': required_rate},
731                                          intf_speed * mult)
732             result['result'] = total_rate >= required_rate
733             return result
734
735         return {'result': True}
736
737     def create_traffic(self, l2frame_size, rates, bidirectional, latency=True, e2e=False):
738         """Program all the streams in Trex server.
739
740         l2frame_size: L2 frame size or IMIX
741         rates: a list of 2 rates to run each direction
742                each rate is a dict like {'rate_pps': '10kpps'}
743         bidirectional: True if bidirectional
744         latency: True if latency measurement is needed
745         e2e: True if performing "end to end" connectivity check
746         """
747         r = self.__is_rate_enough(l2frame_size, rates, bidirectional, latency)
748         if not r['result']:
749             raise TrafficGeneratorException(
750                 'Required rate in total is at least one of: \n{pps}pps \n{bps}bps \n{load}%.'
751                 .format(pps=r['rate_pps'],
752                         bps=r['rate_bps'],
753                         load=r['rate_percent']))
754         # a dict of list of streams indexed by port#
755         # in case of fixed size, has self.chain_count * 2 * 2 streams
756         # (1 normal + 1 latency stream per direction per chain)
757         # for IMIX, has self.chain_count * 2 * 4 streams
758         # (3 normal + 1 latency stream per direction per chain)
759         streamblock = {}
760         for port in self.port_handle:
761             streamblock[port] = []
762         stream_cfgs = [d.get_stream_configs() for d in self.generator_config.devices]
763         self.rates = [utils.to_rate_str(rate) for rate in rates]
764         for chain_id, (fwd_stream_cfg, rev_stream_cfg) in enumerate(zip(*stream_cfgs)):
765             streamblock[0].extend(self.generate_streams(self.port_handle[0],
766                                                         chain_id,
767                                                         fwd_stream_cfg,
768                                                         l2frame_size,
769                                                         latency=latency,
770                                                         e2e=e2e))
771             if len(self.rates) > 1:
772                 streamblock[1].extend(self.generate_streams(self.port_handle[1],
773                                                             chain_id,
774                                                             rev_stream_cfg,
775                                                             l2frame_size,
776                                                             latency=bidirectional and latency,
777                                                             e2e=e2e))
778
779         for port in self.port_handle:
780             if self.config.vxlan:
781                 self.client.set_port_attr(ports=port, vxlan_fs=[4789])
782             else:
783                 self.client.set_port_attr(ports=port, vxlan_fs=None)
784             self.client.add_streams(streamblock[port], ports=port)
785             LOG.info('Created %d traffic streams for port %s.', len(streamblock[port]), port)
786
787     def clear_streamblock(self):
788         """Clear all streams from TRex."""
789         self.rates = []
790         self.client.reset(self.port_handle)
791         LOG.info('Cleared all existing streams')
792
793     def get_stats(self):
794         """Get stats from Trex."""
795         stats = self.client.get_stats()
796         return self.extract_stats(stats)
797
798     def get_macs(self):
799         """Return the Trex local port MAC addresses.
800
801         return: a list of MAC addresses indexed by the port#
802         """
803         return [port['src_mac'] for port in self.port_info]
804
805     def get_port_speed_gbps(self):
806         """Return the Trex local port MAC addresses.
807
808         return: a list of speed in Gbps indexed by the port#
809         """
810         return [port['speed'] for port in self.port_info]
811
812     def clear_stats(self):
813         """Clear all stats in the traffic gneerator."""
814         if self.port_handle:
815             self.client.clear_stats()
816
817     def start_traffic(self):
818         """Start generating traffic in all ports."""
819         for port, rate in zip(self.port_handle, self.rates):
820             self.client.start(ports=port, mult=rate, duration=self.config.duration_sec, force=True)
821
822     def stop_traffic(self):
823         """Stop generating traffic."""
824         self.client.stop(ports=self.port_handle)
825
826     def start_capture(self):
827         """Capture all packets on both ports that are unicast to us."""
828         if self.capture_id:
829             self.stop_capture()
830         # Need to filter out unwanted packets so we do not end up counting
831         # src MACs of frames that are not unicast to us
832         src_mac_list = self.get_macs()
833         bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
834         # ports must be set in service in order to enable capture
835         self.client.set_service_mode(ports=self.port_handle)
836         self.capture_id = self.client.start_capture(rx_ports=self.port_handle,
837                                                     bpf_filter=bpf_filter)
838
839     def fetch_capture_packets(self):
840         """Fetch capture packets in capture mode."""
841         if self.capture_id:
842             self.packet_list = []
843             self.client.fetch_capture_packets(capture_id=self.capture_id['id'],
844                                               output=self.packet_list)
845
846     def stop_capture(self):
847         """Stop capturing packets."""
848         if self.capture_id:
849             self.client.stop_capture(capture_id=self.capture_id['id'])
850             self.capture_id = None
851             # if the capture from TRex console was started before the connectivity step,
852             # it keeps 'service_mode' enabled, otherwise, it disables the 'service_mode'
853             if not self.config.service_mode:
854                 self.client.set_service_mode(ports=self.port_handle, enabled=False)
855
856     def cleanup(self):
857         """Cleanup Trex driver."""
858         if self.client:
859             try:
860                 self.client.reset(self.port_handle)
861                 self.client.disconnect()
862             except STLError:
863                 # TRex does not like a reset while in disconnected state
864                 pass
865
866     def set_service_mode(self, enabled=True):
867         """Enable/disable the 'service_mode'."""
868         self.client.set_service_mode(ports=self.port_handle, enabled=enabled)