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