127b5b1d9b6d7955adbe8420c97361241bc9d03a
[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, False)
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
67 class TrafficGenerator(object):
68     """Traffic Generator."""
69
70     # use one instance of TrafficGenerator for all tests in test suite
71     ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
72
73     def __init__(self):
74         self._result = None
75         self._loss = None
76         self._sent = None
77         self._received = None
78         self._node = None
79         # T-REX interface order mapping
80         self._ifaces_reordered = 0
81
82     def get_loss(self):
83         """Return number of lost packets.
84
85         :return: Number of lost packets.
86         :rtype: str
87         """
88         return self._loss
89
90     def get_sent(self):
91         """Return number of sent packets.
92
93         :return: Number of sent packets.
94         :rtype: str
95         """
96         return self._sent
97
98     def get_received(self):
99         """Return number of received packets.
100
101         :return: Number of received packets.
102         :rtype: str
103         """
104         return self._received
105
106     #pylint: disable=too-many-arguments, too-many-locals
107     def initialize_traffic_generator(self, tg_node, tg_if1, tg_if2,
108                                      dut1_node, dut1_if1, dut1_if2,
109                                      dut2_node, dut2_if1, dut2_if2,
110                                      test_type):
111         """TG initialization.
112
113         :param tg_node: Traffic generator node.
114         :param tg_if1: TG - name of first interface.
115         :param tg_if2: TG - name of second interface.
116         :param dut1_node: DUT1 node.
117         :param dut1_if1: DUT1 - name of first interface.
118         :param dut1_if2: DUT1 - name of second interface.
119         :param dut2_node: DUT2 node.
120         :param dut2_if1: DUT2 - name of first interface.
121         :param dut2_if2: DUT2 - name of second interface.
122         :test_type: 'L2' or 'L3' - src/dst MAC address.
123         :type tg_node: dict
124         :type tg_if1: str
125         :type tg_if2: str
126         :type dut1_node: dict
127         :type dut1_if1: str
128         :type dut1_if2: str
129         :type dut2_node: dict
130         :type dut2_if1: str
131         :type dut2_if2: str
132         :type test_type: str
133         :return: nothing
134         """
135
136         topo = Topology()
137
138         if tg_node['type'] != NodeType.TG:
139             raise Exception('Node type is not a TG')
140         self._node = tg_node
141
142         if tg_node['subtype'] == NodeSubTypeTG.TREX:
143             trex_path = "/opt/trex-core-2.03"
144
145             ssh = SSH()
146             ssh.connect(tg_node)
147
148             (ret, stdout, stderr) = ssh.exec_command(
149                 "sudo sh -c '{}/resources/tools/t-rex/"
150                 "t-rex-installer.sh'".format(Constants.REMOTE_FW_DIR),
151                 timeout=1800)
152             if int(ret) != 0:
153                 logger.error('trex installation failed: {0}'.format(
154                     stdout + stderr))
155                 raise RuntimeError('Installation of TG failed')
156
157             if1_pci = topo.get_interface_pci_addr(tg_node, tg_if1)
158             if2_pci = topo.get_interface_pci_addr(tg_node, tg_if2)
159             if1_mac = topo.get_interface_mac(tg_node, tg_if1)
160             if2_mac = topo.get_interface_mac(tg_node, tg_if2)
161
162             if test_type == 'L2':
163                 if1_adj_mac = if2_mac
164                 if2_adj_mac = if1_mac
165             elif test_type == 'L3':
166                 if1_adj_mac = topo.get_interface_mac(dut1_node, dut1_if1)
167                 if2_adj_mac = topo.get_interface_mac(dut2_node, dut2_if2)
168             else:
169                 raise Exception("test_type unknown")
170
171             if min(if1_pci, if2_pci) != if1_pci:
172                 if1_mac, if2_mac = if2_mac, if1_mac
173                 if1_pci, if2_pci = if2_pci, if1_pci
174                 if1_adj_mac, if2_adj_mac = if2_adj_mac, if1_adj_mac
175                 self._ifaces_reordered = 1
176
177             if1_mac_hex = "0x"+if1_mac.replace(":", ",0x")
178             if2_mac_hex = "0x"+if2_mac.replace(":", ",0x")
179             if1_adj_mac_hex = "0x"+if1_adj_mac.replace(":", ",0x")
180             if2_adj_mac_hex = "0x"+if2_adj_mac.replace(":", ",0x")
181
182             (ret, stdout, stderr) = ssh.exec_command(
183                 "sudo sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
184                 "- port_limit      : 2\n"
185                 "  version         : 2\n"
186                 "  interfaces      : [\"{}\",\"{}\"]\n"
187                 "  port_bandwidth_gb : 10\n"
188                 "  port_info       :\n"
189                 "          - dest_mac        :   [{}]\n"
190                 "            src_mac         :   [{}]\n"
191                 "          - dest_mac        :   [{}]\n"
192                 "            src_mac         :   [{}]\n"
193                 "EOF'"\
194                 .format(if1_pci, if2_pci,
195                         if1_adj_mac_hex, if1_mac_hex,
196                         if2_adj_mac_hex, if2_mac_hex))
197             if int(ret) != 0:
198                 logger.error("failed to create t-rex config: {}"\
199                 .format(stdout + stderr))
200                 raise RuntimeError('trex config generation error')
201
202             (ret, stdout, stderr) = ssh.exec_command(
203                 "sh -c 'cd {0}/scripts/ && sudo ./trex-cfg'".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 '{}/resources/tools/t-rex/"
251             "t-rex-stateless-stop.py'".format(Constants.REMOTE_FW_DIR))
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 '{0}/resources/tools/t-rex/t-rex-stateless.py "
292                 "--duration={1} -r {2} -s {3} "
293                 "--p{4}_src_start_ip 10.10.10.1 "
294                 "--p{4}_src_end_ip 10.10.10.254 "
295                 "--p{4}_dst_start_ip 20.20.20.1 "
296                 "--p{5}_src_start_ip 20.20.20.1 "
297                 "--p{5}_src_end_ip 20.20.20.254 "
298                 "--p{5}_dst_start_ip 10.10.10.1 "
299                 "{6} --warmup_time={7}'".format(Constants.REMOTE_FW_DIR,
300                                                 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 '{0}/resources/tools/t-rex/t-rex-stateless.py "
306                 "--duration={1} -r {2} -s {3} "
307                 "--p{4}_src_start_ip 10.10.10.2 "
308                 "--p{4}_src_end_ip 10.10.10.254 "
309                 "--p{4}_dst_start_ip 20.20.20.2 "
310                 "--p{5}_src_start_ip 20.20.20.2 "
311                 "--p{5}_src_end_ip 20.20.20.254 "
312                 "--p{5}_dst_start_ip 10.10.10.2 "
313                 "{6} --warmup_time={7}'".format(Constants.REMOTE_FW_DIR,
314                                                 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 '{0}/resources/tools/t-rex/t-rex-stateless.py "
320                 "--duration={1} -r {2} -s {3} -6 "
321                 "--p{4}_src_start_ip 2001:1::2 "
322                 "--p{4}_src_end_ip 2001:1::FE "
323                 "--p{4}_dst_start_ip 2001:2::2 "
324                 "--p{5}_src_start_ip 2001:2::2 "
325                 "--p{5}_src_end_ip 2001:2::FE "
326                 "--p{5}_dst_start_ip 2001:1::2 "
327                 "{6} --warmup_time={7}'".format(Constants.REMOTE_FW_DIR,
328                                                 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))