87437d8284d76f6ac8a5ebd034db39fc71b66352
[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         """Runs the traffic and evaluate the measured results.
38
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], ...).
44         :type rate: int
45         :type frame_size: str
46         :type loss_acceptance: float
47         :type loss_acceptance_type: LossAcceptanceType
48         :type traffic_type: str
49         :returns: Drop threshold exceeded? (True/False)
50         :rtype: bool
51         :raises: NotImplementedError if TG is not supported.
52         :raises: RuntimeError if TG is not specified.
53         """
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')
58
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,
65                                                    traffic_type)
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
70
71             logger.trace("comparing: {} < {} {}".format(loss,
72                                                         loss_acceptance,
73                                                         loss_acceptance_type))
74             if float(loss) > float(loss_acceptance):
75                 return False
76             else:
77                 return True
78         else:
79             raise NotImplementedError("TG subtype not supported")
80
81     def get_latency(self):
82         """Returns min/avg/max latency.
83
84         :returns: Latency stats.
85         :rtype: list
86         """
87         tg_instance = BuiltIn().get_library_instance(
88             'resources.libraries.python.TrafficGenerator')
89         return tg_instance.get_latency_int()
90
91 class TrafficGenerator(object):
92     """Traffic Generator."""
93
94     # use one instance of TrafficGenerator for all tests in test suite
95     ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
96
97     def __init__(self):
98         self._result = None
99         self._loss = None
100         self._sent = None
101         self._latency = None
102         self._received = None
103         self._node = None
104         # T-REX interface order mapping
105         self._ifaces_reordered = 0
106
107     def get_loss(self):
108         """Return number of lost packets.
109
110         :returns: Number of lost packets.
111         :rtype: str
112         """
113         return self._loss
114
115     def get_sent(self):
116         """Return number of sent packets.
117
118         :returns: Number of sent packets.
119         :rtype: str
120         """
121         return self._sent
122
123     def get_received(self):
124         """Return number of received packets.
125
126         :returns: Number of received packets.
127         :rtype: str
128         """
129         return self._received
130
131     def get_latency_int(self):
132         """Return rounded min/avg/max latency.
133
134         :returns: Latency stats.
135         :rtype: list
136         """
137         return self._latency
138
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,
142                                      test_type):
143         """TG initialization.
144
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.
153         :type tg_node: dict
154         :type tg_if1: str
155         :type tg_if2: str
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
160         :type test_type: str
161         :returns: nothing
162         :raises: RuntimeError in case of issue during initialization.
163         """
164
165         topo = Topology()
166
167         if tg_node['type'] != NodeType.TG:
168             raise RuntimeError('Node type is not a TG')
169         self._node = tg_node
170
171         if tg_node['subtype'] == NodeSubTypeTG.TREX:
172             trex_path = "/opt/trex-core-2.09"
173
174             ssh = SSH()
175             ssh.connect(tg_node)
176
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),
180                 timeout=1800)
181             if int(ret) != 0:
182                 logger.error('trex installation failed: {0}'.format(
183                     stdout + stderr))
184                 raise RuntimeError('Installation of TG failed')
185
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)
190
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,
196                                                      tg_if1_adj_if)
197                 if2_adj_mac = topo.get_interface_mac(tg_if2_adj_node,
198                                                      tg_if2_adj_if)
199             else:
200                 raise Exception("test_type unknown")
201
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
207
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")
212
213             (ret, stdout, stderr) = ssh.exec_command(
214                 "sudo sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
215                 "- port_limit      : 2\n"
216                 "  version         : 2\n"
217                 "  interfaces      : [\"{}\",\"{}\"]\n"
218                 "  port_info       :\n"
219                 "          - dest_mac        :   [{}]\n"
220                 "            src_mac         :   [{}]\n"
221                 "          - dest_mac        :   [{}]\n"
222                 "            src_mac         :   [{}]\n"
223                 "EOF'"\
224                 .format(if1_pci, if2_pci,
225                         if1_adj_mac_hex, if1_mac_hex,
226                         if2_adj_mac_hex, if2_mac_hex))
227             if int(ret) != 0:
228                 logger.error("failed to create t-rex config: {}"\
229                 .format(stdout + stderr))
230                 raise RuntimeError('trex config generation error')
231
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'")
237
238                 # configure T-rex
239                 (ret, stdout, stderr) = ssh.exec_command(
240                     "sh -c 'cd {0}/scripts/ && sudo ./trex-cfg'"\
241                     .format(trex_path))
242                 if int(ret) != 0:
243                     logger.error('trex-cfg failed: {0}'.format(stdout + stderr))
244                     raise RuntimeError('trex-cfg failed')
245
246                 # start T-rex
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 &'"
250                     "> /dev/null"\
251                     .format(trex_path))
252                 if int(ret) != 0:
253                     raise RuntimeError('t-rex-64 startup failed')
254
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),
259                     timeout=120)
260                 if int(ret) == 0:
261                     # If we get info T-rex is running
262                     return
263                 # try again
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')
268
269
270     @staticmethod
271     def teardown_traffic_generator(node):
272         """TG teardown.
273
274         :param node: Traffic generator node.
275         :type node: dict
276         :returns: nothing
277         :raises: RuntimeError if T-rex teardown failed.
278         :raises: RuntimeError if node type is not a TG.
279         """
280         if node['type'] != NodeType.TG:
281             raise RuntimeError('Node type is not a TG')
282         if node['subtype'] == NodeSubTypeTG.TREX:
283             ssh = SSH()
284             ssh.connect(node)
285             (ret, stdout, stderr) = ssh.exec_command(
286                 "sh -c 'sudo pkill t-rex'")
287             if int(ret) != 0:
288                 logger.error('pkill t-rex failed: {0}'.format(stdout + stderr))
289                 raise RuntimeError('pkill t-rex failed')
290
291     @staticmethod
292     def trex_stl_stop_remote_exec(node):
293         """Execute script on remote node over ssh to stop running traffic.
294
295         :param node: T-REX generator node.
296         :type node: dict
297         :returns: Nothing
298         :raises: RuntimeError if stop traffic script fails.
299         """
300         ssh = SSH()
301         ssh.connect(node)
302
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))
306         logger.trace(ret)
307         logger.trace(stdout)
308         logger.trace(stderr)
309
310         if int(ret) != 0:
311             raise RuntimeError('T-rex stateless runtime error')
312
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.
317
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.
325         :type duration: int
326         :type rate: str
327         :type framesize: int
328         :type traffic_type: str
329         :type async_call: bool
330         :type latency: bool
331         :type warmup_time: int
332         :returns: Nothing
333         :raises: NotImplementedError if traffic type is not supported.
334         :raises: RuntimeError in case of TG driver issue.
335         """
336         ssh = SSH()
337         ssh.connect(self._node)
338
339         _p0 = 1
340         _p1 = 2
341         _async = "--async" if async_call else ""
342         _latency = "--latency" if latency else ""
343
344         if self._ifaces_reordered != 0:
345             _p0, _p1 = _p1, _p0
346
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,
360                                                     warmup_time),
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,
375                                                     warmup_time),
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,
390                                                     warmup_time),
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,
405                                                     warmup_time),
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,
420                                                     warmup_time),
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,
435                                                     warmup_time),
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,
450                                                     warmup_time),
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,
465                                                     warmup_time),
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,
480                                                     warmup_time),
481                 timeout=int(duration)+60)
482         else:
483             raise NotImplementedError('Unsupported traffic type')
484
485         logger.trace(ret)
486         logger.trace(stdout)
487         logger.trace(stderr)
488
489         if int(ret) != 0:
490             raise RuntimeError('T-rex stateless runtime error')
491         elif async_call:
492             #no result
493             self._received = None
494             self._sent = None
495             self._loss = None
496             self._latency = None
497         else:
498             # last line from console output
499             line = stdout.splitlines()[-1]
500
501             self._result = line
502             logger.info('TrafficGen result: {0}'.format(self._result))
503
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]
507
508             self._latency = []
509             self._latency.append(self._result.split(', ')[4].split('=')[1])
510             self._latency.append(self._result.split(', ')[5].split('=')[1])
511
512     def stop_traffic_on_tg(self):
513         """Stop all traffic on TG.
514
515         :returns: Nothing
516         :raises: RuntimeError if TG is not set.
517         """
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)
522
523     def send_traffic_on_tg(self, duration, rate, framesize,
524                            traffic_type, warmup_time=5, async_call=False,
525                            latency=True):
526         """Send traffic from all configured interfaces on TG.
527
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.
535         :type duration: str
536         :type rate: str
537         :type framesize: str
538         :type traffic_type: str
539         :type warmup_time: int
540         :type async_call: bool
541         :type latency: bool
542         :returns: TG output.
543         :rtype: str
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.
547         """
548
549         node = self._node
550         if node is None:
551             raise RuntimeError("TG is not set")
552
553         if node['type'] != NodeType.TG:
554             raise RuntimeError('Node type is not a TG')
555
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)
562         else:
563             raise NotImplementedError("TG subtype not supported")
564
565         return self._result
566
567     def no_traffic_loss_occurred(self):
568         """Fail if loss occurred in traffic run.
569
570         :returns: nothing
571         :raises: Exception if loss occured.
572         """
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))
577
578     def partial_traffic_loss_accepted(self, loss_acceptance,
579                                       loss_acceptance_type):
580         """Fail if loss is higher then accepted in traffic run.
581
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
586         :returns: nothing
587         :raises: Exception if loss is above acceptance criteria.
588         """
589         if self._loss is None:
590             raise Exception('The traffic generation has not been issued')
591
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)
596         else:
597             raise Exception('Loss acceptance type not supported')
598
599         if loss > float(loss_acceptance):
600             raise Exception("Traffic loss {} above loss acceptance: {}".format(
601                 loss, loss_acceptance))