1 # Copyright 2015 Intel Corporation.
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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
22 from collections import OrderedDict
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
33 class TestCase(object):
34 """TestCase base class
36 In this basic form runs RFC2544 throughput test
38 def __init__(self, cfg, results_dir):
39 """Pull out fields from test config
41 :param cfg: A dictionary of string-value pairs describing the test
42 configuration. Both the key and values strings use well-known
44 :param results_dir: Where the csv formatted results are written.
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)
53 framerate = cfg.get('iLoad', 100)
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)
60 self.guest_loopback.append(guest_loopback)
62 if self.deployment == 'pvp':
63 self.guest_loopback.append(S.getValue('GUEST_LOOPBACK')[0])
65 self.guest_loopback = S.getValue('GUEST_LOOPBACK').copy()
67 # read configuration of streams; CLI parameter takes precedence to
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
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']
81 # background load is not requested, so use dummy implementation
82 self._loadgen = "Dummy"
85 self._frame_mod = self._frame_mod.lower()
86 self._results_dir = results_dir
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)})
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')})
108 All setup and teardown through controllers is included.
110 self._logger.debug(self.name)
112 # copy sources of l2 forwarding tools into VM shared dir if needed
113 self._copy_fwd_tools_for_guest()
115 self._logger.debug("Controllers:")
117 traffic_ctl = component_factory.create_traffic(
118 self._traffic['traffic_type'],
119 loader.get_trafficgen_class())
120 vnf_ctl = component_factory.create_vnf(
122 loader.get_vnf_class())
123 vswitch_ctl = component_factory.create_vswitch(
125 loader.get_vswitch_class(),
127 collector = component_factory.create_collector(
128 loader.get_collector_class(),
129 self._results_dir, self.name)
130 loadgen = component_factory.create_loadgen(
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',
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',
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',
174 'actions': ['mod_nw_tos:184', 'goto_table:3']}
175 vswitch.add_flow(bridge, flow)
176 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
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',
184 'actions': ['mod_nw_ttl:251', 'goto_table:3']}
185 vswitch.add_flow(bridge, flow)
186 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
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',
193 'actions': ['mod_nw_src:10.10.10.10',
194 'mod_nw_dst:20.20.20.20',
196 vswitch.add_flow(bridge, flow)
197 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
199 'actions': ['mod_nw_src:20.20.20.20',
200 'mod_nw_dst:10.10.10.10',
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)
221 traffic_ctl.send_traffic(self._traffic)
223 # dump vswitch flows before they are affected by VNF termination
224 vswitch_ctl.dump_vswitch_flows()
226 self._logger.debug("Traffic Results:")
227 traffic_ctl.print_results()
229 self._logger.debug("Collector Results:")
230 collector.print_results()
232 output_file = os.path.join(self._results_dir, "result_" + self.name +
233 "_" + self.deployment + ".csv")
235 tc_results = self._append_results(traffic_ctl.get_results())
236 TestCase._write_result_to_file(tc_results, output_file)
238 report.generate(output_file, tc_results, collector.get_results())
240 def _append_results(self, results):
242 Method appends mandatory Test Case results to list of dictionaries.
244 :param results: list of dictionaries which contains results from
247 :returns: modified list of dictionaries.
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)
262 def _copy_fwd_tools_for_guest(self):
263 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] for use by guests.
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]
270 # create shared dir if it doesn't exist
271 if not os.path.exists(guest_dir):
272 os.makedirs(guest_dir)
274 # copy sources into shared dir only if neccessary
275 if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback:
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')],
281 'Copying DPDK to shared directory...',
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')],
287 'Copying l2fwd to shared directory...',
289 except subprocess.CalledProcessError:
290 self._logger.error('Unable to copy DPDK and l2fwd to shared directory')
296 def _write_result_to_file(results, output):
297 """Write list of dictionaries to a CSV file.
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.
303 :param results: list of dictionaries.
304 :param output: path to output file.
306 with open(output, 'a') as csvfile:
308 logging.info("Write results to file: " + output)
309 fieldnames = TestCase._get_unique_keys(results)
311 writer = csv.DictWriter(csvfile, fieldnames)
313 if not csvfile.tell(): # file is now empty
316 for result in results:
317 writer.writerow(result)
321 def _get_unique_keys(list_of_dicts):
322 """Gets unique key values as ordered list of strings in given dicts
324 :param list_of_dicts: list of dictionaries.
326 :returns: list of unique keys(strings).
328 result = OrderedDict()
329 for item in list_of_dicts:
330 for key in item.keys():
333 return list(result.keys())