vnf: support of vloop_vnf VM
[vswitchperf.git] / testcases / testcase.py
1 # Copyright 2015 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 """TestCase base class
15 """
16
17 import csv
18 import os
19 import logging
20 import subprocess
21 import copy
22 from collections import OrderedDict
23
24 from core.results.results_constants import ResultsConstants
25 import core.component_factory as component_factory
26 from core.loader import Loader
27 from tools import tasks
28 from tools.report import report
29 from conf import settings as S
30 from tools.pkt_gen.trafficgen.trafficgenhelper import TRAFFIC_DEFAULTS
31 from conf import get_test_param
32
33 class TestCase(object):
34     """TestCase base class
35
36     In this basic form runs RFC2544 throughput test
37     """
38     def __init__(self, cfg, results_dir):
39         """Pull out fields from test config
40
41         :param cfg: A dictionary of string-value pairs describing the test
42             configuration. Both the key and values strings use well-known
43             values.
44         :param results_dir: Where the csv formatted results are written.
45         """
46         self._logger = logging.getLogger(__name__)
47         self.name = cfg['Name']
48         self.desc = cfg.get('Description', 'No description given.')
49         self.deployment = cfg['Deployment']
50         self._frame_mod = cfg.get('Frame Modification', None)
51         framerate = get_test_param('iload', None)
52         if framerate == None:
53             framerate = cfg.get('iLoad', 100)
54
55         # identify guest loopback method, so it can be added into reports
56         self.guest_loopback = []
57         if self.deployment in ['pvp', 'pvvp']:
58             guest_loopback = get_test_param('guest_loopback', None)
59             if guest_loopback:
60                 self.guest_loopback.append(guest_loopback)
61             else:
62                 if self.deployment == 'pvp':
63                     self.guest_loopback.append(S.getValue('GUEST_LOOPBACK')[0])
64                 else:
65                     self.guest_loopback = S.getValue('GUEST_LOOPBACK').copy()
66
67         # read configuration of streams; CLI parameter takes precedence to
68         # testcase definition
69         multistream = cfg.get('MultiStream', 0)
70         multistream = get_test_param('multistream', multistream)
71         stream_type = cfg.get('Stream Type', 'L4')
72         stream_type = get_test_param('stream_type', stream_type)
73         pre_installed_flows = False # placeholder for VSPERF-83 implementation
74
75
76         # check if test requires background load and which generator it uses
77         self._load_cfg = cfg.get('Load', None)
78         if self._load_cfg and 'tool' in self._load_cfg:
79             self._loadgen = self._load_cfg['tool']
80         else:
81             # background load is not requested, so use dummy implementation
82             self._loadgen = "Dummy"
83
84         if self._frame_mod:
85             self._frame_mod = self._frame_mod.lower()
86         self._results_dir = results_dir
87
88         # set traffic details, so they can be passed to vswitch and traffic ctls
89         self._traffic = copy.deepcopy(TRAFFIC_DEFAULTS)
90         self._traffic.update({'traffic_type': cfg['Traffic Type'],
91                               'flow_type': cfg.get('Flow Type', 'port'),
92                               'bidir': cfg['biDirectional'],
93                               'multistream': int(multistream),
94                               'stream_type': stream_type,
95                               'pre_installed_flows' : pre_installed_flows,
96                               'frame_rate': int(framerate)})
97
98         # OVS Vanilla requires guest VM MAC address and IPs to work
99         if 'linux_bridge' in self.guest_loopback:
100             self._traffic['l2'].update({'srcmac': S.getValue('GUEST_NET2_MAC')[0],
101                                         'dstmac': S.getValue('GUEST_NET1_MAC')[0]})
102             self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
103                                         'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
104
105     def run(self):
106         """Run the test
107
108         All setup and teardown through controllers is included.
109         """
110         self._logger.debug(self.name)
111
112         # copy sources of l2 forwarding tools into VM shared dir if needed
113         self._copy_fwd_tools_for_guest()
114
115         self._logger.debug("Controllers:")
116         loader = Loader()
117         traffic_ctl = component_factory.create_traffic(
118             self._traffic['traffic_type'],
119             loader.get_trafficgen_class())
120         vnf_ctl = component_factory.create_vnf(
121             self.deployment,
122             loader.get_vnf_class())
123         vswitch_ctl = component_factory.create_vswitch(
124             self.deployment,
125             loader.get_vswitch_class(),
126             self._traffic)
127         collector = component_factory.create_collector(
128             loader.get_collector_class(),
129             self._results_dir, self.name)
130         loadgen = component_factory.create_loadgen(
131             self._loadgen,
132             self._load_cfg)
133
134         self._logger.debug("Setup:")
135         with vswitch_ctl, loadgen:
136             with vnf_ctl, collector:
137                 vswitch = vswitch_ctl.get_vswitch()
138                 # TODO BOM 15-08-07 the frame mod code assumes that the
139                 # physical ports are ports 1 & 2. The actual numbers
140                 # need to be retrived from the vSwitch and the metadata value
141                 # updated accordingly.
142                 bridge = S.getValue('VSWITCH_BRIDGE_NAME')
143                 if self._frame_mod == "vlan":
144                     # 0x8100 => VLAN ethertype
145                     self._logger.debug(" ****   VLAN   ***** ")
146                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
147                             'actions': ['push_vlan:0x8100', 'goto_table:3']}
148                     vswitch.add_flow(bridge, flow)
149                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
150                             'actions': ['push_vlan:0x8100', 'goto_table:3']}
151                     vswitch.add_flow(bridge, flow)
152                 elif self._frame_mod == "mpls":
153                     # 0x8847 => MPLS unicast ethertype
154                     self._logger.debug(" ****   MPLS  ***** ")
155                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
156                             'actions': ['push_mpls:0x8847', 'goto_table:3']}
157                     vswitch.add_flow(bridge, flow)
158                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
159                             'actions': ['push_mpls:0x8847', 'goto_table:3']}
160                     vswitch.add_flow(bridge, flow)
161                 elif self._frame_mod == "mac":
162                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
163                             'actions': ['mod_dl_src:22:22:22:22:22:22',
164                                         'goto_table:3']}
165                     vswitch.add_flow(bridge, flow)
166                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
167                             'actions': ['mod_dl_src:11:11:11:11:11:11',
168                                         'goto_table:3']}
169                     vswitch.add_flow(bridge, flow)
170                 elif self._frame_mod == "dscp":
171                     # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
172                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
173                             'dl_type':'0x0800',
174                             'actions': ['mod_nw_tos:184', 'goto_table:3']}
175                     vswitch.add_flow(bridge, flow)
176                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
177                             'dl_type':'0x0800',
178                             'actions': ['mod_nw_tos:184', 'goto_table:3']}
179                     vswitch.add_flow(bridge, flow)
180                 elif self._frame_mod == "ttl":
181                     # 251 and 241 are the highest prime numbers < 255
182                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
183                             'dl_type':'0x0800',
184                             'actions': ['mod_nw_ttl:251', 'goto_table:3']}
185                     vswitch.add_flow(bridge, flow)
186                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
187                             'dl_type':'0x0800',
188                             'actions': ['mod_nw_ttl:241', 'goto_table:3']}
189                     vswitch.add_flow(bridge, flow)
190                 elif self._frame_mod == "ip_addr":
191                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
192                             'dl_type':'0x0800',
193                             'actions': ['mod_nw_src:10.10.10.10',
194                                         'mod_nw_dst:20.20.20.20',
195                                         'goto_table:3']}
196                     vswitch.add_flow(bridge, flow)
197                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
198                             'dl_type':'0x0800',
199                             'actions': ['mod_nw_src:20.20.20.20',
200                                         'mod_nw_dst:10.10.10.10',
201                                         'goto_table:3']}
202                     vswitch.add_flow(bridge, flow)
203                 elif self._frame_mod == "ip_port":
204                     # TODO BOM 15-08-27 The traffic generated is assumed
205                     # to be UDP (nw_proto 17d) which is the default case but
206                     # we will need to pick up the actual traffic params in use.
207                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
208                             'dl_type':'0x0800', 'nw_proto':'17',
209                             'actions': ['mod_tp_src:44444',
210                                         'mod_tp_dst:44444', 'goto_table:3']}
211                     vswitch.add_flow(bridge, flow)
212                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
213                             'dl_type':'0x0800', 'nw_proto':'17',
214                             'actions': ['mod_tp_src:44444',
215                                         'mod_tp_dst:44444', 'goto_table:3']}
216                     vswitch.add_flow(bridge, flow)
217                 else:
218                     pass
219
220                 with traffic_ctl:
221                     traffic_ctl.send_traffic(self._traffic)
222
223                 # dump vswitch flows before they are affected by VNF termination
224                 vswitch_ctl.dump_vswitch_flows()
225
226         self._logger.debug("Traffic Results:")
227         traffic_ctl.print_results()
228
229         self._logger.debug("Collector Results:")
230         collector.print_results()
231
232         output_file = os.path.join(self._results_dir, "result_" + self.name +
233                                    "_" + self.deployment + ".csv")
234
235         tc_results = self._append_results(traffic_ctl.get_results())
236         TestCase._write_result_to_file(tc_results, output_file)
237
238         report.generate(output_file, tc_results, collector.get_results())
239
240     def _append_results(self, results):
241         """
242         Method appends mandatory Test Case results to list of dictionaries.
243
244         :param results: list of dictionaries which contains results from
245                 traffic generator.
246
247         :returns: modified list of dictionaries.
248         """
249         for item in results:
250             item[ResultsConstants.ID] = self.name
251             item[ResultsConstants.DEPLOYMENT] = self.deployment
252             item[ResultsConstants.TRAFFIC_TYPE] = self._traffic['l3']['proto']
253             if self._traffic['multistream']:
254                 item[ResultsConstants.SCAL_STREAM_COUNT] = self._traffic['multistream']
255                 item[ResultsConstants.SCAL_STREAM_TYPE] = self._traffic['stream_type']
256                 item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows']
257             if len(self.guest_loopback):
258                 item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(self.guest_loopback)
259
260         return results
261
262     def _copy_fwd_tools_for_guest(self):
263         """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] for use by guests.
264         """
265         counter = 0
266         # method is executed only for pvp and pvvp, so let's count number of 'v'
267         while counter < self.deployment.count('v'):
268             guest_dir = S.getValue('GUEST_SHARE_DIR')[counter]
269
270             # create shared dir if it doesn't exist
271             if not os.path.exists(guest_dir):
272                 os.makedirs(guest_dir)
273
274             # copy sources into shared dir only if neccessary
275             if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback:
276                 try:
277                     tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
278                                     os.path.join(S.getValue('RTE_SDK'), ''),
279                                     os.path.join(guest_dir, 'DPDK')],
280                                    self._logger,
281                                    'Copying DPDK to shared directory...',
282                                    True)
283                     tasks.run_task(['rsync', '-a', '-r', '-l',
284                                     os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
285                                     os.path.join(guest_dir, 'l2fwd')],
286                                    self._logger,
287                                    'Copying l2fwd to shared directory...',
288                                    True)
289                 except subprocess.CalledProcessError:
290                     self._logger.error('Unable to copy DPDK and l2fwd to shared directory')
291
292             counter += 1
293
294
295     @staticmethod
296     def _write_result_to_file(results, output):
297         """Write list of dictionaries to a CSV file.
298
299         Each element on list will create separate row in output file.
300         If output file already exists, data will be appended at the end,
301         otherwise it will be created.
302
303         :param results: list of dictionaries.
304         :param output: path to output file.
305         """
306         with open(output, 'a') as csvfile:
307
308             logging.info("Write results to file: " + output)
309             fieldnames = TestCase._get_unique_keys(results)
310
311             writer = csv.DictWriter(csvfile, fieldnames)
312
313             if not csvfile.tell():  # file is now empty
314                 writer.writeheader()
315
316             for result in results:
317                 writer.writerow(result)
318
319
320     @staticmethod
321     def _get_unique_keys(list_of_dicts):
322         """Gets unique key values as ordered list of strings in given dicts
323
324         :param list_of_dicts: list of dictionaries.
325
326         :returns: list of unique keys(strings).
327         """
328         result = OrderedDict()
329         for item in list_of_dicts:
330             for key in item.keys():
331                 result[key] = ''
332
333         return list(result.keys())