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
17 from collections import OrderedDict
27 from conf import settings as S
28 from conf import get_test_param
29 import core.component_factory as component_factory
30 from core.loader import Loader
31 from core.results.results_constants import ResultsConstants
32 from tools import tasks
33 from tools import hugepages
34 from tools import functions
35 from tools.pkt_gen.trafficgen.trafficgenhelper import TRAFFIC_DEFAULTS
37 CHECK_PREFIX = 'validate_'
39 class TestCase(object):
40 """TestCase base class
42 In this basic form runs RFC2544 throughput test
44 def __init__(self, cfg):
45 """Pull out fields from test config
47 :param cfg: A dictionary of string-value pairs describing the test
48 configuration. Both the key and values strings use well-known
50 :param results_dir: Where the csv formatted results are written.
52 self._testcase_start_time = time.time()
53 self._hugepages_mounted = False
54 self._traffic_ctl = None
56 self._vswitch_ctl = None
57 self._collector = None
59 self._output_file = None
60 self._tc_results = None
61 self._settings_original = {}
62 self._settings_paths_modified = False
63 self._testcast_run_time = None
64 # initialization of step driven specific members
65 self._step_check = False # by default don't check result for step driven testcases
66 self._step_vnf_list = {}
67 self._step_result = []
68 self._step_status = None
69 self._testcase_run_time = None
71 # store all GUEST_ specific settings to keep original values before their expansion
72 for key in S.__dict__:
73 if key.startswith('GUEST_'):
74 self._settings_original[key] = S.getValue(key)
76 self._update_settings('VSWITCH', cfg.get('vSwitch', S.getValue('VSWITCH')))
77 self._update_settings('VNF', cfg.get('VNF', S.getValue('VNF')))
78 self._update_settings('TRAFFICGEN', cfg.get('Trafficgen', S.getValue('TRAFFICGEN')))
79 test_params = copy.deepcopy(S.getValue('TEST_PARAMS'))
80 tc_test_params = cfg.get('Parameters', S.getValue('TEST_PARAMS'))
81 test_params.update(tc_test_params)
82 self._update_settings('TEST_PARAMS', test_params)
85 # override all redefined GUEST_ values to have them expanded correctly
86 tmp_test_params = copy.deepcopy(S.getValue('TEST_PARAMS'))
87 for key in tmp_test_params:
88 if key.startswith('GUEST_'):
89 S.setValue(key, S.getValue(key))
90 S.getValue('TEST_PARAMS').pop(key)
92 # update global settings
93 functions.settings_update_paths()
95 # set test parameters; CLI options take precedence to testcase settings
96 self._logger = logging.getLogger(__name__)
97 self.name = cfg['Name']
98 self.desc = cfg.get('Description', 'No description given.')
99 self.test = cfg.get('TestSteps', None)
101 bidirectional = cfg.get('biDirectional', TRAFFIC_DEFAULTS['bidir'])
102 bidirectional = get_test_param('bidirectional', bidirectional)
103 if not isinstance(bidirectional, str):
105 'Bi-dir value must be of type string in testcase configuration')
106 bidirectional = bidirectional.title() # Keep things consistent
108 traffic_type = cfg.get('Traffic Type', TRAFFIC_DEFAULTS['traffic_type'])
109 traffic_type = get_test_param('traffic_type', traffic_type)
111 framerate = cfg.get('iLoad', TRAFFIC_DEFAULTS['frame_rate'])
112 framerate = get_test_param('iload', framerate)
114 self.deployment = cfg['Deployment']
115 self._frame_mod = cfg.get('Frame Modification', None)
117 self._tunnel_type = None
118 self._tunnel_operation = None
120 if self.deployment == 'op2p':
121 self._tunnel_operation = cfg['Tunnel Operation']
123 if 'Tunnel Type' in cfg:
124 self._tunnel_type = cfg['Tunnel Type']
125 self._tunnel_type = get_test_param('tunnel_type',
128 # read configuration of streams; CLI parameter takes precedence to
129 # testcase definition
130 multistream = cfg.get('MultiStream', TRAFFIC_DEFAULTS['multistream'])
131 multistream = get_test_param('multistream', multistream)
132 stream_type = cfg.get('Stream Type', TRAFFIC_DEFAULTS['stream_type'])
133 stream_type = get_test_param('stream_type', stream_type)
134 pre_installed_flows = cfg.get('Pre-installed Flows', TRAFFIC_DEFAULTS['pre_installed_flows'])
135 pre_installed_flows = get_test_param('pre-installed_flows', pre_installed_flows)
137 # check if test requires background load and which generator it uses
138 self._load_cfg = cfg.get('Load', None)
139 if self._load_cfg and 'tool' in self._load_cfg:
140 self._loadgen = self._load_cfg['tool']
142 # background load is not requested, so use dummy implementation
143 self._loadgen = "Dummy"
146 self._frame_mod = self._frame_mod.lower()
147 self._results_dir = S.getValue('RESULTS_PATH')
149 # set traffic details, so they can be passed to vswitch and traffic ctls
150 self._traffic = copy.deepcopy(TRAFFIC_DEFAULTS)
151 self._traffic.update({'traffic_type': traffic_type,
152 'flow_type': cfg.get('Flow Type', TRAFFIC_DEFAULTS['flow_type']),
153 'bidir': bidirectional,
154 'tunnel_type': self._tunnel_type,
155 'multistream': int(multistream),
156 'stream_type': stream_type,
157 'pre_installed_flows' : pre_installed_flows,
158 'frame_rate': int(framerate)})
160 # Packet Forwarding mode
161 self._vswitch_none = 'none' == S.getValue('VSWITCH').strip().lower()
163 # trafficgen configuration required for tests of tunneling protocols
164 if self.deployment == "op2p":
165 self._traffic['l2'].update({'srcmac':
166 S.getValue('TRAFFICGEN_PORT1_MAC'),
168 S.getValue('TRAFFICGEN_PORT2_MAC')})
170 self._traffic['l3'].update({'srcip':
171 S.getValue('TRAFFICGEN_PORT1_IP'),
173 S.getValue('TRAFFICGEN_PORT2_IP')})
175 if self._tunnel_operation == "decapsulation":
176 self._traffic['l2'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L2')
177 self._traffic['l3'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L3')
178 self._traffic['l4'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L4')
179 elif S.getValue('NICS')[0]['type'] == 'vf' or S.getValue('NICS')[1]['type'] == 'vf':
180 mac1 = S.getValue('NICS')[0]['mac']
181 mac2 = S.getValue('NICS')[1]['mac']
183 self._traffic['l2'].update({'srcmac': mac2, 'dstmac': mac1})
185 self._logger.debug("MAC addresses can not be read")
187 # count how many VNFs are involved in TestSteps
189 for step in self.test:
190 if step[0].startswith('vnf'):
191 self._step_vnf_list[step[0]] = None
193 def run_initialize(self):
194 """ Prepare test execution environment
196 self._logger.debug(self.name)
198 # mount hugepages if needed
199 self._mount_hugepages()
201 self._logger.debug("Controllers:")
203 self._traffic_ctl = component_factory.create_traffic(
204 self._traffic['traffic_type'],
205 loader.get_trafficgen_class())
207 self._vnf_ctl = component_factory.create_vnf(
209 loader.get_vnf_class(),
210 len(self._step_vnf_list))
212 # verify enough hugepages are free to run the testcase
213 if not self._check_for_enough_hugepages():
214 raise RuntimeError('Not enough hugepages free to run test.')
216 # perform guest related handling
217 tmp_vm_count = self._vnf_ctl.get_vnfs_number() + len(self._step_vnf_list)
219 # copy sources of l2 forwarding tools into VM shared dir if needed
220 self._copy_fwd_tools_for_all_guests(tmp_vm_count)
222 # in case of multi VM in parallel, set the number of streams to the number of VMs
223 if self.deployment.startswith('pvpv'):
224 # for each VM NIC pair we need an unique stream
226 for vm_nic in S.getValue('GUEST_NICS_NR')[:tmp_vm_count]:
227 streams += int(vm_nic / 2) if vm_nic > 1 else 1
228 self._logger.debug("VMs with parallel connection were detected. "
229 "Thus Number of streams was set to %s", streams)
230 # update streams if needed; In case of additional VNFs deployed by TestSteps
231 # user can define a proper stream count manually
232 if 'multistream' not in self._traffic or self._traffic['multistream'] < streams:
233 self._traffic.update({'multistream': streams})
235 # OVS Vanilla requires guest VM MAC address and IPs to work
236 if 'linux_bridge' in S.getValue('GUEST_LOOPBACK'):
237 self._traffic['l2'].update({'srcmac': S.getValue('VANILLA_TGEN_PORT1_MAC'),
238 'dstmac': S.getValue('VANILLA_TGEN_PORT2_MAC')})
239 self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
240 'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
242 if self._vswitch_none:
243 self._vswitch_ctl = component_factory.create_pktfwd(
245 loader.get_pktfwd_class())
247 self._vswitch_ctl = component_factory.create_vswitch(
249 loader.get_vswitch_class(),
251 self._tunnel_operation)
253 self._collector = component_factory.create_collector(
254 loader.get_collector_class(),
255 self._results_dir, self.name)
256 self._loadgen = component_factory.create_loadgen(
260 self._output_file = os.path.join(self._results_dir, "result_" + self.name +
261 "_" + self.deployment + ".csv")
263 self._step_status = {'status' : True, 'details' : ''}
265 self._logger.debug("Setup:")
267 def run_finalize(self):
268 """ Tear down test execution environment and record test results
270 # Stop all VNFs started by TestSteps in case that something went wrong
271 self.step_stop_vnfs()
273 # umount hugepages if mounted
274 self._umount_hugepages()
276 # restore original settings
277 S.load_from_dict(self._settings_original)
279 # cleanup any namespaces created
280 if os.path.isdir('/tmp/namespaces'):
281 import tools.namespace
282 namespace_list = os.listdir('/tmp/namespaces')
283 if len(namespace_list):
284 self._logger.info('Cleaning up namespaces')
285 for name in namespace_list:
286 tools.namespace.delete_namespace(name)
287 os.rmdir('/tmp/namespaces')
288 # cleanup any veth ports created
289 if os.path.isdir('/tmp/veth'):
291 veth_list = os.listdir('/tmp/veth')
293 self._logger.info('Cleaning up veth ports')
294 for eth in veth_list:
295 port1, port2 = eth.split('-')
296 tools.veth.del_veth_port(port1, port2)
297 os.rmdir('/tmp/veth')
299 def run_report(self):
300 """ Report test results
302 self._logger.debug("self._collector Results:")
303 self._collector.print_results()
305 results = self._traffic_ctl.get_results()
307 self._logger.debug("Traffic Results:")
308 self._traffic_ctl.print_results()
310 self._tc_results = self._append_results(results)
311 TestCase.write_result_to_file(self._tc_results, self._output_file)
316 All setup and teardown through controllers is included.
318 # prepare test execution environment
319 self.run_initialize()
322 with self._vswitch_ctl, self._loadgen:
323 with self._vnf_ctl, self._collector:
324 if not self._vswitch_none:
327 with self._traffic_ctl:
328 # execute test based on TestSteps definition if needed...
330 # ...and continue with traffic generation, but keep
331 # in mind, that clean deployment does not configure
332 # OVS nor executes the traffic
333 if self.deployment != 'clean':
334 self._traffic_ctl.send_traffic(self._traffic)
336 # dump vswitch flows before they are affected by VNF termination
337 if not self._vswitch_none:
338 self._vswitch_ctl.dump_vswitch_flows()
340 # garbage collection for case that TestSteps modify existing deployment
341 self.step_stop_vnfs()
344 # tear down test execution environment and log results
347 self._testcase_run_time = time.strftime("%H:%M:%S",
348 time.gmtime(time.time() -
349 self._testcase_start_time))
350 logging.info("Testcase execution time: " + self._testcase_run_time)
351 # report test results
354 def _update_settings(self, param, value):
355 """ Check value of given configuration parameter
356 In case that new value is different, then testcase
357 specific settings is updated and original value stored
359 :param param: Name of parameter inside settings
360 :param value: Disired parameter value
362 orig_value = S.getValue(param)
363 if orig_value != value:
364 self._settings_original[param] = copy.deepcopy(orig_value)
365 S.setValue(param, value)
367 def _append_results(self, results):
369 Method appends mandatory Test Case results to list of dictionaries.
371 :param results: list of dictionaries which contains results from
374 :returns: modified list of dictionaries.
377 item[ResultsConstants.ID] = self.name
378 item[ResultsConstants.DEPLOYMENT] = self.deployment
379 item[ResultsConstants.TRAFFIC_TYPE] = self._traffic['l3']['proto']
380 item[ResultsConstants.TEST_RUN_TIME] = self._testcase_run_time
381 if self._traffic['multistream']:
382 item[ResultsConstants.SCAL_STREAM_COUNT] = self._traffic['multistream']
383 item[ResultsConstants.SCAL_STREAM_TYPE] = self._traffic['stream_type']
384 item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows']
385 if self._vnf_ctl.get_vnfs_number():
386 item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(S.getValue('GUEST_LOOPBACK'))
387 if self._tunnel_type:
388 item[ResultsConstants.TUNNEL_TYPE] = self._tunnel_type
391 def _copy_fwd_tools_for_all_guests(self, vm_count):
392 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] based on selected deployment.
394 # consider only VNFs involved in the test
395 for guest_dir in set(S.getValue('GUEST_SHARE_DIR')[:vm_count]):
396 self._copy_fwd_tools_for_guest(guest_dir)
398 def _copy_fwd_tools_for_guest(self, guest_dir):
399 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR of VM
401 :param index: Index of VM starting from 1 (i.e. 1st VM has index 1)
403 # remove shared dir if it exists to avoid issues with file consistency
404 if os.path.exists(guest_dir):
405 tasks.run_task(['rm', '-f', '-r', guest_dir], self._logger,
406 'Removing content of shared directory...', True)
408 # directory to share files between host and guest
409 os.makedirs(guest_dir)
411 # copy sources into shared dir only if neccessary
412 guest_loopback = set(S.getValue('GUEST_LOOPBACK'))
413 if 'testpmd' in guest_loopback:
415 # exclude whole .git/ subdirectory and all o-files;
416 # It is assumed, that the same RTE_TARGET is used in both host
417 # and VMs; This simplification significantly speeds up testpmd
418 # build. If we will need a different RTE_TARGET in VM,
419 # then we have to build whole DPDK from the scratch in VM.
420 # In that case we can copy just DPDK sources (e.g. by excluding
421 # all items obtained by git status -unormal --porcelain).
422 # NOTE: Excluding RTE_TARGET directory won't help on systems,
423 # where DPDK is built for multiple targets (e.g. for gcc & icc)
425 exclude.append(r'--exclude=.git/')
426 exclude.append(r'--exclude=*.o')
427 tasks.run_task(['rsync', '-a', '-r', '-l'] + exclude +
428 [os.path.join(S.getValue('TOOLS')['dpdk_src'], ''),
429 os.path.join(guest_dir, 'DPDK')],
431 'Copying DPDK to shared directory...',
433 except subprocess.CalledProcessError:
434 self._logger.error('Unable to copy DPDK to shared directory')
436 if 'l2fwd' in guest_loopback:
438 tasks.run_task(['rsync', '-a', '-r', '-l',
439 os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
440 os.path.join(guest_dir, 'l2fwd')],
442 'Copying l2fwd to shared directory...',
444 except subprocess.CalledProcessError:
445 self._logger.error('Unable to copy l2fwd to shared directory')
448 def _mount_hugepages(self):
449 """Mount hugepages if usage of DPDK or Qemu is detected
451 # hugepages are needed by DPDK and Qemu
452 if not self._hugepages_mounted and \
453 (self.deployment.count('v') or \
454 S.getValue('VSWITCH').lower().count('dpdk') or \
455 self._vswitch_none or \
456 self.test and 'vnf' in [step[0][0:3] for step in self.test]):
457 hugepages.mount_hugepages()
458 self._hugepages_mounted = True
460 def _umount_hugepages(self):
461 """Umount hugepages if they were mounted before
463 if self._hugepages_mounted:
464 hugepages.umount_hugepages()
465 self._hugepages_mounted = False
467 def _check_for_enough_hugepages(self):
468 """Check to make sure enough hugepages are free to satisfy the
472 hugepage_size = hugepages.get_hugepage_size()
473 # get hugepage amounts per guest involved in the test
474 for guest in range(self._vnf_ctl.get_vnfs_number()):
475 hugepages_needed += math.ceil((int(S.getValue(
476 'GUEST_MEMORY')[guest]) * 1000) / hugepage_size)
478 # get hugepage amounts for each socket on dpdk
479 sock0_mem, sock1_mem = 0, 0
480 if S.getValue('VSWITCH').lower().count('dpdk'):
481 # the import below needs to remain here and not put into the module
482 # imports because of an exception due to settings not yet loaded
483 from vswitches import ovs_dpdk_vhost
484 if ovs_dpdk_vhost.OvsDpdkVhost.old_dpdk_config():
486 r'-socket-mem\s+(\d+),(\d+)',
487 ''.join(S.getValue('VSWITCHD_DPDK_ARGS')))
489 sock0_mem, sock1_mem = (int(match.group(1)) * 1024 / hugepage_size,
490 int(match.group(2)) * 1024 / hugepage_size)
493 'Could not parse socket memory config in dpdk params.')
495 sock0_mem, sock1_mem = (
497 'VSWITCHD_DPDK_CONFIG')['dpdk-socket-mem'].split(','))
498 sock0_mem, sock1_mem = (int(sock0_mem) * 1024 / hugepage_size,
499 int(sock1_mem) * 1024 / hugepage_size)
501 # If hugepages needed, verify the amounts are free
502 if any([hugepages_needed, sock0_mem, sock1_mem]):
503 free_hugepages = hugepages.get_free_hugepages()
505 logging.info('Need %s hugepages free for guests',
507 result1 = free_hugepages >= hugepages_needed
508 free_hugepages -= hugepages_needed
513 logging.info('Need %s hugepages free for dpdk socket 0',
515 result2 = hugepages.get_free_hugepages('0') >= sock0_mem
516 free_hugepages -= sock0_mem
521 logging.info('Need %s hugepages free for dpdk socket 1',
523 result3 = hugepages.get_free_hugepages('1') >= sock1_mem
524 free_hugepages -= sock1_mem
528 logging.info('Need a total of {} total hugepages'.format(
529 hugepages_needed + sock1_mem + sock0_mem))
531 # The only drawback here is sometimes dpdk doesn't release
532 # its hugepages on a test failure. This could cause a test
533 # to fail when dpdk would be OK to start because it will just
534 # use the previously allocated hugepages.
535 result4 = True if free_hugepages >= 0 else False
537 return all([result1, result2, result3, result4])
542 def write_result_to_file(results, output):
543 """Write list of dictionaries to a CSV file.
545 Each element on list will create separate row in output file.
546 If output file already exists, data will be appended at the end,
547 otherwise it will be created.
549 :param results: list of dictionaries.
550 :param output: path to output file.
552 with open(output, 'a') as csvfile:
554 logging.info("Write results to file: " + output)
555 fieldnames = TestCase._get_unique_keys(results)
557 writer = csv.DictWriter(csvfile, fieldnames)
559 if not csvfile.tell(): # file is now empty
562 for result in results:
563 writer.writerow(result)
566 def _get_unique_keys(list_of_dicts):
567 """Gets unique key values as ordered list of strings in given dicts
569 :param list_of_dicts: list of dictionaries.
571 :returns: list of unique keys(strings).
573 result = OrderedDict()
574 for item in list_of_dicts:
575 for key in item.keys():
578 return list(result.keys())
580 def _add_flows(self):
581 """Add flows to the vswitch
583 vswitch = self._vswitch_ctl.get_vswitch()
584 # TODO BOM 15-08-07 the frame mod code assumes that the
585 # physical ports are ports 1 & 2. The actual numbers
586 # need to be retrived from the vSwitch and the metadata value
587 # updated accordingly.
588 bridge = S.getValue('VSWITCH_BRIDGE_NAME')
589 if self._frame_mod == "vlan":
590 # 0x8100 => VLAN ethertype
591 self._logger.debug(" **** VLAN ***** ")
592 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
593 'actions': ['push_vlan:0x8100', 'goto_table:3']}
594 vswitch.add_flow(bridge, flow)
595 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
596 'actions': ['push_vlan:0x8100', 'goto_table:3']}
597 vswitch.add_flow(bridge, flow)
598 elif self._frame_mod == "mpls":
599 # 0x8847 => MPLS unicast ethertype
600 self._logger.debug(" **** MPLS ***** ")
601 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
602 'actions': ['push_mpls:0x8847', 'goto_table:3']}
603 vswitch.add_flow(bridge, flow)
604 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
605 'actions': ['push_mpls:0x8847', 'goto_table:3']}
606 vswitch.add_flow(bridge, flow)
607 elif self._frame_mod == "mac":
608 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
609 'actions': ['mod_dl_src:22:22:22:22:22:22',
611 vswitch.add_flow(bridge, flow)
612 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
613 'actions': ['mod_dl_src:11:11:11:11:11:11',
615 vswitch.add_flow(bridge, flow)
616 elif self._frame_mod == "dscp":
617 # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
618 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
620 'actions': ['mod_nw_tos:184', 'goto_table:3']}
621 vswitch.add_flow(bridge, flow)
622 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
624 'actions': ['mod_nw_tos:184', 'goto_table:3']}
625 vswitch.add_flow(bridge, flow)
626 elif self._frame_mod == "ttl":
627 # 251 and 241 are the highest prime numbers < 255
628 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
630 'actions': ['mod_nw_ttl:251', 'goto_table:3']}
631 vswitch.add_flow(bridge, flow)
632 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
634 'actions': ['mod_nw_ttl:241', 'goto_table:3']}
635 vswitch.add_flow(bridge, flow)
636 elif self._frame_mod == "ip_addr":
637 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
639 'actions': ['mod_nw_src:10.10.10.10',
640 'mod_nw_dst:20.20.20.20',
642 vswitch.add_flow(bridge, flow)
643 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
645 'actions': ['mod_nw_src:20.20.20.20',
646 'mod_nw_dst:10.10.10.10',
648 vswitch.add_flow(bridge, flow)
649 elif self._frame_mod == "ip_port":
650 # TODO BOM 15-08-27 The traffic generated is assumed
651 # to be UDP (nw_proto 17d) which is the default case but
652 # we will need to pick up the actual traffic params in use.
653 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
654 'dl_type':'0x0800', 'nw_proto':'17',
655 'actions': ['mod_tp_src:44444',
656 'mod_tp_dst:44444', 'goto_table:3']}
657 vswitch.add_flow(bridge, flow)
658 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
659 'dl_type':'0x0800', 'nw_proto':'17',
660 'actions': ['mod_tp_src:44444',
661 'mod_tp_dst:44444', 'goto_table:3']}
662 vswitch.add_flow(bridge, flow)
668 # TestSteps realted methods
670 def step_report_status(self, label, status):
671 """ Log status of test step
673 self._logger.info("%s ... %s", label, 'OK' if status else 'FAILED')
675 def step_stop_vnfs(self):
676 """ Stop all VNFs started by TestSteps
678 for vnf in self._step_vnf_list:
679 self._step_vnf_list[vnf].stop()
682 def step_eval_param(param, STEP):
683 # pylint: disable=invalid-name
684 """ Helper function for #STEP macro evaluation
686 if isinstance(param, str):
687 # evaluate every #STEP reference inside parameter itself
688 macros = re.findall(r'#STEP\[[\w\[\]\-\'\"]+\]', param)
691 # pylint: disable=eval-used
692 tmp_val = str(eval(macro[1:]))
693 param = param.replace(macro, tmp_val)
695 elif isinstance(param, list) or isinstance(param, tuple):
698 tmp_list.append(TestCase.step_eval_param(item, STEP))
700 elif isinstance(param, dict):
702 for (key, value) in param.items():
703 tmp_dict[key] = TestCase.step_eval_param(value, STEP)
709 def step_eval_params(params, step_result):
710 """ Evaluates referrences to results from previous steps
713 # evaluate all parameters if needed
715 eval_params.append(TestCase.step_eval_param(param, step_result))
719 """ Execute actions specified by TestSteps list
721 :return: False if any error was detected
728 # required for VNFs initialization
730 # initialize list with results
731 self._step_result = [None] * len(self.test)
733 # run test step by step...
734 for i, step in enumerate(self.test):
735 step_ok = not self._step_check
736 if step[0] == 'vswitch':
737 test_object = self._vswitch_ctl.get_vswitch()
738 elif step[0] == 'namespace':
739 test_object = namespace
740 elif step[0] == 'veth':
742 elif step[0] == 'settings':
744 elif step[0] == 'tools':
745 test_object = TestStepsTools()
746 step[1] = step[1].title()
747 elif step[0] == 'trafficgen':
748 test_object = self._traffic_ctl
749 # in case of send_traffic or send_traffic_async methods, ensure
750 # that specified traffic values are merged with existing self._traffic
751 if step[1].startswith('send_traffic'):
752 tmp_traffic = copy.deepcopy(self._traffic)
753 tmp_traffic.update(step[2])
754 step[2] = tmp_traffic
755 elif step[0].startswith('vnf'):
756 if not self._step_vnf_list[step[0]]:
758 self._step_vnf_list[step[0]] = loader.get_vnf_class()()
759 test_object = self._step_vnf_list[step[0]]
760 elif step[0] == 'wait':
761 input(os.linesep + "Step {}: Press Enter to continue with "
762 "the next step...".format(i) + os.linesep + os.linesep)
765 self._logger.error("Unsupported test object %s", step[0])
766 self._step_status = {'status' : False, 'details' : ' '.join(step)}
767 self.step_report_status("Step '{}'".format(' '.join(step)),
768 self._step_status['status'])
771 test_method = getattr(test_object, step[1])
773 test_method_check = getattr(test_object, CHECK_PREFIX + step[1])
775 test_method_check = None
779 # eval parameters, but use only valid step_results
780 # to support negative indexes
781 step_params = TestCase.step_eval_params(step[2:], self._step_result[:i])
782 step_log = '{} {}'.format(' '.join(step[:2]), step_params)
783 self._logger.debug("Step %s '%s' start", i, step_log)
784 self._step_result[i] = test_method(*step_params)
785 self._logger.debug("Step %s '%s' results '%s'", i,
786 step_log, self._step_result[i])
789 step_ok = test_method_check(self._step_result[i], *step_params)
790 except (AssertionError, AttributeError, IndexError) as ex:
792 self._logger.error("Step %s raised %s", i, type(ex).__name__)
795 self.step_report_status("Step {} - '{}'".format(i, step_log), step_ok)
798 self._step_status = {'status' : False, 'details' : step_log}
799 # Stop all VNFs started by TestSteps
800 self.step_stop_vnfs()
803 # all steps processed without any issue