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