1 # Copyright 2015-2017 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 datetime import datetime as dt
28 from conf import settings as S
29 from conf import get_test_param, merge_spec
30 import core.component_factory as component_factory
31 from core.loader import Loader
32 from core.results.results_constants import ResultsConstants
33 from tools import tasks
34 from tools import hugepages
35 from tools import functions
36 from tools import namespace
37 from tools import veth
38 from tools.teststepstools import TestStepsTools
40 CHECK_PREFIX = 'validate_'
42 # pylint: disable=too-many-instance-attributes
43 class TestCase(object):
44 """TestCase base class
46 In this basic form runs RFC2544 throughput test
48 # pylint: disable=too-many-statements
49 def __init__(self, test_cfg):
50 """Pull out fields from test config
52 :param test_cfg: A dictionary of string-value pairs describing the test
53 configuration. Both the key and values strings use well-known
55 :param results_dir: Where the csv formatted results are written.
57 # make a local copy of test configuration to avoid modification of
58 # original content used in vsperf main script
59 cfg = copy.deepcopy(test_cfg)
61 self._testcase_start_time = time.time()
62 self._testcase_stop_time = self._testcase_start_time
63 self._hugepages_mounted = False
64 self._traffic_ctl = None
66 self._vswitch_ctl = None
67 self._collector = None
69 self._output_file = None
70 self._tc_results = None
71 self._settings_original = {}
72 self._settings_paths_modified = False
73 self._testcast_run_time = None
75 # initialization of step driven specific members
76 self._step_check = False # by default don't check result for step driven testcases
77 self._step_vnf_list = {}
78 self._step_result = []
79 self._step_status = None
80 self._testcase_run_time = None
82 # store all GUEST_ specific settings to keep original values before their expansion
83 for key in S.__dict__:
84 if key.startswith('GUEST_'):
85 self._settings_original[key] = S.getValue(key)
87 self._update_settings('VSWITCH', cfg.get('vSwitch', S.getValue('VSWITCH')))
88 self._update_settings('VNF', cfg.get('VNF', S.getValue('VNF')))
89 self._update_settings('TRAFFICGEN', cfg.get('Trafficgen', S.getValue('TRAFFICGEN')))
90 test_params = copy.deepcopy(S.getValue('TEST_PARAMS'))
91 tc_test_params = cfg.get('Parameters', S.getValue('TEST_PARAMS'))
92 test_params = merge_spec(test_params, tc_test_params)
93 self._update_settings('TEST_PARAMS', test_params)
96 # override all redefined GUEST_ values to have them expanded correctly
97 tmp_test_params = copy.deepcopy(S.getValue('TEST_PARAMS'))
98 for key in tmp_test_params:
99 if key.startswith('GUEST_'):
100 S.setValue(key, S.getValue(key))
101 S.getValue('TEST_PARAMS').pop(key)
103 # update global settings
104 functions.settings_update_paths()
106 # set test parameters; CLI options take precedence to testcase settings
107 self._logger = logging.getLogger(__name__)
108 self.name = cfg['Name']
109 self.desc = cfg.get('Description', 'No description given.')
110 self.test = cfg.get('TestSteps', None)
112 bidirectional = S.getValue('TRAFFIC')['bidir']
113 if not isinstance(S.getValue('TRAFFIC')['bidir'], str):
115 'Bi-dir value must be of type string')
116 bidirectional = bidirectional.title() # Keep things consistent
118 self.deployment = cfg['Deployment']
119 self._frame_mod = cfg.get('Frame Modification', None)
121 self._tunnel_type = None
122 self._tunnel_operation = None
124 if self.deployment == 'op2p':
125 self._tunnel_operation = cfg['Tunnel Operation']
127 if 'Tunnel Type' in cfg:
128 self._tunnel_type = cfg['Tunnel Type']
129 self._tunnel_type = get_test_param('TUNNEL_TYPE',
132 # check if test requires background load and which generator it uses
133 self._load_cfg = cfg.get('Load', None)
134 if self._load_cfg and 'tool' in self._load_cfg:
135 self._loadgen = self._load_cfg['tool']
137 # background load is not requested, so use dummy implementation
138 self._loadgen = "Dummy"
141 self._frame_mod = self._frame_mod.lower()
142 self._results_dir = S.getValue('RESULTS_PATH')
144 # set traffic details, so they can be passed to vswitch and traffic ctls
145 self._traffic = copy.deepcopy(S.getValue('TRAFFIC'))
146 self._traffic.update({'bidir': bidirectional,
147 'tunnel_type': self._tunnel_type,})
149 self._traffic = functions.check_traffic(self._traffic)
151 # Packet Forwarding mode
152 self._vswitch_none = str(S.getValue('VSWITCH')).strip().lower() == 'none'
154 # trafficgen configuration required for tests of tunneling protocols
155 if self.deployment == "op2p":
156 self._traffic['l2'].update({'srcmac':
157 S.getValue('TRAFFICGEN_PORT1_MAC'),
159 S.getValue('TRAFFICGEN_PORT2_MAC')})
161 self._traffic['l3'].update({'srcip':
162 S.getValue('TRAFFICGEN_PORT1_IP'),
164 S.getValue('TRAFFICGEN_PORT2_IP')})
166 if self._tunnel_operation == "decapsulation":
167 self._traffic['l2'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L2')
168 self._traffic['l3'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L3')
169 self._traffic['l4'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L4')
170 elif len(S.getValue('NICS')) and \
171 (S.getValue('NICS')[0]['type'] == 'vf' or
172 S.getValue('NICS')[1]['type'] == 'vf'):
173 mac1 = S.getValue('NICS')[0]['mac']
174 mac2 = S.getValue('NICS')[1]['mac']
176 self._traffic['l2'].update({'srcmac': mac2, 'dstmac': mac1})
178 self._logger.debug("MAC addresses can not be read")
180 # count how many VNFs are involved in TestSteps
182 for step in self.test:
183 if step[0].startswith('vnf'):
184 self._step_vnf_list[step[0]] = None
186 def run_initialize(self):
187 """ Prepare test execution environment
189 self._logger.debug(self.name)
191 # mount hugepages if needed
192 self._mount_hugepages()
194 self._logger.debug("Controllers:")
196 self._traffic_ctl = component_factory.create_traffic(
197 self._traffic['traffic_type'],
198 loader.get_trafficgen_class())
200 self._vnf_ctl = component_factory.create_vnf(
202 loader.get_vnf_class(),
203 len(self._step_vnf_list))
205 # verify enough hugepages are free to run the testcase
206 if not self._check_for_enough_hugepages():
207 raise RuntimeError('Not enough hugepages free to run test.')
209 # perform guest related handling
210 tmp_vm_count = self._vnf_ctl.get_vnfs_number() + len(self._step_vnf_list)
212 # copy sources of l2 forwarding tools into VM shared dir if needed
213 self._copy_fwd_tools_for_all_guests(tmp_vm_count)
215 # in case of multi VM in parallel, set the number of streams to the number of VMs
216 if self.deployment.startswith('pvpv'):
217 # for each VM NIC pair we need an unique stream
219 for vm_nic in S.getValue('GUEST_NICS_NR')[:tmp_vm_count]:
220 streams += int(vm_nic / 2) if vm_nic > 1 else 1
221 self._logger.debug("VMs with parallel connection were detected. "
222 "Thus Number of streams was set to %s", streams)
223 # update streams if needed; In case of additional VNFs deployed by TestSteps
224 # user can define a proper stream count manually
225 if 'multistream' not in self._traffic or self._traffic['multistream'] < streams:
226 self._traffic.update({'multistream': streams})
228 # OVS Vanilla requires guest VM MAC address and IPs to work
229 if 'linux_bridge' in S.getValue('GUEST_LOOPBACK'):
230 self._traffic['l2'].update({'srcmac': S.getValue('VANILLA_TGEN_PORT1_MAC'),
231 'dstmac': S.getValue('VANILLA_TGEN_PORT2_MAC')})
232 self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
233 'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
235 if self._vswitch_none:
236 self._vswitch_ctl = component_factory.create_pktfwd(
238 loader.get_pktfwd_class())
240 self._vswitch_ctl = component_factory.create_vswitch(
242 loader.get_vswitch_class(),
244 self._tunnel_operation)
246 self._collector = component_factory.create_collector(
247 loader.get_collector_class(),
248 self._results_dir, self.name)
249 self._loadgen = component_factory.create_loadgen(
253 self._output_file = os.path.join(self._results_dir, "result_" + self.name +
254 "_" + self.deployment + ".csv")
256 self._step_status = {'status' : True, 'details' : ''}
258 self._logger.debug("Setup:")
260 def run_finalize(self):
261 """ Tear down test execution environment and record test results
263 # Stop all VNFs started by TestSteps in case that something went wrong
264 self.step_stop_vnfs()
266 # umount hugepages if mounted
267 self._umount_hugepages()
269 # cleanup any namespaces created
270 if os.path.isdir('/tmp/namespaces'):
271 namespace_list = os.listdir('/tmp/namespaces')
272 if len(namespace_list):
273 self._logger.info('Cleaning up namespaces')
274 for name in namespace_list:
275 namespace.delete_namespace(name)
276 os.rmdir('/tmp/namespaces')
277 # cleanup any veth ports created
278 if os.path.isdir('/tmp/veth'):
279 veth_list = os.listdir('/tmp/veth')
281 self._logger.info('Cleaning up veth ports')
282 for eth in veth_list:
283 port1, port2 = eth.split('-')
284 veth.del_veth_port(port1, port2)
285 os.rmdir('/tmp/veth')
287 def run_report(self):
288 """ Report test results
290 self._logger.debug("self._collector Results:")
291 self._collector.print_results()
293 results = self._traffic_ctl.get_results()
295 self._logger.debug("Traffic Results:")
296 self._traffic_ctl.print_results()
298 if self._tc_results is None:
299 self._tc_results = self._append_results(results)
301 # integration step driven tests have their status and possible
302 # failure details stored inside self._tc_results
303 results = self._append_results(results)
304 if len(self._tc_results) < len(results):
305 if len(self._tc_results) > 1:
306 raise RuntimeError('Testcase results do not match:'
308 'trafficgen results: {}\n',
312 tmp_results = copy.deepcopy(self._tc_results[0])
313 self._tc_results = []
315 tmp_res = copy.deepcopy(tmp_results)
317 self._tc_results.append(tmp_res)
319 for i, result in enumerate(results):
320 self._tc_results[i].update(result)
322 TestCase.write_result_to_file(self._tc_results, self._output_file)
327 All setup and teardown through controllers is included.
329 # prepare test execution environment
330 self.run_initialize()
333 with self._vswitch_ctl, self._loadgen:
334 with self._vnf_ctl, self._collector:
335 if not self._vswitch_none:
338 self._versions += self._vswitch_ctl.get_vswitch().get_version()
340 with self._traffic_ctl:
341 # execute test based on TestSteps definition if needed...
343 # ...and continue with traffic generation, but keep
344 # in mind, that clean deployment does not configure
345 # OVS nor executes the traffic
346 if self.deployment != 'clean':
347 self._traffic_ctl.send_traffic(self._traffic)
349 # dump vswitch flows before they are affected by VNF termination
350 if not self._vswitch_none:
351 self._vswitch_ctl.dump_vswitch_flows()
353 # garbage collection for case that TestSteps modify existing deployment
354 self.step_stop_vnfs()
357 # tear down test execution environment and log results
360 self._testcase_stop_time = time.time()
361 self._testcase_run_time = time.strftime("%H:%M:%S",
362 time.gmtime(self._testcase_stop_time -
363 self._testcase_start_time))
364 logging.info("Testcase execution time: " + self._testcase_run_time)
365 # report test results
368 # restore original settings
369 for key in self._settings_original:
370 S.setValue(key, self._settings_original[key])
372 def _update_settings(self, param, value):
373 """ Check value of given configuration parameter
374 In case that new value is different, then testcase
375 specific settings is updated and original value stored
377 :param param: Name of parameter inside settings
378 :param value: Disired parameter value
380 orig_value = S.getValue(param)
381 if orig_value != value:
382 self._settings_original[param] = copy.deepcopy(orig_value)
383 S.setValue(param, value)
385 def _append_results(self, results):
387 Method appends mandatory Test Case results to list of dictionaries.
389 :param results: list of dictionaries which contains results from
392 :returns: modified list of dictionaries.
395 item[ResultsConstants.ID] = self.name
396 item[ResultsConstants.DEPLOYMENT] = self.deployment
397 item[ResultsConstants.VSWITCH] = S.getValue('VSWITCH')
398 item[ResultsConstants.TRAFFIC_TYPE] = self._traffic['l3']['proto']
399 item[ResultsConstants.TEST_RUN_TIME] = self._testcase_run_time
400 # convert timestamps to human readable format
401 item[ResultsConstants.TEST_START_TIME] = dt.fromtimestamp(
402 self._testcase_start_time).strftime('%Y-%m-%d %H:%M:%S')
403 item[ResultsConstants.TEST_STOP_TIME] = dt.fromtimestamp(
404 self._testcase_stop_time).strftime('%Y-%m-%d %H:%M:%S')
405 if self._traffic['multistream']:
406 item[ResultsConstants.SCAL_STREAM_COUNT] = self._traffic['multistream']
407 item[ResultsConstants.SCAL_STREAM_TYPE] = self._traffic['stream_type']
408 item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows']
409 if self._vnf_ctl.get_vnfs_number():
410 item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(S.getValue('GUEST_LOOPBACK'))
411 if self._tunnel_type:
412 item[ResultsConstants.TUNNEL_TYPE] = self._tunnel_type
415 def _copy_fwd_tools_for_all_guests(self, vm_count):
416 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] based on selected deployment.
418 # consider only VNFs involved in the test
419 for guest_dir in set(S.getValue('GUEST_SHARE_DIR')[:vm_count]):
420 self._copy_fwd_tools_for_guest(guest_dir)
422 def _copy_fwd_tools_for_guest(self, guest_dir):
423 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR of VM
425 :param index: Index of VM starting from 1 (i.e. 1st VM has index 1)
427 # remove shared dir if it exists to avoid issues with file consistency
428 if os.path.exists(guest_dir):
429 tasks.run_task(['rm', '-f', '-r', guest_dir], self._logger,
430 'Removing content of shared directory...', True)
432 # directory to share files between host and guest
433 os.makedirs(guest_dir)
435 # copy sources into shared dir only if neccessary
436 guest_loopback = set(S.getValue('GUEST_LOOPBACK'))
437 if 'testpmd' in guest_loopback:
439 # exclude whole .git/ subdirectory and all o-files;
440 # It is assumed, that the same RTE_TARGET is used in both host
441 # and VMs; This simplification significantly speeds up testpmd
442 # build. If we will need a different RTE_TARGET in VM,
443 # then we have to build whole DPDK from the scratch in VM.
444 # In that case we can copy just DPDK sources (e.g. by excluding
445 # all items obtained by git status -unormal --porcelain).
446 # NOTE: Excluding RTE_TARGET directory won't help on systems,
447 # where DPDK is built for multiple targets (e.g. for gcc & icc)
449 exclude.append(r'--exclude=.git/')
450 exclude.append(r'--exclude=*.o')
451 tasks.run_task(['rsync', '-a', '-r', '-l'] + exclude +
452 [os.path.join(S.getValue('TOOLS')['dpdk_src'], ''),
453 os.path.join(guest_dir, 'DPDK')],
455 'Copying DPDK to shared directory...',
457 except subprocess.CalledProcessError:
458 self._logger.error('Unable to copy DPDK to shared directory')
460 if 'l2fwd' in guest_loopback:
462 tasks.run_task(['rsync', '-a', '-r', '-l',
463 os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
464 os.path.join(guest_dir, 'l2fwd')],
466 'Copying l2fwd to shared directory...',
468 except subprocess.CalledProcessError:
469 self._logger.error('Unable to copy l2fwd to shared directory')
472 def _mount_hugepages(self):
473 """Mount hugepages if usage of DPDK or Qemu is detected
475 # pylint: disable=too-many-boolean-expressions
476 # hugepages are needed by DPDK and Qemu
477 if not self._hugepages_mounted and \
478 (self.deployment.count('v') or \
479 str(S.getValue('VSWITCH')).lower().count('dpdk') or \
480 self._vswitch_none or \
481 self.test and 'vnf' in [step[0][0:3] for step in self.test]):
482 hugepages.mount_hugepages()
483 self._hugepages_mounted = True
485 def _umount_hugepages(self):
486 """Umount hugepages if they were mounted before
488 if self._hugepages_mounted:
489 hugepages.umount_hugepages()
490 self._hugepages_mounted = False
492 def _check_for_enough_hugepages(self):
493 """Check to make sure enough hugepages are free to satisfy the
497 hugepage_size = hugepages.get_hugepage_size()
498 # get hugepage amounts per guest involved in the test
499 for guest in range(self._vnf_ctl.get_vnfs_number()):
500 hugepages_needed += math.ceil((int(S.getValue(
501 'GUEST_MEMORY')[guest]) * 1000) / hugepage_size)
503 # get hugepage amounts for each socket on dpdk
504 sock0_mem, sock1_mem = 0, 0
506 if str(S.getValue('VSWITCH')).lower().count('dpdk'):
507 sock_mem = S.getValue('DPDK_SOCKET_MEM')
508 sock0_mem, sock1_mem = (int(sock_mem[0]) * 1024 / hugepage_size,
509 int(sock_mem[1]) * 1024 / hugepage_size)
511 # If hugepages needed, verify the amounts are free
512 if any([hugepages_needed, sock0_mem, sock1_mem]):
513 free_hugepages = hugepages.get_free_hugepages()
515 logging.info('Need %s hugepages free for guests',
517 result1 = free_hugepages >= hugepages_needed
518 free_hugepages -= hugepages_needed
523 logging.info('Need %s hugepages free for dpdk socket 0',
525 result2 = hugepages.get_free_hugepages('0') >= sock0_mem
526 free_hugepages -= sock0_mem
531 logging.info('Need %s hugepages free for dpdk socket 1',
533 result3 = hugepages.get_free_hugepages('1') >= sock1_mem
534 free_hugepages -= sock1_mem
538 logging.info('Need a total of %s total hugepages',
539 hugepages_needed + sock1_mem + sock0_mem)
541 # The only drawback here is sometimes dpdk doesn't release
542 # its hugepages on a test failure. This could cause a test
543 # to fail when dpdk would be OK to start because it will just
544 # use the previously allocated hugepages.
545 result4 = True if free_hugepages >= 0 else False
547 return all([result1, result2, result3, result4])
552 def write_result_to_file(results, output):
553 """Write list of dictionaries to a CSV file.
555 Each element on list will create separate row in output file.
556 If output file already exists, data will be appended at the end,
557 otherwise it will be created.
559 :param results: list of dictionaries.
560 :param output: path to output file.
562 with open(output, 'a') as csvfile:
564 logging.info("Write results to file: " + output)
565 fieldnames = TestCase._get_unique_keys(results)
567 writer = csv.DictWriter(csvfile, fieldnames)
569 if not csvfile.tell(): # file is now empty
572 for result in results:
573 writer.writerow(result)
576 def _get_unique_keys(list_of_dicts):
577 """Gets unique key values as ordered list of strings in given dicts
579 :param list_of_dicts: list of dictionaries.
581 :returns: list of unique keys(strings).
583 result = OrderedDict()
584 for item in list_of_dicts:
585 for key in item.keys():
588 return list(result.keys())
590 def _add_flows(self):
591 """Add flows to the vswitch
593 vswitch = self._vswitch_ctl.get_vswitch()
594 # NOTE BOM 15-08-07 the frame mod code assumes that the
595 # physical ports are ports 1 & 2. The actual numbers
596 # need to be retrived from the vSwitch and the metadata value
597 # updated accordingly.
598 bridge = S.getValue('VSWITCH_BRIDGE_NAME')
599 if self._frame_mod == "vlan":
600 # 0x8100 => VLAN ethertype
601 self._logger.debug(" **** VLAN ***** ")
602 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
603 'actions': ['push_vlan:0x8100', 'goto_table:3']}
604 vswitch.add_flow(bridge, flow)
605 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
606 'actions': ['push_vlan:0x8100', 'goto_table:3']}
607 vswitch.add_flow(bridge, flow)
608 elif self._frame_mod == "mpls":
609 # 0x8847 => MPLS unicast ethertype
610 self._logger.debug(" **** MPLS ***** ")
611 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
612 'actions': ['push_mpls:0x8847', 'goto_table:3']}
613 vswitch.add_flow(bridge, flow)
614 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
615 'actions': ['push_mpls:0x8847', 'goto_table:3']}
616 vswitch.add_flow(bridge, flow)
617 elif self._frame_mod == "mac":
618 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
619 'actions': ['mod_dl_src:22:22:22:22:22:22',
621 vswitch.add_flow(bridge, flow)
622 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
623 'actions': ['mod_dl_src:11:11:11:11:11:11',
625 vswitch.add_flow(bridge, flow)
626 elif self._frame_mod == "dscp":
627 # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
628 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
630 'actions': ['mod_nw_tos:184', 'goto_table:3']}
631 vswitch.add_flow(bridge, flow)
632 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
634 'actions': ['mod_nw_tos:184', 'goto_table:3']}
635 vswitch.add_flow(bridge, flow)
636 elif self._frame_mod == "ttl":
637 # 251 and 241 are the highest prime numbers < 255
638 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
640 'actions': ['mod_nw_ttl:251', 'goto_table:3']}
641 vswitch.add_flow(bridge, flow)
642 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
644 'actions': ['mod_nw_ttl:241', 'goto_table:3']}
645 vswitch.add_flow(bridge, flow)
646 elif self._frame_mod == "ip_addr":
647 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
649 'actions': ['mod_nw_src:10.10.10.10',
650 'mod_nw_dst:20.20.20.20',
652 vswitch.add_flow(bridge, flow)
653 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
655 'actions': ['mod_nw_src:20.20.20.20',
656 'mod_nw_dst:10.10.10.10',
658 vswitch.add_flow(bridge, flow)
659 elif self._frame_mod == "ip_port":
660 # NOTE BOM 15-08-27 The traffic generated is assumed
661 # to be UDP (nw_proto 17d) which is the default case but
662 # we will need to pick up the actual traffic params in use.
663 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
664 'dl_type':'0x0800', 'nw_proto':'17',
665 'actions': ['mod_tp_src:44444',
666 'mod_tp_dst:44444', 'goto_table:3']}
667 vswitch.add_flow(bridge, flow)
668 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
669 'dl_type':'0x0800', 'nw_proto':'17',
670 'actions': ['mod_tp_src:44444',
671 'mod_tp_dst:44444', 'goto_table:3']}
672 vswitch.add_flow(bridge, flow)
678 # TestSteps realted methods
680 def step_report_status(self, label, status):
681 """ Log status of test step
683 self._logger.info("%s ... %s", label, 'OK' if status else 'FAILED')
685 def step_stop_vnfs(self):
686 """ Stop all VNFs started by TestSteps
688 for vnf in self._step_vnf_list:
689 if self._step_vnf_list[vnf]:
690 self._step_vnf_list[vnf].stop()
692 def step_eval_param(self, param, STEP):
693 # pylint: disable=invalid-name
694 """ Helper function for #STEP macro evaluation
696 if isinstance(param, str):
697 # evaluate every #STEP reference inside parameter itself
698 macros = re.findall(r'#STEP\[[\w\[\]\-\'\"]+\]', param)
701 # pylint: disable=eval-used
702 tmp_val = str(eval(macro[1:]))
703 param = param.replace(macro, tmp_val)
705 # evaluate references to vsperf configuration options
706 macros = re.findall(r'\$(([\w\-]+)(\[[\w\[\]\-\'\"]+\])*)', param)
709 # pylint: disable=eval-used
711 tmp_val = str(eval("S.getValue('{}'){}".format(macro[1], macro[2])))
712 param = param.replace('${}'.format(macro[0]), tmp_val)
713 # ignore that required option can't be evaluated
714 except (IndexError, KeyError, AttributeError):
715 self._logger.debug("Skipping %s as it isn't a configuration "
716 "parameter.", '${}'.format(macro[0]))
718 elif isinstance(param, list) or isinstance(param, tuple):
721 tmp_list.append(self.step_eval_param(item, STEP))
723 elif isinstance(param, dict):
725 for (key, value) in param.items():
726 tmp_dict[key] = self.step_eval_param(value, STEP)
731 def step_eval_params(self, params, step_result):
732 """ Evaluates referrences to results from previous steps
735 # evaluate all parameters if needed
737 eval_params.append(self.step_eval_param(param, step_result))
741 """ Execute actions specified by TestSteps list
743 :return: False if any error was detected
750 # required for VNFs initialization
752 # initialize list with results
753 self._step_result = [None] * len(self.test)
755 # We have to suppress pylint report, because test_object has to be set according
756 # to the test step definition
757 # pylint: disable=redefined-variable-type
758 # run test step by step...
759 for i, step in enumerate(self.test):
760 step_ok = not self._step_check
761 if step[0] == 'vswitch':
762 test_object = self._vswitch_ctl.get_vswitch()
763 elif step[0] == 'namespace':
764 test_object = namespace
765 elif step[0] == 'veth':
767 elif step[0] == 'settings':
769 elif step[0] == 'tools':
770 test_object = TestStepsTools()
771 step[1] = step[1].title()
772 elif step[0] == 'trafficgen':
773 test_object = self._traffic_ctl
774 # in case of send_traffic or send_traffic_async methods, ensure
775 # that specified traffic values are merged with existing self._traffic
776 if step[1].startswith('send_traffic'):
777 tmp_traffic = copy.deepcopy(self._traffic)
778 tmp_traffic.update(step[2])
779 step[2] = tmp_traffic
780 elif step[0].startswith('vnf'):
781 if not self._step_vnf_list[step[0]]:
783 self._step_vnf_list[step[0]] = loader.get_vnf_class()()
784 test_object = self._step_vnf_list[step[0]]
785 elif step[0] == 'wait':
786 input(os.linesep + "Step {}: Press Enter to continue with "
787 "the next step...".format(i) + os.linesep + os.linesep)
789 elif step[0] == 'sleep':
790 self._logger.debug("Sleep %s seconds", step[1])
791 time.sleep(int(step[1]))
794 self._logger.error("Unsupported test object %s", step[0])
795 self._step_status = {'status' : False, 'details' : ' '.join(step)}
796 self.step_report_status("Step '{}'".format(' '.join(step)),
797 self._step_status['status'])
800 test_method = getattr(test_object, step[1])
802 test_method_check = getattr(test_object, CHECK_PREFIX + step[1])
804 test_method_check = None
808 # eval parameters, but use only valid step_results
809 # to support negative indexes
810 step_params = self.step_eval_params(step[2:], self._step_result[:i])
811 step_log = '{} {}'.format(' '.join(step[:2]), step_params)
812 self._logger.debug("Step %s '%s' start", i, step_log)
813 self._step_result[i] = test_method(*step_params)
814 self._logger.debug("Step %s '%s' results '%s'", i,
815 step_log, self._step_result[i])
816 time.sleep(S.getValue('TEST_STEP_DELAY'))
818 step_ok = test_method_check(self._step_result[i], *step_params)
819 except (AssertionError, AttributeError, IndexError) as ex:
821 self._logger.error("Step %s raised %s", i, type(ex).__name__)
824 self.step_report_status("Step {} - '{}'".format(i, step_log), step_ok)
827 self._step_status = {'status' : False, 'details' : step_log}
828 # Stop all VNFs started by TestSteps
829 self.step_stop_vnfs()
832 # all steps processed without any issue
836 # get methods for TestCase members, which needs to be publicly available
838 def get_output_file(self):
839 """Return content of self._output_file member
841 return self._output_file
844 """Return content of self.desc member
848 def get_versions(self):
849 """Return content of self.versions member
851 return self._versions
853 def get_traffic(self):
854 """Return content of self._traffic member
858 def get_tc_results(self):
859 """Return content of self._tc_results member
861 return self._tc_results
863 def get_collector(self):
864 """Return content of self._collector member
866 return self._collector