Reformat python libraries.
[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.ssh import SSH
20 from resources.libraries.python.topology import NodeType
21 from resources.libraries.python.topology import NodeSubTypeTG
22 from resources.libraries.python.topology import Topology
23 from resources.libraries.python.DropRateSearch import DropRateSearch
24
25 __all__ = ['TrafficGenerator', 'TGDropRateSearchImpl']
26
27
28 class TGDropRateSearchImpl(DropRateSearch):
29     """Drop Rate Search implementation."""
30
31     def __init__(self):
32         super(TGDropRateSearchImpl, self).__init__()
33
34     def measure_loss(self, rate, frame_size, loss_acceptance,
35                      loss_acceptance_type, traffic_type):
36
37         # we need instance of TrafficGenerator instantiated by Robot Framework
38         # to be able to use trex_stateless_remote_exec method
39         tg_instance = BuiltIn().get_library_instance(
40             'resources.libraries.python.TrafficGenerator')
41
42         if tg_instance._node['subtype'] is None:
43             raise Exception('TG subtype not defined')
44         elif tg_instance._node['subtype'] == NodeSubTypeTG.TREX:
45             unit_rate = str(rate) + self.get_rate_type_str()
46             tg_instance.trex_stateless_remote_exec(self.get_duration(),
47                                                    unit_rate, frame_size,
48                                                    traffic_type)
49
50             # TODO: getters for tg_instance and loss_acceptance_type
51             logger.trace("comparing: {} < {} ".format(tg_instance._loss,
52                                                       loss_acceptance))
53             if float(tg_instance._loss) > float(loss_acceptance):
54                 return False
55             else:
56                 return True
57         else:
58             raise NotImplementedError("TG subtype not supported")
59
60
61 class TrafficGenerator(object):
62     """Traffic Generator."""
63
64     # use one instance of TrafficGenerator for all tests in test suite
65     ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
66
67     def __init__(self):
68         self._result = None
69         self._loss = None
70         self._sent = None
71         self._received = None
72         self._node = None
73         # T-REX interface order mapping
74         self._ifaces_reordered = 0
75
76     def initialize_traffic_generator(self, tg_node, tg_if1, tg_if2,
77                                      dut1_node, dut1_if1, dut1_if2,
78                                      dut2_node, dut2_if1, dut2_if2,
79                                      test_type):
80         """TG initialization.
81
82         :param tg_node: Traffic generator node.
83         :param tg_if1: TG - name of first interface.
84         :param tg_if2: TG - name of second interface.
85         :param dut1_node: DUT1 node.
86         :param dut1_if1: DUT1 - name of first interface.
87         :param dut1_if2: DUT1 - name of second interface.
88         :param dut2_node: DUT2 node.
89         :param dut2_if1: DUT2 - name of first interface.
90         :param dut2_if2: DUT2 - name of second interface.
91         :test_type: 'L2' or 'L3' - src/dst MAC address.
92         :type tg_node: dict
93         :type tg_if1: str
94         :type tg_if2: str
95         :type dut1_node: dict
96         :type dut1_if1: str
97         :type dut1_if2: str
98         :type dut2_node: dict
99         :type dut2_if1: str
100         :type dut2_if2: str
101         :type test_type: str
102         :return: nothing
103         """
104         trex_path = "/opt/trex-core-1.91"
105
106         topo = Topology()
107
108         if tg_node['type'] != NodeType.TG:
109             raise Exception('Node type is not a TG')
110         self._node = tg_node
111
112         if tg_node['subtype'] == NodeSubTypeTG.TREX:
113             ssh = SSH()
114             ssh.connect(tg_node)
115
116             if1_pci = topo.get_interface_pci_addr(tg_node, tg_if1)
117             if2_pci = topo.get_interface_pci_addr(tg_node, tg_if2)
118             if1_mac = topo.get_interface_mac(tg_node, tg_if1)
119             if2_mac = topo.get_interface_mac(tg_node, tg_if2)
120
121             if test_type == 'L2':
122                 if1_adj_mac = if2_mac
123                 if2_adj_mac = if1_mac
124             elif test_type == 'L3':
125                 if1_adj_mac = topo.get_interface_mac(dut1_node, dut1_if1)
126                 if2_adj_mac = topo.get_interface_mac(dut2_node, dut2_if2)
127             else:
128                 raise Exception("test_type unknown")
129
130             if min(if1_pci, if2_pci) != if1_pci:
131                 if1_mac, if2_mac = if2_mac, if1_mac
132                 if1_pci, if2_pci = if2_pci, if1_pci
133                 if1_adj_mac, if2_adj_mac = if2_adj_mac, if1_adj_mac
134                 self._ifaces_reordered = 1
135
136             if1_mac_hex = "0x"+if1_mac.replace(":", ",0x")
137             if2_mac_hex = "0x"+if2_mac.replace(":", ",0x")
138             if1_adj_mac_hex = "0x"+if1_adj_mac.replace(":", ",0x")
139             if2_adj_mac_hex = "0x"+if2_adj_mac.replace(":", ",0x")
140
141             (ret, stdout, stderr) = ssh.exec_command(
142                 "sudo sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
143                 "- port_limit      : 2\n"
144                 "  version         : 2\n"
145                 "  interfaces      : [\"{}\",\"{}\"]\n"
146                 "  port_bandwidth_gb : 10\n"
147                 "  port_info       :\n"
148                 "          - dest_mac        :   [{}]\n"
149                 "            src_mac         :   [{}]\n"
150                 "          - dest_mac        :   [{}]\n"
151                 "            src_mac         :   [{}]\n"
152                 "EOF'"\
153                 .format(if1_pci, if2_pci,
154                         if1_adj_mac_hex, if1_mac_hex,
155                         if2_adj_mac_hex, if2_mac_hex))
156             if int(ret) != 0:
157                 logger.error("failed to create t-rex config: {}"\
158                 .format(stdout + stderr))
159                 raise RuntimeError('trex config generation error')
160
161             (ret, stdout, stderr) = ssh.exec_command(
162                 "sh -c 'cd {0}/scripts/ && "
163                 "sudo ./trex-cfg'"\
164                 .format(trex_path))
165             if int(ret) != 0:
166                 logger.error('trex-cfg failed: {0}'.format(stdout + stderr))
167                 raise RuntimeError('trex-cfg failed')
168
169             (ret, _, _) = ssh.exec_command(
170                 "sh -c 'cd {0}/scripts/ && "
171                 "sudo nohup ./t-rex-64 -i -c 4 --iom 0 > /dev/null 2>&1 &'"
172                 "> /dev/null"\
173                 .format(trex_path))
174             if int(ret) != 0:
175                 raise RuntimeError('t-rex-64 startup failed')
176
177     @staticmethod
178     def teardown_traffic_generator(node):
179         """TG teardown.
180
181         :param node: Traffic generator node.
182         :type node: dict
183         :return: nothing
184         """
185         if node['type'] != NodeType.TG:
186             raise Exception('Node type is not a TG')
187         if node['subtype'] == NodeSubTypeTG.TREX:
188             ssh = SSH()
189             ssh.connect(node)
190             (ret, stdout, stderr) = ssh.exec_command(
191                 "sh -c 'sudo pkill t-rex'")
192             if int(ret) != 0:
193                 logger.error('pkill t-rex failed: {0}'.format(stdout + stderr))
194                 raise RuntimeError('pkill t-rex failed')
195
196     def trex_stateless_remote_exec(self, duration, rate, framesize,
197                                    traffic_type):
198         """Execute stateless script on remote node over ssh.
199
200         :param traffic_type: Traffic profile.
201         :type traffic_type: str
202         """
203         ssh = SSH()
204         ssh.connect(self._node)
205
206         _p0 = 1
207         _p1 = 2
208
209         if self._ifaces_reordered != 0:
210             _p0, _p1 = _p1, _p0
211
212         if traffic_type in ["3-node-xconnect", "3-node-bridge"]:
213             (ret, stdout, stderr) = ssh.exec_command(
214                 "sh -c '/tmp/openvpp-testing/resources/tools/t-rex/"
215                 "t-rex-stateless.py "
216                 "-d {0} -r {1} -s {2} "
217                 "--p{3}_src_start_ip 10.10.10.1 "
218                 "--p{3}_src_end_ip 10.10.10.254 "
219                 "--p{3}_dst_start_ip 20.20.20.1 "
220                 "--p{4}_src_start_ip 20.20.20.1 "
221                 "--p{4}_src_end_ip 20.20.20.254 "
222                 "--p{4}_dst_start_ip 10.10.10.1'".\
223                 format(duration, rate, framesize, _p0, _p1),\
224                 timeout=int(duration)+60)
225         elif traffic_type in ["3-node-IPv4"]:
226             (ret, stdout, stderr) = ssh.exec_command(
227                 "sh -c '/tmp/openvpp-testing/resources/tools/t-rex/"
228                 "t-rex-stateless.py "
229                 "-d {0} -r {1} -s {2} "
230                 "--p{3}_src_start_ip 10.10.10.2 "
231                 "--p{3}_src_end_ip 10.10.10.254 "
232                 "--p{3}_dst_start_ip 20.20.20.2 "
233                 "--p{4}_src_start_ip 20.20.20.2 "
234                 "--p{4}_src_end_ip 20.20.20.254 "
235                 "--p{4}_dst_start_ip 10.10.10.2'".\
236                 format(duration, rate, framesize, _p0, _p1),\
237                 timeout=int(duration)+60)
238         else:
239             raise NotImplementedError('Unsupported traffic type')
240
241         logger.trace(ret)
242         logger.trace(stdout)
243         logger.trace(stderr)
244
245         # last line from console output
246         line = stdout.splitlines()[-1]
247
248         self._result = line
249         logger.info('TrafficGen result: {0}'.format(self._result))
250
251         self._received = self._result.split(', ')[1].split('=')[1]
252         self._sent = self._result.split(', ')[2].split('=')[1]
253         self._loss = self._result.split(', ')[3].split('=')[1]
254
255     def send_traffic_on(self, node, duration, rate,
256                         framesize, traffic_type):
257         """Send traffic from all configured interfaces on TG.
258
259         :param node: Dictionary containing TG information.
260         :param duration: Duration of test traffic generation in seconds.
261         :param rate: Offered load per interface (e.g. 1%, 3gbps, 4mpps, ...).
262         :param framesize: Frame size (L2) in Bytes.
263         :param traffic_type: Traffic profile.
264         :type node: dict
265         :type duration: str
266         :type rate: str
267         :type framesize: str
268         :type traffic_type: str
269         :return: TG output.
270         :rtype: str
271         """
272         if node['type'] != NodeType.TG:
273             raise Exception('Node type is not a TG')
274
275         if node['subtype'] is None:
276             raise Exception('TG subtype not defined')
277         elif node['subtype'] == NodeSubTypeTG.TREX:
278             self.trex_stateless_remote_exec(duration, rate, framesize,
279                                             traffic_type)
280         else:
281             raise NotImplementedError("TG subtype not supported")
282
283         return self._result
284
285     def no_traffic_loss_occurred(self):
286         """Fail is loss occurred in traffic run.
287
288         :return: nothing
289         """
290         if self._loss is None:
291             raise Exception('The traffic generation has not been issued')
292         if self._loss != '0':
293             raise Exception('Traffic loss occurred: {0}'.format(self._loss))