1 # Copyright 2015-2016 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
23 from collections import OrderedDict
25 from core.results.results_constants import ResultsConstants
26 import core.component_factory as component_factory
27 from core.loader import Loader
28 from tools import tasks
29 from tools import hugepages
30 from tools.report import report
31 from tools.pkt_gen.trafficgen.trafficgenhelper import TRAFFIC_DEFAULTS
32 from conf import settings as S
33 from conf import get_test_param
35 class TestCase(object):
36 """TestCase base class
38 In this basic form runs RFC2544 throughput test
40 def __init__(self, cfg, results_dir, performance_test=True):
41 """Pull out fields from test config
43 :param cfg: A dictionary of string-value pairs describing the test
44 configuration. Both the key and values strings use well-known
46 :param results_dir: Where the csv formatted results are written.
48 self._hugepages_mounted = False
50 # set test parameters; CLI options take precedence to testcase settings
51 self._logger = logging.getLogger(__name__)
52 self.name = cfg['Name']
53 self.desc = cfg.get('Description', 'No description given.')
55 bidirectional = cfg.get('biDirectional', TRAFFIC_DEFAULTS['bidir'])
56 bidirectional = get_test_param('bidirectional', bidirectional)
58 traffic_type = cfg.get('Traffic Type', TRAFFIC_DEFAULTS['traffic_type'])
59 traffic_type = get_test_param('traffic_type', traffic_type)
61 framerate = cfg.get('iLoad', TRAFFIC_DEFAULTS['frame_rate'])
62 framerate = get_test_param('iload', framerate)
64 self.deployment = cfg['Deployment']
65 self._frame_mod = cfg.get('Frame Modification', None)
66 self._performance_test = performance_test
68 self._tunnel_type = None
69 self._tunnel_operation = None
71 if self.deployment == 'op2p':
72 self._tunnel_operation = cfg['Tunnel Operation']
74 if 'Tunnel Type' in cfg:
75 self._tunnel_type = cfg['Tunnel Type']
76 self._tunnel_type = get_test_param('tunnel_type',
80 # identify guest loopback method, so it can be added into reports
81 self.guest_loopback = []
82 if self.deployment in ['pvp', 'pvvp']:
83 guest_loopback = get_test_param('guest_loopback', None)
85 self.guest_loopback.append(guest_loopback)
87 if self.deployment == 'pvp':
88 self.guest_loopback.append(S.getValue('GUEST_LOOPBACK')[0])
90 self.guest_loopback = S.getValue('GUEST_LOOPBACK').copy()
92 # read configuration of streams; CLI parameter takes precedence to
94 multistream = cfg.get('MultiStream', TRAFFIC_DEFAULTS['multistream'])
95 multistream = get_test_param('multistream', multistream)
96 stream_type = cfg.get('Stream Type', TRAFFIC_DEFAULTS['stream_type'])
97 stream_type = get_test_param('stream_type', stream_type)
98 pre_installed_flows = cfg.get('Pre-installed Flows', TRAFFIC_DEFAULTS['pre_installed_flows'])
99 pre_installed_flows = get_test_param('pre-installed_flows', pre_installed_flows)
101 # check if test requires background load and which generator it uses
102 self._load_cfg = cfg.get('Load', None)
103 if self._load_cfg and 'tool' in self._load_cfg:
104 self._loadgen = self._load_cfg['tool']
106 # background load is not requested, so use dummy implementation
107 self._loadgen = "Dummy"
110 self._frame_mod = self._frame_mod.lower()
111 self._results_dir = results_dir
113 # set traffic details, so they can be passed to vswitch and traffic ctls
114 self._traffic = copy.deepcopy(TRAFFIC_DEFAULTS)
115 self._traffic.update({'traffic_type': traffic_type,
116 'flow_type': cfg.get('Flow Type', TRAFFIC_DEFAULTS['flow_type']),
117 'bidir': bidirectional,
118 'tunnel_type': self._tunnel_type,
119 'multistream': int(multistream),
120 'stream_type': stream_type,
121 'pre_installed_flows' : pre_installed_flows,
122 'frame_rate': int(framerate)})
124 # OVS Vanilla requires guest VM MAC address and IPs to work
125 if 'linux_bridge' in self.guest_loopback:
126 self._traffic['l2'].update({'srcmac': S.getValue('GUEST_NET2_MAC')[0],
127 'dstmac': S.getValue('GUEST_NET1_MAC')[0]})
128 self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
129 'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
131 # Packet Forwarding mode
132 self._vswitch_none = 'none' == S.getValue('VSWITCH').strip().lower()
137 All setup and teardown through controllers is included.
139 self._logger.debug(self.name)
141 # mount hugepages if needed
142 self._mount_hugepages()
144 # copy sources of l2 forwarding tools into VM shared dir if needed
145 self._copy_fwd_tools_for_guest()
147 if self.deployment == "op2p":
148 self._traffic['l2'].update({'srcmac':
149 S.getValue('TRAFFICGEN_PORT1_MAC'),
151 S.getValue('TRAFFICGEN_PORT2_MAC')})
153 self._traffic['l3'].update({'srcip':
154 S.getValue('TRAFFICGEN_PORT1_IP'),
156 S.getValue('TRAFFICGEN_PORT2_IP')})
158 if self._tunnel_operation == "decapsulation":
159 self._traffic['l2'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L2')
160 self._traffic['l3'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L3')
161 self._traffic['l4'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L4')
164 self._logger.debug("Controllers:")
166 traffic_ctl = component_factory.create_traffic(
167 self._traffic['traffic_type'],
168 loader.get_trafficgen_class())
169 vnf_ctl = component_factory.create_vnf(
171 loader.get_vnf_class())
173 if self._vswitch_none:
174 vswitch_ctl = component_factory.create_pktfwd(
175 loader.get_pktfwd_class())
177 vswitch_ctl = component_factory.create_vswitch(
179 loader.get_vswitch_class(),
181 self._tunnel_operation)
183 collector = component_factory.create_collector(
184 loader.get_collector_class(),
185 self._results_dir, self.name)
186 loadgen = component_factory.create_loadgen(
190 self._logger.debug("Setup:")
191 with vswitch_ctl, loadgen:
192 with vnf_ctl, collector:
193 if not self._vswitch_none:
194 self._add_flows(vswitch_ctl)
196 # run traffic generator if requested, otherwise wait for manual termination
197 if S.getValue('mode') == 'trafficgen-off':
199 self._logger.debug("All is set. Please run traffic generator manually.")
200 input(os.linesep + "Press Enter to terminate vswitchperf..." + os.linesep + os.linesep)
202 if S.getValue('mode') == 'trafficgen-pause':
204 true_vals = ('yes', 'y', 'ye', None)
206 choice = input(os.linesep + 'Transmission paused, should'
207 ' transmission be resumed? ' + os.linesep).lower()
208 if not choice or choice not in true_vals:
209 print('Please respond with \'yes\' or \'y\' ', end='')
213 traffic_ctl.send_traffic(self._traffic)
215 # dump vswitch flows before they are affected by VNF termination
216 if not self._vswitch_none:
217 vswitch_ctl.dump_vswitch_flows()
219 # umount hugepages if mounted
220 self._umount_hugepages()
222 self._logger.debug("Collector Results:")
223 collector.print_results()
225 if S.getValue('mode') != 'trafficgen-off':
226 self._logger.debug("Traffic Results:")
227 traffic_ctl.print_results()
229 output_file = os.path.join(self._results_dir, "result_" + self.name +
230 "_" + self.deployment + ".csv")
232 tc_results = self._append_results(traffic_ctl.get_results())
233 TestCase._write_result_to_file(tc_results, output_file)
235 report.generate(output_file, tc_results, collector.get_results(), self._performance_test)
237 def _append_results(self, results):
239 Method appends mandatory Test Case results to list of dictionaries.
241 :param results: list of dictionaries which contains results from
244 :returns: modified list of dictionaries.
247 item[ResultsConstants.ID] = self.name
248 item[ResultsConstants.DEPLOYMENT] = self.deployment
249 item[ResultsConstants.TRAFFIC_TYPE] = self._traffic['l3']['proto']
250 if self._traffic['multistream']:
251 item[ResultsConstants.SCAL_STREAM_COUNT] = self._traffic['multistream']
252 item[ResultsConstants.SCAL_STREAM_TYPE] = self._traffic['stream_type']
253 item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows']
254 if len(self.guest_loopback):
255 item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(self.guest_loopback)
259 def _copy_fwd_tools_for_guest(self):
260 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] for use by guests.
263 # method is executed only for pvp and pvvp, so let's count number of 'v'
264 while counter < self.deployment.count('v'):
265 guest_dir = S.getValue('GUEST_SHARE_DIR')[counter]
267 # remove shared dir if it exists to avoid issues with file consistency
268 if os.path.exists(guest_dir):
269 tasks.run_task(['rm', '-f', '-r', guest_dir], self._logger,
270 'Removing content of shared directory...', True)
272 # directory to share files between host and guest
273 os.makedirs(guest_dir)
275 # copy sources into shared dir only if neccessary
276 if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback:
278 tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
279 os.path.join(S.getValue('RTE_SDK'), ''),
280 os.path.join(guest_dir, 'DPDK')],
282 'Copying DPDK to shared directory...',
284 tasks.run_task(['rsync', '-a', '-r', '-l',
285 os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
286 os.path.join(guest_dir, 'l2fwd')],
288 'Copying l2fwd to shared directory...',
290 except subprocess.CalledProcessError:
291 self._logger.error('Unable to copy DPDK and l2fwd to shared directory')
295 def _mount_hugepages(self):
296 """Mount hugepages if usage of DPDK or Qemu is detected
298 # hugepages are needed by DPDK and Qemu
299 if not self._hugepages_mounted and \
300 (self.deployment.count('v') or \
301 S.getValue('VSWITCH').lower().count('dpdk') or \
303 hugepages.mount_hugepages()
304 self._hugepages_mounted = True
306 def _umount_hugepages(self):
307 """Umount hugepages if they were mounted before
309 if self._hugepages_mounted:
310 hugepages.umount_hugepages()
311 self._hugepages_mounted = False
314 def _write_result_to_file(results, output):
315 """Write list of dictionaries to a CSV file.
317 Each element on list will create separate row in output file.
318 If output file already exists, data will be appended at the end,
319 otherwise it will be created.
321 :param results: list of dictionaries.
322 :param output: path to output file.
324 with open(output, 'a') as csvfile:
326 logging.info("Write results to file: " + output)
327 fieldnames = TestCase._get_unique_keys(results)
329 writer = csv.DictWriter(csvfile, fieldnames)
331 if not csvfile.tell(): # file is now empty
334 for result in results:
335 writer.writerow(result)
339 def _get_unique_keys(list_of_dicts):
340 """Gets unique key values as ordered list of strings in given dicts
342 :param list_of_dicts: list of dictionaries.
344 :returns: list of unique keys(strings).
346 result = OrderedDict()
347 for item in list_of_dicts:
348 for key in item.keys():
351 return list(result.keys())
354 def _add_flows(self, vswitch_ctl):
355 """Add flows to the vswitch
357 :param vswitch_ctl vswitch controller
359 vswitch = vswitch_ctl.get_vswitch()
360 # TODO BOM 15-08-07 the frame mod code assumes that the
361 # physical ports are ports 1 & 2. The actual numbers
362 # need to be retrived from the vSwitch and the metadata value
363 # updated accordingly.
364 bridge = S.getValue('VSWITCH_BRIDGE_NAME')
365 if self._frame_mod == "vlan":
366 # 0x8100 => VLAN ethertype
367 self._logger.debug(" **** VLAN ***** ")
368 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
369 'actions': ['push_vlan:0x8100', 'goto_table:3']}
370 vswitch.add_flow(bridge, flow)
371 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
372 'actions': ['push_vlan:0x8100', 'goto_table:3']}
373 vswitch.add_flow(bridge, flow)
374 elif self._frame_mod == "mpls":
375 # 0x8847 => MPLS unicast ethertype
376 self._logger.debug(" **** MPLS ***** ")
377 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
378 'actions': ['push_mpls:0x8847', 'goto_table:3']}
379 vswitch.add_flow(bridge, flow)
380 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
381 'actions': ['push_mpls:0x8847', 'goto_table:3']}
382 vswitch.add_flow(bridge, flow)
383 elif self._frame_mod == "mac":
384 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
385 'actions': ['mod_dl_src:22:22:22:22:22:22',
387 vswitch.add_flow(bridge, flow)
388 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
389 'actions': ['mod_dl_src:11:11:11:11:11:11',
391 vswitch.add_flow(bridge, flow)
392 elif self._frame_mod == "dscp":
393 # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
394 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
396 'actions': ['mod_nw_tos:184', 'goto_table:3']}
397 vswitch.add_flow(bridge, flow)
398 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
400 'actions': ['mod_nw_tos:184', 'goto_table:3']}
401 vswitch.add_flow(bridge, flow)
402 elif self._frame_mod == "ttl":
403 # 251 and 241 are the highest prime numbers < 255
404 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
406 'actions': ['mod_nw_ttl:251', 'goto_table:3']}
407 vswitch.add_flow(bridge, flow)
408 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
410 'actions': ['mod_nw_ttl:241', 'goto_table:3']}
411 vswitch.add_flow(bridge, flow)
412 elif self._frame_mod == "ip_addr":
413 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
415 'actions': ['mod_nw_src:10.10.10.10',
416 'mod_nw_dst:20.20.20.20',
418 vswitch.add_flow(bridge, flow)
419 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
421 'actions': ['mod_nw_src:20.20.20.20',
422 'mod_nw_dst:10.10.10.10',
424 vswitch.add_flow(bridge, flow)
425 elif self._frame_mod == "ip_port":
426 # TODO BOM 15-08-27 The traffic generated is assumed
427 # to be UDP (nw_proto 17d) which is the default case but
428 # we will need to pick up the actual traffic params in use.
429 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
430 'dl_type':'0x0800', 'nw_proto':'17',
431 'actions': ['mod_tp_src:44444',
432 'mod_tp_dst:44444', 'goto_table:3']}
433 vswitch.add_flow(bridge, flow)
434 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
435 'dl_type':'0x0800', 'nw_proto':'17',
436 'actions': ['mod_tp_src:44444',
437 'mod_tp_dst:44444', 'goto_table:3']}
438 vswitch.add_flow(bridge, flow)