Add Honeycomb interface management tests (ipv4, ipv6, ethernet, routing)
[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
105         trex_path = "/opt/trex-core-2.00"
106
107         topo = Topology()
108
109         if tg_node['type'] != NodeType.TG:
110             raise Exception('Node type is not a TG')
111         self._node = tg_node
112
113         if tg_node['subtype'] == NodeSubTypeTG.TREX:
114             ssh = SSH()
115             ssh.connect(tg_node)
116
117             if1_pci = topo.get_interface_pci_addr(tg_node, tg_if1)
118             if2_pci = topo.get_interface_pci_addr(tg_node, tg_if2)
119             if1_mac = topo.get_interface_mac(tg_node, tg_if1)
120             if2_mac = topo.get_interface_mac(tg_node, tg_if2)
121
122             if test_type == 'L2':
123                 if1_adj_mac = if2_mac
124                 if2_adj_mac = if1_mac
125             elif test_type == 'L3':
126                 if1_adj_mac = topo.get_interface_mac(dut1_node, dut1_if1)
127                 if2_adj_mac = topo.get_interface_mac(dut2_node, dut2_if2)
128             else:
129                 raise Exception("test_type unknown")
130
131             if min(if1_pci, if2_pci) != if1_pci:
132                 if1_mac, if2_mac = if2_mac, if1_mac
133                 if1_pci, if2_pci = if2_pci, if1_pci
134                 if1_adj_mac, if2_adj_mac = if2_adj_mac, if1_adj_mac
135                 self._ifaces_reordered = 1
136
137             if1_mac_hex = "0x"+if1_mac.replace(":", ",0x")
138             if2_mac_hex = "0x"+if2_mac.replace(":", ",0x")
139             if1_adj_mac_hex = "0x"+if1_adj_mac.replace(":", ",0x")
140             if2_adj_mac_hex = "0x"+if2_adj_mac.replace(":", ",0x")
141
142             (ret, stdout, stderr) = ssh.exec_command(
143                 "sudo sh -c 'cat << EOF > /etc/trex_cfg.yaml\n"
144                 "- port_limit      : 2\n"
145                 "  version         : 2\n"
146                 "  interfaces      : [\"{}\",\"{}\"]\n"
147                 "  port_bandwidth_gb : 10\n"
148                 "  port_info       :\n"
149                 "          - dest_mac        :   [{}]\n"
150                 "            src_mac         :   [{}]\n"
151                 "          - dest_mac        :   [{}]\n"
152                 "            src_mac         :   [{}]\n"
153                 "EOF'"\
154                 .format(if1_pci, if2_pci,
155                         if1_adj_mac_hex, if1_mac_hex,
156                         if2_adj_mac_hex, if2_mac_hex))
157             if int(ret) != 0:
158                 logger.error("failed to create t-rex config: {}"\
159                 .format(stdout + stderr))
160                 raise RuntimeError('trex config generation error')
161
162             (ret, stdout, stderr) = ssh.exec_command(
163                 "sh -c 'cd {0}/scripts/ && "
164                 "sudo ./trex-cfg'"\
165                 .format(trex_path))
166             if int(ret) != 0:
167                 logger.error('trex-cfg failed: {0}'.format(stdout + stderr))
168                 raise RuntimeError('trex-cfg failed')
169
170             (ret, _, _) = ssh.exec_command(
171                 "sh -c 'cd {0}/scripts/ && "
172                 "sudo nohup ./t-rex-64 -i -c 7 --iom 0 > /dev/null 2>&1 &'"
173                 "> /dev/null"\
174                 .format(trex_path))
175             if int(ret) != 0:
176                 raise RuntimeError('t-rex-64 startup failed')
177
178     @staticmethod
179     def teardown_traffic_generator(node):
180         """TG teardown.
181
182         :param node: Traffic generator node.
183         :type node: dict
184         :return: nothing
185         """
186         if node['type'] != NodeType.TG:
187             raise Exception('Node type is not a TG')
188         if node['subtype'] == NodeSubTypeTG.TREX:
189             ssh = SSH()
190             ssh.connect(node)
191             (ret, stdout, stderr) = ssh.exec_command(
192                 "sh -c 'sudo pkill t-rex'")
193             if int(ret) != 0:
194                 logger.error('pkill t-rex failed: {0}'.format(stdout + stderr))
195                 raise RuntimeError('pkill t-rex failed')
196
197     def trex_stateless_remote_exec(self, duration, rate, framesize,
198                                    traffic_type):
199         """Execute stateless script on remote node over ssh.
200
201         :param traffic_type: Traffic profile.
202         :type traffic_type: str
203         """
204         ssh = SSH()
205         ssh.connect(self._node)
206
207         _p0 = 1
208         _p1 = 2
209
210         if self._ifaces_reordered != 0:
211             _p0, _p1 = _p1, _p0
212
213         if traffic_type in ["3-node-xconnect", "3-node-bridge"]:
214             (ret, stdout, stderr) = ssh.exec_command(
215                 "sh -c '/tmp/openvpp-testing/resources/tools/t-rex/"
216                 "t-rex-stateless.py "
217                 "-d {0} -r {1} -s {2} "
218                 "--p{3}_src_start_ip 10.10.10.1 "
219                 "--p{3}_src_end_ip 10.10.10.254 "
220                 "--p{3}_dst_start_ip 20.20.20.1 "
221                 "--p{4}_src_start_ip 20.20.20.1 "
222                 "--p{4}_src_end_ip 20.20.20.254 "
223                 "--p{4}_dst_start_ip 10.10.10.1'".\
224                 format(duration, rate, framesize, _p0, _p1),\
225                 timeout=int(duration)+60)
226         elif traffic_type in ["3-node-IPv4"]:
227             (ret, stdout, stderr) = ssh.exec_command(
228                 "sh -c '/tmp/openvpp-testing/resources/tools/t-rex/"
229                 "t-rex-stateless.py "
230                 "-d {0} -r {1} -s {2} "
231                 "--p{3}_src_start_ip 10.10.10.2 "
232                 "--p{3}_src_end_ip 10.10.10.254 "
233                 "--p{3}_dst_start_ip 20.20.20.2 "
234                 "--p{4}_src_start_ip 20.20.20.2 "
235                 "--p{4}_src_end_ip 20.20.20.254 "
236                 "--p{4}_dst_start_ip 10.10.10.2'".\
237                 format(duration, rate, framesize, _p0, _p1),\
238                 timeout=int(duration)+60)
239         else:
240             raise NotImplementedError('Unsupported traffic type')
241
242         logger.trace(ret)
243         logger.trace(stdout)
244         logger.trace(stderr)
245
246         if int(ret) != 0:
247             raise RuntimeError('T-rex stateless runtime error')
248         else:
249             # last line from console output
250             line = stdout.splitlines()[-1]
251
252             self._result = line
253             logger.info('TrafficGen result: {0}'.format(self._result))
254
255             self._received = self._result.split(', ')[1].split('=')[1]
256             self._sent = self._result.split(', ')[2].split('=')[1]
257             self._loss = self._result.split(', ')[3].split('=')[1]
258
259     def send_traffic_on(self, node, duration, rate,
260                         framesize, traffic_type):
261         """Send traffic from all configured interfaces on TG.
262
263         :param node: Dictionary containing TG information.
264         :param duration: Duration of test traffic generation in seconds.
265         :param rate: Offered load per interface (e.g. 1%, 3gbps, 4mpps, ...).
266         :param framesize: Frame size (L2) in Bytes.
267         :param traffic_type: Traffic profile.
268         :type node: dict
269         :type duration: str
270         :type rate: str
271         :type framesize: str
272         :type traffic_type: str
273         :return: TG output.
274         :rtype: str
275         """
276         if node['type'] != NodeType.TG:
277             raise Exception('Node type is not a TG')
278
279         if node['subtype'] is None:
280             raise Exception('TG subtype not defined')
281         elif node['subtype'] == NodeSubTypeTG.TREX:
282             self.trex_stateless_remote_exec(duration, rate, framesize,
283                                             traffic_type)
284         else:
285             raise NotImplementedError("TG subtype not supported")
286
287         return self._result
288
289     def no_traffic_loss_occurred(self):
290         """Fail is loss occurred in traffic run.
291
292         :return: nothing
293         """
294         if self._loss is None:
295             raise Exception('The traffic generation has not been issued')
296         if self._loss != '0':
297             raise Exception('Traffic loss occurred: {0}'.format(self._loss))