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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """Performance testing traffic generator library."""
16 from robot.api import logger
17 from robot.libraries.BuiltIn import BuiltIn
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
26 __all__ = ['TrafficGenerator', 'TGDropRateSearchImpl']
29 class TGDropRateSearchImpl(DropRateSearch):
30 """Drop Rate Search implementation."""
33 super(TGDropRateSearchImpl, self).__init__()
35 def measure_loss(self, rate, frame_size, loss_acceptance,
36 loss_acceptance_type, traffic_type):
37 """Runs the traffic and evaluate the measured results.
39 :param rate: Offered traffic load.
40 :param frame_size: Size of frame.
41 :param loss_acceptance: Permitted drop ratio or frames count.
42 :param loss_acceptance_type: Type of permitted loss.
43 :param traffic_type: Traffic profile ([2,3]-node-L[2,3], ...).
46 :type loss_acceptance: float
47 :type loss_acceptance_type: LossAcceptanceType
48 :type traffic_type: str
49 :returns: Drop threshold exceeded? (True/False)
51 :raises: NotImplementedError if TG is not supported.
52 :raises: RuntimeError if TG is not specified.
54 # we need instance of TrafficGenerator instantiated by Robot Framework
55 # to be able to use trex_stl-*()
56 tg_instance = BuiltIn().get_library_instance(
57 'resources.libraries.python.TrafficGenerator')
59 if tg_instance._node['subtype'] is None:
60 raise RuntimeError('TG subtype not defined')
61 elif tg_instance._node['subtype'] == NodeSubTypeTG.TREX:
62 unit_rate = str(rate) + self.get_rate_type_str()
63 tg_instance.trex_stl_start_remote_exec(self.get_duration(),
64 unit_rate, frame_size,
66 loss = tg_instance.get_loss()
67 sent = tg_instance.get_sent()
68 if self.loss_acceptance_type_is_percentage():
69 loss = (float(loss) / float(sent)) * 100
71 logger.trace("comparing: {} < {} {}".format(loss,
73 loss_acceptance_type))
74 if float(loss) > float(loss_acceptance):
79 raise NotImplementedError("TG subtype not supported")
81 def get_latency(self):
82 """Returns min/avg/max latency.
84 :returns: Latency stats.
87 tg_instance = BuiltIn().get_library_instance(
88 'resources.libraries.python.TrafficGenerator')
89 return tg_instance.get_latency_int()
91 class TrafficGenerator(object):
92 """Traffic Generator."""
94 # use one instance of TrafficGenerator for all tests in test suite
95 ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
102 self._received = None
104 # T-REX interface order mapping
105 self._ifaces_reordered = 0
108 """Return number of lost packets.
110 :returns: Number of lost packets.
116 """Return number of sent packets.
118 :returns: Number of sent packets.
123 def get_received(self):
124 """Return number of received packets.
126 :returns: Number of received packets.
129 return self._received
131 def get_latency_int(self):
132 """Return rounded min/avg/max latency.
134 :returns: Latency stats.
139 def initialize_traffic_generator(self, tg_node, tg_if1, tg_if2,
140 tg_if1_adj_node, tg_if1_adj_if,
141 tg_if2_adj_node, tg_if2_adj_if,
143 """TG initialization.
145 :param tg_node: Traffic generator node.
146 :param tg_if1: TG - name of first interface.
147 :param tg_if2: TG - name of second interface.
148 :param tg_if1_adj_node: TG if1 adjecent node.
149 :param tg_if1_adj_if: TG if1 adjecent interface.
150 :param tg_if2_adj_node: TG if2 adjecent node.
151 :param tg_if2_adj_if: TG if2 adjecent interface.
152 :param test_type: 'L2' or 'L3' - src/dst MAC address.
156 :type tg_if1_adj_node: dict
157 :type tg_if1_adj_if: str
158 :type tg_if2_adj_node: dict
159 :type tg_if2_adj_if: str
162 :raises: RuntimeError in case of issue during initialization.
167 if tg_node['type'] != NodeType.TG:
168 raise RuntimeError('Node type is not a TG')
171 if tg_node['subtype'] == NodeSubTypeTG.TREX:
172 trex_path = "/opt/trex-core-2.09"
177 (ret, stdout, stderr) = ssh.exec_command(
178 "sudo -E sh -c '{}/resources/tools/t-rex/"
179 "t-rex-installer.sh'".format(Constants.REMOTE_FW_DIR),
182 logger.error('trex installation failed: {0}'.format(
184 raise RuntimeError('Installation of TG failed')
186 if1_pci = topo.get_interface_pci_addr(tg_node, tg_if1)
187 if2_pci = topo.get_interface_pci_addr(tg_node, tg_if2)
188 if1_mac = topo.get_interface_mac(tg_node, tg_if1)
189 if2_mac = topo.get_interface_mac(tg_node, tg_if2)
191 if test_type == 'L2':
192 if1_adj_mac = if2_mac
193 if2_adj_mac = if1_mac
194 elif test_type == 'L3':
195 if1_adj_mac = topo.get_interface_mac(tg_if1_adj_node,
197 if2_adj_mac = topo.get_interface_mac(tg_if2_adj_node,
200 raise Exception("test_type unknown")
202 if min(if1_pci, if2_pci) != if1_pci:
203 if1_mac, if2_mac = if2_mac, if1_mac
204 if1_pci, if2_pci = if2_pci, if1_pci
205 if1_adj_mac, if2_adj_mac = if2_adj_mac, if1_adj_mac
206 self._ifaces_reordered = 1
208 if1_mac_hex = "0x"+if1_mac.replace(":", ",0x")
209 if2_mac_hex = "0x"+if2_mac.replace(":", ",0x")
210 if1_adj_mac_hex = "0x"+if1_adj_mac.replace(":", ",0x")
211 if2_adj_mac_hex = "0x"+if2_adj_mac.replace(":", ",0x")
213 (ret, stdout, stderr) = ssh.exec_command(
214 "sudo sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
217 " interfaces : [\"{}\",\"{}\"]\n"
219 " - dest_mac : [{}]\n"
221 " - dest_mac : [{}]\n"
224 .format(if1_pci, if2_pci,
225 if1_adj_mac_hex, if1_mac_hex,
226 if2_adj_mac_hex, if2_mac_hex))
228 logger.error("failed to create t-rex config: {}"\
229 .format(stdout + stderr))
230 raise RuntimeError('trex config generation error')
232 max_startup_retries = 3
233 while max_startup_retries > 0:
234 # kill T-rex only if it is already running
235 (ret, _, _) = ssh.exec_command(
236 "sh -c 'pgrep t-rex && sudo pkill t-rex'")
239 (ret, stdout, stderr) = ssh.exec_command(
240 "sh -c 'cd {0}/scripts/ && sudo ./trex-cfg'"\
243 logger.error('trex-cfg failed: {0}'.format(stdout + stderr))
244 raise RuntimeError('trex-cfg failed')
247 (ret, _, _) = ssh.exec_command(
248 "sh -c 'cd {0}/scripts/ && "
249 "sudo nohup ./t-rex-64 -i -c 7 --iom 0 > /dev/null 2>&1 &'"
253 raise RuntimeError('t-rex-64 startup failed')
255 # get T-rex server info
256 (ret, _, _) = ssh.exec_command(
257 "sh -c '{0}/resources/tools/t-rex/t-rex-server-info.py'"\
258 .format(Constants.REMOTE_FW_DIR),
261 # If we get info T-rex is running
264 max_startup_retries -= 1
265 # after max retries T-rex is still not responding to API
266 # critical error occured
267 raise RuntimeError('t-rex-64 startup failed')
271 def teardown_traffic_generator(node):
274 :param node: Traffic generator node.
277 :raises: RuntimeError if T-rex teardown failed.
278 :raises: RuntimeError if node type is not a TG.
280 if node['type'] != NodeType.TG:
281 raise RuntimeError('Node type is not a TG')
282 if node['subtype'] == NodeSubTypeTG.TREX:
285 (ret, stdout, stderr) = ssh.exec_command(
286 "sh -c 'sudo pkill t-rex'")
288 logger.error('pkill t-rex failed: {0}'.format(stdout + stderr))
289 raise RuntimeError('pkill t-rex failed')
292 def trex_stl_stop_remote_exec(node):
293 """Execute script on remote node over ssh to stop running traffic.
295 :param node: T-REX generator node.
298 :raises: RuntimeError if stop traffic script fails.
303 (ret, stdout, stderr) = ssh.exec_command(
304 "sh -c '{}/resources/tools/t-rex/"
305 "t-rex-stateless-stop.py'".format(Constants.REMOTE_FW_DIR))
311 raise RuntimeError('T-rex stateless runtime error')
313 def trex_stl_start_remote_exec(self, duration, rate, framesize,
314 traffic_type, async_call=False,
315 latency=True, warmup_time=5):
316 """Execute script on remote node over ssh to start traffic.
318 :param duration: Time expresed in seconds for how long to send traffic.
319 :param rate: Traffic rate expressed with units (pps, %)
320 :param framesize: L2 frame size to send (without padding and IPG).
321 :param traffic_type: Traffic profile.
322 :param async_call: If enabled then don't wait for all incomming trafic.
323 :param latency: With latency measurement.
324 :param warmup_time: Warmup time period.
328 :type traffic_type: str
329 :type async_call: bool
331 :type warmup_time: int
333 :raises: NotImplementedError if traffic type is not supported.
334 :raises: RuntimeError in case of TG driver issue.
337 ssh.connect(self._node)
341 _async = "--async" if async_call else ""
342 _latency = "--latency" if latency else ""
344 if self._ifaces_reordered != 0:
347 if traffic_type in ["3-node-xconnect", "3-node-bridge"]:
348 (ret, stdout, stderr) = ssh.exec_command(
349 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
350 "--duration={1} -r {2} -s {3} "
351 "--p{4}_src_start_ip 10.10.10.1 "
352 "--p{4}_src_end_ip 10.10.10.254 "
353 "--p{4}_dst_start_ip 20.20.20.1 "
354 "--p{5}_src_start_ip 20.20.20.1 "
355 "--p{5}_src_end_ip 20.20.20.254 "
356 "--p{5}_dst_start_ip 10.10.10.1 "
357 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
358 duration, rate, framesize,
359 _p0, _p1, _async, _latency,
361 timeout=int(duration)+60)
362 elif traffic_type in ["3-node-IPv4"]:
363 (ret, stdout, stderr) = ssh.exec_command(
364 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
365 "--duration={1} -r {2} -s {3} "
366 "--p{4}_src_start_ip 10.10.10.2 "
367 "--p{4}_src_end_ip 10.10.10.254 "
368 "--p{4}_dst_start_ip 20.20.20.2 "
369 "--p{5}_src_start_ip 20.20.20.2 "
370 "--p{5}_src_end_ip 20.20.20.254 "
371 "--p{5}_dst_start_ip 10.10.10.2 "
372 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
373 duration, rate, framesize,
374 _p0, _p1, _async, _latency,
376 timeout=int(duration)+60)
377 elif traffic_type in ["3-node-IPv4-dst-10000"]:
378 (ret, stdout, stderr) = ssh.exec_command(
379 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
380 "--duration={1} -r {2} -s {3} "
381 "--p{4}_src_start_ip 10.0.0.1 "
382 "--p{4}_dst_start_ip 20.0.0.0 "
383 "--p{4}_dst_end_ip 20.0.39.15 "
384 "--p{5}_src_start_ip 20.0.0.1 "
385 "--p{5}_dst_start_ip 10.0.0.0 "
386 "--p{5}_dst_end_ip 10.0.39.15 "
387 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
388 duration, rate, framesize,
389 _p0, _p1, _async, _latency,
391 timeout=int(duration)+60)
392 elif traffic_type in ["3-node-IPv4-dst-100000"]:
393 (ret, stdout, stderr) = ssh.exec_command(
394 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
395 "--duration={1} -r {2} -s {3} "
396 "--p{4}_src_start_ip 10.0.0.1 "
397 "--p{4}_dst_start_ip 20.0.0.0 "
398 "--p{4}_dst_end_ip 20.1.134.159 "
399 "--p{5}_src_start_ip 20.0.0.1 "
400 "--p{5}_dst_start_ip 10.0.0.0 "
401 "--p{5}_dst_end_ip 10.1.134.159 "
402 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
403 duration, rate, framesize,
404 _p0, _p1, _async, _latency,
406 timeout=int(duration)+60)
407 elif traffic_type in ["3-node-IPv4-dst-1000000"]:
408 (ret, stdout, stderr) = ssh.exec_command(
409 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
410 "--duration={1} -r {2} -s {3} "
411 "--p{4}_src_start_ip 10.0.0.1 "
412 "--p{4}_dst_start_ip 20.0.0.0 "
413 "--p{4}_dst_end_ip 20.15.66.63 "
414 "--p{5}_src_start_ip 20.0.0.1 "
415 "--p{5}_dst_start_ip 10.0.0.0 "
416 "--p{5}_dst_end_ip 10.15.66.63 "
417 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
418 duration, rate, framesize,
419 _p0, _p1, _async, _latency,
421 timeout=int(duration)+60)
422 elif traffic_type in ["3-node-IPv6"]:
423 (ret, stdout, stderr) = ssh.exec_command(
424 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
425 "--duration={1} -r {2} -s {3} -6 "
426 "--p{4}_src_start_ip 2001:1::2 "
427 "--p{4}_src_end_ip 2001:1::FE "
428 "--p{4}_dst_start_ip 2001:2::2 "
429 "--p{5}_src_start_ip 2001:2::2 "
430 "--p{5}_src_end_ip 2001:2::FE "
431 "--p{5}_dst_start_ip 2001:1::2 "
432 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
433 duration, rate, framesize,
434 _p0, _p1, _async, _latency,
436 timeout=int(duration)+60)
437 elif traffic_type in ["3-node-IPv6-dst-10000"]:
438 (ret, stdout, stderr) = ssh.exec_command(
439 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
440 "--duration={1} -r {2} -s {3} -6 "
441 "--p{4}_src_start_ip 2001:1::1 "
442 "--p{4}_dst_start_ip 2001:2::0 "
443 "--p{4}_dst_end_ip 2001:2::270F "
444 "--p{5}_src_start_ip 2001:2::1 "
445 "--p{5}_dst_start_ip 2001:1::0 "
446 "--p{5}_dst_end_ip 2001:1::270F "
447 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
448 duration, rate, framesize,
449 _p0, _p1, _async, _latency,
451 timeout=int(duration)+60)
452 elif traffic_type in ["3-node-IPv6-dst-100000"]:
453 (ret, stdout, stderr) = ssh.exec_command(
454 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
455 "--duration={1} -r {2} -s {3} -6 "
456 "--p{4}_src_start_ip 2001:1::1 "
457 "--p{4}_dst_start_ip 2001:2::0 "
458 "--p{4}_dst_end_ip 2001:2::1:869F "
459 "--p{5}_src_start_ip 2001:2::1 "
460 "--p{5}_dst_start_ip 2001:1::0 "
461 "--p{5}_dst_end_ip 2001:1::1:869F "
462 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
463 duration, rate, framesize,
464 _p0, _p1, _async, _latency,
466 timeout=int(duration)+60)
467 elif traffic_type in ["3-node-IPv6-dst-1000000"]:
468 (ret, stdout, stderr) = ssh.exec_command(
469 "sh -c '{0}/resources/tools/t-rex/t-rex-stateless.py "
470 "--duration={1} -r {2} -s {3} -6 "
471 "--p{4}_src_start_ip 2001:1::1 "
472 "--p{4}_dst_start_ip 2001:2::0 "
473 "--p{4}_dst_end_ip 2001:2::F:423F "
474 "--p{5}_src_start_ip 2001:2::1 "
475 "--p{5}_dst_start_ip 2001:1::0 "
476 "--p{5}_dst_end_ip 2001:1::F:423F "
477 "{6} {7} --warmup_time={8}'".format(Constants.REMOTE_FW_DIR,
478 duration, rate, framesize,
479 _p0, _p1, _async, _latency,
481 timeout=int(duration)+60)
483 raise NotImplementedError('Unsupported traffic type')
490 raise RuntimeError('T-rex stateless runtime error')
493 self._received = None
498 # last line from console output
499 line = stdout.splitlines()[-1]
502 logger.info('TrafficGen result: {0}'.format(self._result))
504 self._received = self._result.split(', ')[1].split('=')[1]
505 self._sent = self._result.split(', ')[2].split('=')[1]
506 self._loss = self._result.split(', ')[3].split('=')[1]
509 self._latency.append(self._result.split(', ')[4].split('=')[1])
510 self._latency.append(self._result.split(', ')[5].split('=')[1])
512 def stop_traffic_on_tg(self):
513 """Stop all traffic on TG.
516 :raises: RuntimeError if TG is not set.
518 if self._node is None:
519 raise RuntimeError("TG is not set")
520 if self._node['subtype'] == NodeSubTypeTG.TREX:
521 self.trex_stl_stop_remote_exec(self._node)
523 def send_traffic_on_tg(self, duration, rate, framesize,
524 traffic_type, warmup_time=5, async_call=False,
526 """Send traffic from all configured interfaces on TG.
528 :param duration: Duration of test traffic generation in seconds.
529 :param rate: Offered load per interface (e.g. 1%, 3gbps, 4mpps, ...).
530 :param framesize: Frame size (L2) in Bytes.
531 :param traffic_type: Traffic profile.
532 :param warmup_time: Warmup phase in seconds.
533 :param async_call: Async mode.
534 :param latency: With latency measurement.
538 :type traffic_type: str
539 :type warmup_time: int
540 :type async_call: bool
544 :raises: RuntimeError if TG is not set.
545 :raises: RuntimeError if node is not TG or subtype is not specified.
546 :raises: NotImplementedError if TG is not supported.
551 raise RuntimeError("TG is not set")
553 if node['type'] != NodeType.TG:
554 raise RuntimeError('Node type is not a TG')
556 if node['subtype'] is None:
557 raise RuntimeError('TG subtype not defined')
558 elif node['subtype'] == NodeSubTypeTG.TREX:
559 self.trex_stl_start_remote_exec(duration, rate, framesize,
560 traffic_type, async_call, latency,
561 warmup_time=warmup_time)
563 raise NotImplementedError("TG subtype not supported")
567 def no_traffic_loss_occurred(self):
568 """Fail if loss occurred in traffic run.
571 :raises: Exception if loss occured.
573 if self._loss is None:
574 raise Exception('The traffic generation has not been issued')
575 if self._loss != '0':
576 raise Exception('Traffic loss occurred: {0}'.format(self._loss))
578 def partial_traffic_loss_accepted(self, loss_acceptance,
579 loss_acceptance_type):
580 """Fail if loss is higher then accepted in traffic run.
582 :param loss_acceptance: Permitted drop ratio or frames count.
583 :param loss_acceptance_type: Type of permitted loss.
584 :type loss_acceptance: float
585 :type loss_acceptance_type: LossAcceptanceType
587 :raises: Exception if loss is above acceptance criteria.
589 if self._loss is None:
590 raise Exception('The traffic generation has not been issued')
592 if loss_acceptance_type == 'percentage':
593 loss = (float(self._loss) / float(self._sent)) * 100
594 elif loss_acceptance_type == 'frames':
595 loss = float(self._loss)
597 raise Exception('Loss acceptance type not supported')
599 if loss > float(loss_acceptance):
600 raise Exception("Traffic loss {} above loss acceptance: {}".format(
601 loss, loss_acceptance))