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 = cfg.get('Pre-installed Flows', 'No')
74 pre_installed_flows = get_test_param('pre-installed_flows', pre_installed_flows)
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']
82 # background load is not requested, so use dummy implementation
83 self._loadgen = "Dummy"
86 self._frame_mod = self._frame_mod.lower()
87 self._results_dir = results_dir
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)})
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')})
106 # Packet Forwarding mode
107 self._vswitch_none = 'none' == S.getValue('VSWITCH').strip().lower()
112 All setup and teardown through controllers is included.
114 self._logger.debug(self.name)
116 # copy sources of l2 forwarding tools into VM shared dir if needed
117 self._copy_fwd_tools_for_guest()
119 self._logger.debug("Controllers:")
121 traffic_ctl = component_factory.create_traffic(
122 self._traffic['traffic_type'],
123 loader.get_trafficgen_class())
124 vnf_ctl = component_factory.create_vnf(
126 loader.get_vnf_class())
128 if self._vswitch_none:
129 vswitch_ctl = component_factory.create_pktfwd(
130 loader.get_pktfwd_class())
132 vswitch_ctl = component_factory.create_vswitch(
134 loader.get_vswitch_class(),
137 collector = component_factory.create_collector(
138 loader.get_collector_class(),
139 self._results_dir, self.name)
140 loadgen = component_factory.create_loadgen(
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)
151 traffic_ctl.send_traffic(self._traffic)
153 # dump vswitch flows before they are affected by VNF termination
154 if not self._vswitch_none:
155 vswitch_ctl.dump_vswitch_flows()
157 self._logger.debug("Traffic Results:")
158 traffic_ctl.print_results()
160 self._logger.debug("Collector Results:")
161 collector.print_results()
163 output_file = os.path.join(self._results_dir, "result_" + self.name +
164 "_" + self.deployment + ".csv")
166 tc_results = self._append_results(traffic_ctl.get_results())
167 TestCase._write_result_to_file(tc_results, output_file)
169 report.generate(output_file, tc_results, collector.get_results())
171 def _append_results(self, results):
173 Method appends mandatory Test Case results to list of dictionaries.
175 :param results: list of dictionaries which contains results from
178 :returns: modified list of dictionaries.
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)
193 def _copy_fwd_tools_for_guest(self):
194 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] for use by guests.
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]
201 # create shared dir if it doesn't exist
202 if not os.path.exists(guest_dir):
203 os.makedirs(guest_dir)
205 # copy sources into shared dir only if neccessary
206 if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback:
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')],
212 'Copying DPDK to shared directory...',
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')],
218 'Copying l2fwd to shared directory...',
220 except subprocess.CalledProcessError:
221 self._logger.error('Unable to copy DPDK and l2fwd to shared directory')
227 def _write_result_to_file(results, output):
228 """Write list of dictionaries to a CSV file.
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.
234 :param results: list of dictionaries.
235 :param output: path to output file.
237 with open(output, 'a') as csvfile:
239 logging.info("Write results to file: " + output)
240 fieldnames = TestCase._get_unique_keys(results)
242 writer = csv.DictWriter(csvfile, fieldnames)
244 if not csvfile.tell(): # file is now empty
247 for result in results:
248 writer.writerow(result)
252 def _get_unique_keys(list_of_dicts):
253 """Gets unique key values as ordered list of strings in given dicts
255 :param list_of_dicts: list of dictionaries.
257 :returns: list of unique keys(strings).
259 result = OrderedDict()
260 for item in list_of_dicts:
261 for key in item.keys():
264 return list(result.keys())
267 def _add_flows(vswitch_ctl):
268 """Add flows to the vswitch
270 :param vswitch_ctl vswitch controller
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',
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',
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',
309 'actions': ['mod_nw_tos:184', 'goto_table:3']}
310 vswitch.add_flow(bridge, flow)
311 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
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',
319 'actions': ['mod_nw_ttl:251', 'goto_table:3']}
320 vswitch.add_flow(bridge, flow)
321 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
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',
328 'actions': ['mod_nw_src:10.10.10.10',
329 'mod_nw_dst:20.20.20.20',
331 vswitch.add_flow(bridge, flow)
332 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
334 'actions': ['mod_nw_src:20.20.20.20',
335 'mod_nw_dst:10.10.10.10',
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)