T-REX installer preserve env variables
[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.07"
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             (ret, stdout, stderr) = ssh.exec_command(
219                 "sh -c 'cd {0}/scripts/ && sudo ./trex-cfg'".format(trex_path))
220             if int(ret) != 0:
221                 logger.error('trex-cfg failed: {0}'.format(stdout + stderr))
222                 raise RuntimeError('trex-cfg failed')
223
224             max_startup_retries = 3
225             while max_startup_retries > 0:
226                 # kill T-rex only if it is already running
227                 (ret, _, _) = ssh.exec_command(
228                     "sh -c 'pgrep t-rex && sudo pkill t-rex'")
229
230                 # start T-rex
231                 (ret, _, _) = ssh.exec_command(
232                     "sh -c 'cd {0}/scripts/ && "
233                     "sudo nohup ./t-rex-64 -i -c 7 --iom 0 > /dev/null 2>&1 &'"
234                     "> /dev/null"\
235                     .format(trex_path))
236                 if int(ret) != 0:
237                     raise RuntimeError('t-rex-64 startup failed')
238
239                 # get T-rex server info
240                 (ret, _, _) = ssh.exec_command(
241                     "sh -c '{0}/resources/tools/t-rex/t-rex-server-info.py'"\
242                     .format(Constants.REMOTE_FW_DIR),
243                     timeout=120)
244                 if int(ret) == 0:
245                     # If we get info T-rex is running
246                     return
247                 # try again
248                 max_startup_retries -= 1
249             # after max retries T-rex is still not responding to API
250             # critical error occured
251             raise RuntimeError('t-rex-64 startup failed')
252
253
254     @staticmethod
255     def teardown_traffic_generator(node):
256         """TG teardown.
257
258         :param node: Traffic generator node.
259         :type node: dict
260         :return: nothing
261         """
262         if node['type'] != NodeType.TG:
263             raise Exception('Node type is not a TG')
264         if node['subtype'] == NodeSubTypeTG.TREX:
265             ssh = SSH()
266             ssh.connect(node)
267             (ret, stdout, stderr) = ssh.exec_command(
268                 "sh -c 'sudo pkill t-rex'")
269             if int(ret) != 0:
270                 logger.error('pkill t-rex failed: {0}'.format(stdout + stderr))
271                 raise RuntimeError('pkill t-rex failed')
272
273     @staticmethod
274     def trex_stl_stop_remote_exec(node):
275         """Execute script on remote node over ssh to stop running traffic.
276
277         :param node: T-REX generator node.
278         :type node: dict
279         :return: Nothing
280         """
281         ssh = SSH()
282         ssh.connect(node)
283
284         (ret, stdout, stderr) = ssh.exec_command(
285             "sh -c '{}/resources/tools/t-rex/"
286             "t-rex-stateless-stop.py'".format(Constants.REMOTE_FW_DIR))
287         logger.trace(ret)
288         logger.trace(stdout)
289         logger.trace(stderr)
290
291         if int(ret) != 0:
292             raise RuntimeError('T-rex stateless runtime error')
293
294     def trex_stl_start_remote_exec(self, duration, rate, framesize,
295                                    traffic_type, async_call=False,
296                                    latency=True, warmup_time=5):
297         """Execute script on remote node over ssh to start traffic.
298
299         :param duration: Time expresed in seconds for how long to send traffic.
300         :param rate: Traffic rate expressed with units (pps, %)
301         :param framesize: L2 frame size to send (without padding and IPG).
302         :param traffic_type: Traffic profile.
303         :param async_call: If enabled then don't wait for all incomming trafic.
304         :param latency: With latency measurement.
305         :param warmup_time: Warmup time period.
306         :type duration: int
307         :type rate: str
308         :type framesize: int
309         :type traffic_type: str
310         :type async_call: bool
311         :type latency: bool
312         :type warmup_time: int
313         :return: Nothing
314         """
315         ssh = SSH()
316         ssh.connect(self._node)
317
318         _p0 = 1
319         _p1 = 2
320         _async = "--async" if async_call else ""
321         _latency = "--latency" if latency else ""
322
323         if self._ifaces_reordered != 0:
324             _p0, _p1 = _p1, _p0
325
326         if traffic_type in ["3-node-xconnect", "3-node-bridge"]:
327             (ret, stdout, stderr) = ssh.exec_command(
328                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
329                 "--duration={1} -r {2} -s {3} "
330                 "--p{4}_src_start_ip 10.10.10.1 "
331                 "--p{4}_src_end_ip 10.10.10.254 "
332                 "--p{4}_dst_start_ip 20.20.20.1 "
333                 "--p{5}_src_start_ip 20.20.20.1 "
334                 "--p{5}_src_end_ip 20.20.20.254 "
335                 "--p{5}_dst_start_ip 10.10.10.1 "
336                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
337                                                     duration, rate, framesize,
338                                                     _p0, _p1, _async, _latency,
339                                                     warmup_time),
340                 timeout=int(duration)+60)
341         elif traffic_type in ["3-node-IPv4"]:
342             (ret, stdout, stderr) = ssh.exec_command(
343                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
344                 "--duration={1} -r {2} -s {3} "
345                 "--p{4}_src_start_ip 10.10.10.2 "
346                 "--p{4}_src_end_ip 10.10.10.254 "
347                 "--p{4}_dst_start_ip 20.20.20.2 "
348                 "--p{5}_src_start_ip 20.20.20.2 "
349                 "--p{5}_src_end_ip 20.20.20.254 "
350                 "--p{5}_dst_start_ip 10.10.10.2 "
351                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
352                                                     duration, rate, framesize,
353                                                     _p0, _p1, _async, _latency,
354                                                     warmup_time),
355                 timeout=int(duration)+60)
356         elif traffic_type in ["3-node-IPv4-dst-10000"]:
357             (ret, stdout, stderr) = ssh.exec_command(
358                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
359                 "--duration={1} -r {2} -s {3} "
360                 "--p{4}_src_start_ip 10.0.0.1 "
361                 "--p{4}_dst_start_ip 20.0.0.0 "
362                 "--p{4}_dst_end_ip 20.0.39.15 "
363                 "--p{5}_src_start_ip 20.0.0.1 "
364                 "--p{5}_dst_start_ip 10.0.0.0 "
365                 "--p{5}_dst_end_ip 10.0.39.15 "
366                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
367                                                     duration, rate, framesize,
368                                                     _p0, _p1, _async, _latency,
369                                                     warmup_time),
370                 timeout=int(duration)+60)
371         elif traffic_type in ["3-node-IPv4-dst-100000"]:
372             (ret, stdout, stderr) = ssh.exec_command(
373                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
374                 "--duration={1} -r {2} -s {3} "
375                 "--p{4}_src_start_ip 10.0.0.1 "
376                 "--p{4}_dst_start_ip 20.0.0.0 "
377                 "--p{4}_dst_end_ip 20.1.134.159 "
378                 "--p{5}_src_start_ip 20.0.0.1 "
379                 "--p{5}_dst_start_ip 10.0.0.0 "
380                 "--p{5}_dst_end_ip 10.1.134.159 "
381                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
382                                                     duration, rate, framesize,
383                                                     _p0, _p1, _async, _latency,
384                                                     warmup_time),
385                 timeout=int(duration)+60)
386         elif traffic_type in ["3-node-IPv4-dst-1000000"]:
387             (ret, stdout, stderr) = ssh.exec_command(
388                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
389                 "--duration={1} -r {2} -s {3} "
390                 "--p{4}_src_start_ip 10.0.0.1 "
391                 "--p{4}_dst_start_ip 20.0.0.0 "
392                 "--p{4}_dst_end_ip 20.15.66.63 "
393                 "--p{5}_src_start_ip 20.0.0.1 "
394                 "--p{5}_dst_start_ip 10.0.0.0 "
395                 "--p{5}_dst_end_ip 10.15.66.63 "
396                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
397                                                     duration, rate, framesize,
398                                                     _p0, _p1, _async, _latency,
399                                                     warmup_time),
400                 timeout=int(duration)+60)
401         elif traffic_type in ["3-node-IPv6"]:
402             (ret, stdout, stderr) = ssh.exec_command(
403                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
404                 "--duration={1} -r {2} -s {3} -6 "
405                 "--p{4}_src_start_ip 2001:1::2 "
406                 "--p{4}_src_end_ip 2001:1::FE "
407                 "--p{4}_dst_start_ip 2001:2::2 "
408                 "--p{5}_src_start_ip 2001:2::2 "
409                 "--p{5}_src_end_ip 2001:2::FE "
410                 "--p{5}_dst_start_ip 2001:1::2 "
411                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
412                                                     duration, rate, framesize,
413                                                     _p0, _p1, _async, _latency,
414                                                     warmup_time),
415                 timeout=int(duration)+60)
416         elif traffic_type in ["3-node-IPv6-dst-10000"]:
417             (ret, stdout, stderr) = ssh.exec_command(
418                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
419                 "--duration={1} -r {2} -s {3} -6 "
420                 "--p{4}_src_start_ip 2001:1::1 "
421                 "--p{4}_dst_start_ip 2001:2::0 "
422                 "--p{4}_dst_end_ip 2001:2::270F "
423                 "--p{5}_src_start_ip 2001:2::1 "
424                 "--p{5}_dst_start_ip 2001:1::0 "
425                 "--p{5}_dst_end_ip 2001:1::270F "
426                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
427                                                     duration, rate, framesize,
428                                                     _p0, _p1, _async, _latency,
429                                                     warmup_time),
430                 timeout=int(duration)+60)
431         elif traffic_type in ["3-node-IPv6-dst-100000"]:
432             (ret, stdout, stderr) = ssh.exec_command(
433                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
434                 "--duration={1} -r {2} -s {3} -6 "
435                 "--p{4}_src_start_ip 2001:1::1 "
436                 "--p{4}_dst_start_ip 2001:2::0 "
437                 "--p{4}_dst_end_ip 2001:2::1:869F "
438                 "--p{5}_src_start_ip 2001:2::1 "
439                 "--p{5}_dst_start_ip 2001:1::0 "
440                 "--p{5}_dst_end_ip 2001:1::1:869F "
441                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
442                                                     duration, rate, framesize,
443                                                     _p0, _p1, _async, _latency,
444                                                     warmup_time),
445                 timeout=int(duration)+60)
446         elif traffic_type in ["3-node-IPv6-dst-1000000"]:
447             (ret, stdout, stderr) = ssh.exec_command(
448                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
449                 "--duration={1} -r {2} -s {3} -6 "
450                 "--p{4}_src_start_ip 2001:1::1 "
451                 "--p{4}_dst_start_ip 2001:2::0 "
452                 "--p{4}_dst_end_ip 2001:2::F:423F "
453                 "--p{5}_src_start_ip 2001:2::1 "
454                 "--p{5}_dst_start_ip 2001:1::0 "
455                 "--p{5}_dst_end_ip 2001:1::F:423F "
456                 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
457                                                     duration, rate, framesize,
458                                                     _p0, _p1, _async, _latency,
459                                                     warmup_time),
460                 timeout=int(duration)+60)
461         else:
462             raise NotImplementedError('Unsupported traffic type')
463
464         logger.trace(ret)
465         logger.trace(stdout)
466         logger.trace(stderr)
467
468         if int(ret) != 0:
469             raise RuntimeError('T-rex stateless runtime error')
470         elif async_call:
471             #no result
472             self._received = None
473             self._sent = None
474             self._loss = None
475             self._latency = None
476         else:
477             # last line from console output
478             line = stdout.splitlines()[-1]
479
480             self._result = line
481             logger.info('TrafficGen result: {0}'.format(self._result))
482
483             self._received = self._result.split(', ')[1].split('=')[1]
484             self._sent = self._result.split(', ')[2].split('=')[1]
485             self._loss = self._result.split(', ')[3].split('=')[1]
486
487             self._latency = []
488             self._latency.append(self._result.split(', ')[4].split('=')[1])
489             self._latency.append(self._result.split(', ')[5].split('=')[1])
490
491     def stop_traffic_on_tg(self):
492         """Stop all traffic on TG
493
494         :return: Nothing
495         """
496         if self._node is None:
497             raise RuntimeError("TG is not set")
498         if self._node['subtype'] == NodeSubTypeTG.TREX:
499             self.trex_stl_stop_remote_exec(self._node)
500
501     def send_traffic_on_tg(self, duration, rate, framesize,
502                            traffic_type, warmup_time=5, async_call=False,
503                            latency=True):
504         """Send traffic from all configured interfaces on TG.
505
506         :param duration: Duration of test traffic generation in seconds.
507         :param rate: Offered load per interface (e.g. 1%, 3gbps, 4mpps, ...).
508         :param framesize: Frame size (L2) in Bytes.
509         :param traffic_type: Traffic profile.
510         :param latency: With latency measurement.
511         :type duration: str
512         :type rate: str
513         :type framesize: str
514         :type traffic_type: str
515         :type latency: bool
516         :return: TG output.
517         :rtype: str
518         """
519
520         node = self._node
521         if node is None:
522             raise RuntimeError("TG is not set")
523
524         if node['type'] != NodeType.TG:
525             raise Exception('Node type is not a TG')
526
527         if node['subtype'] is None:
528             raise Exception('TG subtype not defined')
529         elif node['subtype'] == NodeSubTypeTG.TREX:
530             self.trex_stl_start_remote_exec(duration, rate, framesize,
531                                             traffic_type, async_call, latency,
532                                             warmup_time=warmup_time)
533         else:
534             raise NotImplementedError("TG subtype not supported")
535
536         return self._result
537
538     def no_traffic_loss_occurred(self):
539         """Fail if loss occurred in traffic run.
540
541         :return: nothing
542         """
543         if self._loss is None:
544             raise Exception('The traffic generation has not been issued')
545         if self._loss != '0':
546             raise Exception('Traffic loss occurred: {0}'.format(self._loss))
547
548     def partial_traffic_loss_accepted(self, loss_acceptance,
549                                       loss_acceptance_type):
550         """Fail if loss is higher then accepted in traffic run.
551
552         :return: nothing
553         """
554         if self._loss is None:
555             raise Exception('The traffic generation has not been issued')
556
557         if loss_acceptance_type == 'percentage':
558             loss = (float(self._loss) / float(self._sent)) * 100
559         elif loss_acceptance_type == 'frames':
560             loss = float(self._loss)
561         else:
562             raise Exception('Loss acceptance type not supported')
563
564         if loss > float(loss_acceptance):
565             raise Exception("Traffic loss {} above loss acceptance: {}".format(
566                 loss, loss_acceptance))