HC Test: SPAN Port Mirroring Suite
[csit.git] / resources / tools / t-rex / t-rex-stateless.py
1 #!/usr/bin/python
2
3 # Copyright (c) 2016 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 """This script uses T-REX stateless API to drive t-rex instance.
17
18 Requirements:
19 - T-REX: https://github.com/cisco-system-traffic-generator/trex-core
20  - compiled and running T-REX process (eg. ./t-rex-64 -i -c 4)
21  - trex_stl_lib.api library
22 - Script must be executed on a node with T-REX instance
23 - 2 interfaces must be configured in configuretion file /etc/trex_cfg.yaml
24
25 ##################### Example of /etc/trex_cfg.yaml ##########################
26 - port_limit      : 2 # numbers of ports to use
27   version         : 2
28   interfaces      : ["84:00.0","84:00.1"] # PCI address of interfaces
29   port_info       :  # set eth mac addr
30           - dest_mac        :   [0x90,0xe2,0xba,0x1f,0x97,0xd5]  # port 0
31             src_mac         :   [0x90,0xe2,0xba,0x1f,0x97,0xd4]
32           - dest_mac        :   [0x90,0xe2,0xba,0x1f,0x97,0xd4]  # port 1
33             src_mac         :   [0x90,0xe2,0xba,0x1f,0x97,0xd5]
34 ##############################################################################
35
36 Functionality:
37 1. Configure traffic on running T-REX instance
38 2. Clear statistics on all ports
39 3. Ctart traffic with specified duration
40 4. Print statistics to stdout
41
42 """
43
44 import argparse
45 import json
46 import socket
47 import string
48 import struct
49 import sys
50
51 sys.path.insert(0, "/opt/trex-core-2.17/scripts/automation/"+\
52                    "trex_control_plane/stl/")
53 from trex_stl_lib.api import *
54
55 stream_table = {'IMIX_v4_1': [{'size': 64, 'pps': 28, 'isg':0},
56                               {'size': 570, 'pps': 16, 'isg':0.1},
57                               {'size': 1518, 'pps': 4, 'isg':0.2}]
58                }
59
60 def generate_payload(length):
61     """Generate payload.
62
63     :param length: Length of payload.
64     :type length: int
65     :return: Payload filled with chars.
66     :rtype string
67     """
68
69     word = ''
70     alphabet_size = len(string.letters)
71     for i in range(length):
72         word += string.letters[(i % alphabet_size)]
73
74     return word
75
76
77 def get_start_end_ipv6(start_ip, end_ip):
78     """Get start and end host from IPv6 as integer.
79
80     :param start_ip: Start IPv6.
81     :param end_ip: End IPv6.
82     :type start_ip: string
83     :type end_ip: string
84     :return: Start host, end host.
85     :rtype int
86     """
87
88     try:
89         ip1 = socket.inet_pton(socket.AF_INET6, start_ip)
90         ip2 = socket.inet_pton(socket.AF_INET6, end_ip)
91
92         hi1, lo1 = struct.unpack('!QQ', ip1)
93         hi2, lo2 = struct.unpack('!QQ', ip2)
94
95         if ((hi1 << 64) | lo1) > ((hi2 << 64) | lo2):
96             print "IPv6: start_ip is greater then end_ip"
97             sys.exit(2)
98
99         max_p1 = abs(int(lo1) - int(lo2))
100         base_p1 = lo1
101     except AddressValueError as ex_error:
102         print ex_error
103         sys.exit(2)
104
105     return base_p1, max_p1
106
107 def create_streams_v46(base_pkt_a, base_pkt_b, vm1, vm2, frame_size):
108     """Create STLStream streams
109
110     :param base_pkt_a: Base packet a for stream_a
111     :param base_pkt_b: Base packet b for stream_b
112     :param vm1: vm for stream_a
113     :param vm2: vm for stream_b
114     :param frame_size: frame size or name of traffic profile
115     :type base_pkt_a: Eth (scapy)
116     :type base_pkt_b: Eth (scapy)
117     :type vm1: STLScVmRaw
118     :type vm2: STLScVmRaw
119     :frame_size: int or string
120     :return: stream_a, stream_b, stream_a_latency, stream_b_latency
121     :rtype: STLStream, STLStream, STLStream, STLStream
122     """
123
124     if type(frame_size) is int:
125
126         fsize_no_fcs = frame_size - 4 # no FCS
127         pkt_a = STLPktBuilder(pkt=base_pkt_a/generate_payload(
128             max(0, fsize_no_fcs-len(base_pkt_a))), vm=vm1)
129         pkt_b = STLPktBuilder(pkt=base_pkt_b/generate_payload(
130             max(0, fsize_no_fcs-len(base_pkt_b))), vm=vm2)
131         pkt_lat_a = STLPktBuilder(pkt=base_pkt_a/generate_payload(
132             max(0, fsize_no_fcs-len(base_pkt_a))))
133         pkt_lat_b = STLPktBuilder(pkt=base_pkt_b/generate_payload(
134             max(0, fsize_no_fcs-len(base_pkt_b))))
135         lat_stream1 = STLStream(packet=pkt_lat_a,
136                                 flow_stats=STLFlowLatencyStats(pg_id=0),
137                                 mode=STLTXCont(pps=9000))
138         # second traffic stream with a phase of 10ns (inter stream gap)
139         lat_stream2 = STLStream(packet=pkt_lat_b,
140                                 isg=10.0,
141                                 flow_stats=STLFlowLatencyStats(pg_id=1),
142                                 mode=STLTXCont(pps=9000))
143
144         stream1 = STLStream(packet=pkt_a,
145                             mode=STLTXCont(pps=9000))
146         # second traffic stream with a phase of 10ns (inter stream gap)
147         stream2 = STLStream(packet=pkt_b,
148                             isg=10.0,
149                             mode=STLTXCont(pps=9000))
150     elif type(frame_size) is str:
151         lat_stream1 = []
152         lat_stream2 = []
153         stream1 = []
154         stream2 = []
155
156         for x in stream_table[frame_size]:
157             fsize_no_fcs = x['size'] - 4 # no FCS
158             pkt_a = STLPktBuilder(pkt=base_pkt_a/generate_payload(
159                 max(0, fsize_no_fcs-len(base_pkt_a))), vm=vm1)
160             pkt_b = STLPktBuilder(pkt=base_pkt_b/generate_payload(
161                 max(0, fsize_no_fcs-len(base_pkt_b))), vm=vm2)
162
163             stream1.append(STLStream(packet=pkt_a,
164                                isg=x['isg'],
165                                mode=STLTXCont(pps=x['pps'])))
166             stream2.append(STLStream(packet=pkt_b,
167                                isg=x['isg'],
168                                mode=STLTXCont(pps=x['pps'])))
169
170     else:
171         raise ValueError("Unknown frame_size type")
172
173     return (stream1, stream2, lat_stream1, lat_stream2)
174
175 def create_streams(traffic_options, frame_size=64):
176     """Create two IP packets to be used in stream.
177
178     :param traffic_options: Parameters for packets.
179     :param frame_size: Size of L2 frame.
180     :type traffic_options: list
181     :type frame_size: int
182     :return: Packet instances.
183     :rtype: Tuple of STLPktBuilder
184     """
185
186     if type(frame_size) is int and frame_size < 64:
187         print_error("Frame min. size is 64B")
188         sys.exit(1)
189
190     p1_src_start_ip = traffic_options['p1_src_start_ip']
191     p1_src_end_ip = traffic_options['p1_src_end_ip']
192     p1_dst_start_ip = traffic_options['p1_dst_start_ip']
193     p2_src_start_ip = traffic_options['p2_src_start_ip']
194     p2_src_end_ip = traffic_options['p2_src_end_ip']
195     p2_dst_start_ip = traffic_options['p2_dst_start_ip']
196
197     p1_dst_end_ip = traffic_options['p1_dst_end_ip']
198     p2_dst_end_ip = traffic_options['p2_dst_end_ip']
199
200     base_pkt_a = Ether()/IP(src=p1_src_start_ip, dst=p1_dst_start_ip, proto=61)
201     base_pkt_b = Ether()/IP(src=p2_src_start_ip, dst=p2_dst_start_ip, proto=61)
202
203     # The following code applies raw instructions to packet (IP src/dst
204     # increment). It splits the generated traffic by "ip_src"/"ip_dst" variable
205     # to cores and fix IPv4 header checksum.
206     if p1_dst_end_ip and p2_dst_end_ip:
207         vm1 = STLScVmRaw([STLVmFlowVar(name="dst",
208                                        min_value=p1_dst_start_ip,
209                                        max_value=p1_dst_end_ip,
210                                        size=4, op="inc"),
211                           STLVmWrFlowVar(fv_name="dst", pkt_offset="IP.dst"),
212                           STLVmFixIpv4(offset="IP"),
213                          ], split_by_field="dst")
214         vm2 = STLScVmRaw([STLVmFlowVar(name="dst",
215                                        min_value=p2_dst_start_ip,
216                                        max_value=p2_dst_end_ip,
217                                        size=4, op="inc"),
218                           STLVmWrFlowVar(fv_name="dst", pkt_offset="IP.dst"),
219                           STLVmFixIpv4(offset="IP"),
220                          ], split_by_field="dst")
221     else:
222         vm1 = STLScVmRaw([STLVmFlowVar(name="src",
223                                        min_value=p1_src_start_ip,
224                                        max_value=p1_src_end_ip,
225                                        size=4, op="inc"),
226                           STLVmWrFlowVar(fv_name="src", pkt_offset="IP.src"),
227                           STLVmFixIpv4(offset="IP"),
228                          ], split_by_field="src")
229         vm2 = STLScVmRaw([STLVmFlowVar(name="src",
230                                        min_value=p2_src_start_ip,
231                                        max_value=p2_src_end_ip,
232                                        size=4, op="inc"),
233                           STLVmWrFlowVar(fv_name="src", pkt_offset="IP.src"),
234                           STLVmFixIpv4(offset="IP"),
235                          ], split_by_field="src")
236
237     return create_streams_v46(base_pkt_a, base_pkt_b, vm1, vm2, frame_size)
238
239
240 def create_streams_v6(traffic_options, frame_size=78):
241     """Create two IPv6 packets to be used in stream.
242
243     :param traffic_options: Parameters for packets.
244     :param frame_size: Size of L2 frame.
245     :type traffic_options: List
246     :type frame_size: int
247     :return: Packet instances.
248     :rtype: Tuple of STLPktBuilder
249     """
250
251     if type(frame_size) is int and frame_size < 78:
252         print_error("Frame min. size is 78B")
253         sys.exit(2)
254
255     p1_src_start_ip = traffic_options['p1_src_start_ip']
256     p1_src_end_ip = traffic_options['p1_src_end_ip']
257     p1_dst_start_ip = traffic_options['p1_dst_start_ip']
258     p2_src_start_ip = traffic_options['p2_src_start_ip']
259     p2_src_end_ip = traffic_options['p2_src_end_ip']
260     p2_dst_start_ip = traffic_options['p2_dst_start_ip']
261
262     p1_dst_end_ip = traffic_options['p1_dst_end_ip']
263     p2_dst_end_ip = traffic_options['p2_dst_end_ip']
264
265     base_pkt_a = Ether()/IPv6(src=p1_src_start_ip, dst=p1_dst_start_ip)
266     base_pkt_b = Ether()/IPv6(src=p2_src_start_ip, dst=p2_dst_start_ip)
267
268     # The following code applies raw instructions to packet (IP src/dst
269     # increment). It splits the generated traffic by "ip_src"/"ip_dst" variable
270     # to cores.
271     if p1_dst_end_ip and p2_dst_end_ip:
272         base_p1, max_p1 = get_start_end_ipv6(p1_dst_start_ip, p1_dst_end_ip)
273         base_p2, max_p2 = get_start_end_ipv6(p2_dst_start_ip, p2_dst_end_ip)
274
275         vm1 = STLScVmRaw([STLVmFlowVar(name="ipv6_dst",
276                                        min_value=base_p1,
277                                        max_value=max_p1+base_p1,
278                                        size=8, op="inc"),
279                           STLVmWrFlowVar(fv_name="ipv6_dst",
280                                          pkt_offset="IPv6.dst",
281                                          offset_fixup=8)
282                          ]
283                          , split_by_field="ipv6_dst")
284         vm2 = STLScVmRaw([STLVmFlowVar(name="ipv6_dst",
285                                        min_value=base_p2,
286                                        max_value=max_p2+base_p2,
287                                        size=8, op="inc"),
288                           STLVmWrFlowVar(fv_name="ipv6_dst",
289                                          pkt_offset="IPv6.dst",
290                                          offset_fixup=8)
291                          ]
292                          , split_by_field="ipv6_dst")
293     else:
294         base_p1, max_p1 = get_start_end_ipv6(p1_src_start_ip, p1_src_end_ip)
295         base_p2, max_p2 = get_start_end_ipv6(p2_src_start_ip, p2_src_end_ip)
296
297         vm1 = STLScVmRaw([STLVmFlowVar(name="ipv6_src",
298                                        min_value=base_p1,
299                                        max_value=max_p1+base_p1,
300                                        size=8, op="inc"),
301                           STLVmWrFlowVar(fv_name="ipv6_src",
302                                          pkt_offset="IPv6.src",
303                                          offset_fixup=8)
304                          ]
305                          , split_by_field="ipv6_src")
306         vm2 = STLScVmRaw([STLVmFlowVar(name="ipv6_src",
307                                        min_value=base_p2,
308                                        max_value=max_p2+base_p2,
309                                        size=8, op="inc"),
310                           STLVmWrFlowVar(fv_name="ipv6_src",
311                                          pkt_offset="IPv6.src",
312                                          offset_fixup=8)
313                          ]
314                          , split_by_field="ipv6_src")
315
316     return create_streams_v46(base_pkt_a, base_pkt_b, vm1, vm2, frame_size)
317
318 def fmt_latency(lat_min, lat_avg, lat_max):
319     """ Return formatted, rounded latency
320
321     :param lat_min: Min latency
322     :param lat_avg: Average latency
323     :param lat_max: Max latency
324     :type lat_min: string
325     :type lat_avg: string
326     :type lat_max: string
327     :return: Formatted and rounded output "min/avg/max"
328     :rtype: string
329     """
330
331     try:
332         t_min = int(round(float(lat_min)))
333     except ValueError:
334         t_min = int(-1)
335     try:
336         t_avg = int(round(float(lat_avg)))
337     except ValueError:
338         t_avg = int(-1)
339     try:
340         t_max = int(round(float(lat_max)))
341     except ValueError:
342         t_max = int(-1)
343
344     return "/".join(str(tmp) for tmp in (t_min, t_avg, t_max))
345
346 def simple_burst(stream_a, stream_b, stream_lat_a, stream_lat_b, duration, rate,
347                  warmup_time, async_start, latency):
348     """Run the traffic with specific parameters.
349
350     :param pkt_a: Base packet for stream 1.
351     :param pkt_b: Base packet for stream 2.
352     :param pkt_lat_a: Base packet for latency stream 1.
353     :param pkt_lat_b: Base packet for latency stream 2.
354     :param duration: Duration of traffic run in seconds (-1=infinite).
355     :param rate: Rate of traffic run [percentage, pps, bps].
356     :param warmup_time: Warm up duration.
357     :param async_start: Start the traffic and exit.
358     :param latency: With latency stats.
359     :type pkt_a: STLPktBuilder
360     :type pkt_b: STLPktBuilder
361     :type pkt_lat_a: STLPktBuilder
362     :type pkt_lat_b: STLPktBuilder
363     :type duration: int
364     :type rate: string
365     :type warmup_time: int
366     :type async_start: bool
367     :type latency: bool
368     :return: nothing
369     """
370
371     # create client
372     client = STLClient()
373
374     total_rcvd = 0
375     total_sent = 0
376     lost_a = 0
377     lost_b = 0
378     lat_a = "-1/-1/-1"
379     lat_b = "-1/-1/-1"
380
381     try:
382         # turn this off if too many logs
383         #client.set_verbose("high")
384
385         # connect to server
386         client.connect()
387
388         # prepare our ports (my machine has 0 <--> 1 with static route)
389         client.reset(ports=[0, 1])
390
391         client.add_streams(stream_a, ports=[0])
392         client.add_streams(stream_b, ports=[1])
393
394         if latency:
395             try:
396                 client.add_streams(stream_lat_a, ports=[0])
397                 client.add_streams(stream_lat_b, ports=[1])
398             except:
399                 #Disable latency if NIC does not support requested stream type
400                 print "##### FAILED to add latency streams #####"
401                 latency = False
402
403         #warmup phase
404         if warmup_time > 0:
405             # clear the stats before injecting
406             client.clear_stats()
407
408             # choose rate and start traffic
409             client.start(ports=[0, 1], mult=rate, duration=warmup_time)
410
411             # block until done
412             client.wait_on_traffic(ports=[0, 1], timeout=(warmup_time+30))
413
414             if client.get_warnings():
415                 for warning in client.get_warnings():
416                     print(warning)
417
418             # read the stats after the test
419             stats = client.get_stats()
420
421             print "#####warmup statistics#####"
422             print json.dumps(stats, indent=4,
423                              separators=(',', ': '), sort_keys=True)
424             lost_a = stats[0]["opackets"] - stats[1]["ipackets"]
425             lost_b = stats[1]["opackets"] - stats[0]["ipackets"]
426
427             print "\npackets lost from 0 --> 1:   {0} pkts".format(lost_a)
428             print "packets lost from 1 --> 0:   {0} pkts".format(lost_b)
429
430         # clear the stats before injecting
431         client.clear_stats()
432         lost_a = 0
433         lost_b = 0
434
435         # choose rate and start traffic
436         client.start(ports=[0, 1], mult=rate, duration=duration)
437
438         if not async_start:
439             # block until done
440             client.wait_on_traffic(ports=[0, 1], timeout=(duration+30))
441
442             if client.get_warnings():
443                 for warning in client.get_warnings():
444                     print(warning)
445
446             # read the stats after the test
447             stats = client.get_stats()
448
449             print "#####statistics#####"
450             print json.dumps(stats, indent=4,
451                              separators=(',', ': '), sort_keys=True)
452             lost_a = stats[0]["opackets"] - stats[1]["ipackets"]
453             lost_b = stats[1]["opackets"] - stats[0]["ipackets"]
454
455             if latency:
456                 lat_a = fmt_latency(\
457                     str(stats["latency"][0]["latency"]["total_min"]),\
458                     str(stats["latency"][0]["latency"]["average"]),\
459                     str(stats["latency"][0]["latency"]["total_max"]))
460                 lat_b = fmt_latency(\
461                     str(stats["latency"][1]["latency"]["total_min"]),\
462                     str(stats["latency"][1]["latency"]["average"]),\
463                     str(stats["latency"][1]["latency"]["total_max"]))
464
465             total_sent = stats[0]["opackets"] + stats[1]["opackets"]
466             total_rcvd = stats[0]["ipackets"] + stats[1]["ipackets"]
467
468             print "\npackets lost from 0 --> 1:   {0} pkts".format(lost_a)
469             print "packets lost from 1 --> 0:   {0} pkts".format(lost_b)
470
471     except STLError as ex_error:
472         print_error(str(ex_error))
473         sys.exit(1)
474
475     finally:
476         if async_start:
477             client.disconnect(stop_traffic=False, release_ports=True)
478         else:
479             client.disconnect()
480             print "rate={0}, totalReceived={1}, totalSent={2}, "\
481                 "frameLoss={3}, latencyStream0(usec)={4}, "\
482                 "latencyStream1(usec)={5}".format(rate, total_rcvd,\
483                 total_sent, lost_a+lost_b, lat_a, lat_b)
484
485
486 def print_error(msg):
487     """Print error message on stderr.
488
489     :param msg: Error message to print.
490     :type msg: string
491     :return: nothing
492     """
493
494     sys.stderr.write(msg+'\n')
495
496
497 def parse_args():
498     """Parse arguments from cmd line.
499
500     :return: Parsed arguments.
501     :rtype ArgumentParser
502     """
503
504     parser = argparse.ArgumentParser()
505     parser.add_argument("-d", "--duration", required=True, type=int,
506                         help="Duration of traffic run")
507     parser.add_argument("-s", "--frame_size", required=True,
508                         help="Size of a Frame without padding and IPG")
509     parser.add_argument("-r", "--rate", required=True,
510                         help="Traffic rate with included units (%, pps)")
511     parser.add_argument("-6", "--use_IPv6", action="store_true",
512                         default=False,
513                         help="Use IPv6 traffic profile instead of IPv4")
514     parser.add_argument("--async", action="store_true",
515                         default=False,
516                         help="Non-blocking call of the script")
517     parser.add_argument("--latency", action="store_true",
518                         default=False,
519                         help="Add latency stream")
520     parser.add_argument("-w", "--warmup_time", type=int,
521                         default=5,
522                         help="Traffic warmup time in seconds, 0 = disable")
523 #    parser.add_argument("--p1_src_mac",
524 #                        help="Port 1 source MAC address")
525 #    parser.add_argument("--p1_dst_mac",
526 #                        help="Port 1 destination MAC address")
527     parser.add_argument("--p1_src_start_ip", required=True,
528                         help="Port 1 source start IP address")
529     parser.add_argument("--p1_src_end_ip",
530                         default=False,
531                         help="Port 1 source end IP address")
532     parser.add_argument("--p1_dst_start_ip", required=True,
533                         help="Port 1 destination start IP address")
534     parser.add_argument("--p1_dst_end_ip",
535                         default=False,
536                         help="Port 1 destination end IP address")
537 #    parser.add_argument("--p2_src_mac",
538 #                        help="Port 2 source MAC address")
539 #    parser.add_argument("--p2_dst_mac",
540 #                        help="Port 2 destination MAC address")
541     parser.add_argument("--p2_src_start_ip", required=True,
542                         help="Port 2 source start IP address")
543     parser.add_argument("--p2_src_end_ip",
544                         default=False,
545                         help="Port 2 source end IP address")
546     parser.add_argument("--p2_dst_start_ip", required=True,
547                         help="Port 2 destination start IP address")
548     parser.add_argument("--p2_dst_end_ip",
549                         default=False,
550                         help="Port 2 destination end IP address")
551
552     return parser.parse_args()
553
554
555 def main():
556     """Main function."""
557
558     args = parse_args()
559
560     _duration = args.duration
561     _latency = args.latency
562     if args.frame_size.isdigit():
563         _frame_size = int(args.frame_size)
564     else:
565         _frame_size = args.frame_size
566         _latency = False
567     _rate = args.rate
568     _use_ipv6 = args.use_IPv6
569     _async_call = args.async
570     _warmup_time = args.warmup_time
571
572     _traffic_options = {}
573     for attr in [a for a in dir(args) if a.startswith('p')]:
574         if getattr(args, attr) is not None:
575             _traffic_options[attr] = getattr(args, attr)
576
577     if _use_ipv6:
578         stream_a, stream_b, stream_lat_a, stream_lat_b = create_streams_v6(
579             _traffic_options, frame_size=_frame_size)
580     else:
581         stream_a, stream_b, stream_lat_a, stream_lat_b = create_streams(
582             _traffic_options, frame_size=_frame_size)
583
584     simple_burst(stream_a, stream_b, stream_lat_a, stream_lat_b,
585                  _duration, _rate, _warmup_time, _async_call, _latency)
586
587 if __name__ == "__main__":
588     sys.exit(main())