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)
203 traffic_ctl.send_traffic(self._traffic)
205 # dump vswitch flows before they are affected by VNF termination
206 if not self._vswitch_none:
207 vswitch_ctl.dump_vswitch_flows()
209 # umount hugepages if mounted
210 self._umount_hugepages()
212 self._logger.debug("Collector Results:")
213 collector.print_results()
215 if S.getValue('mode') != 'trafficgen-off':
216 self._logger.debug("Traffic Results:")
217 traffic_ctl.print_results()
219 output_file = os.path.join(self._results_dir, "result_" + self.name +
220 "_" + self.deployment + ".csv")
222 tc_results = self._append_results(traffic_ctl.get_results())
223 TestCase._write_result_to_file(tc_results, output_file)
225 report.generate(output_file, tc_results, collector.get_results(), self._performance_test)
227 def _append_results(self, results):
229 Method appends mandatory Test Case results to list of dictionaries.
231 :param results: list of dictionaries which contains results from
234 :returns: modified list of dictionaries.
237 item[ResultsConstants.ID] = self.name
238 item[ResultsConstants.DEPLOYMENT] = self.deployment
239 item[ResultsConstants.TRAFFIC_TYPE] = self._traffic['l3']['proto']
240 if self._traffic['multistream']:
241 item[ResultsConstants.SCAL_STREAM_COUNT] = self._traffic['multistream']
242 item[ResultsConstants.SCAL_STREAM_TYPE] = self._traffic['stream_type']
243 item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows']
244 if len(self.guest_loopback):
245 item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(self.guest_loopback)
249 def _copy_fwd_tools_for_guest(self):
250 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] for use by guests.
253 # method is executed only for pvp and pvvp, so let's count number of 'v'
254 while counter < self.deployment.count('v'):
255 guest_dir = S.getValue('GUEST_SHARE_DIR')[counter]
257 # create shared dir if it doesn't exist
258 if not os.path.exists(guest_dir):
259 os.makedirs(guest_dir)
261 # copy sources into shared dir only if neccessary
262 if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback:
264 tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
265 os.path.join(S.getValue('RTE_SDK'), ''),
266 os.path.join(guest_dir, 'DPDK')],
268 'Copying DPDK to shared directory...',
270 tasks.run_task(['rsync', '-a', '-r', '-l',
271 os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
272 os.path.join(guest_dir, 'l2fwd')],
274 'Copying l2fwd to shared directory...',
276 except subprocess.CalledProcessError:
277 self._logger.error('Unable to copy DPDK and l2fwd to shared directory')
281 def _mount_hugepages(self):
282 """Mount hugepages if usage of DPDK or Qemu is detected
284 # hugepages are needed by DPDK and Qemu
285 if not self._hugepages_mounted and \
286 (self.deployment.count('v') or \
287 S.getValue('VSWITCH').lower().count('dpdk') or \
289 hugepages.mount_hugepages()
290 self._hugepages_mounted = True
292 def _umount_hugepages(self):
293 """Umount hugepages if they were mounted before
295 if self._hugepages_mounted:
296 hugepages.umount_hugepages()
297 self._hugepages_mounted = False
300 def _write_result_to_file(results, output):
301 """Write list of dictionaries to a CSV file.
303 Each element on list will create separate row in output file.
304 If output file already exists, data will be appended at the end,
305 otherwise it will be created.
307 :param results: list of dictionaries.
308 :param output: path to output file.
310 with open(output, 'a') as csvfile:
312 logging.info("Write results to file: " + output)
313 fieldnames = TestCase._get_unique_keys(results)
315 writer = csv.DictWriter(csvfile, fieldnames)
317 if not csvfile.tell(): # file is now empty
320 for result in results:
321 writer.writerow(result)
325 def _get_unique_keys(list_of_dicts):
326 """Gets unique key values as ordered list of strings in given dicts
328 :param list_of_dicts: list of dictionaries.
330 :returns: list of unique keys(strings).
332 result = OrderedDict()
333 for item in list_of_dicts:
334 for key in item.keys():
337 return list(result.keys())
340 def _add_flows(self, vswitch_ctl):
341 """Add flows to the vswitch
343 :param vswitch_ctl vswitch controller
345 vswitch = vswitch_ctl.get_vswitch()
346 # TODO BOM 15-08-07 the frame mod code assumes that the
347 # physical ports are ports 1 & 2. The actual numbers
348 # need to be retrived from the vSwitch and the metadata value
349 # updated accordingly.
350 bridge = S.getValue('VSWITCH_BRIDGE_NAME')
351 if self._frame_mod == "vlan":
352 # 0x8100 => VLAN ethertype
353 self._logger.debug(" **** VLAN ***** ")
354 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
355 'actions': ['push_vlan:0x8100', 'goto_table:3']}
356 vswitch.add_flow(bridge, flow)
357 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
358 'actions': ['push_vlan:0x8100', 'goto_table:3']}
359 vswitch.add_flow(bridge, flow)
360 elif self._frame_mod == "mpls":
361 # 0x8847 => MPLS unicast ethertype
362 self._logger.debug(" **** MPLS ***** ")
363 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
364 'actions': ['push_mpls:0x8847', 'goto_table:3']}
365 vswitch.add_flow(bridge, flow)
366 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
367 'actions': ['push_mpls:0x8847', 'goto_table:3']}
368 vswitch.add_flow(bridge, flow)
369 elif self._frame_mod == "mac":
370 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
371 'actions': ['mod_dl_src:22:22:22:22:22:22',
373 vswitch.add_flow(bridge, flow)
374 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
375 'actions': ['mod_dl_src:11:11:11:11:11:11',
377 vswitch.add_flow(bridge, flow)
378 elif self._frame_mod == "dscp":
379 # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
380 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
382 'actions': ['mod_nw_tos:184', 'goto_table:3']}
383 vswitch.add_flow(bridge, flow)
384 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
386 'actions': ['mod_nw_tos:184', 'goto_table:3']}
387 vswitch.add_flow(bridge, flow)
388 elif self._frame_mod == "ttl":
389 # 251 and 241 are the highest prime numbers < 255
390 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
392 'actions': ['mod_nw_ttl:251', 'goto_table:3']}
393 vswitch.add_flow(bridge, flow)
394 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
396 'actions': ['mod_nw_ttl:241', 'goto_table:3']}
397 vswitch.add_flow(bridge, flow)
398 elif self._frame_mod == "ip_addr":
399 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
401 'actions': ['mod_nw_src:10.10.10.10',
402 'mod_nw_dst:20.20.20.20',
404 vswitch.add_flow(bridge, flow)
405 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
407 'actions': ['mod_nw_src:20.20.20.20',
408 'mod_nw_dst:10.10.10.10',
410 vswitch.add_flow(bridge, flow)
411 elif self._frame_mod == "ip_port":
412 # TODO BOM 15-08-27 The traffic generated is assumed
413 # to be UDP (nw_proto 17d) which is the default case but
414 # we will need to pick up the actual traffic params in use.
415 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
416 'dl_type':'0x0800', 'nw_proto':'17',
417 'actions': ['mod_tp_src:44444',
418 'mod_tp_dst:44444', 'goto_table:3']}
419 vswitch.add_flow(bridge, flow)
420 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
421 'dl_type':'0x0800', 'nw_proto':'17',
422 'actions': ['mod_tp_src:44444',
423 'mod_tp_dst:44444', 'goto_table:3']}
424 vswitch.add_flow(bridge, flow)