TB2 XL710 topology files
[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                                      tg_if1_adj_node, tg_if1_adj_if,
109                                      tg_if2_adj_node, tg_if2_adj_if,
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 tg_if1_adj_node: TG if1 adjecent node.
117         :param tg_if1_adj_if: TG if1 adjecent interface.
118         :param tg_if2_adj_node: TG if2 adjecent node.
119         :param tg_if2_adj_if: TG if2 adjecent interface.
120         :test_type: 'L2' or 'L3' - src/dst MAC address.
121         :type tg_node: dict
122         :type tg_if1: str
123         :type tg_if2: str
124         :type tg_if1_adj_node: dict
125         :type tg_if1_adj_if: str
126         :type tg_if2_adj_node: dict
127         :type tg_if2_adj_if: str
128         :type test_type: str
129         :return: nothing
130         """
131
132         topo = Topology()
133
134         if tg_node['type'] != NodeType.TG:
135             raise Exception('Node type is not a TG')
136         self._node = tg_node
137
138         if tg_node['subtype'] == NodeSubTypeTG.TREX:
139             trex_path = "/opt/trex-core-2.03"
140
141             ssh = SSH()
142             ssh.connect(tg_node)
143
144             (ret, stdout, stderr) = ssh.exec_command(
145                 "sudo sh -c '{}/resources/tools/t-rex/"
146                 "t-rex-installer.sh'".format(Constants.REMOTE_FW_DIR),
147                 timeout=1800)
148             if int(ret) != 0:
149                 logger.error('trex installation failed: {0}'.format(
150                     stdout + stderr))
151                 raise RuntimeError('Installation of TG failed')
152
153             if1_pci = topo.get_interface_pci_addr(tg_node, tg_if1)
154             if2_pci = topo.get_interface_pci_addr(tg_node, tg_if2)
155             if1_mac = topo.get_interface_mac(tg_node, tg_if1)
156             if2_mac = topo.get_interface_mac(tg_node, tg_if2)
157
158             if test_type == 'L2':
159                 if1_adj_mac = if2_mac
160                 if2_adj_mac = if1_mac
161             elif test_type == 'L3':
162                 if1_adj_mac = topo.get_interface_mac(tg_if1_adj_node,
163                                                      tg_if1_adj_if)
164                 if2_adj_mac = topo.get_interface_mac(tg_if2_adj_node,
165                                                      tg_if2_adj_if)
166             else:
167                 raise Exception("test_type unknown")
168
169             if min(if1_pci, if2_pci) != if1_pci:
170                 if1_mac, if2_mac = if2_mac, if1_mac
171                 if1_pci, if2_pci = if2_pci, if1_pci
172                 if1_adj_mac, if2_adj_mac = if2_adj_mac, if1_adj_mac
173                 self._ifaces_reordered = 1
174
175             if1_mac_hex = "0x"+if1_mac.replace(":", ",0x")
176             if2_mac_hex = "0x"+if2_mac.replace(":", ",0x")
177             if1_adj_mac_hex = "0x"+if1_adj_mac.replace(":", ",0x")
178             if2_adj_mac_hex = "0x"+if2_adj_mac.replace(":", ",0x")
179
180             (ret, stdout, stderr) = ssh.exec_command(
181                 "sudo sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
182                 "- port_limit      : 2\n"
183                 "  version         : 2\n"
184                 "  interfaces      : [\"{}\",\"{}\"]\n"
185                 "  port_info       :\n"
186                 "          - dest_mac        :   [{}]\n"
187                 "            src_mac         :   [{}]\n"
188                 "          - dest_mac        :   [{}]\n"
189                 "            src_mac         :   [{}]\n"
190                 "EOF'"\
191                 .format(if1_pci, if2_pci,
192                         if1_adj_mac_hex, if1_mac_hex,
193                         if2_adj_mac_hex, if2_mac_hex))
194             if int(ret) != 0:
195                 logger.error("failed to create t-rex config: {}"\
196                 .format(stdout + stderr))
197                 raise RuntimeError('trex config generation error')
198
199             (ret, stdout, stderr) = ssh.exec_command(
200                 "sh -c 'cd {0}/scripts/ && sudo ./trex-cfg'".format(trex_path))
201             if int(ret) != 0:
202                 logger.error('trex-cfg failed: {0}'.format(stdout + stderr))
203                 raise RuntimeError('trex-cfg failed')
204
205             max_startup_retries = 3
206             while max_startup_retries > 0:
207                 # kill T-rex only if it is already running
208                 (ret, _, _) = ssh.exec_command(
209                     "sh -c 'pgrep t-rex && sudo pkill t-rex'")
210
211                 # start T-rex
212                 (ret, _, _) = ssh.exec_command(
213                     "sh -c 'cd {0}/scripts/ && "
214                     "sudo nohup ./t-rex-64 -i -c 7 --iom 0 > /dev/null 2>&1 &'"
215                     "> /dev/null"\
216                     .format(trex_path))
217                 if int(ret) != 0:
218                     raise RuntimeError('t-rex-64 startup failed')
219
220                 # get T-rex server info
221                 (ret, _, _) = ssh.exec_command(
222                     "sh -c '{0}/resources/tools/t-rex/t-rex-server-info.py'"\
223                     .format(Constants.REMOTE_FW_DIR),
224                     timeout=120)
225                 if int(ret) == 0:
226                     # If we get info T-rex is running
227                     return
228                 # try again
229                 max_startup_retries -= 1
230             # after max retries T-rex is still not responding to API
231             # critical error occured
232             raise RuntimeError('t-rex-64 startup failed')
233
234
235     @staticmethod
236     def teardown_traffic_generator(node):
237         """TG teardown.
238
239         :param node: Traffic generator node.
240         :type node: dict
241         :return: nothing
242         """
243         if node['type'] != NodeType.TG:
244             raise Exception('Node type is not a TG')
245         if node['subtype'] == NodeSubTypeTG.TREX:
246             ssh = SSH()
247             ssh.connect(node)
248             (ret, stdout, stderr) = ssh.exec_command(
249                 "sh -c 'sudo pkill t-rex'")
250             if int(ret) != 0:
251                 logger.error('pkill t-rex failed: {0}'.format(stdout + stderr))
252                 raise RuntimeError('pkill t-rex failed')
253
254     @staticmethod
255     def trex_stl_stop_remote_exec(node):
256         """Execute script on remote node over ssh to stop running traffic.
257
258         :param node: T-REX generator node.
259         :type node: dict
260         :return: Nothing
261         """
262         ssh = SSH()
263         ssh.connect(node)
264
265         (ret, stdout, stderr) = ssh.exec_command(
266             "sh -c '{}/resources/tools/t-rex/"
267             "t-rex-stateless-stop.py'".format(Constants.REMOTE_FW_DIR))
268         logger.trace(ret)
269         logger.trace(stdout)
270         logger.trace(stderr)
271
272         if int(ret) != 0:
273             raise RuntimeError('T-rex stateless runtime error')
274
275     def trex_stl_start_remote_exec(self, duration, rate, framesize,
276                                    traffic_type, async_call, warmup_time=5):
277         """Execute script on remote node over ssh to start traffic.
278
279         :param duration: Time expresed in seconds for how long to send traffic.
280         :param rate: Traffic rate expressed with units (pps, %)
281         :param framesize: L2 frame size to send (without padding and IPG).
282         :param traffic_type: Traffic profile.
283         :param async_call: If enabled then don't wait for all incomming trafic.
284         :param warmup_time: Warmup time period.
285         :type duration: int
286         :type rate: str
287         :type framesize: int
288         :type traffic_type: str
289         :type async_call: bool
290         :type warmup_time: int
291         :return: Nothing
292         """
293         ssh = SSH()
294         ssh.connect(self._node)
295
296         _p0 = 1
297         _p1 = 2
298         _async = ""
299
300         if async_call:
301             _async = "--async"
302         if self._ifaces_reordered != 0:
303             _p0, _p1 = _p1, _p0
304
305         if traffic_type in ["3-node-xconnect", "3-node-bridge"]:
306             (ret, stdout, stderr) = ssh.exec_command(
307                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
308                 "--duration={1} -r {2} -s {3} "
309                 "--p{4}_src_start_ip 10.10.10.1 "
310                 "--p{4}_src_end_ip 10.10.10.254 "
311                 "--p{4}_dst_start_ip 20.20.20.1 "
312                 "--p{5}_src_start_ip 20.20.20.1 "
313                 "--p{5}_src_end_ip 20.20.20.254 "
314                 "--p{5}_dst_start_ip 10.10.10.1 "
315                 "{6} --warmup_time={7}'".format(Constants.REMOTE_FW_DIR,
316                                                 duration, rate, framesize, _p0,
317                                                 _p1, _async, warmup_time),
318                 timeout=int(duration)+60)
319         elif traffic_type in ["3-node-IPv4"]:
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.2 "
324                 "--p{4}_src_end_ip 10.10.10.254 "
325                 "--p{4}_dst_start_ip 20.20.20.2 "
326                 "--p{5}_src_start_ip 20.20.20.2 "
327                 "--p{5}_src_end_ip 20.20.20.254 "
328                 "--p{5}_dst_start_ip 10.10.10.2 "
329                 "{6} --warmup_time={7}'".format(Constants.REMOTE_FW_DIR,
330                                                 duration, rate, framesize, _p0,
331                                                 _p1, _async, warmup_time),
332                 timeout=int(duration)+60)
333         elif traffic_type in ["3-node-IPv6"]:
334             (ret, stdout, stderr) = ssh.exec_command(
335                 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
336                 "--duration={1} -r {2} -s {3} -6 "
337                 "--p{4}_src_start_ip 2001:1::2 "
338                 "--p{4}_src_end_ip 2001:1::FE "
339                 "--p{4}_dst_start_ip 2001:2::2 "
340                 "--p{5}_src_start_ip 2001:2::2 "
341                 "--p{5}_src_end_ip 2001:2::FE "
342                 "--p{5}_dst_start_ip 2001:1::2 "
343                 "{6} --warmup_time={7}'".format(Constants.REMOTE_FW_DIR,
344                                                 duration, rate, framesize, _p0,
345                                                 _p1, _async, warmup_time),
346                 timeout=int(duration)+60)
347         else:
348             raise NotImplementedError('Unsupported traffic type')
349
350         logger.trace(ret)
351         logger.trace(stdout)
352         logger.trace(stderr)
353
354         if int(ret) != 0:
355             raise RuntimeError('T-rex stateless runtime error')
356         elif async_call:
357             #no result
358             self._received = None
359             self._sent = None
360             self._loss = None
361         else:
362             # last line from console output
363             line = stdout.splitlines()[-1]
364
365             self._result = line
366             logger.info('TrafficGen result: {0}'.format(self._result))
367
368             self._received = self._result.split(', ')[1].split('=')[1]
369             self._sent = self._result.split(', ')[2].split('=')[1]
370             self._loss = self._result.split(', ')[3].split('=')[1]
371
372     def stop_traffic_on_tg(self):
373         """Stop all traffic on TG
374
375         :return: Nothing
376         """
377         if self._node is None:
378             raise RuntimeError("TG is not set")
379         if self._node['subtype'] == NodeSubTypeTG.TREX:
380             self.trex_stl_stop_remote_exec(self._node)
381
382     def send_traffic_on_tg(self, duration, rate, framesize,
383                            traffic_type, warmup_time=5, async_call=False):
384         """Send traffic from all configured interfaces on TG.
385
386         :param duration: Duration of test traffic generation in seconds.
387         :param rate: Offered load per interface (e.g. 1%, 3gbps, 4mpps, ...).
388         :param framesize: Frame size (L2) in Bytes.
389         :param traffic_type: Traffic profile.
390         :type duration: str
391         :type rate: str
392         :type framesize: str
393         :type traffic_type: str
394         :return: TG output.
395         :rtype: str
396         """
397
398         node = self._node
399         if node is None:
400             raise RuntimeError("TG is not set")
401
402         if node['type'] != NodeType.TG:
403             raise Exception('Node type is not a TG')
404
405         if node['subtype'] is None:
406             raise Exception('TG subtype not defined')
407         elif node['subtype'] == NodeSubTypeTG.TREX:
408             self.trex_stl_start_remote_exec(duration, rate, framesize,
409                                             traffic_type, async_call,
410                                             warmup_time=warmup_time)
411         else:
412             raise NotImplementedError("TG subtype not supported")
413
414         return self._result
415
416     def no_traffic_loss_occurred(self):
417         """Fail if loss occurred in traffic run.
418
419         :return: nothing
420         """
421         if self._loss is None:
422             raise Exception('The traffic generation has not been issued')
423         if self._loss != '0':
424             raise Exception('Traffic loss occurred: {0}'.format(self._loss))
425
426     def partial_traffic_loss_accepted(self, loss_acceptance,
427                                       loss_acceptance_type):
428         """Fail if loss is higher then accepted in traffic run.
429
430         :return: nothing
431         """
432         if self._loss is None:
433             raise Exception('The traffic generation has not been issued')
434
435         if loss_acceptance_type == 'percentage':
436             loss = (float(self._loss) / float(self._sent)) * 100
437         elif loss_acceptance_type == 'frames':
438             loss = float(self._loss)
439         else:
440             raise Exception('Loss acceptance type not supported')
441
442         if loss > float(loss_acceptance):
443             raise Exception("Traffic loss {} above loss acceptance: {}".format(
444                 loss, loss_acceptance))