CSIT-224 Update T-rex version to v2.06
[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)) + 1
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     base_pkt_a = Ether()/IP(src=p1_src_start_ip, dst=p1_dst_start_ip, proto=61)
198     base_pkt_b = Ether()/IP(src=p2_src_start_ip, dst=p2_dst_start_ip, proto=61)
199
200     # The following code applies raw instructions to packet (IP src increment).
201     # It splits the generated traffic by "ip_src" variable to cores and fix
202     # IPv4 header checksum.
203     vm1 = STLScVmRaw([STLVmFlowVar(name="src",
204                                    min_value=p1_src_start_ip,
205                                    max_value=p1_src_end_ip,
206                                    size=4, op="inc"),
207                       STLVmWrFlowVar(fv_name="src", pkt_offset="IP.src"),
208                       STLVmFixIpv4(offset="IP"),
209                      ], split_by_field="src")
210     # The following code applies raw instructions to packet (IP src increment).
211     # It splits the generated traffic by "ip_src" variable to cores and fix
212     # IPv4 header checksum.
213     vm2 = STLScVmRaw([STLVmFlowVar(name="src",
214                                    min_value=p2_src_start_ip,
215                                    max_value=p2_src_end_ip,
216                                    size=4, op="inc"),
217                       STLVmWrFlowVar(fv_name="src", pkt_offset="IP.src"),
218                       STLVmFixIpv4(offset="IP"),
219                      ], split_by_field="src")
220
221     return create_streams_v46(base_pkt_a, base_pkt_b, vm1, vm2, frame_size)
222
223
224 def create_streams_v6(traffic_options, frame_size=78):
225     """Create two IPv6 packets to be used in stream.
226
227     :param traffic_options: Parameters for packets.
228     :param frame_size: Size of L2 frame.
229     :type traffic_options: List
230     :type frame_size: int
231     :return: Packet instances.
232     :rtype: Tuple of STLPktBuilder
233     """
234
235     if type(frame_size) is int and frame_size < 78:
236         print_error("Frame min. size is 78B")
237         sys.exit(2)
238
239     p1_src_start_ip = traffic_options['p1_src_start_ip']
240     p1_src_end_ip = traffic_options['p1_src_end_ip']
241     p1_dst_start_ip = traffic_options['p1_dst_start_ip']
242     p2_src_start_ip = traffic_options['p2_src_start_ip']
243     p2_src_end_ip = traffic_options['p2_src_end_ip']
244     p2_dst_start_ip = traffic_options['p2_dst_start_ip']
245
246     base_p1, max_p1 = get_start_end_ipv6(p1_src_start_ip, p1_src_end_ip)
247     base_p2, max_p2 = get_start_end_ipv6(p2_src_start_ip, p2_src_end_ip)
248
249     base_pkt_a = Ether()/IPv6(src=p1_src_start_ip, dst=p1_dst_start_ip)
250     base_pkt_b = Ether()/IPv6(src=p2_src_start_ip, dst=p2_dst_start_ip)
251
252     # The following code applies raw instructions to packet (IP src increment).
253     # It splits the generated traffic by "ip_src" variable to cores
254     vm1 = STLScVmRaw([STLVmFlowVar(name="ipv6_src",
255                                    min_value=base_p1,
256                                    max_value=max_p1+base_p1,
257                                    size=8, op="inc"),
258                       STLVmWrFlowVar(fv_name="ipv6_src", pkt_offset="IPv6.src",
259                                      offset_fixup=8)
260                      ]
261                      , split_by_field="ipv6_src")
262
263     # The following code applies raw instructions to packet (IP src increment).
264     # It splits the generated traffic by "ip_src" variable to cores
265     vm2 = STLScVmRaw([STLVmFlowVar(name="ipv6_src",
266                                    min_value=base_p2,
267                                    max_value=max_p2+base_p2,
268                                    size=8, op="inc"),
269                       STLVmWrFlowVar(fv_name="ipv6_src", pkt_offset="IPv6.src",
270                                      offset_fixup=8)
271                      ]
272                      , split_by_field="ipv6_src")
273
274     return create_streams_v46(base_pkt_a, base_pkt_b, vm1, vm2, frame_size)
275
276 def simple_burst(stream_a, stream_b, stream_lat_a, stream_lat_b, duration, rate,
277                  warmup_time, async_start, latency):
278     """Run the traffic with specific parameters.
279
280     :param pkt_a: Base packet for stream 1.
281     :param pkt_b: Base packet for stream 2.
282     :param pkt_lat_a: Base packet for latency stream 1.
283     :param pkt_lat_b: Base packet for latency stream 2.
284     :param duration: Duration of traffic run in seconds (-1=infinite).
285     :param rate: Rate of traffic run [percentage, pps, bps].
286     :param warmup_time: Warm up duration.
287     :param async_start: Start the traffic and exit.
288     :param latency: With latency stats.
289     :type pkt_a: STLPktBuilder
290     :type pkt_b: STLPktBuilder
291     :type pkt_lat_a: STLPktBuilder
292     :type pkt_lat_b: STLPktBuilder
293     :type duration: int
294     :type rate: string
295     :type warmup_time: int
296     :type async_start: bool
297     :type latency: bool
298     :return: nothing
299     """
300
301     # create client
302     client = STLClient()
303
304     total_rcvd = 0
305     total_sent = 0
306     lost_a = 0
307     lost_b = 0
308     lat_a = 'NA'
309     lat_b = 'NA'
310
311     try:
312         # turn this off if too many logs
313         #client.set_verbose("high")
314
315         # connect to server
316         client.connect()
317
318         # prepare our ports (my machine has 0 <--> 1 with static route)
319         client.reset(ports=[0, 1])
320
321         client.add_streams(stream_a, ports=[0])
322         client.add_streams(stream_b, ports=[1])
323
324         if latency:
325             client.add_streams(stream_lat_a, ports=[0])
326             client.add_streams(stream_lat_b, ports=[1])
327
328         #warmup phase
329         if warmup_time > 0:
330             # clear the stats before injecting
331             client.clear_stats()
332
333             # choose rate and start traffic
334             client.start(ports=[0, 1], mult=rate, duration=warmup_time)
335
336             # block until done
337             client.wait_on_traffic(ports=[0, 1], timeout=(warmup_time+30))
338
339             if client.get_warnings():
340                 for warning in client.get_warnings():
341                     print_error(warning)
342
343             # read the stats after the test
344             stats = client.get_stats()
345
346             print "#####warmup statistics#####"
347             print json.dumps(stats, indent=4,
348                              separators=(',', ': '), sort_keys=True)
349             lost_a = stats[0]["opackets"] - stats[1]["ipackets"]
350             lost_b = stats[1]["opackets"] - stats[0]["ipackets"]
351
352             print "\npackets lost from 0 --> 1:   {0} pkts".format(lost_a)
353             print "packets lost from 1 --> 0:   {0} pkts".format(lost_b)
354
355         # clear the stats before injecting
356         client.clear_stats()
357         lost_a = 0
358         lost_b = 0
359
360         # choose rate and start traffic
361         client.start(ports=[0, 1], mult=rate, duration=duration)
362
363         if not async_start:
364             # block until done
365             client.wait_on_traffic(ports=[0, 1], timeout=(duration+30))
366
367             if client.get_warnings():
368                 for warning in client.get_warnings():
369                     print_error(warning)
370
371             # read the stats after the test
372             stats = client.get_stats()
373
374             print "#####statistics#####"
375             print json.dumps(stats, indent=4,
376                              separators=(',', ': '), sort_keys=True)
377             lost_a = stats[0]["opackets"] - stats[1]["ipackets"]
378             lost_b = stats[1]["opackets"] - stats[0]["ipackets"]
379
380             if latency:
381                 lat_a = "/".join((\
382                     str(stats["latency"][0]["latency"]["total_min"]),\
383                     str(stats["latency"][0]["latency"]["average"]),\
384                     str(stats["latency"][0]["latency"]["total_max"])))
385                 lat_b = "/".join((\
386                     str(stats["latency"][1]["latency"]["total_min"]),\
387                     str(stats["latency"][1]["latency"]["average"]),\
388                     str(stats["latency"][1]["latency"]["total_max"])))
389
390             total_sent = stats[0]["opackets"] + stats[1]["opackets"]
391             total_rcvd = stats[0]["ipackets"] + stats[1]["ipackets"]
392
393             print "\npackets lost from 0 --> 1:   {0} pkts".format(lost_a)
394             print "packets lost from 1 --> 0:   {0} pkts".format(lost_b)
395
396     except STLError as ex_error:
397         print_error(str(ex_error))
398         sys.exit(1)
399
400     finally:
401         if async_start:
402             client.disconnect(stop_traffic=False, release_ports=True)
403         else:
404             client.disconnect()
405             print "rate={0}, totalReceived={1}, totalSent={2}, "\
406                 "frameLoss={3}, latencyStream0(usec)={4}, "\
407                 "latencyStream1(usec)={5}".format(rate, total_rcvd,\
408                 total_sent, lost_a+lost_b, lat_a, lat_b)
409
410
411 def print_error(msg):
412     """Print error message on stderr.
413
414     :param msg: Error message to print.
415     :type msg: string
416     :return: nothing
417     """
418
419     sys.stderr.write(msg+'\n')
420
421
422 def parse_args():
423     """Parse arguments from cmd line.
424
425     :return: Parsed arguments.
426     :rtype ArgumentParser
427     """
428
429     parser = argparse.ArgumentParser()
430     parser.add_argument("-d", "--duration", required=True, type=int,
431                         help="Duration of traffic run")
432     parser.add_argument("-s", "--frame_size", required=True,
433                         help="Size of a Frame without padding and IPG")
434     parser.add_argument("-r", "--rate", required=True,
435                         help="Traffic rate with included units (%, pps)")
436     parser.add_argument("-6", "--use_IPv6", action="store_true",
437                         default=False,
438                         help="Use IPv6 traffic profile instead of IPv4")
439     parser.add_argument("--async", action="store_true",
440                         default=False,
441                         help="Non-blocking call of the script")
442     parser.add_argument("--latency", action="store_true",
443                         default=False,
444                         help="Add latency stream")
445     parser.add_argument("-w", "--warmup_time", type=int,
446                         default=5,
447                         help="Traffic warmup time in seconds, 0 = disable")
448 #    parser.add_argument("--p1_src_mac",
449 #                        help="Port 1 source MAC address")
450 #    parser.add_argument("--p1_dst_mac",
451 #                        help="Port 1 destination MAC address")
452     parser.add_argument("--p1_src_start_ip", required=True,
453                         help="Port 1 source start IP address")
454     parser.add_argument("--p1_src_end_ip", required=True,
455                         help="Port 1 source end IP address")
456     parser.add_argument("--p1_dst_start_ip", required=True,
457                         help="Port 1 destination start IP address")
458 #    parser.add_argument("--p1_dst_end_ip",
459 #                        help="Port 1 destination end IP address")
460 #    parser.add_argument("--p2_src_mac",
461 #                        help="Port 2 source MAC address")
462 #    parser.add_argument("--p2_dst_mac",
463 #                        help="Port 2 destination MAC address")
464     parser.add_argument("--p2_src_start_ip", required=True,
465                         help="Port 2 source start IP address")
466     parser.add_argument("--p2_src_end_ip", required=True,
467                         help="Port 2 source end IP address")
468     parser.add_argument("--p2_dst_start_ip", required=True,
469                         help="Port 2 destination start IP address")
470 #    parser.add_argument("--p2_dst_end_ip",
471 #                        help="Port 2 destination end IP address")
472
473     return parser.parse_args()
474
475
476 def main():
477     """Main function."""
478
479     args = parse_args()
480
481     _duration = args.duration
482     _latency = args.latency
483     if args.frame_size.isdigit():
484         _frame_size = int(args.frame_size)
485     else:
486         _frame_size = args.frame_size
487         _latency = False
488     _rate = args.rate
489     _use_ipv6 = args.use_IPv6
490     _async_call = args.async
491     _warmup_time = args.warmup_time
492
493     _traffic_options = {}
494     for attr in [a for a in dir(args) if a.startswith('p')]:
495         if getattr(args, attr) is not None:
496             _traffic_options[attr] = getattr(args, attr)
497
498     if _use_ipv6:
499         # WARNING: Trex limitation to IPv4 only. IPv6 is not yet supported.
500         print_error('IPv6 latency is not supported yet. Running without lat.')
501         _latency = False
502
503         stream_a, stream_b, stream_lat_a, stream_lat_b = create_streams_v6(
504             _traffic_options, frame_size=_frame_size)
505     else:
506         stream_a, stream_b, stream_lat_a, stream_lat_b = create_streams(
507             _traffic_options, frame_size=_frame_size)
508
509     simple_burst(stream_a, stream_b, stream_lat_a, stream_lat_b,
510                  _duration, _rate, _warmup_time, _async_call, _latency)
511
512 if __name__ == "__main__":
513     sys.exit(main())