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
21 from collections import OrderedDict
23 from core.results.results_constants import ResultsConstants
24 import core.component_factory as component_factory
25 from core.loader import Loader
26 from tools import tasks
27 from tools.report import report
28 from conf import settings as S
29 from tools.pkt_gen.trafficgen.trafficgenhelper import TRAFFIC_DEFAULTS
30 from conf import get_test_param
32 class TestCase(object):
33 """TestCase base class
35 In this basic form runs RFC2544 throughput test
37 def __init__(self, cfg, results_dir):
38 """Pull out fields from test config
40 :param cfg: A dictionary of string-value pairs describing the test
41 configuration. Both the key and values strings use well-known
43 :param results_dir: Where the csv formatted results are written.
45 self._logger = logging.getLogger(__name__)
46 self.name = cfg['Name']
47 self.desc = cfg.get('Description', 'No description given.')
48 self.deployment = cfg['Deployment']
49 self._frame_mod = cfg.get('Frame Modification', None)
50 framerate = get_test_param('iload', None)
52 framerate = cfg.get('iLoad', 100)
54 # identify guest loopback method, so it can be added into reports
55 self.guest_loopback = []
56 if self.deployment in ['pvp', 'pvvp']:
57 guest_loopback = get_test_param('guest_loopback', None)
59 self.guest_loopback.append(guest_loopback)
61 if self.deployment == 'pvp':
62 self.guest_loopback.append(S.getValue('GUEST_LOOPBACK')[0])
64 self.guest_loopback = S.getValue('GUEST_LOOPBACK').copy()
66 # check if test requires background load and which generator it uses
67 self._load_cfg = cfg.get('Load', None)
68 if self._load_cfg and 'tool' in self._load_cfg:
69 self._loadgen = self._load_cfg['tool']
71 # background load is not requested, so use dummy implementation
72 self._loadgen = "Dummy"
75 self._frame_mod = self._frame_mod.lower()
76 self._results_dir = results_dir
78 # set traffic details, so they can be passed to vswitch and traffic ctls
79 self._traffic = TRAFFIC_DEFAULTS.copy()
80 self._traffic.update({'traffic_type': cfg['Traffic Type'],
81 'flow_type': cfg.get('Flow Type', 'port'),
82 'bidir': cfg['biDirectional'],
83 'multistream': cfg.get('MultiStream', 0),
84 'frame_rate': int(framerate)})
86 # OVS Vanilla requires guest VM MAC address and IPs to work
87 if 'linux_bridge' in self.guest_loopback:
88 self._traffic['l2'] = {'srcmac': S.getValue('GUEST_NET2_MAC')[0],
89 'dstmac': S.getValue('GUEST_NET1_MAC')[0]}
90 self._traffic['l3'] = {'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
91 'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')}
96 All setup and teardown through controllers is included.
98 self._logger.debug(self.name)
100 # copy sources of l2 forwarding tools into VM shared dir if needed
101 if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback:
102 self._copy_fwd_tools_for_guest()
104 self._logger.debug("Controllers:")
106 traffic_ctl = component_factory.create_traffic(
107 self._traffic['traffic_type'],
108 loader.get_trafficgen_class())
109 vnf_ctl = component_factory.create_vnf(
111 loader.get_vnf_class())
112 vswitch_ctl = component_factory.create_vswitch(
114 loader.get_vswitch_class(),
116 collector = component_factory.create_collector(
117 loader.get_collector_class(),
118 self._results_dir, self.name)
119 loadgen = component_factory.create_loadgen(
123 self._logger.debug("Setup:")
124 with vswitch_ctl, loadgen:
125 with vnf_ctl, collector:
126 vswitch = vswitch_ctl.get_vswitch()
127 # TODO BOM 15-08-07 the frame mod code assumes that the
128 # physical ports are ports 1 & 2. The actual numbers
129 # need to be retrived from the vSwitch and the metadata value
130 # updated accordingly.
131 bridge = S.getValue('VSWITCH_BRIDGE_NAME')
132 if self._frame_mod == "vlan":
133 # 0x8100 => VLAN ethertype
134 self._logger.debug(" **** VLAN ***** ")
135 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
136 'actions': ['push_vlan:0x8100', 'goto_table:3']}
137 vswitch.add_flow(bridge, flow)
138 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
139 'actions': ['push_vlan:0x8100', 'goto_table:3']}
140 vswitch.add_flow(bridge, flow)
141 elif self._frame_mod == "mpls":
142 # 0x8847 => MPLS unicast ethertype
143 self._logger.debug(" **** MPLS ***** ")
144 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
145 'actions': ['push_mpls:0x8847', 'goto_table:3']}
146 vswitch.add_flow(bridge, flow)
147 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
148 'actions': ['push_mpls:0x8847', 'goto_table:3']}
149 vswitch.add_flow(bridge, flow)
150 elif self._frame_mod == "mac":
151 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
152 'actions': ['mod_dl_src:22:22:22:22:22:22',
154 vswitch.add_flow(bridge, flow)
155 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
156 'actions': ['mod_dl_src:11:11:11:11:11:11',
158 vswitch.add_flow(bridge, flow)
159 elif self._frame_mod == "dscp":
160 # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
161 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
163 'actions': ['mod_nw_tos:184', 'goto_table:3']}
164 vswitch.add_flow(bridge, flow)
165 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
167 'actions': ['mod_nw_tos:184', 'goto_table:3']}
168 vswitch.add_flow(bridge, flow)
169 elif self._frame_mod == "ttl":
170 # 251 and 241 are the highest prime numbers < 255
171 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
173 'actions': ['mod_nw_ttl:251', 'goto_table:3']}
174 vswitch.add_flow(bridge, flow)
175 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
177 'actions': ['mod_nw_ttl:241', 'goto_table:3']}
178 vswitch.add_flow(bridge, flow)
179 elif self._frame_mod == "ip_addr":
180 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
182 'actions': ['mod_nw_src:10.10.10.10',
183 'mod_nw_dst:20.20.20.20',
185 vswitch.add_flow(bridge, flow)
186 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
188 'actions': ['mod_nw_src:20.20.20.20',
189 'mod_nw_dst:10.10.10.10',
191 vswitch.add_flow(bridge, flow)
192 elif self._frame_mod == "ip_port":
193 # TODO BOM 15-08-27 The traffic generated is assumed
194 # to be UDP (nw_proto 17d) which is the default case but
195 # we will need to pick up the actual traffic params in use.
196 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
197 'dl_type':'0x0800', 'nw_proto':'17',
198 'actions': ['mod_tp_src:44444',
199 'mod_tp_dst:44444', 'goto_table:3']}
200 vswitch.add_flow(bridge, flow)
201 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
202 'dl_type':'0x0800', 'nw_proto':'17',
203 'actions': ['mod_tp_src:44444',
204 'mod_tp_dst:44444', 'goto_table:3']}
205 vswitch.add_flow(bridge, flow)
210 traffic_ctl.send_traffic(self._traffic)
212 # dump vswitch flows before they are affected by VNF termination
213 vswitch_ctl.dump_vswitch_flows()
215 self._logger.debug("Traffic Results:")
216 traffic_ctl.print_results()
218 self._logger.debug("Collector Results:")
219 collector.print_results()
221 output_file = os.path.join(self._results_dir, "result_" + self.name +
222 "_" + self.deployment + ".csv")
224 tc_results = self._append_results(traffic_ctl.get_results())
225 TestCase._write_result_to_file(tc_results, output_file)
227 report.generate(output_file, tc_results, collector.get_results())
229 def _append_results(self, results):
231 Method appends mandatory Test Case results to list of dictionaries.
233 :param results: list of dictionaries which contains results from
236 :returns: modified list of dictionaries.
239 item[ResultsConstants.ID] = self.name
240 item[ResultsConstants.DEPLOYMENT] = self.deployment
241 if len(self.guest_loopback):
242 item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(self.guest_loopback)
246 def _copy_fwd_tools_for_guest(self):
247 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] for use by guests.
250 # method is executed only for pvp and pvvp, so let's count number of 'v'
251 while counter < self.deployment.count('v'):
252 guest_dir = S.getValue('GUEST_SHARE_DIR')[counter]
254 if not os.path.exists(guest_dir):
255 os.makedirs(guest_dir)
258 tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
259 os.path.join(S.getValue('RTE_SDK'), ''),
260 os.path.join(guest_dir, 'DPDK')],
262 'Copying DPDK to shared directory...',
264 tasks.run_task(['rsync', '-a', '-r', '-l',
265 os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
266 os.path.join(guest_dir, 'l2fwd')],
268 'Copying l2fwd to shared directory...',
270 except subprocess.CalledProcessError:
271 self._logger.error('Unable to copy DPDK and l2fwd to shared directory')
277 def _write_result_to_file(results, output):
278 """Write list of dictionaries to a CSV file.
280 Each element on list will create separate row in output file.
281 If output file already exists, data will be appended at the end,
282 otherwise it will be created.
284 :param results: list of dictionaries.
285 :param output: path to output file.
287 with open(output, 'a') as csvfile:
289 logging.info("Write results to file: " + output)
290 fieldnames = TestCase._get_unique_keys(results)
292 writer = csv.DictWriter(csvfile, fieldnames)
294 if not csvfile.tell(): # file is now empty
297 for result in results:
298 writer.writerow(result)
302 def _get_unique_keys(list_of_dicts):
303 """Gets unique key values as ordered list of strings in given dicts
305 :param list_of_dicts: list of dictionaries.
307 :returns: list of unique keys(strings).
309 result = OrderedDict()
310 for item in list_of_dicts:
311 for key in item.keys():
314 return list(result.keys())