Flow Classification extension
[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 from collections import OrderedDict
21
22 from core.results.results_constants import ResultsConstants
23 import core.component_factory as component_factory
24 from core.loader import Loader
25 from tools.report import report
26 from conf import settings as S
27 from tools.pkt_gen.trafficgen.trafficgenhelper import TRAFFIC_DEFAULTS
28
29 class TestCase(object):
30     """TestCase base class
31
32     In this basic form runs RFC2544 throughput test
33     """
34     def __init__(self, cfg, results_dir):
35         """Pull out fields from test config
36
37         :param cfg: A dictionary of string-value pairs describing the test
38             configuration. Both the key and values strings use well-known
39             values.
40         :param results_dir: Where the csv formatted results are written.
41         """
42         self._logger = logging.getLogger(__name__)
43         self.name = cfg['Name']
44         self.desc = cfg.get('Description', 'No description given.')
45         self.deployment = cfg['Deployment']
46         self._frame_mod = cfg.get('Frame Modification', None)
47
48         # check if test requires background load and which generator it uses
49         self._load_cfg = cfg.get('Load', None)
50         if self._load_cfg and 'tool' in self._load_cfg:
51             self._loadgen = self._load_cfg['tool']
52         else:
53             # background load is not requested, so use dummy implementation
54             self._loadgen = "Dummy"
55
56         if self._frame_mod:
57             self._frame_mod = self._frame_mod.lower()
58         self._results_dir = results_dir
59
60         # set traffic details, so they can be passed to vswitch and traffic ctls
61         self._traffic = TRAFFIC_DEFAULTS.copy()
62         self._traffic.update({'traffic_type': cfg['Traffic Type'],
63                               'flow_type': cfg.get('Flow Type', 'port'),
64                               'bidir': cfg['biDirectional'],
65                               'multistream': cfg.get('MultiStream', 0)})
66
67     def run(self):
68         """Run the test
69
70         All setup and teardown through controllers is included.
71         """
72         self._logger.debug(self.name)
73
74         # OVS Vanilla requires guest VM MAC address and IPs
75         # to work
76         if (self.deployment in ["pvp", "pvvp"] and S.getValue('VSWITCH') == "OvsVanilla"):
77             self._traffic['l2'] = {'srcmac': S.getValue('GUEST_NET2_MAC')[0],
78                                    'dstmac': S.getValue('GUEST_NET1_MAC')[0]}
79             self._traffic['l3'] = {'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
80                                    'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')}
81
82         self._logger.debug("Controllers:")
83         loader = Loader()
84         traffic_ctl = component_factory.create_traffic(
85             self._traffic['traffic_type'],
86             loader.get_trafficgen_class())
87         vnf_ctl = component_factory.create_vnf(
88             self.deployment,
89             loader.get_vnf_class())
90         vswitch_ctl = component_factory.create_vswitch(
91             self.deployment,
92             loader.get_vswitch_class(),
93             self._traffic)
94         collector = component_factory.create_collector(
95             loader.get_collector_class(),
96             self._results_dir, self.name)
97         loadgen = component_factory.create_loadgen(
98             self._loadgen,
99             self._load_cfg)
100
101         self._logger.debug("Setup:")
102         with vswitch_ctl, loadgen:
103             with vnf_ctl, collector:
104                 vswitch = vswitch_ctl.get_vswitch()
105                 # TODO BOM 15-08-07 the frame mod code assumes that the
106                 # physical ports are ports 1 & 2. The actual numbers
107                 # need to be retrived from the vSwitch and the metadata value
108                 # updated accordingly.
109                 bridge = S.getValue('VSWITCH_BRIDGE_NAME')
110                 if self._frame_mod == "vlan":
111                     # 0x8100 => VLAN ethertype
112                     self._logger.debug(" ****   VLAN   ***** ")
113                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
114                             'actions': ['push_vlan:0x8100', 'goto_table:3']}
115                     vswitch.add_flow(bridge, flow)
116                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
117                             'actions': ['push_vlan:0x8100', 'goto_table:3']}
118                     vswitch.add_flow(bridge, flow)
119                 elif self._frame_mod == "mpls":
120                     # 0x8847 => MPLS unicast ethertype
121                     self._logger.debug(" ****   MPLS  ***** ")
122                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
123                             'actions': ['push_mpls:0x8847', 'goto_table:3']}
124                     vswitch.add_flow(bridge, flow)
125                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
126                             'actions': ['push_mpls:0x8847', 'goto_table:3']}
127                     vswitch.add_flow(bridge, flow)
128                 elif self._frame_mod == "mac":
129                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
130                             'actions': ['mod_dl_src:22:22:22:22:22:22',
131                                         'goto_table:3']}
132                     vswitch.add_flow(bridge, flow)
133                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
134                             'actions': ['mod_dl_src:11:11:11:11:11:11',
135                                         'goto_table:3']}
136                     vswitch.add_flow(bridge, flow)
137                 elif self._frame_mod == "dscp":
138                     # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
139                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
140                             'dl_type':'0x0800',
141                             'actions': ['mod_nw_tos:184', 'goto_table:3']}
142                     vswitch.add_flow(bridge, flow)
143                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
144                             'dl_type':'0x0800',
145                             'actions': ['mod_nw_tos:184', 'goto_table:3']}
146                     vswitch.add_flow(bridge, flow)
147                 elif self._frame_mod == "ttl":
148                     # 251 and 241 are the highest prime numbers < 255
149                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
150                             'dl_type':'0x0800',
151                             'actions': ['mod_nw_ttl:251', 'goto_table:3']}
152                     vswitch.add_flow(bridge, flow)
153                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
154                             'dl_type':'0x0800',
155                             'actions': ['mod_nw_ttl:241', 'goto_table:3']}
156                     vswitch.add_flow(bridge, flow)
157                 elif self._frame_mod == "ip_addr":
158                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
159                             'dl_type':'0x0800',
160                             'actions': ['mod_nw_src:10.10.10.10',
161                                         'mod_nw_dst:20.20.20.20',
162                                         'goto_table:3']}
163                     vswitch.add_flow(bridge, flow)
164                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
165                             'dl_type':'0x0800',
166                             'actions': ['mod_nw_src:20.20.20.20',
167                                         'mod_nw_dst:10.10.10.10',
168                                         'goto_table:3']}
169                     vswitch.add_flow(bridge, flow)
170                 elif self._frame_mod == "ip_port":
171                     # TODO BOM 15-08-27 The traffic generated is assumed
172                     # to be UDP (nw_proto 17d) which is the default case but
173                     # we will need to pick up the actual traffic params in use.
174                     flow = {'table':'2', 'priority':'1000', 'metadata':'2',
175                             'dl_type':'0x0800', 'nw_proto':'17',
176                             'actions': ['mod_tp_src:44444',
177                                         'mod_tp_dst:44444', 'goto_table:3']}
178                     vswitch.add_flow(bridge, flow)
179                     flow = {'table':'2', 'priority':'1000', 'metadata':'1',
180                             'dl_type':'0x0800', 'nw_proto':'17',
181                             'actions': ['mod_tp_src:44444',
182                                         'mod_tp_dst:44444', 'goto_table:3']}
183                     vswitch.add_flow(bridge, flow)
184                 else:
185                     pass
186
187                 with traffic_ctl:
188                     traffic_ctl.send_traffic(self._traffic)
189
190                 # dump vswitch flows before they are affected by VNF termination
191                 vswitch_ctl.dump_vswitch_flows()
192
193         self._logger.debug("Traffic Results:")
194         traffic_ctl.print_results()
195
196         self._logger.debug("Collector Results:")
197         collector.print_results()
198
199         output_file = os.path.join(self._results_dir, "result_" + self.name +
200                                    "_" + self.deployment + ".csv")
201
202         tc_results = self._append_results(traffic_ctl.get_results())
203         TestCase._write_result_to_file(tc_results, output_file)
204
205         report.generate(output_file, tc_results, collector.get_results())
206
207     def _append_results(self, results):
208         """
209         Method appends mandatory Test Case results to list of dictionaries.
210
211         :param results: list of dictionaries which contains results from
212                 traffic generator.
213
214         :returns: modified list of dictionaries.
215         """
216         for item in results:
217             item[ResultsConstants.ID] = self.name
218             item[ResultsConstants.DEPLOYMENT] = self.deployment
219
220         return results
221
222
223     @staticmethod
224     def _write_result_to_file(results, output):
225         """Write list of dictionaries to a CSV file.
226
227         Each element on list will create separate row in output file.
228         If output file already exists, data will be appended at the end,
229         otherwise it will be created.
230
231         :param results: list of dictionaries.
232         :param output: path to output file.
233         """
234         with open(output, 'a') as csvfile:
235
236             logging.info("Write results to file: " + output)
237             fieldnames = TestCase._get_unique_keys(results)
238
239             writer = csv.DictWriter(csvfile, fieldnames)
240
241             if not csvfile.tell():  # file is now empty
242                 writer.writeheader()
243
244             for result in results:
245                 writer.writerow(result)
246
247
248     @staticmethod
249     def _get_unique_keys(list_of_dicts):
250         """Gets unique key values as ordered list of strings in given dicts
251
252         :param list_of_dicts: list of dictionaries.
253
254         :returns: list of unique keys(strings).
255         """
256         result = OrderedDict()
257         for item in list_of_dicts:
258             for key in item.keys():
259                 result[key] = ''
260
261         return list(result.keys())