Merge "Add new Kubernetes resource kind: "Network""
[yardstick.git] / yardstick / benchmark / scenarios / networking / vsperf_dpdk.py
1 # Copyright 2016 Intel Corporation.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #   http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 """ VsperfDPDK specific scenario definition """
15
16 from __future__ import absolute_import
17 import pkg_resources
18 import logging
19 import os
20 import subprocess
21 import csv
22 import time
23
24 import yardstick.ssh as ssh
25 import yardstick.common.utils as utils
26 from yardstick.benchmark.scenarios import base
27
28 LOG = logging.getLogger(__name__)
29
30
31 class VsperfDPDK(base.Scenario):
32     """Execute vsperf with defined parameters
33
34   Parameters:
35     traffic_type - to specify the type of traffic executed by traffic generator
36     the valid values are "rfc2544", "continuous", "back2back"
37         type:    string
38         default: "rfc2544"
39     frame_size - a frame size for which test should be executed;
40         Multiple frame sizes can be tested by modification of sequence runner
41         section inside TC YAML definition.
42         type:    string
43         default: "64"
44     bidirectional - speficies if traffic will be uni (False) or bi-directional
45         (True)
46         type:    string
47         default: False
48     iload - specifies frame rate
49         type:    string
50         default: 100
51     multistream - the number of simulated streams
52         type:    string
53         default: 0 (disabled)
54     stream_type - specifies network layer used for multistream simulation
55         the valid values are "L4", "L3" and "L2"
56         type:    string
57         default: "L4"
58     test_params - specifies a string with a list of vsperf configuration
59         parameters, which will be passed to the '--test-params' CLI argument;
60         Parameters should be stated in the form of 'param=value' and separated
61         by a semicolon. Please check VSPERF documentation for details about
62         available configuration parameters and their data types.
63         In case that both 'test_params' and 'conf_file' are specified,
64         then values from 'test_params' will override values defined
65         in the configuration file.
66         type:    string
67         default: NA
68     conf_file - path to the vsperf configuration file, which will be uploaded
69         to the VM;
70         In case that both 'test_params' and 'conf_file' are specified,
71         then values from 'test_params' will override values defined
72         in configuration file.
73         type:   string
74         default: NA
75     setup_script - path to the setup script, which will be executed during
76         setup and teardown phases
77         type:   string
78         default: NA
79     trafficgen_port1 - specifies device name of 1st interface connected to
80         the trafficgen
81         type:   string
82         default: NA
83     trafficgen_port2 - specifies device name of 2nd interface connected to
84         the trafficgen
85         type:   string
86         default: NA
87     external_bridge - specifies name of external bridge configured in OVS
88         type:   string
89         default: "br-ex"
90
91     """
92     __scenario_type__ = "VsperfDPDK"
93
94     TESTPMD_SCRIPT = 'testpmd_vsperf.bash'
95
96     def __init__(self, scenario_cfg, context_cfg):
97         self.scenario_cfg = scenario_cfg
98         self.context_cfg = context_cfg
99         self.moongen_host_ip = \
100             scenario_cfg['options'].get('moongen_host_ip', "127.0.0.1")
101         self.moongen_port1_mac = \
102             scenario_cfg['options'].get('moongen_port1_mac', None)
103         self.moongen_port2_mac = \
104             scenario_cfg['options'].get('moongen_port2_mac', None)
105         self.dpdk_setup_done = False
106         self.setup_done = False
107         self.client = None
108         self.tg_port1 = \
109             self.scenario_cfg['options'].get('trafficgen_port1', None)
110         self.tg_port2 = \
111             self.scenario_cfg['options'].get('trafficgen_port2', None)
112         self.tgen_port1_mac = None
113         self.tgen_port2_mac = None
114         self.br_ex = self.scenario_cfg['options'].get('external_bridge',
115                                                       'br-ex')
116         self.vsperf_conf = self.scenario_cfg['options'].get('conf_file', None)
117         if self.vsperf_conf:
118             self.vsperf_conf = os.path.expanduser(self.vsperf_conf)
119
120         self.moongen_helper = \
121             self.scenario_cfg['options'].get('moongen_helper_file', None)
122         if self.moongen_helper:
123             self.moongen_helper = os.path.expanduser(self.moongen_helper)
124
125         self.setup_script = self.scenario_cfg['options'].get('setup_script',
126                                                              None)
127         if self.setup_script:
128             self.setup_script = os.path.expanduser(self.setup_script)
129
130         self.test_params = self.scenario_cfg['options'].get('test-params',
131                                                             None)
132
133     def setup(self):
134         """scenario setup"""
135         vsperf = self.context_cfg['host']
136
137         task_id = self.scenario_cfg['task_id']
138         context_number = task_id.split('-')[0]
139         self.tg_port1_nw = vsperf.get('name', 'demo') + \
140             "-" + context_number + "-" + \
141             self.scenario_cfg['options'].get('trafficgen_port1_nw', 'test2')
142         self.tg_port2_nw = vsperf.get('name', 'demo') + \
143             "-" + context_number + "-" + \
144             self.scenario_cfg['options'].get('trafficgen_port2_nw', 'test3')
145
146         # copy vsperf conf to VM
147         self.client = ssh.SSH.from_node(vsperf, defaults={
148             "user": "ubuntu", "password": "ubuntu"
149         })
150         # traffic generation could last long
151         self.client.wait(timeout=1800)
152
153         # copy script to host
154         self.client._put_file_shell(self.vsperf_conf, '~/vsperf.conf')
155
156         self.client._put_file_shell(
157             self.moongen_helper,
158             '~/vswitchperf/tools/pkt_gen/moongen/moongen.py')
159
160         # execute external setup script
161         if self.setup_script:
162             cmd = "%s setup" % (self.setup_script)
163             LOG.info("Execute setup script \"%s\"", cmd)
164             subprocess.call(cmd, shell=True)
165
166         self.setup_done = True
167
168     def dpdk_setup(self):
169         """dpdk setup"""
170
171         # setup dpdk loopback in VM
172         self.testpmd_script = pkg_resources.resource_filename(
173             'yardstick.benchmark.scenarios.networking',
174             VsperfDPDK.TESTPMD_SCRIPT)
175
176         self.client._put_file_shell(self.testpmd_script,
177                                     '~/testpmd_vsperf.sh')
178
179         # disable Address Space Layout Randomization (ASLR)
180         cmd = "echo 0 | sudo tee /proc/sys/kernel/randomize_va_space"
181         self.client.send_command(cmd)
182
183         if not self._is_dpdk_setup():
184             self.tgen_port1_ip = \
185                 utils.get_port_ip(self.client, self.tg_port1)
186             self.tgen_port1_mac = \
187                 utils.get_port_mac(self.client, self.tg_port1)
188             self.client.run("tee ~/.testpmd.ipaddr.port1 > /dev/null",
189                             stdin=self.tgen_port1_ip)
190             self.client.run("tee ~/.testpmd.macaddr.port1 > /dev/null",
191                             stdin=self.tgen_port1_mac)
192             self.tgen_port2_ip = \
193                 utils.get_port_ip(self.client, self.tg_port2)
194             self.tgen_port2_mac = \
195                 utils.get_port_mac(self.client, self.tg_port2)
196             self.client.run("tee ~/.testpmd.ipaddr.port2 > /dev/null",
197                             stdin=self.tgen_port2_ip)
198             self.client.run("tee ~/.testpmd.macaddr.port2 > /dev/null",
199                             stdin=self.tgen_port2_mac)
200             cmd = "ip link set %s down" % (self.tg_port1)
201             LOG.debug("Executing command: %s", cmd)
202             self.client.send_command(cmd)
203             cmd = "ip link set %s down" % (self.tg_port2)
204             LOG.debug("Executing command: %s", cmd)
205             self.client.send_command(cmd)
206         else:
207             cmd = "cat ~/.testpmd.macaddr.port1"
208             status, stdout, stderr = self.client.execute(cmd)
209             if status:
210                 raise RuntimeError(stderr)
211             self.tgen_port1_mac = stdout
212             cmd = "cat ~/.testpmd.macaddr.port2"
213             status, stdout, stderr = self.client.execute(cmd)
214             if status:
215                 raise RuntimeError(stderr)
216             self.tgen_port2_mac = stdout
217
218         cmd = "screen -d -m sudo -E bash ~/testpmd_vsperf.sh %s %s" % \
219             (self.moongen_port1_mac, self.moongen_port2_mac)
220         LOG.debug("Executing command: %s", cmd)
221         status, stdout, stderr = self.client.execute(cmd)
222         if status:
223             raise RuntimeError(stderr)
224
225         time.sleep(1)
226
227         self.dpdk_setup_done = True
228
229     def _is_dpdk_setup(self):
230         """Is dpdk already setup in the host?"""
231         is_run = True
232         cmd = "ip a | grep %s 2>/dev/null" % (self.tg_port1)
233         LOG.debug("Executing command: %s", cmd)
234         _, stdout, _ = self.client.execute(cmd)
235         if stdout:
236             is_run = False
237         return is_run
238
239     def run(self, result):
240         """ execute the vsperf benchmark and return test results
241             within result dictionary
242         """
243
244         if not self.setup_done:
245             self.setup()
246
247         # remove results from previous tests
248         self.client.execute("rm -rf /tmp/results*")
249
250         # get vsperf options
251         options = self.scenario_cfg['options']
252         test_params = []
253         traffic_type = self.scenario_cfg['options'].\
254             get("traffic_type", "rfc2544_throughput")
255         multistream = self.scenario_cfg['options'].get("multistream", 1)
256
257         if not self.dpdk_setup_done:
258             self.dpdk_setup()
259
260         if 'frame_size' in options:
261             test_params.append("%s=(%s,)" % ('TRAFFICGEN_PKT_SIZES',
262                                              options['frame_size']))
263
264         cmd = "openstack network show %s | grep segmentation_id | " \
265               "cut -d '|' -f 3" % (self.tg_port1_nw)
266         LOG.debug("Executing command: %s", cmd)
267         tg_port1_vlan = subprocess.check_output(cmd, shell=True)
268
269         cmd = "openstack network show %s | grep segmentation_id | " \
270               "cut -d '|' -f 3" % (self.tg_port2_nw)
271         LOG.debug("Executing command: %s", cmd)
272         tg_port2_vlan = subprocess.check_output(cmd, shell=True)
273
274         additional_params = \
275             'TRAFFIC={"traffic_type":"%s", "multistream":%d, ' \
276             '"l2":{"srcmac":"{\'%s\',\'%s\'}", "dstmac":"{\'%s\',\'%s\'}"}, ' \
277             '"vlan":{"enabled":"True", "id":"{%d,%d}"}}' \
278             % (traffic_type, multistream,
279                self.moongen_port1_mac, self.moongen_port2_mac,
280                self.tgen_port1_mac, self.tgen_port2_mac,
281                int(tg_port1_vlan), int(tg_port2_vlan))
282
283         if 'test_params' in options:
284             test_params.append(options['test_params'] + additional_params)
285
286         # filter empty parameters and escape quotes and double quotes
287         test_params = [tp.replace('"', '\\"').replace("'", "\\'")
288                        for tp in test_params if tp]
289
290         # Set password less access to MoonGen
291         cmd = "sshpass -p yardstick ssh-copy-id -o StrictHostKeyChecking=no " \
292               "root@%s -p 22" % (self.moongen_host_ip)
293         LOG.debug("Executing command: %s", cmd)
294         status, stdout, stderr = self.client.execute(cmd)
295         if status:
296             raise RuntimeError(stderr)
297
298         # execute vsperf
299         cmd = "source ~/vsperfenv/bin/activate ; cd vswitchperf ; "
300         cmd += "./vsperf --mode trafficgen "
301         if self.vsperf_conf:
302             cmd += "--conf-file ~/vsperf.conf "
303         cmd += "--test-params=\"%s\"" % (';'.join(test_params))
304         LOG.debug("Executing command: %s", cmd)
305         status, stdout, stderr = self.client.execute(cmd)
306
307         if status:
308             raise RuntimeError(stderr)
309
310         # get test results
311         cmd = "cat /tmp/results*/result.csv"
312         LOG.debug("Executing command: %s", cmd)
313         status, stdout, stderr = self.client.execute(cmd)
314
315         if status:
316             raise RuntimeError(stderr)
317
318         # convert result.csv to JSON format
319         reader = csv.DictReader(stdout.split('\r\n'))
320         result.update(next(reader))
321         result['nrFlows'] = multistream
322
323         # sla check; go through all defined SLAs and check if values measured
324         # by VSPERF are higher then those defined by SLAs
325         if 'sla' in self.scenario_cfg and \
326            'metrics' in self.scenario_cfg['sla']:
327             for metric in self.scenario_cfg['sla']['metrics'].split(','):
328                 self.verify_SLA(metric in result,
329                                 '%s was not collected by VSPERF' % metric)
330                 self.verify_SLA(metric in self.scenario_cfg['sla'],
331                                 '%s is not defined in SLA' % metric)
332                 vs_res = float(result[metric])
333                 sla_res = float(self.scenario_cfg['sla'][metric])
334                 self.verify_SLA(vs_res >= sla_res,
335                                 'VSPERF_%s(%f) < SLA_%s(%f)'
336                                 % (metric, vs_res, metric, sla_res))
337
338     def teardown(self):
339         """cleanup after the test execution"""
340
341         # execute external setup script
342         if self.setup_script:
343             cmd = "%s teardown" % (self.setup_script)
344             LOG.info("Execute setup script \"%s\"", cmd)
345             subprocess.call(cmd, shell=True)
346
347         self.setup_done = False