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