1 # Copyright 2015-2018 Intel Corporation, Tieto and others.
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 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
39 from tools.llc_management import rmd
41 # Validation methods required for integration TCs will have following prefix before the name
43 CHECK_PREFIX = 'validate_'
45 # Several parameters can be defined by both TC definition keywords and configuration parameters.
46 # Following mapping table is used to correctly evaluate priority of testcase configuration, where
47 # TC definition keywords (i.e. mapping table keys) have higher priority than appropriate TC
48 # parameters (i.e. mapping table values). TC parameters can be defined within "Parameters"
49 # section, via CLI parameters or within configuration files.
50 MAPPING_TC_CFG2CONF = {'vSwitch':'VSWITCH', 'VNF':'VNF', 'Trafficgen':'TRAFFICGEN', 'Tunnel Type':'TUNNEL_TYPE'}
52 # pylint: disable=too-many-instance-attributes
53 class TestCase(object):
54 """TestCase base class
56 In this basic form runs RFC2544 throughput test
58 # pylint: disable=too-many-statements
59 def __init__(self, test_cfg):
60 """Pull out fields from test config
62 :param test_cfg: A dictionary of string-value pairs describing the test
63 configuration. Both the key and values strings use well-known
65 :param results_dir: Where the csv formatted results are written.
67 # make a local copy of test configuration to avoid modification of
68 # original content used in vsperf main script
69 cfg = copy.deepcopy(test_cfg)
71 self._testcase_start_time = time.time()
72 self._testcase_stop_time = self._testcase_start_time
73 self._hugepages_mounted = False
74 self._traffic_ctl = None
76 self._vswitch_ctl = None
77 self._collector = None
79 self._output_file = None
80 self._tc_results = None
81 self._settings_paths_modified = False
82 self._testcast_run_time = None
84 # initialization of step driven specific members
85 self._step_check = False # by default don't check result for step driven testcases
86 self._step_vnf_list = {}
87 self._step_result = []
88 self._step_result_mapping = {}
89 self._step_status = None
90 self._step_send_traffic = False # indication if send_traffic was called within test steps
92 self._testcase_run_time = None
94 S.setValue('VSWITCH', cfg.get('vSwitch', S.getValue('VSWITCH')))
95 S.setValue('VNF', cfg.get('VNF', S.getValue('VNF')))
96 S.setValue('TRAFFICGEN', cfg.get('Trafficgen', S.getValue('TRAFFICGEN')))
97 S.setValue('TUNNEL_TYPE', cfg.get('Tunnel Type', S.getValue('TUNNEL_TYPE')))
98 test_params = copy.deepcopy(S.getValue('TEST_PARAMS'))
99 tc_test_params = cfg.get('Parameters', S.getValue('TEST_PARAMS'))
100 test_params = merge_spec(test_params, tc_test_params)
102 # ensure that parameters from TC definition have the highest priority, see MAPPING_TC_CFG2CONF
103 for (cfg_param, param) in MAPPING_TC_CFG2CONF.items():
104 if cfg_param in cfg and param in test_params:
105 del test_params[param]
107 S.setValue('TEST_PARAMS', test_params)
108 S.check_test_params()
110 # override all redefined GUEST_ values to have them expanded correctly
111 tmp_test_params = copy.deepcopy(S.getValue('TEST_PARAMS'))
112 for key in tmp_test_params:
113 if key.startswith('GUEST_'):
114 S.setValue(key, S.getValue(key))
115 S.getValue('TEST_PARAMS').pop(key)
117 # update global settings
118 functions.settings_update_paths()
120 # set test parameters; CLI options take precedence to testcase settings
121 self._logger = logging.getLogger(__name__)
122 self.name = cfg['Name']
123 self.desc = cfg.get('Description', 'No description given.')
124 self.test = cfg.get('TestSteps', None)
126 # log testcase name and details
127 tmp_desc = functions.format_description(self.desc, 50)
128 self._logger.info('############################################################')
129 self._logger.info('# Test: %s', self.name)
130 self._logger.info('# Details: %s', tmp_desc[0])
131 for i in range(1, len(tmp_desc)):
132 self._logger.info('# %s', tmp_desc[i])
133 self._logger.info('############################################################')
135 bidirectional = S.getValue('TRAFFIC')['bidir']
136 if not isinstance(S.getValue('TRAFFIC')['bidir'], str):
138 'Bi-dir value must be of type string')
139 bidirectional = bidirectional.title() # Keep things consistent
141 self.deployment = cfg['Deployment']
142 self._frame_mod = cfg.get('Frame Modification', None)
144 self._tunnel_operation = cfg.get('Tunnel Operation', None)
146 # check if test requires background load and which generator it uses
147 self._load_cfg = cfg.get('Load', None)
150 self._frame_mod = self._frame_mod.lower()
151 self._results_dir = S.getValue('RESULTS_PATH')
153 # set traffic details, so they can be passed to vswitch and traffic ctls
154 self._traffic = copy.deepcopy(S.getValue('TRAFFIC'))
155 self._traffic.update({'bidir': bidirectional})
157 # Packet Forwarding mode
158 self._vswitch_none = str(S.getValue('VSWITCH')).strip().lower() == 'none'
160 # trafficgen configuration required for tests of tunneling protocols
161 if self._tunnel_operation:
162 self._traffic.update({'tunnel_type': S.getValue('TUNNEL_TYPE')})
163 self._traffic['l2'].update({'srcmac':
164 S.getValue('TRAFFICGEN_PORT1_MAC'),
166 S.getValue('TRAFFICGEN_PORT2_MAC')})
168 self._traffic['l3'].update({'srcip':
169 S.getValue('TRAFFICGEN_PORT1_IP'),
171 S.getValue('TRAFFICGEN_PORT2_IP')})
173 if self._tunnel_operation == "decapsulation":
174 self._traffic['l2'].update(S.getValue(S.getValue('TUNNEL_TYPE').upper() + '_FRAME_L2'))
175 self._traffic['l3'].update(S.getValue(S.getValue('TUNNEL_TYPE').upper() + '_FRAME_L3'))
176 self._traffic['l4'].update(S.getValue(S.getValue('TUNNEL_TYPE').upper() + '_FRAME_L4'))
177 self._traffic['l2']['dstmac'] = S.getValue('NICS')[1]['mac']
178 elif len(S.getValue('NICS')) >= 2 and \
179 (S.getValue('NICS')[0]['type'] == 'vf' or
180 S.getValue('NICS')[1]['type'] == 'vf'):
181 mac1 = S.getValue('NICS')[0]['mac']
182 mac2 = S.getValue('NICS')[1]['mac']
184 self._traffic['l2'].update({'srcmac': mac2, 'dstmac': mac1})
186 self._logger.debug("MAC addresses can not be read")
188 self._traffic = functions.check_traffic(self._traffic)
190 # count how many VNFs are involved in TestSteps
192 for step in self.test:
193 if step[0].startswith('vnf'):
194 self._step_vnf_list[step[0]] = None
196 # if llc allocation is required, initialize it.
197 if S.getValue('LLC_ALLOCATION'):
198 self._rmd = rmd.CacheAllocator()
200 def run_initialize(self):
201 """ Prepare test execution environment
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 self._vnf_list = self._vnf_ctl.get_vnfs()
219 # verify enough hugepages are free to run the testcase
220 if not self._check_for_enough_hugepages():
221 raise RuntimeError('Not enough hugepages free to run test.')
223 # perform guest related handling
224 tmp_vm_count = self._vnf_ctl.get_vnfs_number() + len(self._step_vnf_list)
226 # copy sources of l2 forwarding tools into VM shared dir if needed
227 self._copy_fwd_tools_for_all_guests(tmp_vm_count)
229 # in case of multi VM in parallel, set the number of streams to the number of VMs
230 if self.deployment.startswith('pvpv'):
231 # for each VM NIC pair we need an unique stream
233 for vm_nic in S.getValue('GUEST_NICS_NR')[:tmp_vm_count]:
234 streams += int(vm_nic / 2) if vm_nic > 1 else 1
235 self._logger.debug("VMs with parallel connection were detected. "
236 "Thus Number of streams was set to %s", streams)
237 # update streams if needed; In case of additional VNFs deployed by TestSteps
238 # user can define a proper stream count manually
239 if 'multistream' not in self._traffic or self._traffic['multistream'] < streams:
240 self._traffic.update({'multistream': streams})
242 # OVS Vanilla requires guest VM MAC address and IPs to work
243 if 'linux_bridge' in S.getValue('GUEST_LOOPBACK'):
244 self._traffic['l2'].update({'srcmac': S.getValue('VANILLA_TGEN_PORT1_MAC'),
245 'dstmac': S.getValue('VANILLA_TGEN_PORT2_MAC')})
246 self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
247 'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
249 if self._vswitch_none:
250 self._vswitch_ctl = component_factory.create_pktfwd(
252 loader.get_pktfwd_class())
254 self._vswitch_ctl = component_factory.create_vswitch(
256 loader.get_vswitch_class(),
258 self._tunnel_operation)
260 self._collector = component_factory.create_collector(
261 loader.get_collector_class(),
262 self._results_dir, self.name)
263 self._loadgen = component_factory.create_loadgen(
264 loader.get_loadgen_class(),
267 self._output_file = os.path.join(self._results_dir, "result_{}_{}_{}.csv".format(
268 str(S.getValue('_TEST_INDEX')), self.name, self.deployment))
270 self._step_status = {'status' : True, 'details' : ''}
272 # Perform LLC-allocations
273 if S.getValue('LLC_ALLOCATION'):
274 self._rmd.setup_llc_allocation()
276 self._logger.debug("Setup:")
278 def run_finalize(self):
279 """ Tear down test execution environment and record test results
281 # Stop all VNFs started by TestSteps in case that something went wrong
282 self.step_stop_vnfs()
284 # Cleanup any LLC-allocations
285 if S.getValue('LLC_ALLOCATION'):
286 self._rmd.cleanup_llc_allocation()
288 # Stop all processes executed by testcase
289 tasks.terminate_all_tasks(self._logger)
291 # umount hugepages if mounted
292 self._umount_hugepages()
294 # cleanup any namespaces created
295 if os.path.isdir('/tmp/namespaces'):
296 namespace_list = os.listdir('/tmp/namespaces')
298 self._logger.info('Cleaning up namespaces')
299 for name in namespace_list:
300 namespace.delete_namespace(name)
301 os.rmdir('/tmp/namespaces')
302 # cleanup any veth ports created
303 if os.path.isdir('/tmp/veth'):
304 veth_list = os.listdir('/tmp/veth')
306 self._logger.info('Cleaning up veth ports')
307 for eth in veth_list:
308 port1, port2 = eth.split('-')
309 veth.del_veth_port(port1, port2)
310 os.rmdir('/tmp/veth')
312 def run_report(self):
313 """ Report test results
315 self._logger.debug("self._collector Results:")
316 self._collector.print_results()
318 results = self._traffic_ctl.get_results()
320 self._logger.debug("Traffic Results:")
321 self._traffic_ctl.print_results()
323 if self._tc_results is None:
324 self._tc_results = self._append_results(results)
326 # integration step driven tests have their status and possible
327 # failure details stored inside self._tc_results
328 results = self._append_results(results)
329 if len(self._tc_results) < len(results):
330 if len(self._tc_results) > 1:
331 raise RuntimeError('Testcase results do not match:'
333 'trafficgen results: %s\n' %
337 tmp_results = copy.deepcopy(self._tc_results[0])
338 self._tc_results = []
340 tmp_res = copy.deepcopy(tmp_results)
342 self._tc_results.append(tmp_res)
344 for i, result in enumerate(results):
345 self._tc_results[i].update(result)
347 TestCase.write_result_to_file(self._tc_results, self._output_file)
352 All setup and teardown through controllers is included.
354 # prepare test execution environment
355 self.run_initialize()
358 with self._vswitch_ctl:
359 with self._vnf_ctl, self._collector, self._loadgen:
360 if not self._vswitch_none:
363 self._versions += self._vswitch_ctl.get_vswitch().get_version()
365 with self._traffic_ctl:
366 # execute test based on TestSteps definition if needed...
368 # ...and continue with traffic generation, but keep
369 # in mind, that clean deployment does not configure
370 # OVS nor executes the traffic
371 if self.deployment != 'clean' and not self._step_send_traffic:
372 self._traffic_ctl.send_traffic(self._traffic)
374 # dump vswitch flows before they are affected by VNF termination
375 if not self._vswitch_none:
376 self._vswitch_ctl.dump_vswitch_connections()
378 # garbage collection for case that TestSteps modify existing deployment
379 self.step_stop_vnfs()
382 # tear down test execution environment and log results
385 self._testcase_stop_time = time.time()
386 self._testcase_run_time = time.strftime("%H:%M:%S",
387 time.gmtime(self._testcase_stop_time -
388 self._testcase_start_time))
389 logging.info("Testcase execution time: %s", self._testcase_run_time)
390 # report test results
393 def _append_results(self, results):
395 Method appends mandatory Test Case results to list of dictionaries.
397 :param results: list of dictionaries which contains results from
400 :returns: modified list of dictionaries.
403 item[ResultsConstants.ID] = self.name
404 item[ResultsConstants.DEPLOYMENT] = self.deployment
405 item[ResultsConstants.VSWITCH] = S.getValue('VSWITCH')
406 item[ResultsConstants.TRAFFIC_TYPE] = self._traffic['l3']['proto']
407 item[ResultsConstants.TEST_RUN_TIME] = self._testcase_run_time
408 # convert timestamps to human readable format
409 item[ResultsConstants.TEST_START_TIME] = dt.fromtimestamp(
410 self._testcase_start_time).strftime('%Y-%m-%d %H:%M:%S')
411 item[ResultsConstants.TEST_STOP_TIME] = dt.fromtimestamp(
412 self._testcase_stop_time).strftime('%Y-%m-%d %H:%M:%S')
413 if self._traffic['multistream']:
414 item[ResultsConstants.SCAL_STREAM_COUNT] = self._traffic['multistream']
415 item[ResultsConstants.SCAL_STREAM_TYPE] = self._traffic['stream_type']
416 item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows']
417 if self._vnf_ctl.get_vnfs_number():
418 item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(S.getValue('GUEST_LOOPBACK'))
419 if self._tunnel_operation:
420 item[ResultsConstants.TUNNEL_TYPE] = S.getValue('TUNNEL_TYPE')
423 def _copy_fwd_tools_for_all_guests(self, vm_count):
424 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] based on selected deployment.
426 # consider only VNFs involved in the test
427 for guest_dir in set(S.getValue('GUEST_SHARE_DIR')[:vm_count]):
428 self._copy_fwd_tools_for_guest(guest_dir)
430 def _copy_fwd_tools_for_guest(self, guest_dir):
431 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR of VM
433 :param index: Index of VM starting from 1 (i.e. 1st VM has index 1)
435 # remove shared dir if it exists to avoid issues with file consistency
436 if os.path.exists(guest_dir):
437 tasks.run_task(['rm', '-f', '-r', guest_dir], self._logger,
438 'Removing content of shared directory...', True)
440 # directory to share files between host and guest
441 os.makedirs(guest_dir)
443 # copy sources into shared dir only if neccessary
444 guest_loopback = set(S.getValue('GUEST_LOOPBACK'))
445 if 'testpmd' in guest_loopback:
447 # exclude whole .git/ subdirectory and all o-files;
448 # It is assumed, that the same RTE_TARGET is used in both host
449 # and VMs; This simplification significantly speeds up testpmd
450 # build. If we will need a different RTE_TARGET in VM,
451 # then we have to build whole DPDK from the scratch in VM.
452 # In that case we can copy just DPDK sources (e.g. by excluding
453 # all items obtained by git status -unormal --porcelain).
454 # NOTE: Excluding RTE_TARGET directory won't help on systems,
455 # where DPDK is built for multiple targets (e.g. for gcc & icc)
457 exclude.append(r'--exclude=.git/')
458 exclude.append(r'--exclude=*.o')
459 tasks.run_task(['rsync', '-a', '-r', '-l'] + exclude +
460 [os.path.join(S.getValue('TOOLS')['dpdk_src'], ''),
461 os.path.join(guest_dir, 'DPDK')],
463 'Copying DPDK to shared directory...',
465 except subprocess.CalledProcessError:
466 self._logger.error('Unable to copy DPDK to shared directory')
468 if 'l2fwd' in guest_loopback:
470 tasks.run_task(['rsync', '-a', '-r', '-l',
471 os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
472 os.path.join(guest_dir, 'l2fwd')],
474 'Copying l2fwd to shared directory...',
476 except subprocess.CalledProcessError:
477 self._logger.error('Unable to copy l2fwd to shared directory')
480 def _mount_hugepages(self):
481 """Mount hugepages if usage of DPDK or Qemu is detected
483 # pylint: disable=too-many-boolean-expressions
484 # hugepages are needed by DPDK and Qemu
485 if not self._hugepages_mounted and \
486 (self.deployment.count('v') or \
487 str(S.getValue('VSWITCH')).lower().count('dpdk') or \
488 self._vswitch_none or \
489 self.test and 'vnf' in [step[0][0:3] for step in self.test]):
490 hugepages.mount_hugepages()
491 self._hugepages_mounted = True
493 def _umount_hugepages(self):
494 """Umount hugepages if they were mounted before
496 if self._hugepages_mounted:
497 hugepages.umount_hugepages()
498 self._hugepages_mounted = False
500 def _check_for_enough_hugepages(self):
501 """Check to make sure enough hugepages are free to satisfy the
505 hugepage_size = hugepages.get_hugepage_size()
506 # get hugepage amounts per guest involved in the test
507 for guest in range(self._vnf_ctl.get_vnfs_number()):
508 hugepages_needed += math.ceil((int(S.getValue(
509 'GUEST_MEMORY')[guest]) * 1000) / hugepage_size)
511 # get hugepage amounts for each socket on dpdk
512 sock0_mem, sock1_mem = 0, 0
514 if str(S.getValue('VSWITCH')).lower().count('dpdk'):
515 sock_mem = S.getValue('DPDK_SOCKET_MEM')
516 sock0_mem, sock1_mem = (int(sock_mem[0]) * 1024 / hugepage_size,
517 int(sock_mem[1]) * 1024 / hugepage_size)
519 # If hugepages needed, verify the amounts are free
520 if any([hugepages_needed, sock0_mem, sock1_mem]):
521 free_hugepages = hugepages.get_free_hugepages()
523 logging.info('Need %s hugepages free for guests',
525 result1 = free_hugepages >= hugepages_needed
526 free_hugepages -= hugepages_needed
531 logging.info('Need %s hugepages free for dpdk socket 0',
533 result2 = hugepages.get_free_hugepages('0') >= sock0_mem
534 free_hugepages -= sock0_mem
539 logging.info('Need %s hugepages free for dpdk socket 1',
541 result3 = hugepages.get_free_hugepages('1') >= sock1_mem
542 free_hugepages -= sock1_mem
546 logging.info('Need a total of %s total hugepages',
547 hugepages_needed + sock1_mem + sock0_mem)
549 # The only drawback here is sometimes dpdk doesn't release
550 # its hugepages on a test failure. This could cause a test
551 # to fail when dpdk would be OK to start because it will just
552 # use the previously allocated hugepages.
553 result4 = True if free_hugepages >= 0 else False
555 return all([result1, result2, result3, result4])
560 def write_result_to_file(results, output):
561 """Write list of dictionaries to a CSV file.
563 Each element on list will create separate row in output file.
564 If output file already exists, data will be appended at the end,
565 otherwise it will be created.
567 :param results: list of dictionaries.
568 :param output: path to output file.
570 with open(output, 'a') as csvfile:
572 logging.info("Write results to file: %s", output)
573 fieldnames = TestCase._get_unique_keys(results)
575 writer = csv.DictWriter(csvfile, fieldnames)
577 if not csvfile.tell(): # file is now empty
580 for result in results:
581 writer.writerow(result)
584 def _get_unique_keys(list_of_dicts):
585 """Gets unique key values as ordered list of strings in given dicts
587 :param list_of_dicts: list of dictionaries.
589 :returns: list of unique keys(strings).
591 result = OrderedDict()
592 for item in list_of_dicts:
593 for key in item.keys():
596 return list(result.keys())
598 def _add_flows(self):
599 """Add flows to the vswitch
601 vswitch = self._vswitch_ctl.get_vswitch()
602 # NOTE BOM 15-08-07 the frame mod code assumes that the
603 # physical ports are ports 1 & 2. The actual numbers
604 # need to be retrived from the vSwitch and the metadata value
605 # updated accordingly.
606 bridge = S.getValue('VSWITCH_BRIDGE_NAME')
607 if self._frame_mod == "vlan":
608 # 0x8100 => VLAN ethertype
609 self._logger.debug(" **** VLAN ***** ")
610 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
611 'actions': ['push_vlan:0x8100', 'goto_table:3']}
612 vswitch.add_flow(bridge, flow)
613 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
614 'actions': ['push_vlan:0x8100', 'goto_table:3']}
615 vswitch.add_flow(bridge, flow)
616 elif self._frame_mod == "mpls":
617 # 0x8847 => MPLS unicast ethertype
618 self._logger.debug(" **** MPLS ***** ")
619 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
620 'actions': ['push_mpls:0x8847', 'goto_table:3']}
621 vswitch.add_flow(bridge, flow)
622 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
623 'actions': ['push_mpls:0x8847', 'goto_table:3']}
624 vswitch.add_flow(bridge, flow)
625 elif self._frame_mod == "mac":
626 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
627 'actions': ['mod_dl_src:22:22:22:22:22:22',
629 vswitch.add_flow(bridge, flow)
630 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
631 'actions': ['mod_dl_src:11:11:11:11:11:11',
633 vswitch.add_flow(bridge, flow)
634 elif self._frame_mod == "dscp":
635 # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
636 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
638 'actions': ['mod_nw_tos:184', 'goto_table:3']}
639 vswitch.add_flow(bridge, flow)
640 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
642 'actions': ['mod_nw_tos:184', 'goto_table:3']}
643 vswitch.add_flow(bridge, flow)
644 elif self._frame_mod == "ttl":
645 # 251 and 241 are the highest prime numbers < 255
646 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
648 'actions': ['mod_nw_ttl:251', 'goto_table:3']}
649 vswitch.add_flow(bridge, flow)
650 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
652 'actions': ['mod_nw_ttl:241', 'goto_table:3']}
653 vswitch.add_flow(bridge, flow)
654 elif self._frame_mod == "ip_addr":
655 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
657 'actions': ['mod_nw_src:10.10.10.10',
658 'mod_nw_dst:20.20.20.20',
660 vswitch.add_flow(bridge, flow)
661 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
663 'actions': ['mod_nw_src:20.20.20.20',
664 'mod_nw_dst:10.10.10.10',
666 vswitch.add_flow(bridge, flow)
667 elif self._frame_mod == "ip_port":
668 # NOTE BOM 15-08-27 The traffic generated is assumed
669 # to be UDP (nw_proto 17d) which is the default case but
670 # we will need to pick up the actual traffic params in use.
671 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
672 'dl_type':'0x0800', 'nw_proto':'17',
673 'actions': ['mod_tp_src:44444',
674 'mod_tp_dst:44444', 'goto_table:3']}
675 vswitch.add_flow(bridge, flow)
676 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
677 'dl_type':'0x0800', 'nw_proto':'17',
678 'actions': ['mod_tp_src:44444',
679 'mod_tp_dst:44444', 'goto_table:3']}
680 vswitch.add_flow(bridge, flow)
686 # TestSteps realted methods
688 def step_report_status(self, label, status):
689 """ Log status of test step
691 self._logger.info("%s ... %s", label, 'OK' if status else 'FAILED')
693 def step_stop_vnfs(self):
694 """ Stop all VNFs started by TestSteps
696 for vnf in self._step_vnf_list:
697 if self._step_vnf_list[vnf]:
698 self._step_vnf_list[vnf].stop()
700 def step_eval_param(self, param, step_result):
701 """ Helper function for #STEP macro evaluation
703 if isinstance(param, str):
704 # evaluate every #STEP reference inside parameter itself
705 macros = re.findall(r'(#STEP\[([\w\-:]+)\]((\[[\w\-\'\"]+\])*))', param)
709 if macro[1] in self._step_result_mapping:
710 key = self._step_result_mapping[macro[1]]
713 # pylint: disable=eval-used
714 tmp_val = str(eval('step_result[{}]{}'.format(key, macro[2])))
715 param = param.replace(macro[0], tmp_val)
717 # evaluate references to vsperf configuration options
718 macros = re.findall(r'\$(([\w\-]+)(\[[\w\[\]\-\'\"]+\])*)', param)
721 # pylint: disable=eval-used
723 tmp_val = str(eval("S.getValue('{}'){}".format(macro[1], macro[2])))
724 param = param.replace('${}'.format(macro[0]), tmp_val)
725 # ignore that required option can't be evaluated
726 except (IndexError, KeyError, AttributeError):
727 self._logger.debug("Skipping %s as it isn't a configuration "
728 "parameter.", '${}'.format(macro[0]))
730 elif isinstance(param, (list, tuple)):
733 tmp_list.append(self.step_eval_param(item, step_result))
735 elif isinstance(param, dict):
737 for (key, value) in param.items():
738 tmp_dict[key] = self.step_eval_param(value, step_result)
743 def step_eval_params(self, params, step_result):
744 """ Evaluates referrences to results from previous steps
747 # evaluate all parameters if needed
749 eval_params.append(self.step_eval_param(param, step_result))
752 # pylint: disable=too-many-locals, too-many-branches, too-many-statements
754 """ Execute actions specified by TestSteps list
756 :return: False if any error was detected
763 # required for VNFs initialization
765 # initialize list with results
766 self._step_result = [None] * len(self.test)
768 # run test step by step...
769 for i, step in enumerate(self.test):
770 step_ok = not self._step_check
771 step_check = self._step_check
773 # configure step result mapping if step alias/label is detected
774 if step[0].startswith('#'):
777 raise RuntimeError('Step alias can\'t be an integer value {}'.format(key))
778 if key in self._step_result_mapping:
779 raise RuntimeError('Step alias {} has been used already for step '
780 '{}'.format(key, self._step_result_mapping[key]))
781 self._step_result_mapping[step[0][1:]] = i
784 # store regex filter if it is specified
785 if isinstance(step[-1], str) and step[-1].startswith('|'):
786 # evalute macros and variables used in regex
787 regex = self.step_eval_params([step[-1][1:]], self._step_result[:i])[0]
790 # check if step verification should be suppressed
791 if step[0].startswith('!'):
794 step[0] = step[0][1:]
795 if step[0] == 'vswitch':
796 test_object = self._vswitch_ctl.get_vswitch()
797 elif step[0] == 'namespace':
798 test_object = namespace
799 elif step[0] == 'veth':
801 elif step[0] == 'settings':
803 elif step[0] == 'tools':
804 test_object = TestStepsTools()
805 step[1] = step[1].title()
806 elif step[0] == 'trafficgen':
807 test_object = self._traffic_ctl
808 # in case of send_traffic or send_traffic_async methods, ensure
809 # that specified traffic values are merged with existing self._traffic
810 if step[1].startswith('send_traffic'):
811 tmp_traffic = copy.deepcopy(self._traffic)
812 tmp_traffic.update(step[2])
813 step[2] = tmp_traffic
814 # store indication that traffic has been sent
815 # so it is not sent again after the execution of teststeps
816 self._step_send_traffic = True
817 elif step[0].startswith('vnf'):
818 # use vnf started within TestSteps
819 if not self._step_vnf_list[step[0]]:
821 self._step_vnf_list[step[0]] = loader.get_vnf_class()()
822 test_object = self._step_vnf_list[step[0]]
823 elif step[0].startswith('VNF'):
824 if step[1] in ('start', 'stop'):
825 raise RuntimeError("Cannot execute start() or stop() method of "
826 "VNF deployed automatically by scenario.")
827 # use vnf started by scenario deployment (e.g. pvp)
828 vnf_index = int(step[0][3:])
830 test_object = self._vnf_list[vnf_index]
832 raise RuntimeError("VNF with index {} is not running.".format(vnf_index))
833 elif step[0] == 'wait':
834 input(os.linesep + "Step {}: Press Enter to continue with "
835 "the next step...".format(i) + os.linesep + os.linesep)
837 elif step[0] == 'sleep':
838 self._logger.debug("Sleep %s seconds", step[1])
839 time.sleep(int(step[1]))
841 elif step[0] == 'log':
842 test_object = self._logger
843 # there isn't a need for validation of log entry
846 elif step[0] == 'pdb':
851 self._logger.error("Unsupported test object %s", step[0])
852 self._step_status = {'status' : False, 'details' : ' '.join(step)}
853 self.step_report_status("Step '{}'".format(' '.join(step)),
854 self._step_status['status'])
857 test_method = getattr(test_object, step[1])
859 test_method_check = getattr(test_object, CHECK_PREFIX + step[1])
861 test_method_check = None
865 # eval parameters, but use only valid step_results
866 # to support negative indexes
867 step_params = self.step_eval_params(step[2:], self._step_result[:i])
868 step_log = '{} {}'.format(' '.join(step[:2]), step_params)
869 step_log += ' filter "{}"'.format(regex) if regex else ''
870 self._logger.debug("Step %s '%s' start", i, step_log)
871 self._step_result[i] = test_method(*step_params)
873 # apply regex to step output
874 self._step_result[i] = functions.filter_output(
875 self._step_result[i], regex)
877 self._logger.debug("Step %s '%s' results '%s'", i,
878 step_log, self._step_result[i])
879 time.sleep(S.getValue('TEST_STEP_DELAY'))
881 step_ok = test_method_check(self._step_result[i], *step_params)
882 except (AssertionError, AttributeError, IndexError) as ex:
884 self._logger.error("Step %s raised %s", i, type(ex).__name__)
887 self.step_report_status("Step {} - '{}'".format(i, step_log), step_ok)
890 self._step_status = {'status' : False, 'details' : step_log}
891 # Stop all VNFs started by TestSteps
892 self.step_stop_vnfs()
895 # all steps processed without any issue
899 # get methods for TestCase members, which needs to be publicly available
901 def get_output_file(self):
902 """Return content of self._output_file member
904 return self._output_file
907 """Return content of self.desc member
911 def get_versions(self):
912 """Return content of self.versions member
914 return self._versions
916 def get_traffic(self):
917 """Return content of self._traffic member
921 def get_tc_results(self):
922 """Return content of self._tc_results member
924 return self._tc_results
926 def get_collector(self):
927 """Return content of self._collector member
929 return self._collector