4 Vhosts setup test
[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             loss = tg_instance.get_loss()
50             sent = tg_instance.get_sent()
51             if self.loss_acceptance_type_is_percentage():
52                 loss = (float(loss) / float(sent)) * 100
53
54             # TODO: getters for tg_instance
55             logger.trace("comparing: {} < {} {}".format(loss,
56                                                         loss_acceptance,
57                                                         loss_acceptance_type))
58             if float(loss) > float(loss_acceptance):
59                 return False
60             else:
61                 return True
62         else:
63             raise NotImplementedError("TG subtype not supported")
64
65
66 class TrafficGenerator(object):
67     """Traffic Generator."""
68
69     # use one instance of TrafficGenerator for all tests in test suite
70     ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
71
72     def __init__(self):
73         self._result = None
74         self._loss = None
75         self._sent = None
76         self._received = None
77         self._node = None
78         # T-REX interface order mapping
79         self._ifaces_reordered = 0
80
81     def get_loss(self):
82         """Return number of lost packets.
83
84         :return: Number of lost packets.
85         :rtype: str
86         """
87         return self._loss
88
89     def get_sent(self):
90         """Return number of sent packets.
91
92         :return: Number of sent packets.
93         :rtype: str
94         """
95         return self._sent
96
97     def get_received(self):
98         """Return number of received packets.
99
100         :return: Number of received packets.
101         :rtype: str
102         """
103         return self._received
104
105     #pylint: disable=too-many-arguments, too-many-locals
106     def initialize_traffic_generator(self, tg_node, tg_if1, tg_if2,
107                                      dut1_node, dut1_if1, dut1_if2,
108                                      dut2_node, dut2_if1, dut2_if2,
109                                      test_type):
110         """TG initialization.
111
112         :param tg_node: Traffic generator node.
113         :param tg_if1: TG - name of first interface.
114         :param tg_if2: TG - name of second interface.
115         :param dut1_node: DUT1 node.
116         :param dut1_if1: DUT1 - name of first interface.
117         :param dut1_if2: DUT1 - name of second interface.
118         :param dut2_node: DUT2 node.
119         :param dut2_if1: DUT2 - name of first interface.
120         :param dut2_if2: DUT2 - name of second interface.
121         :test_type: 'L2' or 'L3' - src/dst MAC address.
122         :type tg_node: dict
123         :type tg_if1: str
124         :type tg_if2: str
125         :type dut1_node: dict
126         :type dut1_if1: str
127         :type dut1_if2: str
128         :type dut2_node: dict
129         :type dut2_if1: str
130         :type dut2_if2: str
131         :type test_type: str
132         :return: nothing
133         """
134
135         topo = Topology()
136
137         if tg_node['type'] != NodeType.TG:
138             raise Exception('Node type is not a TG')
139         self._node = tg_node
140
141         if tg_node['subtype'] == NodeSubTypeTG.TREX:
142             trex_path = "/opt/trex-core-2.00"
143
144             ssh = SSH()
145             ssh.connect(tg_node)
146
147 #            (ret, stdout, stderr) = ssh.exec_command(
148 #                "sudo sh -c '/tmp/openvpp-testing/resources/tools/t-rex/"
149 #                "t-rex-installer.sh'", timeout=600)
150 #            if int(ret) != 0:
151 #                logger.error('trex installation failed: {0}'.format(
152 #                    stdout + stderr))
153 #                raise RuntimeError('Installation of TG failed')
154
155             if1_pci = topo.get_interface_pci_addr(tg_node, tg_if1)
156             if2_pci = topo.get_interface_pci_addr(tg_node, tg_if2)
157             if1_mac = topo.get_interface_mac(tg_node, tg_if1)
158             if2_mac = topo.get_interface_mac(tg_node, tg_if2)
159
160             if test_type == 'L2':
161                 if1_adj_mac = if2_mac
162                 if2_adj_mac = if1_mac
163             elif test_type == 'L3':
164                 if1_adj_mac = topo.get_interface_mac(dut1_node, dut1_if1)
165                 if2_adj_mac = topo.get_interface_mac(dut2_node, dut2_if2)
166             else:
167                 raise Exception("test_type unknown")
168
169             if min(if1_pci, if2_pci) != if1_pci:
170                 if1_mac, if2_mac = if2_mac, if1_mac
171                 if1_pci, if2_pci = if2_pci, if1_pci
172                 if1_adj_mac, if2_adj_mac = if2_adj_mac, if1_adj_mac
173                 self._ifaces_reordered = 1
174
175             if1_mac_hex = "0x"+if1_mac.replace(":", ",0x")
176             if2_mac_hex = "0x"+if2_mac.replace(":", ",0x")
177             if1_adj_mac_hex = "0x"+if1_adj_mac.replace(":", ",0x")
178             if2_adj_mac_hex = "0x"+if2_adj_mac.replace(":", ",0x")
179
180             (ret, stdout, stderr) = ssh.exec_command(
181                 "sudo sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
182                 "- port_limit      : 2\n"
183                 "  version         : 2\n"
184                 "  interfaces      : [\"{}\",\"{}\"]\n"
185                 "  port_bandwidth_gb : 10\n"
186                 "  port_info       :\n"
187                 "          - dest_mac        :   [{}]\n"
188                 "            src_mac         :   [{}]\n"
189                 "          - dest_mac        :   [{}]\n"
190                 "            src_mac         :   [{}]\n"
191                 "EOF'"\
192                 .format(if1_pci, if2_pci,
193                         if1_adj_mac_hex, if1_mac_hex,
194                         if2_adj_mac_hex, if2_mac_hex))
195             if int(ret) != 0:
196                 logger.error("failed to create t-rex config: {}"\
197                 .format(stdout + stderr))
198                 raise RuntimeError('trex config generation error')
199
200             (ret, stdout, stderr) = ssh.exec_command(
201                 "sh -c 'cd {0}/scripts/ && "
202                 "sudo ./trex-cfg'"\
203                 .format(trex_path))
204             if int(ret) != 0:
205                 logger.error('trex-cfg failed: {0}'.format(stdout + stderr))
206                 raise RuntimeError('trex-cfg failed')
207
208             (ret, _, _) = ssh.exec_command(
209                 "sh -c 'pgrep t-rex && sudo pkill t-rex'")
210
211             (ret, _, _) = ssh.exec_command(
212                 "sh -c 'cd {0}/scripts/ && "
213                 "sudo nohup ./t-rex-64 -i -c 7 --iom 0 > /dev/null 2>&1 &'"
214                 "> /dev/null"\
215                 .format(trex_path))
216             if int(ret) != 0:
217                 raise RuntimeError('t-rex-64 startup failed')
218
219     @staticmethod
220     def teardown_traffic_generator(node):
221         """TG teardown.
222
223         :param node: Traffic generator node.
224         :type node: dict
225         :return: nothing
226         """
227         if node['type'] != NodeType.TG:
228             raise Exception('Node type is not a TG')
229         if node['subtype'] == NodeSubTypeTG.TREX:
230             ssh = SSH()
231             ssh.connect(node)
232             (ret, stdout, stderr) = ssh.exec_command(
233                 "sh -c 'sudo pkill t-rex'")
234             if int(ret) != 0:
235                 logger.error('pkill t-rex failed: {0}'.format(stdout + stderr))
236                 raise RuntimeError('pkill t-rex failed')
237
238     @staticmethod
239     def trex_stl_stop_remote_exec(node):
240         """Execute script on remote node over ssh to stop running traffic.
241
242         :param node: T-REX generator node.
243         :type node: dict
244         :return: Nothing
245         """
246         ssh = SSH()
247         ssh.connect(node)
248
249         (ret, stdout, stderr) = ssh.exec_command(
250             "sh -c '/tmp/openvpp-testing/resources/tools/t-rex/"
251             "t-rex-stateless-stop.py'")
252         logger.trace(ret)
253         logger.trace(stdout)
254         logger.trace(stderr)
255
256         if int(ret) != 0:
257             raise RuntimeError('T-rex stateless runtime error')
258
259     def trex_stl_start_remote_exec(self, duration, rate, framesize,
260                                    traffic_type, async_call, warmup_time=5):
261         """Execute script on remote node over ssh to start traffic.
262
263         :param duration: Time expresed in seconds for how long to send traffic.
264         :param rate: Traffic rate expressed with units (pps, %)
265         :param framesize: L2 frame size to send (without padding and IPG).
266         :param traffic_type: Traffic profile.
267         :param async_call: If enabled then don't wait for all incomming trafic.
268         :param warmup_time: Warmup time period.
269         :type duration: int
270         :type rate: str
271         :type framesize: int
272         :type traffic_type: str
273         :type async_call: bool
274         :type warmup_time: int
275         :return: Nothing
276         """
277         ssh = SSH()
278         ssh.connect(self._node)
279
280         _p0 = 1
281         _p1 = 2
282         _async = ""
283
284         if async_call:
285             _async = "--async"
286         if self._ifaces_reordered != 0:
287             _p0, _p1 = _p1, _p0
288
289         if traffic_type in ["3-node-xconnect", "3-node-bridge"]:
290             (ret, stdout, stderr) = ssh.exec_command(
291                 "sh -c '/tmp/openvpp-testing/resources/tools/t-rex/"
292                 "t-rex-stateless.py "
293                 "--duration={0} -r {1} -s {2} "
294                 "--p{3}_src_start_ip 10.10.10.1 "
295                 "--p{3}_src_end_ip 10.10.10.254 "
296                 "--p{3}_dst_start_ip 20.20.20.1 "
297                 "--p{4}_src_start_ip 20.20.20.1 "
298                 "--p{4}_src_end_ip 20.20.20.254 "
299                 "--p{4}_dst_start_ip 10.10.10.1 "
300                 "{5} --warmup_time={6}'".format(duration, rate, framesize, _p0,
301                                                 _p1, _async, warmup_time),
302                 timeout=int(duration)+60)
303         elif traffic_type in ["3-node-IPv4"]:
304             (ret, stdout, stderr) = ssh.exec_command(
305                 "sh -c '/tmp/openvpp-testing/resources/tools/t-rex/"
306                 "t-rex-stateless.py "
307                 "--duration={0} -r {1} -s {2} "
308                 "--p{3}_src_start_ip 10.10.10.2 "
309                 "--p{3}_src_end_ip 10.10.10.254 "
310                 "--p{3}_dst_start_ip 20.20.20.2 "
311                 "--p{4}_src_start_ip 20.20.20.2 "
312                 "--p{4}_src_end_ip 20.20.20.254 "
313                 "--p{4}_dst_start_ip 10.10.10.2 "
314                 "{5} --warmup_time={6}'".format(duration, rate, framesize, _p0,
315                                                 _p1, _async, warmup_time),
316                 timeout=int(duration)+60)
317         elif traffic_type in ["3-node-IPv6"]:
318             (ret, stdout, stderr) = ssh.exec_command(
319                 "sh -c '/tmp/openvpp-testing/resources/tools/t-rex/"
320                 "t-rex-stateless.py "
321                 "--duration={0} -r {1} -s {2} -6 "
322                 "--p{3}_src_start_ip 2001:1::2 "
323                 "--p{3}_src_end_ip 2001:1::FE "
324                 "--p{3}_dst_start_ip 2001:2::2 "
325                 "--p{4}_src_start_ip 2001:2::2 "
326                 "--p{4}_src_end_ip 2001:2::FE "
327                 "--p{4}_dst_start_ip 2001:1::2 "
328                 "{5} --warmup_time={6}'".format(duration, rate, framesize, _p0,
329                                                 _p1, _async, warmup_time),
330                 timeout=int(duration)+60)
331         else:
332             raise NotImplementedError('Unsupported traffic type')
333
334         logger.trace(ret)
335         logger.trace(stdout)
336         logger.trace(stderr)
337
338         if int(ret) != 0:
339             raise RuntimeError('T-rex stateless runtime error')
340         elif async_call:
341             #no result
342             self._received = None
343             self._sent = None
344             self._loss = None
345         else:
346             # last line from console output
347             line = stdout.splitlines()[-1]
348
349             self._result = line
350             logger.info('TrafficGen result: {0}'.format(self._result))
351
352             self._received = self._result.split(', ')[1].split('=')[1]
353             self._sent = self._result.split(', ')[2].split('=')[1]
354             self._loss = self._result.split(', ')[3].split('=')[1]
355
356     def stop_traffic_on_tg(self):
357         """Stop all traffic on TG
358
359         :return: Nothing
360         """
361         if self._node is None:
362             raise RuntimeError("TG is not set")
363         if self._node['subtype'] == NodeSubTypeTG.TREX:
364             self.trex_stl_stop_remote_exec(self._node)
365
366     def send_traffic_on_tg(self, duration, rate, framesize,
367                            traffic_type, warmup_time=5, async_call=False):
368         """Send traffic from all configured interfaces on TG.
369
370         :param duration: Duration of test traffic generation in seconds.
371         :param rate: Offered load per interface (e.g. 1%, 3gbps, 4mpps, ...).
372         :param framesize: Frame size (L2) in Bytes.
373         :param traffic_type: Traffic profile.
374         :type duration: str
375         :type rate: str
376         :type framesize: str
377         :type traffic_type: str
378         :return: TG output.
379         :rtype: str
380         """
381
382         node = self._node
383         if node is None:
384             raise RuntimeError("TG is not set")
385
386         if node['type'] != NodeType.TG:
387             raise Exception('Node type is not a TG')
388
389         if node['subtype'] is None:
390             raise Exception('TG subtype not defined')
391         elif node['subtype'] == NodeSubTypeTG.TREX:
392             self.trex_stl_start_remote_exec(duration, rate, framesize,
393                                             traffic_type, async_call,
394                                             warmup_time=warmup_time)
395         else:
396             raise NotImplementedError("TG subtype not supported")
397
398         return self._result
399
400     def no_traffic_loss_occurred(self):
401         """Fail if loss occurred in traffic run.
402
403         :return: nothing
404         """
405         if self._loss is None:
406             raise Exception('The traffic generation has not been issued')
407         if self._loss != '0':
408             raise Exception('Traffic loss occurred: {0}'.format(self._loss))
409
410     def partial_traffic_loss_accepted(self, loss_acceptance,
411                                       loss_acceptance_type):
412         """Fail if loss is higher then accepted in traffic run.
413
414         :return: nothing
415         """
416         if self._loss is None:
417             raise Exception('The traffic generation has not been issued')
418
419         if loss_acceptance_type == 'percentage':
420             loss = (float(self._loss) / float(self._sent)) * 100
421         elif loss_acceptance_type == 'frames':
422             loss = float(self._loss)
423         else:
424             raise Exception('Loss acceptance type not supported')
425
426         if loss > float(loss_acceptance):
427             raise Exception("Traffic loss {} above loss acceptance: {}".format(
428                 loss, loss_acceptance))