6c7579d0c391081d3a1e90334494bc469a710e70
[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.06/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=1000))
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=1000))
143
144         stream1 = STLStream(packet=pkt_a,
145                             mode=STLTXCont(pps=1000))
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=1000))
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 simple_burst(stream_a, stream_b, stream_lat_a, stream_lat_b, duration, rate,
319                  warmup_time, async_start, latency):
320     """Run the traffic with specific parameters.
321
322     :param pkt_a: Base packet for stream 1.
323     :param pkt_b: Base packet for stream 2.
324     :param pkt_lat_a: Base packet for latency stream 1.
325     :param pkt_lat_b: Base packet for latency stream 2.
326     :param duration: Duration of traffic run in seconds (-1=infinite).
327     :param rate: Rate of traffic run [percentage, pps, bps].
328     :param warmup_time: Warm up duration.
329     :param async_start: Start the traffic and exit.
330     :param latency: With latency stats.
331     :type pkt_a: STLPktBuilder
332     :type pkt_b: STLPktBuilder
333     :type pkt_lat_a: STLPktBuilder
334     :type pkt_lat_b: STLPktBuilder
335     :type duration: int
336     :type rate: string
337     :type warmup_time: int
338     :type async_start: bool
339     :type latency: bool
340     :return: nothing
341     """
342
343     # create client
344     client = STLClient()
345
346     total_rcvd = 0
347     total_sent = 0
348     lost_a = 0
349     lost_b = 0
350     lat_a = 'NA'
351     lat_b = 'NA'
352
353     try:
354         # turn this off if too many logs
355         #client.set_verbose("high")
356
357         # connect to server
358         client.connect()
359
360         # prepare our ports (my machine has 0 <--> 1 with static route)
361         client.reset(ports=[0, 1])
362
363         client.add_streams(stream_a, ports=[0])
364         client.add_streams(stream_b, ports=[1])
365
366         if latency:
367             client.add_streams(stream_lat_a, ports=[0])
368             client.add_streams(stream_lat_b, ports=[1])
369
370         #warmup phase
371         if warmup_time > 0:
372             # clear the stats before injecting
373             client.clear_stats()
374
375             # choose rate and start traffic
376             client.start(ports=[0, 1], mult=rate, duration=warmup_time)
377
378             # block until done
379             client.wait_on_traffic(ports=[0, 1], timeout=(warmup_time+30))
380
381             if client.get_warnings():
382                 for warning in client.get_warnings():
383                     print_error(warning)
384
385             # read the stats after the test
386             stats = client.get_stats()
387
388             print "#####warmup statistics#####"
389             print json.dumps(stats, indent=4,
390                              separators=(',', ': '), sort_keys=True)
391             lost_a = stats[0]["opackets"] - stats[1]["ipackets"]
392             lost_b = stats[1]["opackets"] - stats[0]["ipackets"]
393
394             print "\npackets lost from 0 --> 1:   {0} pkts".format(lost_a)
395             print "packets lost from 1 --> 0:   {0} pkts".format(lost_b)
396
397         # clear the stats before injecting
398         client.clear_stats()
399         lost_a = 0
400         lost_b = 0
401
402         # choose rate and start traffic
403         client.start(ports=[0, 1], mult=rate, duration=duration)
404
405         if not async_start:
406             # block until done
407             client.wait_on_traffic(ports=[0, 1], timeout=(duration+30))
408
409             if client.get_warnings():
410                 for warning in client.get_warnings():
411                     print_error(warning)
412
413             # read the stats after the test
414             stats = client.get_stats()
415
416             print "#####statistics#####"
417             print json.dumps(stats, indent=4,
418                              separators=(',', ': '), sort_keys=True)
419             lost_a = stats[0]["opackets"] - stats[1]["ipackets"]
420             lost_b = stats[1]["opackets"] - stats[0]["ipackets"]
421
422             if latency:
423                 lat_a = "/".join((\
424                     str(stats["latency"][0]["latency"]["total_min"]),\
425                     str(stats["latency"][0]["latency"]["average"]),\
426                     str(stats["latency"][0]["latency"]["total_max"])))
427                 lat_b = "/".join((\
428                     str(stats["latency"][1]["latency"]["total_min"]),\
429                     str(stats["latency"][1]["latency"]["average"]),\
430                     str(stats["latency"][1]["latency"]["total_max"])))
431
432             total_sent = stats[0]["opackets"] + stats[1]["opackets"]
433             total_rcvd = stats[0]["ipackets"] + stats[1]["ipackets"]
434
435             print "\npackets lost from 0 --> 1:   {0} pkts".format(lost_a)
436             print "packets lost from 1 --> 0:   {0} pkts".format(lost_b)
437
438     except STLError as ex_error:
439         print_error(str(ex_error))
440         sys.exit(1)
441
442     finally:
443         if async_start:
444             client.disconnect(stop_traffic=False, release_ports=True)
445         else:
446             client.disconnect()
447             print "rate={0}, totalReceived={1}, totalSent={2}, "\
448                 "frameLoss={3}, latencyStream0(usec)={4}, "\
449                 "latencyStream1(usec)={5}".format(rate, total_rcvd,\
450                 total_sent, lost_a+lost_b, lat_a, lat_b)
451
452
453 def print_error(msg):
454     """Print error message on stderr.
455
456     :param msg: Error message to print.
457     :type msg: string
458     :return: nothing
459     """
460
461     sys.stderr.write(msg+'\n')
462
463
464 def parse_args():
465     """Parse arguments from cmd line.
466
467     :return: Parsed arguments.
468     :rtype ArgumentParser
469     """
470
471     parser = argparse.ArgumentParser()
472     parser.add_argument("-d", "--duration", required=True, type=int,
473                         help="Duration of traffic run")
474     parser.add_argument("-s", "--frame_size", required=True,
475                         help="Size of a Frame without padding and IPG")
476     parser.add_argument("-r", "--rate", required=True,
477                         help="Traffic rate with included units (%, pps)")
478     parser.add_argument("-6", "--use_IPv6", action="store_true",
479                         default=False,
480                         help="Use IPv6 traffic profile instead of IPv4")
481     parser.add_argument("--async", action="store_true",
482                         default=False,
483                         help="Non-blocking call of the script")
484     parser.add_argument("--latency", action="store_true",
485                         default=False,
486                         help="Add latency stream")
487     parser.add_argument("-w", "--warmup_time", type=int,
488                         default=5,
489                         help="Traffic warmup time in seconds, 0 = disable")
490 #    parser.add_argument("--p1_src_mac",
491 #                        help="Port 1 source MAC address")
492 #    parser.add_argument("--p1_dst_mac",
493 #                        help="Port 1 destination MAC address")
494     parser.add_argument("--p1_src_start_ip", required=True,
495                         help="Port 1 source start IP address")
496     parser.add_argument("--p1_src_end_ip",
497                         default=False,
498                         help="Port 1 source end IP address")
499     parser.add_argument("--p1_dst_start_ip", required=True,
500                         help="Port 1 destination start IP address")
501     parser.add_argument("--p1_dst_end_ip",
502                         default=False,
503                         help="Port 1 destination end IP address")
504 #    parser.add_argument("--p2_src_mac",
505 #                        help="Port 2 source MAC address")
506 #    parser.add_argument("--p2_dst_mac",
507 #                        help="Port 2 destination MAC address")
508     parser.add_argument("--p2_src_start_ip", required=True,
509                         help="Port 2 source start IP address")
510     parser.add_argument("--p2_src_end_ip",
511                         default=False,
512                         help="Port 2 source end IP address")
513     parser.add_argument("--p2_dst_start_ip", required=True,
514                         help="Port 2 destination start IP address")
515     parser.add_argument("--p2_dst_end_ip",
516                         default=False,
517                         help="Port 2 destination end IP address")
518
519     return parser.parse_args()
520
521
522 def main():
523     """Main function."""
524
525     args = parse_args()
526
527     _duration = args.duration
528     _latency = args.latency
529     if args.frame_size.isdigit():
530         _frame_size = int(args.frame_size)
531     else:
532         _frame_size = args.frame_size
533         _latency = False
534     _rate = args.rate
535     _use_ipv6 = args.use_IPv6
536     _async_call = args.async
537     _warmup_time = args.warmup_time
538
539     _traffic_options = {}
540     for attr in [a for a in dir(args) if a.startswith('p')]:
541         if getattr(args, attr) is not None:
542             _traffic_options[attr] = getattr(args, attr)
543
544     if _use_ipv6:
545         # WARNING: Trex limitation to IPv4 only. IPv6 is not yet supported.
546         print_error('IPv6 latency is not supported yet. Running without lat.')
547         _latency = False
548
549         stream_a, stream_b, stream_lat_a, stream_lat_b = create_streams_v6(
550             _traffic_options, frame_size=_frame_size)
551     else:
552         stream_a, stream_b, stream_lat_a, stream_lat_b = create_streams(
553             _traffic_options, frame_size=_frame_size)
554
555     simple_burst(stream_a, stream_b, stream_lat_a, stream_lat_b,
556                  _duration, _rate, _warmup_time, _async_call, _latency)
557
558 if __name__ == "__main__":
559     sys.exit(main())