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
70 # store all GUEST_ specific settings to keep original values before their expansion
71 for key in S.__dict__:
72 if key.startswith('GUEST_'):
73 self._settings_original[key] = S.getValue(key)
75 self._update_settings('VSWITCH', cfg.get('vSwitch', S.getValue('VSWITCH')))
76 self._update_settings('VNF', cfg.get('VNF', S.getValue('VNF')))
77 self._update_settings('TRAFFICGEN', cfg.get('Trafficgen', S.getValue('TRAFFICGEN')))
78 self._update_settings('TEST_PARAMS', cfg.get('Parameters', S.getValue('TEST_PARAMS')))
80 # update global settings
81 functions.settings_update_paths()
82 guest_loopback = get_test_param('guest_loopback', None)
84 # we can put just one item, it'll be expanded automatically for all VMs
85 self._update_settings('GUEST_LOOPBACK', [guest_loopback])
87 # set test parameters; CLI options take precedence to testcase settings
88 self._logger = logging.getLogger(__name__)
89 self.name = cfg['Name']
90 self.desc = cfg.get('Description', 'No description given.')
91 self.test = cfg.get('TestSteps', None)
93 bidirectional = cfg.get('biDirectional', TRAFFIC_DEFAULTS['bidir'])
94 bidirectional = get_test_param('bidirectional', bidirectional)
95 if not isinstance(bidirectional, str):
97 'Bi-dir value must be of type string in testcase configuration')
98 bidirectional = bidirectional.title() # Keep things consistent
100 traffic_type = cfg.get('Traffic Type', TRAFFIC_DEFAULTS['traffic_type'])
101 traffic_type = get_test_param('traffic_type', traffic_type)
103 framerate = cfg.get('iLoad', TRAFFIC_DEFAULTS['frame_rate'])
104 framerate = get_test_param('iload', framerate)
106 self.deployment = cfg['Deployment']
107 self._frame_mod = cfg.get('Frame Modification', None)
109 self._tunnel_type = None
110 self._tunnel_operation = None
112 if self.deployment == 'op2p':
113 self._tunnel_operation = cfg['Tunnel Operation']
115 if 'Tunnel Type' in cfg:
116 self._tunnel_type = cfg['Tunnel Type']
117 self._tunnel_type = get_test_param('tunnel_type',
120 # read configuration of streams; CLI parameter takes precedence to
121 # testcase definition
122 multistream = cfg.get('MultiStream', TRAFFIC_DEFAULTS['multistream'])
123 multistream = get_test_param('multistream', multistream)
124 stream_type = cfg.get('Stream Type', TRAFFIC_DEFAULTS['stream_type'])
125 stream_type = get_test_param('stream_type', stream_type)
126 pre_installed_flows = cfg.get('Pre-installed Flows', TRAFFIC_DEFAULTS['pre_installed_flows'])
127 pre_installed_flows = get_test_param('pre-installed_flows', pre_installed_flows)
129 # check if test requires background load and which generator it uses
130 self._load_cfg = cfg.get('Load', None)
131 if self._load_cfg and 'tool' in self._load_cfg:
132 self._loadgen = self._load_cfg['tool']
134 # background load is not requested, so use dummy implementation
135 self._loadgen = "Dummy"
138 self._frame_mod = self._frame_mod.lower()
139 self._results_dir = S.getValue('RESULTS_PATH')
141 # set traffic details, so they can be passed to vswitch and traffic ctls
142 self._traffic = copy.deepcopy(TRAFFIC_DEFAULTS)
143 self._traffic.update({'traffic_type': traffic_type,
144 'flow_type': cfg.get('Flow Type', TRAFFIC_DEFAULTS['flow_type']),
145 'bidir': bidirectional,
146 'tunnel_type': self._tunnel_type,
147 'multistream': int(multistream),
148 'stream_type': stream_type,
149 'pre_installed_flows' : pre_installed_flows,
150 'frame_rate': int(framerate)})
152 # Packet Forwarding mode
153 self._vswitch_none = 'none' == S.getValue('VSWITCH').strip().lower()
155 # trafficgen configuration required for tests of tunneling protocols
156 if self.deployment == "op2p":
157 self._traffic['l2'].update({'srcmac':
158 S.getValue('TRAFFICGEN_PORT1_MAC'),
160 S.getValue('TRAFFICGEN_PORT2_MAC')})
162 self._traffic['l3'].update({'srcip':
163 S.getValue('TRAFFICGEN_PORT1_IP'),
165 S.getValue('TRAFFICGEN_PORT2_IP')})
167 if self._tunnel_operation == "decapsulation":
168 self._traffic['l2'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L2')
169 self._traffic['l3'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L3')
170 self._traffic['l4'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L4')
171 elif S.getValue('NICS')[0]['type'] == 'vf' or S.getValue('NICS')[1]['type'] == 'vf':
172 mac1 = S.getValue('NICS')[0]['mac']
173 mac2 = S.getValue('NICS')[1]['mac']
175 self._traffic['l2'].update({'srcmac': mac2, 'dstmac': mac1})
177 self._logger.debug("MAC addresses can not be read")
179 # count how many VNFs are involved in TestSteps
181 for step in self.test:
182 if step[0].startswith('vnf'):
183 self._step_vnf_list[step[0]] = None
185 def run_initialize(self):
186 """ Prepare test execution environment
188 self._logger.debug(self.name)
190 # mount hugepages if needed
191 self._mount_hugepages()
193 self._logger.debug("Controllers:")
195 self._traffic_ctl = component_factory.create_traffic(
196 self._traffic['traffic_type'],
197 loader.get_trafficgen_class())
199 self._vnf_ctl = component_factory.create_vnf(
201 loader.get_vnf_class(),
202 len(self._step_vnf_list))
204 # verify enough hugepages are free to run the testcase
205 if not self._check_for_enough_hugepages():
206 raise RuntimeError('Not enough hugepages free to run test.')
208 # perform guest related handling
209 tmp_vm_count = self._vnf_ctl.get_vnfs_number() + len(self._step_vnf_list)
211 # copy sources of l2 forwarding tools into VM shared dir if needed
212 self._copy_fwd_tools_for_all_guests(tmp_vm_count)
214 # in case of multi VM in parallel, set the number of streams to the number of VMs
215 if self.deployment.startswith('pvpv'):
216 # for each VM NIC pair we need an unique stream
218 for vm_nic in S.getValue('GUEST_NICS_NR')[:tmp_vm_count]:
219 streams += int(vm_nic / 2) if vm_nic > 1 else 1
220 self._logger.debug("VMs with parallel connection were detected. "
221 "Thus Number of streams was set to %s", streams)
222 # update streams if needed; In case of additional VNFs deployed by TestSteps
223 # user can define a proper stream count manually
224 if 'multistream' not in self._traffic or self._traffic['multistream'] < streams:
225 self._traffic.update({'multistream': streams})
227 # OVS Vanilla requires guest VM MAC address and IPs to work
228 if 'linux_bridge' in S.getValue('GUEST_LOOPBACK'):
229 self._traffic['l2'].update({'srcmac': S.getValue('VANILLA_TGEN_PORT1_MAC'),
230 'dstmac': S.getValue('VANILLA_TGEN_PORT2_MAC')})
231 self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
232 'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
234 if self._vswitch_none:
235 self._vswitch_ctl = component_factory.create_pktfwd(
237 loader.get_pktfwd_class())
239 self._vswitch_ctl = component_factory.create_vswitch(
241 loader.get_vswitch_class(),
243 self._tunnel_operation)
245 self._collector = component_factory.create_collector(
246 loader.get_collector_class(),
247 self._results_dir, self.name)
248 self._loadgen = component_factory.create_loadgen(
252 self._output_file = os.path.join(self._results_dir, "result_" + self.name +
253 "_" + self.deployment + ".csv")
255 self._step_status = {'status' : True, 'details' : ''}
257 self._logger.debug("Setup:")
259 def run_finalize(self):
260 """ Tear down test execution environment and record test results
262 # Stop all VNFs started by TestSteps in case that something went wrong
263 self.step_stop_vnfs()
265 # umount hugepages if mounted
266 self._umount_hugepages()
268 # restore original settings
269 S.load_from_dict(self._settings_original)
271 # cleanup any namespaces created
272 if os.path.isdir('/tmp/namespaces'):
273 import tools.namespace
274 namespace_list = os.listdir('/tmp/namespaces')
275 if len(namespace_list):
276 self._logger.info('Cleaning up namespaces')
277 for name in namespace_list:
278 tools.namespace.delete_namespace(name)
279 os.rmdir('/tmp/namespaces')
280 # cleanup any veth ports created
281 if os.path.isdir('/tmp/veth'):
283 veth_list = os.listdir('/tmp/veth')
285 self._logger.info('Cleaning up veth ports')
286 for eth in veth_list:
287 port1, port2 = eth.split('-')
288 tools.veth.del_veth_port(port1, port2)
289 os.rmdir('/tmp/veth')
291 def run_report(self):
292 """ Report test results
294 self._logger.debug("self._collector Results:")
295 self._collector.print_results()
297 results = self._traffic_ctl.get_results()
299 self._logger.debug("Traffic Results:")
300 self._traffic_ctl.print_results()
302 self._tc_results = self._append_results(results)
303 TestCase.write_result_to_file(self._tc_results, self._output_file)
308 All setup and teardown through controllers is included.
310 # prepare test execution environment
311 self.run_initialize()
314 with self._vswitch_ctl, self._loadgen:
315 with self._vnf_ctl, self._collector:
316 if not self._vswitch_none:
319 with self._traffic_ctl:
320 # execute test based on TestSteps definition if needed...
322 # ...and continue with traffic generation, but keep
323 # in mind, that clean deployment does not configure
324 # OVS nor executes the traffic
325 if self.deployment != 'clean':
326 self._traffic_ctl.send_traffic(self._traffic)
328 # dump vswitch flows before they are affected by VNF termination
329 if not self._vswitch_none:
330 self._vswitch_ctl.dump_vswitch_flows()
332 # garbage collection for case that TestSteps modify existing deployment
333 self.step_stop_vnfs()
336 # tear down test execution environment and log results
339 self._testcase_run_time = time.strftime("%H:%M:%S",
340 time.gmtime(time.time() - self._testcase_start_time))
341 logging.info("Testcase execution time: " + self._testcase_run_time)
342 # report test results
345 def _update_settings(self, param, value):
346 """ Check value of given configuration parameter
347 In case that new value is different, then testcase
348 specific settings is updated and original value stored
350 :param param: Name of parameter inside settings
351 :param value: Disired parameter value
353 orig_value = S.getValue(param)
354 if orig_value != value:
355 self._settings_original[param] = orig_value
356 S.setValue(param, value)
358 def _append_results(self, results):
360 Method appends mandatory Test Case results to list of dictionaries.
362 :param results: list of dictionaries which contains results from
365 :returns: modified list of dictionaries.
368 item[ResultsConstants.ID] = self.name
369 item[ResultsConstants.DEPLOYMENT] = self.deployment
370 item[ResultsConstants.TRAFFIC_TYPE] = self._traffic['l3']['proto']
371 item[ResultsConstants.TEST_RUN_TIME] = self._testcase_run_time
372 if self._traffic['multistream']:
373 item[ResultsConstants.SCAL_STREAM_COUNT] = self._traffic['multistream']
374 item[ResultsConstants.SCAL_STREAM_TYPE] = self._traffic['stream_type']
375 item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows']
376 if self._vnf_ctl.get_vnfs_number():
377 item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(S.getValue('GUEST_LOOPBACK'))
378 if self._tunnel_type:
379 item[ResultsConstants.TUNNEL_TYPE] = self._tunnel_type
382 def _copy_fwd_tools_for_all_guests(self, vm_count):
383 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] based on selected deployment.
385 # consider only VNFs involved in the test
386 for guest_dir in set(S.getValue('GUEST_SHARE_DIR')[:vm_count]):
387 self._copy_fwd_tools_for_guest(guest_dir)
389 def _copy_fwd_tools_for_guest(self, guest_dir):
390 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR of VM
392 :param index: Index of VM starting from 1 (i.e. 1st VM has index 1)
394 # remove shared dir if it exists to avoid issues with file consistency
395 if os.path.exists(guest_dir):
396 tasks.run_task(['rm', '-f', '-r', guest_dir], self._logger,
397 'Removing content of shared directory...', True)
399 # directory to share files between host and guest
400 os.makedirs(guest_dir)
402 # copy sources into shared dir only if neccessary
403 guest_loopback = set(S.getValue('GUEST_LOOPBACK'))
404 if 'testpmd' in guest_loopback:
406 # exclude whole .git/ subdirectory and all o-files;
407 # It is assumed, that the same RTE_TARGET is used in both host
408 # and VMs; This simplification significantly speeds up testpmd
409 # build. If we will need a different RTE_TARGET in VM,
410 # then we have to build whole DPDK from the scratch in VM.
411 # In that case we can copy just DPDK sources (e.g. by excluding
412 # all items obtained by git status -unormal --porcelain).
413 # NOTE: Excluding RTE_TARGET directory won't help on systems,
414 # where DPDK is built for multiple targets (e.g. for gcc & icc)
416 exclude.append(r'--exclude=.git/')
417 exclude.append(r'--exclude=*.o')
418 tasks.run_task(['rsync', '-a', '-r', '-l'] + exclude +
419 [os.path.join(S.getValue('TOOLS')['dpdk_src'], ''),
420 os.path.join(guest_dir, 'DPDK')],
422 'Copying DPDK to shared directory...',
424 except subprocess.CalledProcessError:
425 self._logger.error('Unable to copy DPDK to shared directory')
427 if 'l2fwd' in guest_loopback:
429 tasks.run_task(['rsync', '-a', '-r', '-l',
430 os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
431 os.path.join(guest_dir, 'l2fwd')],
433 'Copying l2fwd to shared directory...',
435 except subprocess.CalledProcessError:
436 self._logger.error('Unable to copy l2fwd to shared directory')
439 def _mount_hugepages(self):
440 """Mount hugepages if usage of DPDK or Qemu is detected
442 # hugepages are needed by DPDK and Qemu
443 if not self._hugepages_mounted and \
444 (self.deployment.count('v') or \
445 S.getValue('VSWITCH').lower().count('dpdk') or \
446 self._vswitch_none or \
447 self.test and 'vnf' in [step[0][0:3] for step in self.test]):
448 hugepages.mount_hugepages()
449 self._hugepages_mounted = True
451 def _umount_hugepages(self):
452 """Umount hugepages if they were mounted before
454 if self._hugepages_mounted:
455 hugepages.umount_hugepages()
456 self._hugepages_mounted = False
458 def _check_for_enough_hugepages(self):
459 """Check to make sure enough hugepages are free to satisfy the
463 hugepage_size = hugepages.get_hugepage_size()
464 # get hugepage amounts per guest involved in the test
465 for guest in range(self._vnf_ctl.get_vnfs_number()):
466 hugepages_needed += math.ceil((int(S.getValue(
467 'GUEST_MEMORY')[guest]) * 1000) / hugepage_size)
469 # get hugepage amounts for each socket on dpdk
470 sock0_mem, sock1_mem = 0, 0
471 if S.getValue('VSWITCH').lower().count('dpdk'):
472 # the import below needs to remain here and not put into the module
473 # imports because of an exception due to settings not yet loaded
474 from vswitches import ovs_dpdk_vhost
475 if ovs_dpdk_vhost.OvsDpdkVhost.old_dpdk_config():
477 r'-socket-mem\s+(\d+),(\d+)',
478 ''.join(S.getValue('VSWITCHD_DPDK_ARGS')))
480 sock0_mem, sock1_mem = (int(match.group(1)) * 1024 / hugepage_size,
481 int(match.group(2)) * 1024 / hugepage_size)
484 'Could not parse socket memory config in dpdk params.')
486 sock0_mem, sock1_mem = (
488 'VSWITCHD_DPDK_CONFIG')['dpdk-socket-mem'].split(','))
489 sock0_mem, sock1_mem = (int(sock0_mem) * 1024 / hugepage_size,
490 int(sock1_mem) * 1024 / hugepage_size)
492 # If hugepages needed, verify the amounts are free
493 if any([hugepages_needed, sock0_mem, sock1_mem]):
494 free_hugepages = hugepages.get_free_hugepages()
496 logging.info('Need %s hugepages free for guests',
498 result1 = free_hugepages >= hugepages_needed
499 free_hugepages -= hugepages_needed
504 logging.info('Need %s hugepages free for dpdk socket 0',
506 result2 = hugepages.get_free_hugepages('0') >= sock0_mem
507 free_hugepages -= sock0_mem
512 logging.info('Need %s hugepages free for dpdk socket 1',
514 result3 = hugepages.get_free_hugepages('1') >= sock1_mem
515 free_hugepages -= sock1_mem
519 logging.info('Need a total of {} total hugepages'.format(
520 hugepages_needed + sock1_mem + sock0_mem))
522 # The only drawback here is sometimes dpdk doesn't release
523 # its hugepages on a test failure. This could cause a test
524 # to fail when dpdk would be OK to start because it will just
525 # use the previously allocated hugepages.
526 result4 = True if free_hugepages >= 0 else False
528 return all([result1, result2, result3, result4])
533 def write_result_to_file(results, output):
534 """Write list of dictionaries to a CSV file.
536 Each element on list will create separate row in output file.
537 If output file already exists, data will be appended at the end,
538 otherwise it will be created.
540 :param results: list of dictionaries.
541 :param output: path to output file.
543 with open(output, 'a') as csvfile:
545 logging.info("Write results to file: " + output)
546 fieldnames = TestCase._get_unique_keys(results)
548 writer = csv.DictWriter(csvfile, fieldnames)
550 if not csvfile.tell(): # file is now empty
553 for result in results:
554 writer.writerow(result)
557 def _get_unique_keys(list_of_dicts):
558 """Gets unique key values as ordered list of strings in given dicts
560 :param list_of_dicts: list of dictionaries.
562 :returns: list of unique keys(strings).
564 result = OrderedDict()
565 for item in list_of_dicts:
566 for key in item.keys():
569 return list(result.keys())
571 def _add_flows(self):
572 """Add flows to the vswitch
574 vswitch = self._vswitch_ctl.get_vswitch()
575 # TODO BOM 15-08-07 the frame mod code assumes that the
576 # physical ports are ports 1 & 2. The actual numbers
577 # need to be retrived from the vSwitch and the metadata value
578 # updated accordingly.
579 bridge = S.getValue('VSWITCH_BRIDGE_NAME')
580 if self._frame_mod == "vlan":
581 # 0x8100 => VLAN ethertype
582 self._logger.debug(" **** VLAN ***** ")
583 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
584 'actions': ['push_vlan:0x8100', 'goto_table:3']}
585 vswitch.add_flow(bridge, flow)
586 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
587 'actions': ['push_vlan:0x8100', 'goto_table:3']}
588 vswitch.add_flow(bridge, flow)
589 elif self._frame_mod == "mpls":
590 # 0x8847 => MPLS unicast ethertype
591 self._logger.debug(" **** MPLS ***** ")
592 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
593 'actions': ['push_mpls:0x8847', 'goto_table:3']}
594 vswitch.add_flow(bridge, flow)
595 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
596 'actions': ['push_mpls:0x8847', 'goto_table:3']}
597 vswitch.add_flow(bridge, flow)
598 elif self._frame_mod == "mac":
599 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
600 'actions': ['mod_dl_src:22:22:22:22:22:22',
602 vswitch.add_flow(bridge, flow)
603 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
604 'actions': ['mod_dl_src:11:11:11:11:11:11',
606 vswitch.add_flow(bridge, flow)
607 elif self._frame_mod == "dscp":
608 # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
609 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
611 'actions': ['mod_nw_tos:184', 'goto_table:3']}
612 vswitch.add_flow(bridge, flow)
613 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
615 'actions': ['mod_nw_tos:184', 'goto_table:3']}
616 vswitch.add_flow(bridge, flow)
617 elif self._frame_mod == "ttl":
618 # 251 and 241 are the highest prime numbers < 255
619 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
621 'actions': ['mod_nw_ttl:251', 'goto_table:3']}
622 vswitch.add_flow(bridge, flow)
623 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
625 'actions': ['mod_nw_ttl:241', 'goto_table:3']}
626 vswitch.add_flow(bridge, flow)
627 elif self._frame_mod == "ip_addr":
628 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
630 'actions': ['mod_nw_src:10.10.10.10',
631 'mod_nw_dst:20.20.20.20',
633 vswitch.add_flow(bridge, flow)
634 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
636 'actions': ['mod_nw_src:20.20.20.20',
637 'mod_nw_dst:10.10.10.10',
639 vswitch.add_flow(bridge, flow)
640 elif self._frame_mod == "ip_port":
641 # TODO BOM 15-08-27 The traffic generated is assumed
642 # to be UDP (nw_proto 17d) which is the default case but
643 # we will need to pick up the actual traffic params in use.
644 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
645 'dl_type':'0x0800', 'nw_proto':'17',
646 'actions': ['mod_tp_src:44444',
647 'mod_tp_dst:44444', 'goto_table:3']}
648 vswitch.add_flow(bridge, flow)
649 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
650 'dl_type':'0x0800', 'nw_proto':'17',
651 'actions': ['mod_tp_src:44444',
652 'mod_tp_dst:44444', 'goto_table:3']}
653 vswitch.add_flow(bridge, flow)
659 # TestSteps realted methods
661 def step_report_status(self, label, status):
662 """ Log status of test step
664 self._logger.info("%s ... %s", label, 'OK' if status else 'FAILED')
666 def step_stop_vnfs(self):
667 """ Stop all VNFs started by TestSteps
669 for vnf in self._step_vnf_list:
670 self._step_vnf_list[vnf].stop()
673 def step_eval_param(param, STEP):
674 # pylint: disable=invalid-name
675 """ Helper function for #STEP macro evaluation
677 if isinstance(param, str):
678 # evaluate every #STEP reference inside parameter itself
679 macros = re.findall(r'#STEP\[[\w\[\]\-\'\"]+\]', param)
682 # pylint: disable=eval-used
683 tmp_val = str(eval(macro[1:]))
684 param = param.replace(macro, tmp_val)
686 elif isinstance(param, list) or isinstance(param, tuple):
689 tmp_list.append(TestCase.step_eval_param(item, STEP))
691 elif isinstance(param, dict):
693 for (key, value) in param.items():
694 tmp_dict[key] = TestCase.step_eval_param(value, STEP)
700 def step_eval_params(params, step_result):
701 """ Evaluates referrences to results from previous steps
704 # evaluate all parameters if needed
706 eval_params.append(TestCase.step_eval_param(param, step_result))
710 """ Execute actions specified by TestSteps list
712 :return: False if any error was detected
719 # required for VNFs initialization
721 # initialize list with results
722 self._step_result = [None] * len(self.test)
724 # run test step by step...
725 for i, step in enumerate(self.test):
726 step_ok = not self._step_check
727 if step[0] == 'vswitch':
728 test_object = self._vswitch_ctl.get_vswitch()
729 elif step[0] == 'namespace':
730 test_object = namespace
731 elif step[0] == 'veth':
733 elif step[0] == 'settings':
735 elif step[0] == 'tools':
736 test_object = TestStepsTools()
737 step[1] = step[1].title()
738 elif step[0] == 'trafficgen':
739 test_object = self._traffic_ctl
740 # in case of send_traffic or send_traffic_async methods, ensure
741 # that specified traffic values are merged with existing self._traffic
742 if step[1].startswith('send_traffic'):
743 tmp_traffic = copy.deepcopy(self._traffic)
744 tmp_traffic.update(step[2])
745 step[2] = tmp_traffic
746 elif step[0].startswith('vnf'):
747 if not self._step_vnf_list[step[0]]:
749 self._step_vnf_list[step[0]] = loader.get_vnf_class()()
750 test_object = self._step_vnf_list[step[0]]
751 elif step[0] == 'wait':
752 input(os.linesep + "Step {}: Press Enter to continue with "
753 "the next step...".format(i) + os.linesep + os.linesep)
756 self._logger.error("Unsupported test object %s", step[0])
757 self._step_status = {'status' : False, 'details' : ' '.join(step)}
758 self.step_report_status("Step '{}'".format(' '.join(step)),
759 self._step_status['status'])
762 test_method = getattr(test_object, step[1])
764 test_method_check = getattr(test_object, CHECK_PREFIX + step[1])
766 test_method_check = None
770 # eval parameters, but use only valid step_results
771 # to support negative indexes
772 step_params = TestCase.step_eval_params(step[2:], self._step_result[:i])
773 step_log = '{} {}'.format(' '.join(step[:2]), step_params)
774 self._logger.debug("Step %s '%s' start", i, step_log)
775 self._step_result[i] = test_method(*step_params)
776 self._logger.debug("Step %s '%s' results '%s'", i,
777 step_log, self._step_result[i])
780 step_ok = test_method_check(self._step_result[i], *step_params)
781 except (AssertionError, AttributeError, IndexError) as ex:
783 self._logger.error("Step %s raised %s", i, type(ex).__name__)
786 self.step_report_status("Step {} - '{}'".format(i, step_log), step_ok)
789 self._step_status = {'status' : False, 'details' : step_log}
790 # Stop all VNFs started by TestSteps
791 self.step_stop_vnfs()
794 # all steps processed without any issue