Refactor remote command execution in vsperf_dpdk
[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             _, stdout, _ = self.client.execute(cmd, raise_on_error=True)
209             self.tgen_port1_mac = stdout
210
211             cmd = "cat ~/.testpmd.macaddr.port2"
212             _, stdout, _ = self.client.execute(cmd, raise_on_error=True)
213             self.tgen_port2_mac = stdout
214
215         cmd = "screen -d -m sudo -E bash ~/testpmd_vsperf.sh %s %s" % \
216             (self.moongen_port1_mac, self.moongen_port2_mac)
217         LOG.debug("Executing command: %s", cmd)
218         self.client.run(cmd)
219
220         time.sleep(1)
221
222         self.dpdk_setup_done = True
223
224     def _is_dpdk_setup(self):
225         """Is dpdk already setup in the host?"""
226         is_run = True
227         cmd = "ip a | grep %s 2>/dev/null" % (self.tg_port1)
228         LOG.debug("Executing command: %s", cmd)
229         _, stdout, _ = self.client.execute(cmd)
230         if stdout:
231             is_run = False
232         return is_run
233
234     def run(self, result):
235         """ execute the vsperf benchmark and return test results
236             within result dictionary
237         """
238
239         if not self.setup_done:
240             self.setup()
241
242         # remove results from previous tests
243         self.client.run("rm -rf /tmp/results*", raise_on_error=False)
244
245         # get vsperf options
246         options = self.scenario_cfg['options']
247         test_params = []
248         traffic_type = self.scenario_cfg['options'].\
249             get("traffic_type", "rfc2544_throughput")
250         multistream = self.scenario_cfg['options'].get("multistream", 1)
251
252         if not self.dpdk_setup_done:
253             self.dpdk_setup()
254
255         if 'frame_size' in options:
256             test_params.append("%s=(%s,)" % ('TRAFFICGEN_PKT_SIZES',
257                                              options['frame_size']))
258
259         cmd = "openstack network show %s | grep segmentation_id | " \
260               "cut -d '|' -f 3" % (self.tg_port1_nw)
261         LOG.debug("Executing command: %s", cmd)
262         tg_port1_vlan = subprocess.check_output(cmd, shell=True)
263
264         cmd = "openstack network show %s | grep segmentation_id | " \
265               "cut -d '|' -f 3" % (self.tg_port2_nw)
266         LOG.debug("Executing command: %s", cmd)
267         tg_port2_vlan = subprocess.check_output(cmd, shell=True)
268
269         additional_params = \
270             'TRAFFIC={"traffic_type":"%s", "multistream":%d, ' \
271             '"l2":{"srcmac":"{\'%s\',\'%s\'}", "dstmac":"{\'%s\',\'%s\'}"}, ' \
272             '"vlan":{"enabled":"True", "id":"{%d,%d}"}}' \
273             % (traffic_type, multistream,
274                self.moongen_port1_mac, self.moongen_port2_mac,
275                self.tgen_port1_mac, self.tgen_port2_mac,
276                int(tg_port1_vlan), int(tg_port2_vlan))
277
278         if 'test_params' in options:
279             test_params.append(options['test_params'] + additional_params)
280
281         # filter empty parameters and escape quotes and double quotes
282         test_params = [tp.replace('"', '\\"').replace("'", "\\'")
283                        for tp in test_params if tp]
284
285         # Set password less access to MoonGen
286         cmd = "sshpass -p yardstick ssh-copy-id -o StrictHostKeyChecking=no " \
287               "root@%s -p 22" % (self.moongen_host_ip)
288         LOG.debug("Executing command: %s", cmd)
289         self.client.run(cmd)
290
291         # execute vsperf
292         cmd = "source ~/vsperfenv/bin/activate ; cd vswitchperf ; "
293         cmd += "./vsperf --mode trafficgen "
294         if self.vsperf_conf:
295             cmd += "--conf-file ~/vsperf.conf "
296         cmd += "--test-params=\"%s\"" % (';'.join(test_params))
297         LOG.debug("Executing command: %s", cmd)
298         self.client.run(cmd)
299
300         # get test results
301         cmd = "cat /tmp/results*/result.csv"
302         LOG.debug("Executing command: %s", cmd)
303         _, stdout, _ = self.client.execute(cmd, raise_on_error=True)
304
305         # convert result.csv to JSON format
306         reader = csv.DictReader(stdout.split('\r\n'))
307         try:
308             result.update(next(reader))
309         except StopIteration:
310             pass
311         result['nrFlows'] = multistream
312
313         # sla check; go through all defined SLAs and check if values measured
314         # by VSPERF are higher then those defined by SLAs
315         if 'sla' in self.scenario_cfg and \
316            'metrics' in self.scenario_cfg['sla']:
317             for metric in self.scenario_cfg['sla']['metrics'].split(','):
318                 self.verify_SLA(metric in result,
319                                 '%s was not collected by VSPERF' % metric)
320                 self.verify_SLA(metric in self.scenario_cfg['sla'],
321                                 '%s is not defined in SLA' % metric)
322                 vs_res = float(result[metric])
323                 sla_res = float(self.scenario_cfg['sla'][metric])
324                 self.verify_SLA(vs_res >= sla_res,
325                                 'VSPERF_%s(%f) < SLA_%s(%f)'
326                                 % (metric, vs_res, metric, sla_res))
327
328     def teardown(self):
329         """cleanup after the test execution"""
330
331         # execute external setup script
332         if self.setup_script:
333             cmd = "%s teardown" % (self.setup_script)
334             LOG.info("Execute setup script \"%s\"", cmd)
335             subprocess.call(cmd, shell=True)
336
337         self.setup_done = False