T-REX stl traffic send improvement for async calls
[csit.git] / resources / libraries / python / TrafficGenerator.py
1 # Copyright (c) 2016 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Performance testing traffic generator library."""
15
16 from robot.api import logger
17 from robot.libraries.BuiltIn import BuiltIn
18
19 from resources.libraries.python.ssh import SSH
20 from resources.libraries.python.topology import NodeType
21 from resources.libraries.python.topology import NodeSubTypeTG
22 from resources.libraries.python.topology import Topology
23 from resources.libraries.python.DropRateSearch import DropRateSearch
24
25 __all__ = ['TrafficGenerator', 'TGDropRateSearchImpl']
26
27
28 class TGDropRateSearchImpl(DropRateSearch):
29     """Drop Rate Search implementation."""
30
31     def __init__(self):
32         super(TGDropRateSearchImpl, self).__init__()
33
34     def measure_loss(self, rate, frame_size, loss_acceptance,
35                      loss_acceptance_type, traffic_type):
36
37         # we need instance of TrafficGenerator instantiated by Robot Framework
38         # to be able to use trex_stl-*()
39         tg_instance = BuiltIn().get_library_instance(
40             'resources.libraries.python.TrafficGenerator')
41
42         if tg_instance._node['subtype'] is None:
43             raise Exception('TG subtype not defined')
44         elif tg_instance._node['subtype'] == NodeSubTypeTG.TREX:
45             unit_rate = str(rate) + self.get_rate_type_str()
46             tg_instance.trex_stl_start_remote_exec(self.get_duration(),
47                                                    unit_rate, frame_size,
48                                                    traffic_type, False)
49
50             # TODO: getters for tg_instance and loss_acceptance_type
51             logger.trace("comparing: {} < {} ".format(tg_instance._loss,
52                                                       loss_acceptance))
53             if float(tg_instance._loss) > float(loss_acceptance):
54                 return False
55             else:
56                 return True
57         else:
58             raise NotImplementedError("TG subtype not supported")
59
60
61 class TrafficGenerator(object):
62     """Traffic Generator."""
63
64     # use one instance of TrafficGenerator for all tests in test suite
65     ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
66
67     def __init__(self):
68         self._result = None
69         self._loss = None
70         self._sent = None
71         self._received = None
72         self._node = None
73         # T-REX interface order mapping
74         self._ifaces_reordered = 0
75
76     #pylint: disable=too-many-arguments, too-many-locals
77     def initialize_traffic_generator(self, tg_node, tg_if1, tg_if2,
78                                      dut1_node, dut1_if1, dut1_if2,
79                                      dut2_node, dut2_if1, dut2_if2,
80                                      test_type):
81         """TG initialization.
82
83         :param tg_node: Traffic generator node.
84         :param tg_if1: TG - name of first interface.
85         :param tg_if2: TG - name of second interface.
86         :param dut1_node: DUT1 node.
87         :param dut1_if1: DUT1 - name of first interface.
88         :param dut1_if2: DUT1 - name of second interface.
89         :param dut2_node: DUT2 node.
90         :param dut2_if1: DUT2 - name of first interface.
91         :param dut2_if2: DUT2 - name of second interface.
92         :test_type: 'L2' or 'L3' - src/dst MAC address.
93         :type tg_node: dict
94         :type tg_if1: str
95         :type tg_if2: str
96         :type dut1_node: dict
97         :type dut1_if1: str
98         :type dut1_if2: str
99         :type dut2_node: dict
100         :type dut2_if1: str
101         :type dut2_if2: str
102         :type test_type: str
103         :return: nothing
104         """
105
106         trex_path = "/opt/trex-core-2.00"
107
108         topo = Topology()
109
110         if tg_node['type'] != NodeType.TG:
111             raise Exception('Node type is not a TG')
112         self._node = tg_node
113
114         if tg_node['subtype'] == NodeSubTypeTG.TREX:
115             ssh = SSH()
116             ssh.connect(tg_node)
117
118             if1_pci = topo.get_interface_pci_addr(tg_node, tg_if1)
119             if2_pci = topo.get_interface_pci_addr(tg_node, tg_if2)
120             if1_mac = topo.get_interface_mac(tg_node, tg_if1)
121             if2_mac = topo.get_interface_mac(tg_node, tg_if2)
122
123             if test_type == 'L2':
124                 if1_adj_mac = if2_mac
125                 if2_adj_mac = if1_mac
126             elif test_type == 'L3':
127                 if1_adj_mac = topo.get_interface_mac(dut1_node, dut1_if1)
128                 if2_adj_mac = topo.get_interface_mac(dut2_node, dut2_if2)
129             else:
130                 raise Exception("test_type unknown")
131
132             if min(if1_pci, if2_pci) != if1_pci:
133                 if1_mac, if2_mac = if2_mac, if1_mac
134                 if1_pci, if2_pci = if2_pci, if1_pci
135                 if1_adj_mac, if2_adj_mac = if2_adj_mac, if1_adj_mac
136                 self._ifaces_reordered = 1
137
138             if1_mac_hex = "0x"+if1_mac.replace(":", ",0x")
139             if2_mac_hex = "0x"+if2_mac.replace(":", ",0x")
140             if1_adj_mac_hex = "0x"+if1_adj_mac.replace(":", ",0x")
141             if2_adj_mac_hex = "0x"+if2_adj_mac.replace(":", ",0x")
142
143             (ret, stdout, stderr) = ssh.exec_command(
144                 "sudo sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
145                 "- port_limit      : 2\n"
146                 "  version         : 2\n"
147                 "  interfaces      : [\"{}\",\"{}\"]\n"
148                 "  port_bandwidth_gb : 10\n"
149                 "  port_info       :\n"
150                 "          - dest_mac        :   [{}]\n"
151                 "            src_mac         :   [{}]\n"
152                 "          - dest_mac        :   [{}]\n"
153                 "            src_mac         :   [{}]\n"
154                 "EOF'"\
155                 .format(if1_pci, if2_pci,
156                         if1_adj_mac_hex, if1_mac_hex,
157                         if2_adj_mac_hex, if2_mac_hex))
158             if int(ret) != 0:
159                 logger.error("failed to create t-rex config: {}"\
160                 .format(stdout + stderr))
161                 raise RuntimeError('trex config generation error')
162
163             (ret, stdout, stderr) = ssh.exec_command(
164                 "sh -c 'cd {0}/scripts/ && "
165                 "sudo ./trex-cfg'"\
166                 .format(trex_path))
167             if int(ret) != 0:
168                 logger.error('trex-cfg failed: {0}'.format(stdout + stderr))
169                 raise RuntimeError('trex-cfg failed')
170
171             (ret, _, _) = ssh.exec_command(
172                 "sh -c 'pgrep t-rex && sudo pkill t-rex'")
173
174             (ret, _, _) = ssh.exec_command(
175                 "sh -c 'cd {0}/scripts/ && "
176                 "sudo nohup ./t-rex-64 -i -c 7 --iom 0 > /dev/null 2>&1 &'"
177                 "> /dev/null"\
178                 .format(trex_path))
179             if int(ret) != 0:
180                 raise RuntimeError('t-rex-64 startup failed')
181
182     @staticmethod
183     def teardown_traffic_generator(node):
184         """TG teardown.
185
186         :param node: Traffic generator node.
187         :type node: dict
188         :return: nothing
189         """
190         if node['type'] != NodeType.TG:
191             raise Exception('Node type is not a TG')
192         if node['subtype'] == NodeSubTypeTG.TREX:
193             ssh = SSH()
194             ssh.connect(node)
195             (ret, stdout, stderr) = ssh.exec_command(
196                 "sh -c 'sudo pkill t-rex'")
197             if int(ret) != 0:
198                 logger.error('pkill t-rex failed: {0}'.format(stdout + stderr))
199                 raise RuntimeError('pkill t-rex failed')
200
201     @staticmethod
202     def trex_stl_stop_remote_exec(node):
203         """Execute script on remote node over ssh to stop running traffic.
204
205         :param node: T-REX generator node.
206         :type node: dict
207         :return: Nothing
208         """
209         ssh = SSH()
210         ssh.connect(node)
211
212         (ret, stdout, stderr) = ssh.exec_command(
213             "sh -c '/tmp/openvpp-testing/resources/tools/t-rex/"
214             "t-rex-stateless-stop.py'")
215         logger.trace(ret)
216         logger.trace(stdout)
217         logger.trace(stderr)
218
219         if int(ret) != 0:
220             raise RuntimeError('T-rex stateless runtime error')
221
222     def trex_stl_start_remote_exec(self, duration, rate, framesize,
223                                    traffic_type, async_call, warmup_time=5):
224         """Execute script on remote node over ssh to start traffic.
225
226         :param duration: Time expresed in seconds for how long to send traffic.
227         :param rate: Traffic rate expressed with units (pps, %)
228         :param framesize: L2 frame size to send (without padding and IPG).
229         :param traffic_type: Traffic profile.
230         :param async_call: If enabled then don't wait for all incomming trafic.
231         :param warmup_time: Warmup time period.
232         :type duration: int
233         :type rate: str
234         :type framesize: int
235         :type traffic_type: str
236         :type async_call: bool
237         :type warmup_time: int
238         :return: Nothing
239         """
240         ssh = SSH()
241         ssh.connect(self._node)
242
243         _p0 = 1
244         _p1 = 2
245         _async = ""
246
247         if async_call:
248             _async = "--async"
249         if self._ifaces_reordered != 0:
250             _p0, _p1 = _p1, _p0
251
252         if traffic_type in ["3-node-xconnect", "3-node-bridge"]:
253             (ret, stdout, stderr) = ssh.exec_command(
254                 "sh -c '/tmp/openvpp-testing/resources/tools/t-rex/"
255                 "t-rex-stateless.py "
256                 "--duration={0} -r {1} -s {2} "
257                 "--p{3}_src_start_ip 10.10.10.1 "
258                 "--p{3}_src_end_ip 10.10.10.254 "
259                 "--p{3}_dst_start_ip 20.20.20.1 "
260                 "--p{4}_src_start_ip 20.20.20.1 "
261                 "--p{4}_src_end_ip 20.20.20.254 "
262                 "--p{4}_dst_start_ip 10.10.10.1 "
263                 "{5} --warmup_time={6}'".format(duration, rate, framesize, _p0,
264                                                 _p1, _async, warmup_time),
265                 timeout=int(duration)+60)
266         elif traffic_type in ["3-node-IPv4"]:
267             (ret, stdout, stderr) = ssh.exec_command(
268                 "sh -c '/tmp/openvpp-testing/resources/tools/t-rex/"
269                 "t-rex-stateless.py "
270                 "--duration={0} -r {1} -s {2} "
271                 "--p{3}_src_start_ip 10.10.10.2 "
272                 "--p{3}_src_end_ip 10.10.10.254 "
273                 "--p{3}_dst_start_ip 20.20.20.2 "
274                 "--p{4}_src_start_ip 20.20.20.2 "
275                 "--p{4}_src_end_ip 20.20.20.254 "
276                 "--p{4}_dst_start_ip 10.10.10.2 "
277                 "{5} --warmup_time={6}'".format(duration, rate, framesize, _p0,
278                                                 _p1, _async, warmup_time),
279                 timeout=int(duration)+60)
280         elif traffic_type in ["3-node-IPv6"]:
281             (ret, stdout, stderr) = ssh.exec_command(
282                 "sh -c '/tmp/openvpp-testing/resources/tools/t-rex/"
283                 "t-rex-stateless.py "
284                 "--duration={0} -r {1} -s {2} -6 "
285                 "--p{3}_src_start_ip 2001:1::2 "
286                 "--p{3}_src_end_ip 2001:1::FE "
287                 "--p{3}_dst_start_ip 2001:2::2 "
288                 "--p{4}_src_start_ip 2001:2::2 "
289                 "--p{4}_src_end_ip 2001:2::FE "
290                 "--p{4}_dst_start_ip 2001:1::2 "
291                 "{5} --warmup_time={6}'".format(duration, rate, framesize, _p0,
292                                                 _p1, _async, warmup_time),
293                 timeout=int(duration)+60)
294         else:
295             raise NotImplementedError('Unsupported traffic type')
296
297         logger.trace(ret)
298         logger.trace(stdout)
299         logger.trace(stderr)
300
301         if int(ret) != 0:
302             raise RuntimeError('T-rex stateless runtime error')
303         elif async_call:
304             #no result
305             self._received = None
306             self._sent = None
307             self._loss = None
308         else:
309             # last line from console output
310             line = stdout.splitlines()[-1]
311
312             self._result = line
313             logger.info('TrafficGen result: {0}'.format(self._result))
314
315             self._received = self._result.split(', ')[1].split('=')[1]
316             self._sent = self._result.split(', ')[2].split('=')[1]
317             self._loss = self._result.split(', ')[3].split('=')[1]
318
319     def stop_traffic_on_tg(self):
320         """Stop all traffic on TG
321
322         :return: Nothing
323         """
324         if self._node is None:
325             raise RuntimeError("TG is not set")
326         if self._node['subtype'] == NodeSubTypeTG.TREX:
327             self.trex_stl_stop_remote_exec(self._node)
328
329     def send_traffic_on_tg(self, duration, rate, framesize,
330                            traffic_type, warmup_time=5, async_call=False):
331         """Send traffic from all configured interfaces on TG.
332
333         :param duration: Duration of test traffic generation in seconds.
334         :param rate: Offered load per interface (e.g. 1%, 3gbps, 4mpps, ...).
335         :param framesize: Frame size (L2) in Bytes.
336         :param traffic_type: Traffic profile.
337         :type duration: str
338         :type rate: str
339         :type framesize: str
340         :type traffic_type: str
341         :return: TG output.
342         :rtype: str
343         """
344
345         node = self._node
346         if node is None:
347             raise RuntimeError("TG is not set")
348
349         if node['type'] != NodeType.TG:
350             raise Exception('Node type is not a TG')
351
352         if node['subtype'] is None:
353             raise Exception('TG subtype not defined')
354         elif node['subtype'] == NodeSubTypeTG.TREX:
355             self.trex_stl_start_remote_exec(duration, rate, framesize,
356                                             traffic_type, async_call,
357                                             warmup_time=warmup_time)
358         else:
359             raise NotImplementedError("TG subtype not supported")
360
361         return self._result
362
363     def no_traffic_loss_occurred(self):
364         """Fail is loss occurred in traffic run.
365
366         :return: nothing
367         """
368         if self._loss is None:
369             raise Exception('The traffic generation has not been issued')
370         if self._loss != '0':
371             raise Exception('Traffic loss occurred: {0}'.format(self._loss))