4b2751b5473f60ba5b086b71966ff4183d2190a9
[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 = cfg.get('Pre-installed Flows', 'No')
74         pre_installed_flows = get_test_param('pre-installed_flows', pre_installed_flows)
75
76
77         # check if test requires background load and which generator it uses
78         self._load_cfg = cfg.get('Load', None)
79         if self._load_cfg and 'tool' in self._load_cfg:
80             self._loadgen = self._load_cfg['tool']
81         else:
82             # background load is not requested, so use dummy implementation
83             self._loadgen = "Dummy"
84
85         if self._frame_mod:
86             self._frame_mod = self._frame_mod.lower()
87         self._results_dir = results_dir
88
89         # set traffic details, so they can be passed to vswitch and traffic ctls
90         self._traffic = copy.deepcopy(TRAFFIC_DEFAULTS)
91         self._traffic.update({'traffic_type': cfg['Traffic Type'],
92                               'flow_type': cfg.get('Flow Type', 'port'),
93                               'bidir': cfg['biDirectional'],
94                               'multistream': int(multistream),
95                               'stream_type': stream_type,
96                               'pre_installed_flows' : pre_installed_flows,
97                               'frame_rate': int(framerate)})
98
99         # OVS Vanilla requires guest VM MAC address and IPs to work
100         if 'linux_bridge' in self.guest_loopback:
101             self._traffic['l2'].update({'srcmac': S.getValue('GUEST_NET2_MAC')[0],
102                                         'dstmac': S.getValue('GUEST_NET1_MAC')[0]})
103             self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
104                                         'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
105
106         # Packet Forwarding mode
107         self._vswitch_none = 'none' == S.getValue('VSWITCH').strip().lower()
108
109     def run(self):
110         """Run the test
111
112         All setup and teardown through controllers is included.
113         """
114         self._logger.debug(self.name)
115
116         # copy sources of l2 forwarding tools into VM shared dir if needed
117         self._copy_fwd_tools_for_guest()
118
119         self._logger.debug("Controllers:")
120         loader = Loader()
121         traffic_ctl = component_factory.create_traffic(
122             self._traffic['traffic_type'],
123             loader.get_trafficgen_class())
124         vnf_ctl = component_factory.create_vnf(
125             self.deployment,
126             loader.get_vnf_class())
127
128         if self._vswitch_none:
129             vswitch_ctl = component_factory.create_pktfwd(
130                 loader.get_pktfwd_class())
131         else:
132             vswitch_ctl = component_factory.create_vswitch(
133                 self.deployment,
134                 loader.get_vswitch_class(),
135                 self._traffic)
136
137         collector = component_factory.create_collector(
138             loader.get_collector_class(),
139             self._results_dir, self.name)
140         loadgen = component_factory.create_loadgen(
141             self._loadgen,
142             self._load_cfg)
143
144         self._logger.debug("Setup:")
145         with vswitch_ctl, loadgen:
146             with vnf_ctl, collector:
147                 if not self._vswitch_none:
148                     self._add_flows(vswitch_ctl)
149
150                 with traffic_ctl:
151                     traffic_ctl.send_traffic(self._traffic)
152
153                 # dump vswitch flows before they are affected by VNF termination
154                 if not self._vswitch_none:
155                     vswitch_ctl.dump_vswitch_flows()
156
157         self._logger.debug("Traffic Results:")
158         traffic_ctl.print_results()
159
160         self._logger.debug("Collector Results:")
161         collector.print_results()
162
163         output_file = os.path.join(self._results_dir, "result_" + self.name +
164                                    "_" + self.deployment + ".csv")
165
166         tc_results = self._append_results(traffic_ctl.get_results())
167         TestCase._write_result_to_file(tc_results, output_file)
168
169         report.generate(output_file, tc_results, collector.get_results())
170
171     def _append_results(self, results):
172         """
173         Method appends mandatory Test Case results to list of dictionaries.
174
175         :param results: list of dictionaries which contains results from
176                 traffic generator.
177
178         :returns: modified list of dictionaries.
179         """
180         for item in results:
181             item[ResultsConstants.ID] = self.name
182             item[ResultsConstants.DEPLOYMENT] = self.deployment
183             item[ResultsConstants.TRAFFIC_TYPE] = self._traffic['l3']['proto']
184             if self._traffic['multistream']:
185                 item[ResultsConstants.SCAL_STREAM_COUNT] = self._traffic['multistream']
186                 item[ResultsConstants.SCAL_STREAM_TYPE] = self._traffic['stream_type']
187                 item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows']
188             if len(self.guest_loopback):
189                 item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(self.guest_loopback)
190
191         return results
192
193     def _copy_fwd_tools_for_guest(self):
194         """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] for use by guests.
195         """
196         counter = 0
197         # method is executed only for pvp and pvvp, so let's count number of 'v'
198         while counter < self.deployment.count('v'):
199             guest_dir = S.getValue('GUEST_SHARE_DIR')[counter]
200
201             # create shared dir if it doesn't exist
202             if not os.path.exists(guest_dir):
203                 os.makedirs(guest_dir)
204
205             # copy sources into shared dir only if neccessary
206             if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback:
207                 try:
208                     tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
209                                     os.path.join(S.getValue('RTE_SDK'), ''),
210                                     os.path.join(guest_dir, 'DPDK')],
211                                    self._logger,
212                                    'Copying DPDK to shared directory...',
213                                    True)
214                     tasks.run_task(['rsync', '-a', '-r', '-l',
215                                     os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
216                                     os.path.join(guest_dir, 'l2fwd')],
217                                    self._logger,
218                                    'Copying l2fwd to shared directory...',
219                                    True)
220                 except subprocess.CalledProcessError:
221                     self._logger.error('Unable to copy DPDK and l2fwd to shared directory')
222
223             counter += 1
224
225
226     @staticmethod
227     def _write_result_to_file(results, output):
228         """Write list of dictionaries to a CSV file.
229
230         Each element on list will create separate row in output file.
231         If output file already exists, data will be appended at the end,
232         otherwise it will be created.
233
234         :param results: list of dictionaries.
235         :param output: path to output file.
236         """
237         with open(output, 'a') as csvfile:
238
239             logging.info("Write results to file: " + output)
240             fieldnames = TestCase._get_unique_keys(results)
241
242             writer = csv.DictWriter(csvfile, fieldnames)
243
244             if not csvfile.tell():  # file is now empty
245                 writer.writeheader()
246
247             for result in results:
248                 writer.writerow(result)
249
250
251     @staticmethod
252     def _get_unique_keys(list_of_dicts):
253         """Gets unique key values as ordered list of strings in given dicts
254
255         :param list_of_dicts: list of dictionaries.
256
257         :returns: list of unique keys(strings).
258         """
259         result = OrderedDict()
260         for item in list_of_dicts:
261             for key in item.keys():
262                 result[key] = ''
263
264         return list(result.keys())
265
266
267     def _add_flows(vswitch_ctl):
268         """Add flows to the vswitch
269
270         :param vswitch_ctl vswitch controller
271         """
272         vswitch = vswitch_ctl.get_vswitch()
273         # TODO BOM 15-08-07 the frame mod code assumes that the
274         # physical ports are ports 1 & 2. The actual numbers
275         # need to be retrived from the vSwitch and the metadata value
276         # updated accordingly.
277         bridge = S.getValue('VSWITCH_BRIDGE_NAME')
278         if self._frame_mod == "vlan":
279             # 0x8100 => VLAN ethertype
280             self._logger.debug(" ****   VLAN   ***** ")
281             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
282                     'actions': ['push_vlan:0x8100', 'goto_table:3']}
283             vswitch.add_flow(bridge, flow)
284             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
285                     'actions': ['push_vlan:0x8100', 'goto_table:3']}
286             vswitch.add_flow(bridge, flow)
287         elif self._frame_mod == "mpls":
288             # 0x8847 => MPLS unicast ethertype
289             self._logger.debug(" ****   MPLS  ***** ")
290             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
291                     'actions': ['push_mpls:0x8847', 'goto_table:3']}
292             vswitch.add_flow(bridge, flow)
293             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
294                     'actions': ['push_mpls:0x8847', 'goto_table:3']}
295             vswitch.add_flow(bridge, flow)
296         elif self._frame_mod == "mac":
297             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
298                     'actions': ['mod_dl_src:22:22:22:22:22:22',
299                                 'goto_table:3']}
300             vswitch.add_flow(bridge, flow)
301             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
302                     'actions': ['mod_dl_src:11:11:11:11:11:11',
303                                 'goto_table:3']}
304             vswitch.add_flow(bridge, flow)
305         elif self._frame_mod == "dscp":
306             # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
307             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
308                     'dl_type':'0x0800',
309                     'actions': ['mod_nw_tos:184', 'goto_table:3']}
310             vswitch.add_flow(bridge, flow)
311             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
312                     'dl_type':'0x0800',
313                     'actions': ['mod_nw_tos:184', 'goto_table:3']}
314             vswitch.add_flow(bridge, flow)
315         elif self._frame_mod == "ttl":
316             # 251 and 241 are the highest prime numbers < 255
317             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
318                     'dl_type':'0x0800',
319                     'actions': ['mod_nw_ttl:251', 'goto_table:3']}
320             vswitch.add_flow(bridge, flow)
321             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
322                     'dl_type':'0x0800',
323                     'actions': ['mod_nw_ttl:241', 'goto_table:3']}
324             vswitch.add_flow(bridge, flow)
325         elif self._frame_mod == "ip_addr":
326             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
327                     'dl_type':'0x0800',
328                     'actions': ['mod_nw_src:10.10.10.10',
329                                 'mod_nw_dst:20.20.20.20',
330                                 'goto_table:3']}
331             vswitch.add_flow(bridge, flow)
332             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
333                     'dl_type':'0x0800',
334                     'actions': ['mod_nw_src:20.20.20.20',
335                                 'mod_nw_dst:10.10.10.10',
336                                 'goto_table:3']}
337             vswitch.add_flow(bridge, flow)
338         elif self._frame_mod == "ip_port":
339             # TODO BOM 15-08-27 The traffic generated is assumed
340             # to be UDP (nw_proto 17d) which is the default case but
341             # we will need to pick up the actual traffic params in use.
342             flow = {'table':'2', 'priority':'1000', 'metadata':'2',
343                     'dl_type':'0x0800', 'nw_proto':'17',
344                     'actions': ['mod_tp_src:44444',
345                                 'mod_tp_dst:44444', 'goto_table:3']}
346             vswitch.add_flow(bridge, flow)
347             flow = {'table':'2', 'priority':'1000', 'metadata':'1',
348                     'dl_type':'0x0800', 'nw_proto':'17',
349                     'actions': ['mod_tp_src:44444',
350                                 'mod_tp_dst:44444', 'goto_table:3']}
351             vswitch.add_flow(bridge, flow)
352         else:
353             pass