77895d7b68c9e0c6969e2cb54786096ddfb6567e
[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.03/scripts/automation/"+\
52                    "trex_control_plane/stl/")
53 from trex_stl_lib.api import *
54
55
56 def generate_payload(length):
57     """Generate payload.
58
59     :param length: Length of payload.
60     :type length: int
61     :return: Payload filled with chars.
62     :rtype string
63     """
64
65     word = ''
66     alphabet_size = len(string.letters)
67     for i in range(length):
68         word += string.letters[(i % alphabet_size)]
69
70     return word
71
72
73 def get_start_end_ipv6(start_ip, end_ip):
74     """Get start and end host from IPv6 as integer.
75
76     :param start_ip: Start IPv6.
77     :param end_ip: End IPv6.
78     :type start_ip: string
79     :type end_ip: string
80     :return: Start host, end host.
81     :rtype int
82     """
83
84     try:
85         ip1 = socket.inet_pton(socket.AF_INET6, start_ip)
86         ip2 = socket.inet_pton(socket.AF_INET6, end_ip)
87
88         hi1, lo1 = struct.unpack('!QQ', ip1)
89         hi2, lo2 = struct.unpack('!QQ', ip2)
90
91         if ((hi1 << 64) | lo1) > ((hi2 << 64) | lo2):
92             print "IPv6: start_ip is greater then end_ip"
93             sys.exit(2)
94
95         max_p1 = abs(int(lo1) - int(lo2)) + 1
96         base_p1 = lo1
97     except AddressValueError as ex_error:
98         print ex_error
99         sys.exit(2)
100
101     return base_p1, max_p1
102
103
104 def create_packets(traffic_options, frame_size=64):
105     """Create two IP packets to be used in stream.
106
107     :param traffic_options: Parameters for packets.
108     :param frame_size: Size of L2 frame.
109     :type traffic_options: list
110     :type frame_size: int
111     :return: Packet instances.
112     :rtype STLPktBuilder
113     """
114
115     if frame_size < 64:
116         print_error("Packet min. size is 64B")
117         sys.exit(1)
118
119     fsize_no_fcs = frame_size - 4 # no FCS
120
121     p1_src_start_ip = traffic_options['p1_src_start_ip']
122     p1_src_end_ip = traffic_options['p1_src_end_ip']
123     p1_dst_start_ip = traffic_options['p1_dst_start_ip']
124     p2_src_start_ip = traffic_options['p2_src_start_ip']
125     p2_src_end_ip = traffic_options['p2_src_end_ip']
126     p2_dst_start_ip = traffic_options['p2_dst_start_ip']
127
128     base_pkt_a = Ether()/IP(src=p1_src_start_ip, dst=p1_dst_start_ip, proto=61)
129     base_pkt_b = Ether()/IP(src=p2_src_start_ip, dst=p2_dst_start_ip, proto=61)
130
131     # The following code applies raw instructions to packet (IP src increment).
132     # It splits the generated traffic by "ip_src" variable to cores and fix
133     # IPv4 header checksum.
134     vm1 = STLScVmRaw([STLVmFlowVar(name="src",
135                                    min_value=p1_src_start_ip,
136                                    max_value=p1_src_end_ip,
137                                    size=4, op="inc"),
138                       STLVmWrFlowVar(fv_name="src", pkt_offset="IP.src"),
139                       STLVmFixIpv4(offset="IP"),
140                      ], split_by_field="src")
141     # The following code applies raw instructions to packet (IP src increment).
142     # It splits the generated traffic by "ip_src" variable to cores and fix
143     # IPv4 header checksum.
144     vm2 = STLScVmRaw([STLVmFlowVar(name="src",
145                                    min_value=p2_src_start_ip,
146                                    max_value=p2_src_end_ip,
147                                    size=4, op="inc"),
148                       STLVmWrFlowVar(fv_name="src", pkt_offset="IP.src"),
149                       STLVmFixIpv4(offset="IP"),
150                      ], split_by_field="src")
151
152     pkt_a = STLPktBuilder(pkt=base_pkt_a/generate_payload(
153         fsize_no_fcs-len(base_pkt_a)), vm=vm1)
154     pkt_b = STLPktBuilder(pkt=base_pkt_b/generate_payload(
155         fsize_no_fcs-len(base_pkt_b)), vm=vm2)
156
157     return(pkt_a, pkt_b)
158
159
160 def create_packets_v6(traffic_options, frame_size=78):
161     """Create two IPv6 packets to be used in stream.
162
163     :param traffic_options: Parameters for packets.
164     :param frame_size: Size of L2 frame.
165     :type traffic_options: List
166     :type frame_size: int
167     :return: Packet instances.
168     :rtype STLPktBuilder
169     """
170
171     if frame_size < 78:
172         print "Packet min. size is 78B"
173         sys.exit(2)
174
175     fsize_no_fcs = frame_size - 4 # no FCS
176
177     p1_src_start_ip = traffic_options['p1_src_start_ip']
178     p1_src_end_ip = traffic_options['p1_src_end_ip']
179     p1_dst_start_ip = traffic_options['p1_dst_start_ip']
180     p2_src_start_ip = traffic_options['p2_src_start_ip']
181     p2_src_end_ip = traffic_options['p2_src_end_ip']
182     p2_dst_start_ip = traffic_options['p2_dst_start_ip']
183
184     base_p1, max_p1 = get_start_end_ipv6(p1_src_start_ip, p1_src_end_ip)
185     base_p2, max_p2 = get_start_end_ipv6(p2_src_start_ip, p2_src_end_ip)
186
187     base_pkt_a = Ether()/IPv6(src=p1_src_start_ip, dst=p1_dst_start_ip)
188     base_pkt_b = Ether()/IPv6(src=p2_src_start_ip, dst=p2_dst_start_ip)
189
190     # The following code applies raw instructions to packet (IP src increment).
191     # It splits the generated traffic by "ip_src" variable to cores
192     vm1 = STLScVmRaw([STLVmFlowVar(name="ipv6_src",
193                                    min_value=base_p1,
194                                    max_value=max_p1+base_p1,
195                                    size=8, op="inc"),
196                       STLVmWrFlowVar(fv_name="ipv6_src", pkt_offset="IPv6.src",
197                                      offset_fixup=8)
198                      ]
199                      , split_by_field="ipv6_src")
200
201     # The following code applies raw instructions to packet (IP src increment).
202     # It splits the generated traffic by "ip_src" variable to cores
203     vm2 = STLScVmRaw([STLVmFlowVar(name="ipv6_src",
204                                    min_value=base_p2,
205                                    max_value=max_p2+base_p2,
206                                    size=8, op="inc"),
207                       STLVmWrFlowVar(fv_name="ipv6_src", pkt_offset="IPv6.src",
208                                      offset_fixup=8)
209                      ]
210                      , split_by_field="ipv6_src")
211
212     pkt_a = STLPktBuilder(pkt=base_pkt_a/generate_payload(
213         max(0, fsize_no_fcs-len(base_pkt_a))), vm=vm1)
214     pkt_b = STLPktBuilder(pkt=base_pkt_b/generate_payload(
215         max(0, fsize_no_fcs-len(base_pkt_b))), vm=vm2)
216
217     return(pkt_a, pkt_b)
218
219
220 def simple_burst(pkt_a, pkt_b, duration, rate, warmup_time, async_start):
221     """Run the traffic with specific parameters.
222
223     :param pkt_a: Base packet for stream 1.
224     :param pkt_b: Base packet for stream 2.
225     :param duration: Duration of traffic run in seconds (-1=infinite).
226     :param rate: Rate of traffic run [percentage, pps, bps].
227     :param warmup_time: Warm up duration.
228     :async_start: Start the traffic and exit
229     :type pkt_a: STLPktBuilder
230     :type pkt_b: STLPktBuilder
231     :type duration: int
232     :type rate: string
233     :type warmup_time: int
234     :type async_start: bool
235     :return: nothing
236     """
237
238     # create client
239     client = STLClient()
240
241     total_rcvd = 0
242     total_sent = 0
243     lost_a = 0
244     lost_b = 0
245
246     try:
247         # turn this off if too many logs
248         #client.set_verbose("high")
249
250         # create two streams
251         stream1 = STLStream(packet=pkt_a,
252                             mode=STLTXCont(pps=100))
253
254         # second stream with a phase of 10ns (inter stream gap)
255         stream2 = STLStream(packet=pkt_b,
256                             isg=10.0,
257                             mode=STLTXCont(pps=100))
258
259         # connect to server
260         client.connect()
261
262         # prepare our ports (my machine has 0 <--> 1 with static route)
263         client.reset(ports=[0, 1])
264
265         # add both streams to ports
266         client.add_streams(stream1, ports=[0])
267         client.add_streams(stream2, ports=[1])
268
269         #warmup phase
270         if warmup_time > 0:
271             client.clear_stats()
272             client.start(ports=[0, 1], mult=rate, duration=warmup_time)
273             client.wait_on_traffic(ports=[0, 1], timeout=(warmup_time+30))
274             stats = client.get_stats()
275             print stats
276             print "#####warmup statistics#####"
277             print json.dumps(stats, indent=4,
278                              separators=(',', ': '), sort_keys=True)
279             lost_a = stats[0]["opackets"] - stats[1]["ipackets"]
280             lost_b = stats[1]["opackets"] - stats[0]["ipackets"]
281
282             print "\npackets lost from 0 --> 1:   {0} pkts".format(lost_a)
283             print "packets lost from 1 --> 0:   {0} pkts".format(lost_b)
284
285
286         # clear the stats before injecting
287         client.clear_stats()
288         total_rcvd = 0
289         total_sent = 0
290         lost_a = 0
291         lost_b = 0
292
293         # choose rate and start traffic
294         client.start(ports=[0, 1], mult=rate, duration=duration)
295
296         if not async_start:
297             # block until done
298             client.wait_on_traffic(ports=[0, 1], timeout=(duration+30))
299
300             # read the stats after the test
301             stats = client.get_stats()
302
303             print "#####statistics#####"
304             print json.dumps(stats, indent=4,
305                              separators=(',', ': '), sort_keys=True)
306
307             lost_a = stats[0]["opackets"] - stats[1]["ipackets"]
308             lost_b = stats[1]["opackets"] - stats[0]["ipackets"]
309
310             total_sent = stats[0]["opackets"] + stats[1]["opackets"]
311             total_rcvd = stats[0]["ipackets"] + stats[1]["ipackets"]
312
313             print "\npackets lost from 0 --> 1:   {0} pkts".format(lost_a)
314             print "packets lost from 1 --> 0:   {0} pkts".format(lost_b)
315
316     except STLError as ex_error:
317         print_error(str(ex_error))
318         sys.exit(1)
319
320     finally:
321         if async_start:
322             client.disconnect(stop_traffic=False, release_ports=True)
323         else:
324             client.disconnect()
325             print "rate={0}, totalReceived={1}, totalSent={2}, frameLoss={3}"\
326                 .format(rate, total_rcvd, total_sent, lost_a+lost_b)
327
328 def print_error(msg):
329     """Print error message on stderr.
330
331     :param msg: Error message to print.
332     :type msg: string
333     :return: nothing
334     """
335
336     sys.stderr.write(msg+'\n')
337
338
339 def main():
340     """Main function."""
341
342     _traffic_options = {}
343     #default L3 profile is IPv4
344     _use_ipv6 = False
345     #default warmup time is 5 seconds
346     _warmup_time = 5
347     #default behaviour of this script is sychronous
348     _async_call = False
349
350     parser = argparse.ArgumentParser()
351     parser.add_argument("-d", "--duration", required=True, type=int,
352                         help="Duration of traffic run")
353     parser.add_argument("-s", "--frame_size", required=True, type=int,
354                         help="Size of a Frame without padding and IPG")
355     parser.add_argument("-r", "--rate", required=True,
356                         help="Traffic rate with included units (%, pps)")
357     parser.add_argument("-6", "--use_IPv6", action="store_true",
358                         help="Use IPv6 traffic profile instead of IPv4")
359     parser.add_argument("--async", action="store_true",
360                         help="Non-blocking call of the script")
361     parser.add_argument("-w", "--warmup_time", type=int,
362                         help="Traffic warmup time in seconds, 0 = disable")
363 #    parser.add_argument("--p1_src_mac",
364 #                        help="Port 1 source MAC address")
365 #    parser.add_argument("--p1_dst_mac",
366 #                        help="Port 1 destination MAC address")
367     parser.add_argument("--p1_src_start_ip", required=True,
368                         help="Port 1 source start IP address")
369     parser.add_argument("--p1_src_end_ip", required=True,
370                         help="Port 1 source end IP address")
371     parser.add_argument("--p1_dst_start_ip", required=True,
372                         help="Port 1 destination start IP address")
373 #    parser.add_argument("--p1_dst_end_ip",
374 #                        help="Port 1 destination end IP address")
375 #    parser.add_argument("--p2_src_mac",
376 #                        help="Port 2 source MAC address")
377 #    parser.add_argument("--p2_dst_mac",
378 #                        help="Port 2 destination MAC address")
379     parser.add_argument("--p2_src_start_ip", required=True,
380                         help="Port 2 source start IP address")
381     parser.add_argument("--p2_src_end_ip", required=True,
382                         help="Port 2 source end IP address")
383     parser.add_argument("--p2_dst_start_ip", required=True,
384                         help="Port 2 destination start IP address")
385 #    parser.add_argument("--p2_dst_end_ip",
386 #                        help="Port 2 destination end IP address")
387
388     args = parser.parse_args()
389
390     _duration = args.duration
391     _frame_size = args.frame_size
392     _rate = args.rate
393     _use_ipv6 = args.use_IPv6
394     _async_call = args.async
395
396     if args.warmup_time is not None:
397         _warmup_time = args.warmup_time
398
399     for attr in [a for a in dir(args) if a.startswith('p')]:
400         if getattr(args, attr) is not None:
401             _traffic_options[attr] = getattr(args, attr)
402
403     if _use_ipv6:
404         pkt_a, pkt_b = create_packets_v6(_traffic_options,
405                                          frame_size=_frame_size)
406     else:
407         pkt_a, pkt_b = create_packets(_traffic_options,
408                                       frame_size=_frame_size)
409
410     simple_burst(pkt_a, pkt_b, _duration, _rate, _warmup_time, _async_call)
411
412 if __name__ == "__main__":
413     sys.exit(main())