CSIT-179 IPv6 Scale - performance
[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.constants import Constants
20 from resources.libraries.python.ssh import SSH
21 from resources.libraries.python.topology import NodeType
22 from resources.libraries.python.topology import NodeSubTypeTG
23 from resources.libraries.python.topology import Topology
24 from resources.libraries.python.DropRateSearch import DropRateSearch
25
26 __all__ = ['TrafficGenerator', 'TGDropRateSearchImpl']
27
28
29 class TGDropRateSearchImpl(DropRateSearch):
30     """Drop Rate Search implementation."""
31
32     def __init__(self):
33         super(TGDropRateSearchImpl, self).__init__()
34
35     def measure_loss(self, rate, frame_size, loss_acceptance,
36                      loss_acceptance_type, traffic_type):
37
38         # we need instance of TrafficGenerator instantiated by Robot Framework
39         # to be able to use trex_stl-*()
40         tg_instance = BuiltIn().get_library_instance(
41             'resources.libraries.python.TrafficGenerator')
42
43         if tg_instance._node['subtype'] is None:
44             raise Exception('TG subtype not defined')
45         elif tg_instance._node['subtype'] == NodeSubTypeTG.TREX:
46             unit_rate = str(rate) + self.get_rate_type_str()
47             tg_instance.trex_stl_start_remote_exec(self.get_duration(),
48                                                    unit_rate, frame_size,
49                                                    traffic_type)
50             # Get latency stats from stream
51             self._latency_stats = tg_instance.get_latency()
52
53             loss = tg_instance.get_loss()
54             sent = tg_instance.get_sent()
55             if self.loss_acceptance_type_is_percentage():
56                 loss = (float(loss) / float(sent)) * 100
57
58             # TODO: getters for tg_instance
59             logger.trace("comparing: {} < {} {}".format(loss,
60                                                         loss_acceptance,
61                                                         loss_acceptance_type))
62             if float(loss) > float(loss_acceptance):
63                 return False
64             else:
65                 return True
66         else:
67             raise NotImplementedError("TG subtype not supported")
68
69
70 class TrafficGenerator(object):
71     """Traffic Generator."""
72
73     # use one instance of TrafficGenerator for all tests in test suite
74     ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
75
76     def __init__(self):
77         self._result = None
78         self._loss = None
79         self._sent = None
80         self._latency = None
81         self._received = None
82         self._node = None
83         # T-REX interface order mapping
84         self._ifaces_reordered = 0
85
86     def get_loss(self):
87         """Return number of lost packets.
88
89         :return: Number of lost packets.
90         :rtype: str
91         """
92         return self._loss
93
94     def get_sent(self):
95         """Return number of sent packets.
96
97         :return: Number of sent packets.
98         :rtype: str
99         """
100         return self._sent
101
102     def get_received(self):
103         """Return number of received packets.
104
105         :return: Number of received packets.
106         :rtype: str
107         """
108         return self._received
109
110     def get_latency(self):
111         """Return min/avg/max latency.
112
113         :return: Latency stats.
114         :rtype: list
115         """
116         return self._latency
117
118     #pylint: disable=too-many-arguments, too-many-locals
119     def initialize_traffic_generator(self, tg_node, tg_if1, tg_if2,
120                                      tg_if1_adj_node, tg_if1_adj_if,
121                                      tg_if2_adj_node, tg_if2_adj_if,
122                                      test_type):
123         """TG initialization.
124
125         :param tg_node: Traffic generator node.
126         :param tg_if1: TG - name of first interface.
127         :param tg_if2: TG - name of second interface.
128         :param tg_if1_adj_node: TG if1 adjecent node.
129         :param tg_if1_adj_if: TG if1 adjecent interface.
130         :param tg_if2_adj_node: TG if2 adjecent node.
131         :param tg_if2_adj_if: TG if2 adjecent interface.
132         :test_type: 'L2' or 'L3' - src/dst MAC address.
133         :type tg_node: dict
134         :type tg_if1: str
135         :type tg_if2: str
136         :type tg_if1_adj_node: dict
137         :type tg_if1_adj_if: str
138         :type tg_if2_adj_node: dict
139         :type tg_if2_adj_if: str
140         :type test_type: str
141         :return: nothing
142         """
143
144         topo = Topology()
145
146         if tg_node['type'] != NodeType.TG:
147             raise Exception('Node type is not a TG')
148         self._node = tg_node
149
150         if tg_node['subtype'] == NodeSubTypeTG.TREX:
151             trex_path = "/opt/trex-core-2.06"
152
153             ssh = SSH()
154             ssh.connect(tg_node)
155
156             (ret, stdout, stderr) = ssh.exec_command(
157                 "sudo sh -c '{}/resources/tools/t-rex/"
158                 "t-rex-installer.sh'".format(Constants.REMOTE_FW_DIR),
159                 timeout=1800)
160             if int(ret) != 0:
161                 logger.error('trex installation failed: {0}'.format(
162                     stdout + stderr))
163                 raise RuntimeError('Installation of TG failed')
164
165             if1_pci = topo.get_interface_pci_addr(tg_node, tg_if1)
166             if2_pci = topo.get_interface_pci_addr(tg_node, tg_if2)
167             if1_mac = topo.get_interface_mac(tg_node, tg_if1)
168             if2_mac = topo.get_interface_mac(tg_node, tg_if2)
169
170             if test_type == 'L2':
171                 if1_adj_mac = if2_mac
172                 if2_adj_mac = if1_mac
173             elif test_type == 'L3':
174                 if1_adj_mac = topo.get_interface_mac(tg_if1_adj_node,
175                                                      tg_if1_adj_if)
176                 if2_adj_mac = topo.get_interface_mac(tg_if2_adj_node,
177                                                      tg_if2_adj_if)
178             else:
179                 raise Exception("test_type unknown")
180
181             if min(if1_pci, if2_pci) != if1_pci:
182                 if1_mac, if2_mac = if2_mac, if1_mac
183                 if1_pci, if2_pci = if2_pci, if1_pci
184                 if1_adj_mac, if2_adj_mac = if2_adj_mac, if1_adj_mac
185                 self._ifaces_reordered = 1
186
187             if1_mac_hex = "0x"+if1_mac.replace(":", ",0x")
188             if2_mac_hex = "0x"+if2_mac.replace(":", ",0x")
189             if1_adj_mac_hex = "0x"+if1_adj_mac.replace(":", ",0x")
190             if2_adj_mac_hex = "0x"+if2_adj_mac.replace(":", ",0x")
191
192             (ret, stdout, stderr) = ssh.exec_command(
193                 "sudo sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
194                 "- port_limit      : 2\n"
195                 "  version         : 2\n"
196                 "  interfaces      : [\"{}\",\"{}\"]\n"
197                 "  port_info       :\n"
198                 "          - dest_mac        :   [{}]\n"
199                 "            src_mac         :   [{}]\n"
200                 "          - dest_mac        :   [{}]\n"
201                 "            src_mac         :   [{}]\n"
202                 "EOF'"\
203                 .format(if1_pci, if2_pci,
204                         if1_adj_mac_hex, if1_mac_hex,
205                         if2_adj_mac_hex, if2_mac_hex))
206             if int(ret) != 0:
207                 logger.error("failed to create t-rex config: {}"\
208                 .format(stdout + stderr))
209                 raise RuntimeError('trex config generation error')
210
211             (ret, stdout, stderr) = ssh.exec_command(
212                 "sh -c 'cd {0}/scripts/ && sudo ./trex-cfg'".format(trex_path))
213             if int(ret) != 0:
214                 logger.error('trex-cfg failed: {0}'.format(stdout + stderr))
215                 raise RuntimeError('trex-cfg failed')
216
217             max_startup_retries = 3
218             while max_startup_retries > 0:
219                 # kill T-rex only if it is already running
220                 (ret, _, _) = ssh.exec_command(
221                     "sh -c 'pgrep t-rex && sudo pkill t-rex'")
222
223                 # start T-rex
224                 (ret, _, _) = ssh.exec_command(
225                     "sh -c 'cd {0}/scripts/ && "
226                     "sudo nohup ./t-rex-64 -i -c 7 --iom 0 > /dev/null 2>&1 &'"
227                     "> /dev/null"\
228                     .format(trex_path))
229                 if int(ret) != 0:
230                     raise RuntimeError('t-rex-64 startup failed')
231
232                 # get T-rex server info
233                 (ret, _, _) = ssh.exec_command(
234                     "sh -c '{0}/resources/tools/t-rex/t-rex-server-info.py'"\
235                     .format(Constants.REMOTE_FW_DIR),
236                     timeout=120)
237                 if int(ret) == 0:
238                     # If we get info T-rex is running
239                     return
240                 # try again
241                 max_startup_retries -= 1
242             # after max retries T-rex is still not responding to API
243             # critical error occured
244             raise RuntimeError('t-rex-64 startup failed')
245
246
247     @staticmethod
248     def teardown_traffic_generator(node):
249         """TG teardown.
250
251         :param node: Traffic generator node.
252         :type node: dict
253         :return: nothing
254         """
255         if node['type'] != NodeType.TG:
256             raise Exception('Node type is not a TG')
257         if node['subtype'] == NodeSubTypeTG.TREX:
258             ssh = SSH()
259             ssh.connect(node)
260             (ret, stdout, stderr) = ssh.exec_command(
261                 "sh -c 'sudo pkill t-rex'")
262             if int(ret) != 0:
263                 logger.error('pkill t-rex failed: {0}'.format(stdout + stderr))
264                 raise RuntimeError('pkill t-rex failed')
265
266     @staticmethod
267     def trex_stl_stop_remote_exec(node):
268         """Execute script on remote node over ssh to stop running traffic.
269
270         :param node: T-REX generator node.
271         :type node: dict
272         :return: Nothing
273         """
274         ssh = SSH()
275         ssh.connect(node)
276
277         (ret, stdout, stderr) = ssh.exec_command(
278             "sh -c '{}/resources/tools/t-rex/"
279             "t-rex-stateless-stop.py'".format(Constants.REMOTE_FW_DIR))
280         logger.trace(ret)
281         logger.trace(stdout)
282         logger.trace(stderr)
283
284         if int(ret) != 0:
285             raise RuntimeError('T-rex stateless runtime error')
286
287     def trex_stl_start_remote_exec(self, duration, rate, framesize,
288                                    traffic_type, async_call=False,
289                                    latency=True, warmup_time=5):
290         """Execute script on remote node over ssh to start traffic.
291
292         :param duration: Time expresed in seconds for how long to send traffic.
293         :param rate: Traffic rate expressed with units (pps, %)
294         :param framesize: L2 frame size to send (without padding and IPG).
295         :param traffic_type: Traffic profile.
296         :param async_call: If enabled then don't wait for all incomming trafic.
297         :param latency: With latency measurement.
298         :param warmup_time: Warmup time period.
299         :type duration: int
300         :type rate: str
301         :type framesize: int
302         :type traffic_type: str
303         :type async_call: bool
304         :type latency: bool
305         :type warmup_time: int
306         :return: Nothing
307         """
308         ssh = SSH()
309         ssh.connect(self._node)
310
311         _p0 = 1
312         _p1 = 2
313         _async = "--async" if async_call else ""
314         _latency = "--latency" if latency else ""
315
316         if self._ifaces_reordered != 0:
317             _p0, _p1 = _p1, _p0
318
319         if traffic_type in ["3-node-xconnect", "3-node-bridge"]:
320             (ret, stdout, stderr) = ssh.exec_command(
321                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
322                 "--duration={1} -r {2} -s {3} "
323                 "--p{4}_src_start_ip 10.10.10.1 "
324                 "--p{4}_src_end_ip 10.10.10.254 "
325                 "--p{4}_dst_start_ip 20.20.20.1 "
326                 "--p{5}_src_start_ip 20.20.20.1 "
327                 "--p{5}_src_end_ip 20.20.20.254 "
328                 "--p{5}_dst_start_ip 10.10.10.1 "
329                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
330                                                     duration, rate, framesize,
331                                                     _p0, _p1, _async, _latency,
332                                                     warmup_time),
333                 timeout=int(duration)+60)
334         elif traffic_type in ["3-node-IPv4"]:
335             (ret, stdout, stderr) = ssh.exec_command(
336                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
337                 "--duration={1} -r {2} -s {3} "
338                 "--p{4}_src_start_ip 10.10.10.2 "
339                 "--p{4}_src_end_ip 10.10.10.254 "
340                 "--p{4}_dst_start_ip 20.20.20.2 "
341                 "--p{5}_src_start_ip 20.20.20.2 "
342                 "--p{5}_src_end_ip 20.20.20.254 "
343                 "--p{5}_dst_start_ip 10.10.10.2 "
344                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
345                                                     duration, rate, framesize,
346                                                     _p0, _p1, _async, _latency,
347                                                     warmup_time),
348                 timeout=int(duration)+60)
349         elif traffic_type in ["3-node-IPv4-dst-10000"]:
350             (ret, stdout, stderr) = ssh.exec_command(
351                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
352                 "--duration={1} -r {2} -s {3} "
353                 "--p{4}_src_start_ip 10.0.0.1 "
354                 "--p{4}_dst_start_ip 20.0.0.0 "
355                 "--p{4}_dst_end_ip 20.0.39.15 "
356                 "--p{5}_src_start_ip 20.0.0.1 "
357                 "--p{5}_dst_start_ip 10.0.0.0 "
358                 "--p{5}_dst_end_ip 10.0.39.15 "
359                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
360                                                     duration, rate, framesize,
361                                                     _p0, _p1, _async, _latency,
362                                                     warmup_time),
363                 timeout=int(duration)+60)
364         elif traffic_type in ["3-node-IPv4-dst-100000"]:
365             (ret, stdout, stderr) = ssh.exec_command(
366                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
367                 "--duration={1} -r {2} -s {3} "
368                 "--p{4}_src_start_ip 10.0.0.1 "
369                 "--p{4}_dst_start_ip 20.0.0.0 "
370                 "--p{4}_dst_end_ip 20.1.134.159 "
371                 "--p{5}_src_start_ip 20.0.0.1 "
372                 "--p{5}_dst_start_ip 10.0.0.0 "
373                 "--p{5}_dst_end_ip 10.1.134.159 "
374                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
375                                                     duration, rate, framesize,
376                                                     _p0, _p1, _async, _latency,
377                                                     warmup_time),
378                 timeout=int(duration)+60)
379         elif traffic_type in ["3-node-IPv4-dst-1000000"]:
380             (ret, stdout, stderr) = ssh.exec_command(
381                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
382                 "--duration={1} -r {2} -s {3} "
383                 "--p{4}_src_start_ip 10.0.0.1 "
384                 "--p{4}_dst_start_ip 20.0.0.0 "
385                 "--p{4}_dst_end_ip 20.15.66.63 "
386                 "--p{5}_src_start_ip 20.0.0.1 "
387                 "--p{5}_dst_start_ip 10.0.0.0 "
388                 "--p{5}_dst_end_ip 10.15.66.63 "
389                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
390                                                     duration, rate, framesize,
391                                                     _p0, _p1, _async, _latency,
392                                                     warmup_time),
393                 timeout=int(duration)+60)
394         elif traffic_type in ["3-node-IPv6"]:
395             (ret, stdout, stderr) = ssh.exec_command(
396                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
397                 "--duration={1} -r {2} -s {3} -6 "
398                 "--p{4}_src_start_ip 2001:1::2 "
399                 "--p{4}_src_end_ip 2001:1::FE "
400                 "--p{4}_dst_start_ip 2001:2::2 "
401                 "--p{5}_src_start_ip 2001:2::2 "
402                 "--p{5}_src_end_ip 2001:2::FE "
403                 "--p{5}_dst_start_ip 2001:1::2 "
404                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
405                                                     duration, rate, framesize,
406                                                     _p0, _p1, _async, _latency,
407                                                     warmup_time),
408                 timeout=int(duration)+60)
409         elif traffic_type in ["3-node-IPv6-dst-10000"]:
410             (ret, stdout, stderr) = ssh.exec_command(
411                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
412                 "--duration={1} -r {2} -s {3} -6 "
413                 "--p{4}_src_start_ip 2001:1::1 "
414                 "--p{4}_dst_start_ip 2001:2::0 "
415                 "--p{4}_dst_end_ip 2001:2::270F "
416                 "--p{5}_src_start_ip 2001:2::1 "
417                 "--p{5}_dst_start_ip 2001:1::0 "
418                 "--p{5}_dst_end_ip 2001:1::270F "
419                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
420                                                     duration, rate, framesize,
421                                                     _p0, _p1, _async, _latency,
422                                                     warmup_time),
423                 timeout=int(duration)+60)
424         elif traffic_type in ["3-node-IPv6-dst-100000"]:
425             (ret, stdout, stderr) = ssh.exec_command(
426                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
427                 "--duration={1} -r {2} -s {3} -6 "
428                 "--p{4}_src_start_ip 2001:1::1 "
429                 "--p{4}_dst_start_ip 2001:2::0 "
430                 "--p{4}_dst_end_ip 2001:2::1:869F "
431                 "--p{5}_src_start_ip 2001:2::1 "
432                 "--p{5}_dst_start_ip 2001:1::0 "
433                 "--p{5}_dst_end_ip 2001:1::1:869F "
434                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
435                                                     duration, rate, framesize,
436                                                     _p0, _p1, _async, _latency,
437                                                     warmup_time),
438                 timeout=int(duration)+60)
439         elif traffic_type in ["3-node-IPv6-dst-1000000"]:
440             (ret, stdout, stderr) = ssh.exec_command(
441                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
442                 "--duration={1} -r {2} -s {3} -6 "
443                 "--p{4}_src_start_ip 2001:1::1 "
444                 "--p{4}_dst_start_ip 2001:2::0 "
445                 "--p{4}_dst_end_ip 2001:2::F:423F "
446                 "--p{5}_src_start_ip 2001:2::1 "
447                 "--p{5}_dst_start_ip 2001:1::0 "
448                 "--p{5}_dst_end_ip 2001:1::F:423F "
449                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
450                                                     duration, rate, framesize,
451                                                     _p0, _p1, _async, _latency,
452                                                     warmup_time),
453                 timeout=int(duration)+60)
454         else:
455             raise NotImplementedError('Unsupported traffic type')
456
457         logger.trace(ret)
458         logger.trace(stdout)
459         logger.trace(stderr)
460
461         if int(ret) != 0:
462             raise RuntimeError('T-rex stateless runtime error')
463         elif async_call:
464             #no result
465             self._received = None
466             self._sent = None
467             self._loss = None
468             self._latency = None
469         else:
470             # last line from console output
471             line = stdout.splitlines()[-1]
472
473             self._result = line
474             logger.info('TrafficGen result: {0}'.format(self._result))
475
476             self._received = self._result.split(', ')[1].split('=')[1]
477             self._sent = self._result.split(', ')[2].split('=')[1]
478             self._loss = self._result.split(', ')[3].split('=')[1]
479             self._latency = []
480             self._latency.append(self._result.split(', ')[4].split('=')[1])
481             self._latency.append(self._result.split(', ')[5].split('=')[1])
482
483     def stop_traffic_on_tg(self):
484         """Stop all traffic on TG
485
486         :return: Nothing
487         """
488         if self._node is None:
489             raise RuntimeError("TG is not set")
490         if self._node['subtype'] == NodeSubTypeTG.TREX:
491             self.trex_stl_stop_remote_exec(self._node)
492
493     def send_traffic_on_tg(self, duration, rate, framesize,
494                            traffic_type, warmup_time=5, async_call=False,
495                            latency=True):
496         """Send traffic from all configured interfaces on TG.
497
498         :param duration: Duration of test traffic generation in seconds.
499         :param rate: Offered load per interface (e.g. 1%, 3gbps, 4mpps, ...).
500         :param framesize: Frame size (L2) in Bytes.
501         :param traffic_type: Traffic profile.
502         :param latency: With latency measurement.
503         :type duration: str
504         :type rate: str
505         :type framesize: str
506         :type traffic_type: str
507         :type latency: bool
508         :return: TG output.
509         :rtype: str
510         """
511
512         node = self._node
513         if node is None:
514             raise RuntimeError("TG is not set")
515
516         if node['type'] != NodeType.TG:
517             raise Exception('Node type is not a TG')
518
519         if node['subtype'] is None:
520             raise Exception('TG subtype not defined')
521         elif node['subtype'] == NodeSubTypeTG.TREX:
522             self.trex_stl_start_remote_exec(duration, rate, framesize,
523                                             traffic_type, async_call, latency,
524                                             warmup_time=warmup_time)
525         else:
526             raise NotImplementedError("TG subtype not supported")
527
528         return self._result
529
530     def no_traffic_loss_occurred(self):
531         """Fail if loss occurred in traffic run.
532
533         :return: nothing
534         """
535         if self._loss is None:
536             raise Exception('The traffic generation has not been issued')
537         if self._loss != '0':
538             raise Exception('Traffic loss occurred: {0}'.format(self._loss))
539
540     def partial_traffic_loss_accepted(self, loss_acceptance,
541                                       loss_acceptance_type):
542         """Fail if loss is higher then accepted in traffic run.
543
544         :return: nothing
545         """
546         if self._loss is None:
547             raise Exception('The traffic generation has not been issued')
548
549         if loss_acceptance_type == 'percentage':
550             loss = (float(self._loss) / float(self._sent)) * 100
551         elif loss_acceptance_type == 'frames':
552             loss = float(self._loss)
553         else:
554             raise Exception('Loss acceptance type not supported')
555
556         if loss > float(loss_acceptance):
557             raise Exception("Traffic loss {} above loss acceptance: {}".format(
558                 loss, loss_acceptance))