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', False)
56 bidirectional = get_test_param('bidirectional', bidirectional)
58 traffic_type = cfg.get('Traffic Type', 'rfc2544')
59 traffic_type = get_test_param('traffic_type', traffic_type)
61 framerate = cfg.get('iLoad', 100)
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', 0)
95 multistream = get_test_param('multistream', multistream)
96 stream_type = cfg.get('Stream Type', 'L4')
97 stream_type = get_test_param('stream_type', stream_type)
98 pre_installed_flows = cfg.get('Pre-installed Flows', 'No')
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', 'port'),
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 # create shared dir if it doesn't exist
268 if not os.path.exists(guest_dir):
269 os.makedirs(guest_dir)
271 # copy sources into shared dir only if neccessary
272 if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback:
274 tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
275 os.path.join(S.getValue('RTE_SDK'), ''),
276 os.path.join(guest_dir, 'DPDK')],
278 'Copying DPDK to shared directory...',
280 tasks.run_task(['rsync', '-a', '-r', '-l',
281 os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
282 os.path.join(guest_dir, 'l2fwd')],
284 'Copying l2fwd to shared directory...',
286 except subprocess.CalledProcessError:
287 self._logger.error('Unable to copy DPDK and l2fwd to shared directory')
291 def _mount_hugepages(self):
292 """Mount hugepages if usage of DPDK or Qemu is detected
294 # hugepages are needed by DPDK and Qemu
295 if not self._hugepages_mounted and \
296 (self.deployment.count('v') or \
297 S.getValue('VSWITCH').lower().count('dpdk') or \
299 hugepages.mount_hugepages()
300 self._hugepages_mounted = True
302 def _umount_hugepages(self):
303 """Umount hugepages if they were mounted before
305 if self._hugepages_mounted:
306 hugepages.umount_hugepages()
307 self._hugepages_mounted = False
310 def _write_result_to_file(results, output):
311 """Write list of dictionaries to a CSV file.
313 Each element on list will create separate row in output file.
314 If output file already exists, data will be appended at the end,
315 otherwise it will be created.
317 :param results: list of dictionaries.
318 :param output: path to output file.
320 with open(output, 'a') as csvfile:
322 logging.info("Write results to file: " + output)
323 fieldnames = TestCase._get_unique_keys(results)
325 writer = csv.DictWriter(csvfile, fieldnames)
327 if not csvfile.tell(): # file is now empty
330 for result in results:
331 writer.writerow(result)
335 def _get_unique_keys(list_of_dicts):
336 """Gets unique key values as ordered list of strings in given dicts
338 :param list_of_dicts: list of dictionaries.
340 :returns: list of unique keys(strings).
342 result = OrderedDict()
343 for item in list_of_dicts:
344 for key in item.keys():
347 return list(result.keys())
350 def _add_flows(self, vswitch_ctl):
351 """Add flows to the vswitch
353 :param vswitch_ctl vswitch controller
355 vswitch = vswitch_ctl.get_vswitch()
356 # TODO BOM 15-08-07 the frame mod code assumes that the
357 # physical ports are ports 1 & 2. The actual numbers
358 # need to be retrived from the vSwitch and the metadata value
359 # updated accordingly.
360 bridge = S.getValue('VSWITCH_BRIDGE_NAME')
361 if self._frame_mod == "vlan":
362 # 0x8100 => VLAN ethertype
363 self._logger.debug(" **** VLAN ***** ")
364 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
365 'actions': ['push_vlan:0x8100', 'goto_table:3']}
366 vswitch.add_flow(bridge, flow)
367 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
368 'actions': ['push_vlan:0x8100', 'goto_table:3']}
369 vswitch.add_flow(bridge, flow)
370 elif self._frame_mod == "mpls":
371 # 0x8847 => MPLS unicast ethertype
372 self._logger.debug(" **** MPLS ***** ")
373 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
374 'actions': ['push_mpls:0x8847', 'goto_table:3']}
375 vswitch.add_flow(bridge, flow)
376 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
377 'actions': ['push_mpls:0x8847', 'goto_table:3']}
378 vswitch.add_flow(bridge, flow)
379 elif self._frame_mod == "mac":
380 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
381 'actions': ['mod_dl_src:22:22:22:22:22:22',
383 vswitch.add_flow(bridge, flow)
384 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
385 'actions': ['mod_dl_src:11:11:11:11:11:11',
387 vswitch.add_flow(bridge, flow)
388 elif self._frame_mod == "dscp":
389 # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
390 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
392 'actions': ['mod_nw_tos:184', 'goto_table:3']}
393 vswitch.add_flow(bridge, flow)
394 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
396 'actions': ['mod_nw_tos:184', 'goto_table:3']}
397 vswitch.add_flow(bridge, flow)
398 elif self._frame_mod == "ttl":
399 # 251 and 241 are the highest prime numbers < 255
400 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
402 'actions': ['mod_nw_ttl:251', 'goto_table:3']}
403 vswitch.add_flow(bridge, flow)
404 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
406 'actions': ['mod_nw_ttl:241', 'goto_table:3']}
407 vswitch.add_flow(bridge, flow)
408 elif self._frame_mod == "ip_addr":
409 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
411 'actions': ['mod_nw_src:10.10.10.10',
412 'mod_nw_dst:20.20.20.20',
414 vswitch.add_flow(bridge, flow)
415 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
417 'actions': ['mod_nw_src:20.20.20.20',
418 'mod_nw_dst:10.10.10.10',
420 vswitch.add_flow(bridge, flow)
421 elif self._frame_mod == "ip_port":
422 # TODO BOM 15-08-27 The traffic generated is assumed
423 # to be UDP (nw_proto 17d) which is the default case but
424 # we will need to pick up the actual traffic params in use.
425 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
426 'dl_type':'0x0800', 'nw_proto':'17',
427 'actions': ['mod_tp_src:44444',
428 'mod_tp_dst:44444', 'goto_table:3']}
429 vswitch.add_flow(bridge, flow)
430 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
431 'dl_type':'0x0800', 'nw_proto':'17',
432 'actions': ['mod_tp_src:44444',
433 'mod_tp_dst:44444', 'goto_table:3']}
434 vswitch.add_flow(bridge, flow)