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 conf import settings as S
32 from tools.pkt_gen.trafficgen.trafficgenhelper import TRAFFIC_DEFAULTS
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):
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)
67 # identify guest loopback method, so it can be added into reports
68 self.guest_loopback = []
69 if self.deployment in ['pvp', 'pvvp']:
70 guest_loopback = get_test_param('guest_loopback', None)
72 self.guest_loopback.append(guest_loopback)
74 if self.deployment == 'pvp':
75 self.guest_loopback.append(S.getValue('GUEST_LOOPBACK')[0])
77 self.guest_loopback = S.getValue('GUEST_LOOPBACK').copy()
79 # read configuration of streams; CLI parameter takes precedence to
81 multistream = cfg.get('MultiStream', 0)
82 multistream = get_test_param('multistream', multistream)
83 stream_type = cfg.get('Stream Type', 'L4')
84 stream_type = get_test_param('stream_type', stream_type)
85 pre_installed_flows = cfg.get('Pre-installed Flows', 'No')
86 pre_installed_flows = get_test_param('pre-installed_flows', pre_installed_flows)
88 # check if test requires background load and which generator it uses
89 self._load_cfg = cfg.get('Load', None)
90 if self._load_cfg and 'tool' in self._load_cfg:
91 self._loadgen = self._load_cfg['tool']
93 # background load is not requested, so use dummy implementation
94 self._loadgen = "Dummy"
97 self._frame_mod = self._frame_mod.lower()
98 self._results_dir = results_dir
100 # set traffic details, so they can be passed to vswitch and traffic ctls
101 self._traffic = copy.deepcopy(TRAFFIC_DEFAULTS)
102 self._traffic.update({'traffic_type': traffic_type,
103 'flow_type': cfg.get('Flow Type', 'port'),
104 'bidir': bidirectional,
105 'multistream': int(multistream),
106 'stream_type': stream_type,
107 'pre_installed_flows' : pre_installed_flows,
108 'frame_rate': int(framerate)})
110 # OVS Vanilla requires guest VM MAC address and IPs to work
111 if 'linux_bridge' in self.guest_loopback:
112 self._traffic['l2'].update({'srcmac': S.getValue('GUEST_NET2_MAC')[0],
113 'dstmac': S.getValue('GUEST_NET1_MAC')[0]})
114 self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
115 'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
117 # Packet Forwarding mode
118 self._vswitch_none = 'none' == S.getValue('VSWITCH').strip().lower()
123 All setup and teardown through controllers is included.
125 self._logger.debug(self.name)
127 # mount hugepages if needed
128 self._mount_hugepages()
130 # copy sources of l2 forwarding tools into VM shared dir if needed
131 self._copy_fwd_tools_for_guest()
133 self._logger.debug("Controllers:")
135 traffic_ctl = component_factory.create_traffic(
136 self._traffic['traffic_type'],
137 loader.get_trafficgen_class())
138 vnf_ctl = component_factory.create_vnf(
140 loader.get_vnf_class())
142 if self._vswitch_none:
143 vswitch_ctl = component_factory.create_pktfwd(
144 loader.get_pktfwd_class())
146 vswitch_ctl = component_factory.create_vswitch(
148 loader.get_vswitch_class(),
151 collector = component_factory.create_collector(
152 loader.get_collector_class(),
153 self._results_dir, self.name)
154 loadgen = component_factory.create_loadgen(
158 self._logger.debug("Setup:")
159 with vswitch_ctl, loadgen:
160 with vnf_ctl, collector:
161 if not self._vswitch_none:
162 self._add_flows(vswitch_ctl)
164 # run traffic generator if requested, otherwise wait for manual termination
165 if S.getValue('mode') == 'trafficgen-off':
167 self._logger.debug("All is set. Please run traffic generator manually.")
168 input(os.linesep + "Press Enter to terminate vswitchperf..." + os.linesep + os.linesep)
171 traffic_ctl.send_traffic(self._traffic)
173 # dump vswitch flows before they are affected by VNF termination
174 if not self._vswitch_none:
175 vswitch_ctl.dump_vswitch_flows()
177 # umount hugepages if mounted
178 self._umount_hugepages()
180 self._logger.debug("Collector Results:")
181 collector.print_results()
183 if S.getValue('mode') != 'trafficgen-off':
184 self._logger.debug("Traffic Results:")
185 traffic_ctl.print_results()
187 output_file = os.path.join(self._results_dir, "result_" + self.name +
188 "_" + self.deployment + ".csv")
190 tc_results = self._append_results(traffic_ctl.get_results())
191 TestCase._write_result_to_file(tc_results, output_file)
193 report.generate(output_file, tc_results, collector.get_results())
195 def _append_results(self, results):
197 Method appends mandatory Test Case results to list of dictionaries.
199 :param results: list of dictionaries which contains results from
202 :returns: modified list of dictionaries.
205 item[ResultsConstants.ID] = self.name
206 item[ResultsConstants.DEPLOYMENT] = self.deployment
207 item[ResultsConstants.TRAFFIC_TYPE] = self._traffic['l3']['proto']
208 if self._traffic['multistream']:
209 item[ResultsConstants.SCAL_STREAM_COUNT] = self._traffic['multistream']
210 item[ResultsConstants.SCAL_STREAM_TYPE] = self._traffic['stream_type']
211 item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows']
212 if len(self.guest_loopback):
213 item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(self.guest_loopback)
217 def _copy_fwd_tools_for_guest(self):
218 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] for use by guests.
221 # method is executed only for pvp and pvvp, so let's count number of 'v'
222 while counter < self.deployment.count('v'):
223 guest_dir = S.getValue('GUEST_SHARE_DIR')[counter]
225 # create shared dir if it doesn't exist
226 if not os.path.exists(guest_dir):
227 os.makedirs(guest_dir)
229 # copy sources into shared dir only if neccessary
230 if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback:
232 tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
233 os.path.join(S.getValue('RTE_SDK'), ''),
234 os.path.join(guest_dir, 'DPDK')],
236 'Copying DPDK to shared directory...',
238 tasks.run_task(['rsync', '-a', '-r', '-l',
239 os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
240 os.path.join(guest_dir, 'l2fwd')],
242 'Copying l2fwd to shared directory...',
244 except subprocess.CalledProcessError:
245 self._logger.error('Unable to copy DPDK and l2fwd to shared directory')
249 def _mount_hugepages(self):
250 """Mount hugepages if usage of DPDK or Qemu is detected
252 # hugepages are needed by DPDK and Qemu
253 if not self._hugepages_mounted and \
254 (self.deployment.count('v') or \
255 S.getValue('VSWITCH').lower().count('dpdk') or \
257 hugepages.mount_hugepages()
258 self._hugepages_mounted = True
260 def _umount_hugepages(self):
261 """Umount hugepages if they were mounted before
263 if self._hugepages_mounted:
264 hugepages.umount_hugepages()
265 self._hugepages_mounted = False
268 def _write_result_to_file(results, output):
269 """Write list of dictionaries to a CSV file.
271 Each element on list will create separate row in output file.
272 If output file already exists, data will be appended at the end,
273 otherwise it will be created.
275 :param results: list of dictionaries.
276 :param output: path to output file.
278 with open(output, 'a') as csvfile:
280 logging.info("Write results to file: " + output)
281 fieldnames = TestCase._get_unique_keys(results)
283 writer = csv.DictWriter(csvfile, fieldnames)
285 if not csvfile.tell(): # file is now empty
288 for result in results:
289 writer.writerow(result)
293 def _get_unique_keys(list_of_dicts):
294 """Gets unique key values as ordered list of strings in given dicts
296 :param list_of_dicts: list of dictionaries.
298 :returns: list of unique keys(strings).
300 result = OrderedDict()
301 for item in list_of_dicts:
302 for key in item.keys():
305 return list(result.keys())
308 def _add_flows(self, vswitch_ctl):
309 """Add flows to the vswitch
311 :param vswitch_ctl vswitch controller
313 vswitch = vswitch_ctl.get_vswitch()
314 # TODO BOM 15-08-07 the frame mod code assumes that the
315 # physical ports are ports 1 & 2. The actual numbers
316 # need to be retrived from the vSwitch and the metadata value
317 # updated accordingly.
318 bridge = S.getValue('VSWITCH_BRIDGE_NAME')
319 if self._frame_mod == "vlan":
320 # 0x8100 => VLAN ethertype
321 self._logger.debug(" **** VLAN ***** ")
322 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
323 'actions': ['push_vlan:0x8100', 'goto_table:3']}
324 vswitch.add_flow(bridge, flow)
325 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
326 'actions': ['push_vlan:0x8100', 'goto_table:3']}
327 vswitch.add_flow(bridge, flow)
328 elif self._frame_mod == "mpls":
329 # 0x8847 => MPLS unicast ethertype
330 self._logger.debug(" **** MPLS ***** ")
331 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
332 'actions': ['push_mpls:0x8847', 'goto_table:3']}
333 vswitch.add_flow(bridge, flow)
334 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
335 'actions': ['push_mpls:0x8847', 'goto_table:3']}
336 vswitch.add_flow(bridge, flow)
337 elif self._frame_mod == "mac":
338 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
339 'actions': ['mod_dl_src:22:22:22:22:22:22',
341 vswitch.add_flow(bridge, flow)
342 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
343 'actions': ['mod_dl_src:11:11:11:11:11:11',
345 vswitch.add_flow(bridge, flow)
346 elif self._frame_mod == "dscp":
347 # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
348 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
350 'actions': ['mod_nw_tos:184', 'goto_table:3']}
351 vswitch.add_flow(bridge, flow)
352 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
354 'actions': ['mod_nw_tos:184', 'goto_table:3']}
355 vswitch.add_flow(bridge, flow)
356 elif self._frame_mod == "ttl":
357 # 251 and 241 are the highest prime numbers < 255
358 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
360 'actions': ['mod_nw_ttl:251', 'goto_table:3']}
361 vswitch.add_flow(bridge, flow)
362 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
364 'actions': ['mod_nw_ttl:241', 'goto_table:3']}
365 vswitch.add_flow(bridge, flow)
366 elif self._frame_mod == "ip_addr":
367 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
369 'actions': ['mod_nw_src:10.10.10.10',
370 'mod_nw_dst:20.20.20.20',
372 vswitch.add_flow(bridge, flow)
373 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
375 'actions': ['mod_nw_src:20.20.20.20',
376 'mod_nw_dst:10.10.10.10',
378 vswitch.add_flow(bridge, flow)
379 elif self._frame_mod == "ip_port":
380 # TODO BOM 15-08-27 The traffic generated is assumed
381 # to be UDP (nw_proto 17d) which is the default case but
382 # we will need to pick up the actual traffic params in use.
383 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
384 'dl_type':'0x0800', 'nw_proto':'17',
385 'actions': ['mod_tp_src:44444',
386 'mod_tp_dst:44444', 'goto_table:3']}
387 vswitch.add_flow(bridge, flow)
388 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
389 'dl_type':'0x0800', 'nw_proto':'17',
390 'actions': ['mod_tp_src:44444',
391 'mod_tp_dst:44444', 'goto_table:3']}
392 vswitch.add_flow(bridge, flow)