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
78 self._vswitch_ctl = None
79 self._collector = None
81 self._output_file = None
82 self._tc_results = None
83 self._settings_paths_modified = False
84 self._testcast_run_time = None
87 # initialization of step driven specific members
88 self._step_check = False # by default don't check result for step driven testcases
89 self._step_vnf_list = {}
90 self._step_result = []
91 self._step_result_mapping = {}
92 self._step_status = None
93 self._step_send_traffic = False # indication if send_traffic was called within test steps
95 self._testcase_run_time = None
97 S.setValue('VSWITCH', cfg.get('vSwitch', S.getValue('VSWITCH')))
98 S.setValue('VNF', cfg.get('VNF', S.getValue('VNF')))
99 S.setValue('TRAFFICGEN', cfg.get('Trafficgen', S.getValue('TRAFFICGEN')))
100 S.setValue('TUNNEL_TYPE', cfg.get('Tunnel Type', S.getValue('TUNNEL_TYPE')))
101 test_params = copy.deepcopy(S.getValue('TEST_PARAMS'))
102 tc_test_params = cfg.get('Parameters', S.getValue('TEST_PARAMS'))
103 test_params = merge_spec(test_params, tc_test_params)
105 # ensure that parameters from TC definition have the highest priority, see MAPPING_TC_CFG2CONF
106 for (cfg_param, param) in MAPPING_TC_CFG2CONF.items():
107 if cfg_param in cfg and param in test_params:
108 del test_params[param]
110 S.setValue('TEST_PARAMS', test_params)
111 S.check_test_params()
113 # override all redefined GUEST_ values to have them expanded correctly
114 tmp_test_params = copy.deepcopy(S.getValue('TEST_PARAMS'))
115 for key in tmp_test_params:
116 if key.startswith('GUEST_'):
117 S.setValue(key, S.getValue(key))
118 S.getValue('TEST_PARAMS').pop(key)
120 # update global settings
121 functions.settings_update_paths()
123 # set test parameters; CLI options take precedence to testcase settings
124 self._logger = logging.getLogger(__name__)
125 self.name = cfg['Name']
126 self.desc = cfg.get('Description', 'No description given.')
127 self.test = cfg.get('TestSteps', None)
129 # log testcase name and details
130 tmp_desc = functions.format_description(self.desc, 50)
131 self._logger.info('############################################################')
132 self._logger.info('# Test: %s', self.name)
133 self._logger.info('# Details: %s', tmp_desc[0])
134 for i in range(1, len(tmp_desc)):
135 self._logger.info('# %s', tmp_desc[i])
136 self._logger.info('############################################################')
138 bidirectional = S.getValue('TRAFFIC')['bidir']
139 if not isinstance(S.getValue('TRAFFIC')['bidir'], str):
141 'Bi-dir value must be of type string')
142 bidirectional = bidirectional.title() # Keep things consistent
144 self.deployment = cfg['Deployment']
145 self._frame_mod = cfg.get('Frame Modification', None)
147 self._tunnel_operation = cfg.get('Tunnel Operation', None)
149 # check if test requires background load and which generator it uses
150 self._load_cfg = cfg.get('Load', None)
153 self._frame_mod = self._frame_mod.lower()
154 self._results_dir = S.getValue('RESULTS_PATH')
156 # set traffic details, so they can be passed to vswitch and traffic ctls
157 self._traffic = copy.deepcopy(S.getValue('TRAFFIC'))
158 self._traffic.update({'bidir': bidirectional})
160 # Packet Forwarding mode
161 self._vswitch_none = str(S.getValue('VSWITCH')).strip().lower() == 'none'
163 # trafficgen configuration required for tests of tunneling protocols
164 if self._tunnel_operation:
165 self._traffic.update({'tunnel_type': S.getValue('TUNNEL_TYPE')})
166 self._traffic['l2'].update({'srcmac':
167 S.getValue('TRAFFICGEN_PORT1_MAC'),
169 S.getValue('TRAFFICGEN_PORT2_MAC')})
171 self._traffic['l3'].update({'srcip':
172 S.getValue('TRAFFICGEN_PORT1_IP'),
174 S.getValue('TRAFFICGEN_PORT2_IP')})
176 if self._tunnel_operation == "decapsulation":
177 self._traffic['l2'].update(S.getValue(S.getValue('TUNNEL_TYPE').upper() + '_FRAME_L2'))
178 self._traffic['l3'].update(S.getValue(S.getValue('TUNNEL_TYPE').upper() + '_FRAME_L3'))
179 self._traffic['l4'].update(S.getValue(S.getValue('TUNNEL_TYPE').upper() + '_FRAME_L4'))
180 self._traffic['l2']['dstmac'] = S.getValue('NICS')[1]['mac']
181 elif len(S.getValue('NICS')) >= 2 and \
182 (S.getValue('NICS')[0]['type'] == 'vf' or
183 S.getValue('NICS')[1]['type'] == 'vf'):
184 mac1 = S.getValue('NICS')[0]['mac']
185 mac2 = S.getValue('NICS')[1]['mac']
187 self._traffic['l2'].update({'srcmac': mac2, 'dstmac': mac1})
189 self._logger.debug("MAC addresses can not be read")
191 self._traffic = functions.check_traffic(self._traffic)
193 # count how many VNFs are involved in TestSteps
195 for step in self.test:
196 if step[0].startswith('vnf'):
197 self._step_vnf_list[step[0]] = None
199 # if llc allocation is required, initialize it.
200 if S.getValue('LLC_ALLOCATION'):
201 self._rmd = rmd.CacheAllocator()
203 def run_initialize(self):
204 """ Prepare test execution environment
206 # mount hugepages if needed
207 self._mount_hugepages()
209 self._logger.debug("Controllers:")
211 self._traffic_ctl = component_factory.create_traffic(
212 self._traffic['traffic_type'],
213 loader.get_trafficgen_class())
215 self._vnf_ctl = component_factory.create_vnf(
217 loader.get_vnf_class(),
218 len(self._step_vnf_list))
220 self._vnf_list = self._vnf_ctl.get_vnfs()
222 self._pod_ctl = component_factory.create_pod(
224 loader.get_pod_class())
226 self._pod_list = self._pod_ctl.get_pods()
228 # verify enough hugepages are free to run the testcase
229 if not self._check_for_enough_hugepages():
230 raise RuntimeError('Not enough hugepages free to run test.')
232 # perform guest related handling
233 tmp_vm_count = self._vnf_ctl.get_vnfs_number() + len(self._step_vnf_list)
235 # copy sources of l2 forwarding tools into VM shared dir if needed
236 self._copy_fwd_tools_for_all_guests(tmp_vm_count)
238 # in case of multi VM in parallel, set the number of streams to the number of VMs
239 if self.deployment.startswith('pvpv'):
240 # for each VM NIC pair we need an unique stream
242 for vm_nic in S.getValue('GUEST_NICS_NR')[:tmp_vm_count]:
243 streams += int(vm_nic / 2) if vm_nic > 1 else 1
244 self._logger.debug("VMs with parallel connection were detected. "
245 "Thus Number of streams was set to %s", streams)
246 # update streams if needed; In case of additional VNFs deployed by TestSteps
247 # user can define a proper stream count manually
248 if 'multistream' not in self._traffic or self._traffic['multistream'] < streams:
249 self._traffic.update({'multistream': streams})
251 # OVS Vanilla requires guest VM MAC address and IPs to work
252 if 'linux_bridge' in S.getValue('GUEST_LOOPBACK'):
253 self._traffic['l2'].update({'srcmac': S.getValue('VANILLA_TGEN_PORT1_MAC'),
254 'dstmac': S.getValue('VANILLA_TGEN_PORT2_MAC')})
255 self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
256 'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
258 if self._vswitch_none:
259 self._vswitch_ctl = component_factory.create_pktfwd(
261 loader.get_pktfwd_class())
263 self._vswitch_ctl = component_factory.create_vswitch(
265 loader.get_vswitch_class(),
267 self._tunnel_operation)
269 self._collector = component_factory.create_collector(
270 loader.get_collector_class(),
271 self._results_dir, self.name)
272 self._loadgen = component_factory.create_loadgen(
273 loader.get_loadgen_class(),
276 self._output_file = os.path.join(self._results_dir, "result_{}_{}_{}.csv".format(
277 str(S.getValue('_TEST_INDEX')), self.name, self.deployment))
279 self._step_status = {'status' : True, 'details' : ''}
281 # Perform LLC-allocations
282 if S.getValue('LLC_ALLOCATION'):
283 self._rmd.setup_llc_allocation()
285 self._logger.debug("Setup:")
287 def run_finalize(self):
288 """ Tear down test execution environment and record test results
290 # Stop all VNFs started by TestSteps in case that something went wrong
291 self.step_stop_vnfs()
297 # Cleanup any LLC-allocations
298 if S.getValue('LLC_ALLOCATION'):
299 self._rmd.cleanup_llc_allocation()
301 # Stop all processes executed by testcase
302 tasks.terminate_all_tasks(self._logger)
304 # umount hugepages if mounted
305 self._umount_hugepages()
307 # cleanup any namespaces created
308 if os.path.isdir('/tmp/namespaces'):
309 namespace_list = os.listdir('/tmp/namespaces')
311 self._logger.info('Cleaning up namespaces')
312 for name in namespace_list:
313 namespace.delete_namespace(name)
314 os.rmdir('/tmp/namespaces')
315 # cleanup any veth ports created
316 if os.path.isdir('/tmp/veth'):
317 veth_list = os.listdir('/tmp/veth')
319 self._logger.info('Cleaning up veth ports')
320 for eth in veth_list:
321 port1, port2 = eth.split('-')
322 veth.del_veth_port(port1, port2)
323 os.rmdir('/tmp/veth')
325 def run_report(self):
326 """ Report test results
328 self._logger.debug("self._collector Results:")
329 self._collector.print_results()
331 results = self._traffic_ctl.get_results()
333 self._logger.debug("Traffic Results:")
334 self._traffic_ctl.print_results()
336 if self._tc_results is None:
337 self._tc_results = self._append_results(results)
339 # integration step driven tests have their status and possible
340 # failure details stored inside self._tc_results
341 results = self._append_results(results)
342 if len(self._tc_results) < len(results):
343 if len(self._tc_results) > 1:
344 raise RuntimeError('Testcase results do not match:'
346 'trafficgen results: %s\n' %
350 tmp_results = copy.deepcopy(self._tc_results[0])
351 self._tc_results = []
353 tmp_res = copy.deepcopy(tmp_results)
355 self._tc_results.append(tmp_res)
357 for i, result in enumerate(results):
358 self._tc_results[i].update(result)
360 TestCase.write_result_to_file(self._tc_results, self._output_file)
365 All setup and teardown through controllers is included.
368 # prepare test execution environment
369 self.run_initialize()
372 with self._vswitch_ctl:
373 with self._vnf_ctl, self._pod_ctl, self._collector, self._loadgen:
374 if not self._vswitch_none and not self._k8s:
377 self._add_connections()
379 self._versions += self._vswitch_ctl.get_vswitch().get_version()
381 with self._traffic_ctl:
382 # execute test based on TestSteps definition if needed...
384 # ...and continue with traffic generation, but keep
385 # in mind, that clean deployment does not configure
386 # OVS nor executes the traffic
387 if self.deployment != 'clean' and not self._step_send_traffic:
388 self._traffic_ctl.send_traffic(self._traffic)
390 # dump vswitch flows before they are affected by VNF termination
391 if not self._vswitch_none:
392 self._vswitch_ctl.dump_vswitch_connections()
394 # garbage collection for case that TestSteps modify existing deployment
395 self.step_stop_vnfs()
398 # tear down test execution environment and log results
401 self._testcase_stop_time = time.time()
402 self._testcase_run_time = time.strftime("%H:%M:%S",
403 time.gmtime(self._testcase_stop_time -
404 self._testcase_start_time))
405 logging.info("Testcase execution time: %s", self._testcase_run_time)
406 # report test results
409 def _append_results(self, results):
411 Method appends mandatory Test Case results to list of dictionaries.
413 :param results: list of dictionaries which contains results from
416 :returns: modified list of dictionaries.
419 item[ResultsConstants.ID] = self.name
420 item[ResultsConstants.DEPLOYMENT] = self.deployment
421 item[ResultsConstants.VSWITCH] = S.getValue('VSWITCH')
422 item[ResultsConstants.TRAFFIC_TYPE] = self._traffic['l3']['proto']
423 item[ResultsConstants.TEST_RUN_TIME] = self._testcase_run_time
424 # convert timestamps to human readable format
425 item[ResultsConstants.TEST_START_TIME] = dt.fromtimestamp(
426 self._testcase_start_time).strftime('%Y-%m-%d %H:%M:%S')
427 item[ResultsConstants.TEST_STOP_TIME] = dt.fromtimestamp(
428 self._testcase_stop_time).strftime('%Y-%m-%d %H:%M:%S')
429 if self._traffic['multistream']:
430 item[ResultsConstants.SCAL_STREAM_COUNT] = self._traffic['multistream']
431 item[ResultsConstants.SCAL_STREAM_TYPE] = self._traffic['stream_type']
432 item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows']
433 if self._vnf_ctl.get_vnfs_number():
434 item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(S.getValue('GUEST_LOOPBACK'))
435 if self._tunnel_operation:
436 item[ResultsConstants.TUNNEL_TYPE] = S.getValue('TUNNEL_TYPE')
439 def _copy_fwd_tools_for_all_guests(self, vm_count):
440 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] based on selected deployment.
442 # consider only VNFs involved in the test
443 for guest_dir in set(S.getValue('GUEST_SHARE_DIR')[:vm_count]):
444 self._copy_fwd_tools_for_guest(guest_dir)
446 def _copy_fwd_tools_for_guest(self, guest_dir):
447 """Copy dpdk and l2fwd code to GUEST_SHARE_DIR of VM
449 :param index: Index of VM starting from 1 (i.e. 1st VM has index 1)
451 # remove shared dir if it exists to avoid issues with file consistency
452 if os.path.exists(guest_dir):
453 tasks.run_task(['rm', '-f', '-r', guest_dir], self._logger,
454 'Removing content of shared directory...', True)
456 # directory to share files between host and guest
457 os.makedirs(guest_dir)
459 # copy sources into shared dir only if neccessary
460 guest_loopback = set(S.getValue('GUEST_LOOPBACK'))
461 if 'testpmd' in guest_loopback:
463 # exclude whole .git/ subdirectory and all o-files;
464 # It is assumed, that the same RTE_TARGET is used in both host
465 # and VMs; This simplification significantly speeds up testpmd
466 # build. If we will need a different RTE_TARGET in VM,
467 # then we have to build whole DPDK from the scratch in VM.
468 # In that case we can copy just DPDK sources (e.g. by excluding
469 # all items obtained by git status -unormal --porcelain).
470 # NOTE: Excluding RTE_TARGET directory won't help on systems,
471 # where DPDK is built for multiple targets (e.g. for gcc & icc)
473 exclude.append(r'--exclude=.git/')
474 exclude.append(r'--exclude=*.o')
475 tasks.run_task(['rsync', '-a', '-r', '-l'] + exclude +
476 [os.path.join(S.getValue('TOOLS')['dpdk_src'], ''),
477 os.path.join(guest_dir, 'DPDK')],
479 'Copying DPDK to shared directory...',
481 except subprocess.CalledProcessError:
482 self._logger.error('Unable to copy DPDK to shared directory')
484 if 'l2fwd' in guest_loopback:
486 tasks.run_task(['rsync', '-a', '-r', '-l',
487 os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
488 os.path.join(guest_dir, 'l2fwd')],
490 'Copying l2fwd to shared directory...',
492 except subprocess.CalledProcessError:
493 self._logger.error('Unable to copy l2fwd to shared directory')
496 def _mount_hugepages(self):
497 """Mount hugepages if usage of DPDK or Qemu is detected
499 # pylint: disable=too-many-boolean-expressions
500 # hugepages are needed by DPDK and Qemu
501 if not self._hugepages_mounted and \
502 (self.deployment.count('v') or \
503 str(S.getValue('VSWITCH')).lower().count('dpdk') or \
504 self._vswitch_none or \
505 self.test and 'vnf' in [step[0][0:3] for step in self.test]):
506 hugepages.mount_hugepages()
507 self._hugepages_mounted = True
509 def _umount_hugepages(self):
510 """Umount hugepages if they were mounted before
512 if self._hugepages_mounted:
513 hugepages.umount_hugepages()
514 self._hugepages_mounted = False
516 def _check_for_enough_hugepages(self):
517 """Check to make sure enough hugepages are free to satisfy the
521 hugepage_size = hugepages.get_hugepage_size()
522 # get hugepage amounts per guest involved in the test
523 for guest in range(self._vnf_ctl.get_vnfs_number()):
524 hugepages_needed += math.ceil((int(S.getValue(
525 'GUEST_MEMORY')[guest]) * 1000) / hugepage_size)
527 # get hugepage amounts for each socket on dpdk
528 sock0_mem, sock1_mem = 0, 0
530 if str(S.getValue('VSWITCH')).lower().count('dpdk'):
531 sock_mem = S.getValue('DPDK_SOCKET_MEM')
532 sock0_mem, sock1_mem = (int(sock_mem[0]) * 1024 / hugepage_size,
533 int(sock_mem[1]) * 1024 / hugepage_size)
535 # If hugepages needed, verify the amounts are free
536 if any([hugepages_needed, sock0_mem, sock1_mem]):
537 free_hugepages = hugepages.get_free_hugepages()
539 logging.info('Need %s hugepages free for guests',
541 result1 = free_hugepages >= hugepages_needed
542 free_hugepages -= hugepages_needed
547 logging.info('Need %s hugepages free for dpdk socket 0',
549 result2 = hugepages.get_free_hugepages('0') >= sock0_mem
550 free_hugepages -= sock0_mem
555 logging.info('Need %s hugepages free for dpdk socket 1',
557 result3 = hugepages.get_free_hugepages('1') >= sock1_mem
558 free_hugepages -= sock1_mem
562 logging.info('Need a total of %s total hugepages',
563 hugepages_needed + sock1_mem + sock0_mem)
565 # The only drawback here is sometimes dpdk doesn't release
566 # its hugepages on a test failure. This could cause a test
567 # to fail when dpdk would be OK to start because it will just
568 # use the previously allocated hugepages.
569 result4 = True if free_hugepages >= 0 else False
571 return all([result1, result2, result3, result4])
576 def write_result_to_file(results, output):
577 """Write list of dictionaries to a CSV file.
579 Each element on list will create separate row in output file.
580 If output file already exists, data will be appended at the end,
581 otherwise it will be created.
583 :param results: list of dictionaries.
584 :param output: path to output file.
586 with open(output, 'a') as csvfile:
588 logging.info("Write results to file: %s", output)
589 fieldnames = TestCase._get_unique_keys(results)
591 writer = csv.DictWriter(csvfile, fieldnames)
593 if not csvfile.tell(): # file is now empty
596 for result in results:
597 writer.writerow(result)
600 def _get_unique_keys(list_of_dicts):
601 """Gets unique key values as ordered list of strings in given dicts
603 :param list_of_dicts: list of dictionaries.
605 :returns: list of unique keys(strings).
607 result = OrderedDict()
608 for item in list_of_dicts:
609 for key in item.keys():
612 return list(result.keys())
614 def _add_connections(self):
616 Add connections for Kubernetes Usecases
618 logging.info("Kubernetes: Adding Connections")
619 vswitch = self._vswitch_ctl.get_vswitch()
620 bridge = S.getValue('VSWITCH_BRIDGE_NAME')
621 if S.getValue('K8S') and 'sriov' not in S.getValue('PLUGIN'):
622 if 'Ovs' in S.getValue('VSWITCH'):
624 logging.info("Kubernetes: Adding OVS Connections")
625 flow = {'table':'0', 'in_port':'1',
626 'idle_timeout':'0', 'actions': ['output:3']}
627 vswitch.add_flow(bridge, flow)
628 flow = {'table':'0', 'in_port':'3',
629 'idle_timeout':'0', 'actions': ['output:1']}
630 vswitch.add_flow(bridge, flow)
631 flow = {'table':'0', 'in_port':'2',
632 'idle_timeout':'0', 'actions': ['output:4']}
633 vswitch.add_flow(bridge, flow)
634 flow = {'table':'0', 'in_port':'4',
635 'idle_timeout':'0', 'actions': ['output:2']}
636 vswitch.add_flow(bridge, flow)
637 elif 'vpp' in S.getValue('VSWITCH'):
638 phy_ports = vswitch.get_ports()
639 virt_port0 = 'memif1/0'
640 virt_port1 = 'memif2/0'
641 vswitch.add_connection(bridge, phy_ports[0],
643 vswitch.add_connection(bridge, virt_port0,
645 vswitch.add_connection(bridge, phy_ports[1],
647 vswitch.add_connection(bridge, virt_port1,
651 def _add_flows(self):
652 """Add flows to the vswitch
654 vswitch = self._vswitch_ctl.get_vswitch()
655 # NOTE BOM 15-08-07 the frame mod code assumes that the
656 # physical ports are ports 1 & 2. The actual numbers
657 # need to be retrived from the vSwitch and the metadata value
658 # updated accordingly.
659 bridge = S.getValue('VSWITCH_BRIDGE_NAME')
660 if self._frame_mod == "vlan":
661 # 0x8100 => VLAN ethertype
662 self._logger.debug(" **** VLAN ***** ")
663 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
664 'actions': ['push_vlan:0x8100', 'goto_table:3']}
665 vswitch.add_flow(bridge, flow)
666 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
667 'actions': ['push_vlan:0x8100', 'goto_table:3']}
668 vswitch.add_flow(bridge, flow)
669 elif self._frame_mod == "mpls":
670 # 0x8847 => MPLS unicast ethertype
671 self._logger.debug(" **** MPLS ***** ")
672 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
673 'actions': ['push_mpls:0x8847', 'goto_table:3']}
674 vswitch.add_flow(bridge, flow)
675 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
676 'actions': ['push_mpls:0x8847', 'goto_table:3']}
677 vswitch.add_flow(bridge, flow)
678 elif self._frame_mod == "mac":
679 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
680 'actions': ['mod_dl_src:22:22:22:22:22:22',
682 vswitch.add_flow(bridge, flow)
683 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
684 'actions': ['mod_dl_src:11:11:11:11:11:11',
686 vswitch.add_flow(bridge, flow)
687 elif self._frame_mod == "dscp":
688 # DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
689 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
691 'actions': ['mod_nw_tos:184', 'goto_table:3']}
692 vswitch.add_flow(bridge, flow)
693 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
695 'actions': ['mod_nw_tos:184', 'goto_table:3']}
696 vswitch.add_flow(bridge, flow)
697 elif self._frame_mod == "ttl":
698 # 251 and 241 are the highest prime numbers < 255
699 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
701 'actions': ['mod_nw_ttl:251', 'goto_table:3']}
702 vswitch.add_flow(bridge, flow)
703 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
705 'actions': ['mod_nw_ttl:241', 'goto_table:3']}
706 vswitch.add_flow(bridge, flow)
707 elif self._frame_mod == "ip_addr":
708 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
710 'actions': ['mod_nw_src:10.10.10.10',
711 'mod_nw_dst:20.20.20.20',
713 vswitch.add_flow(bridge, flow)
714 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
716 'actions': ['mod_nw_src:20.20.20.20',
717 'mod_nw_dst:10.10.10.10',
719 vswitch.add_flow(bridge, flow)
720 elif self._frame_mod == "ip_port":
721 # NOTE BOM 15-08-27 The traffic generated is assumed
722 # to be UDP (nw_proto 17d) which is the default case but
723 # we will need to pick up the actual traffic params in use.
724 flow = {'table':'2', 'priority':'1000', 'metadata':'2',
725 'dl_type':'0x0800', 'nw_proto':'17',
726 'actions': ['mod_tp_src:44444',
727 'mod_tp_dst:44444', 'goto_table:3']}
728 vswitch.add_flow(bridge, flow)
729 flow = {'table':'2', 'priority':'1000', 'metadata':'1',
730 'dl_type':'0x0800', 'nw_proto':'17',
731 'actions': ['mod_tp_src:44444',
732 'mod_tp_dst:44444', 'goto_table:3']}
733 vswitch.add_flow(bridge, flow)
739 # TestSteps realted methods
741 def step_report_status(self, label, status):
742 """ Log status of test step
744 self._logger.info("%s ... %s", label, 'OK' if status else 'FAILED')
746 def step_stop_vnfs(self):
747 """ Stop all VNFs started by TestSteps
749 for vnf in self._step_vnf_list:
750 if self._step_vnf_list[vnf]:
751 self._step_vnf_list[vnf].stop()
753 def step_eval_param(self, param, step_result):
754 """ Helper function for #STEP macro evaluation
756 if isinstance(param, str):
757 # evaluate every #STEP reference inside parameter itself
758 macros = re.findall(r'(#STEP\[([\w\-:]+)\]((\[[\w\-\'\"]+\])*))', param)
762 if macro[1] in self._step_result_mapping:
763 key = self._step_result_mapping[macro[1]]
766 # pylint: disable=eval-used
767 tmp_val = str(eval('step_result[{}]{}'.format(key, macro[2])))
768 param = param.replace(macro[0], tmp_val)
770 # evaluate references to vsperf configuration options
771 macros = re.findall(r'\$(([\w\-]+)(\[[\w\[\]\-\'\"]+\])*)', param)
774 # pylint: disable=eval-used
776 tmp_val = str(eval("S.getValue('{}'){}".format(macro[1], macro[2])))
777 param = param.replace('${}'.format(macro[0]), tmp_val)
778 # ignore that required option can't be evaluated
779 except (IndexError, KeyError, AttributeError):
780 self._logger.debug("Skipping %s as it isn't a configuration "
781 "parameter.", '${}'.format(macro[0]))
783 elif isinstance(param, (list, tuple)):
786 tmp_list.append(self.step_eval_param(item, step_result))
788 elif isinstance(param, dict):
790 for (key, value) in param.items():
791 tmp_dict[key] = self.step_eval_param(value, step_result)
796 def step_eval_params(self, params, step_result):
797 """ Evaluates referrences to results from previous steps
800 # evaluate all parameters if needed
802 eval_params.append(self.step_eval_param(param, step_result))
805 # pylint: disable=too-many-locals, too-many-branches, too-many-statements
807 """ Execute actions specified by TestSteps list
809 :return: False if any error was detected
816 # required for VNFs initialization
818 # initialize list with results
819 self._step_result = [None] * len(self.test)
821 # run test step by step...
822 for i, step in enumerate(self.test):
823 step_ok = not self._step_check
824 step_check = self._step_check
826 # configure step result mapping if step alias/label is detected
827 if step[0].startswith('#'):
830 raise RuntimeError('Step alias can\'t be an integer value {}'.format(key))
831 if key in self._step_result_mapping:
832 raise RuntimeError('Step alias {} has been used already for step '
833 '{}'.format(key, self._step_result_mapping[key]))
834 self._step_result_mapping[step[0][1:]] = i
837 # store regex filter if it is specified
838 if isinstance(step[-1], str) and step[-1].startswith('|'):
839 # evalute macros and variables used in regex
840 regex = self.step_eval_params([step[-1][1:]], self._step_result[:i])[0]
843 # check if step verification should be suppressed
844 if step[0].startswith('!'):
847 step[0] = step[0][1:]
848 if step[0] == 'vswitch':
849 test_object = self._vswitch_ctl.get_vswitch()
850 elif step[0] == 'namespace':
851 test_object = namespace
852 elif step[0] == 'veth':
854 elif step[0] == 'settings':
856 elif step[0] == 'tools':
857 test_object = TestStepsTools()
858 step[1] = step[1].title()
859 elif step[0] == 'trafficgen':
860 test_object = self._traffic_ctl
861 # in case of send_traffic or send_traffic_async methods, ensure
862 # that specified traffic values are merged with existing self._traffic
863 if step[1].startswith('send_traffic'):
864 tmp_traffic = copy.deepcopy(self._traffic)
865 tmp_traffic.update(step[2])
866 step[2] = tmp_traffic
867 # store indication that traffic has been sent
868 # so it is not sent again after the execution of teststeps
869 self._step_send_traffic = True
870 elif step[0].startswith('vnf'):
871 # use vnf started within TestSteps
872 if not self._step_vnf_list[step[0]]:
874 self._step_vnf_list[step[0]] = loader.get_vnf_class()()
875 test_object = self._step_vnf_list[step[0]]
876 elif step[0].startswith('VNF'):
877 if step[1] in ('start', 'stop'):
878 raise RuntimeError("Cannot execute start() or stop() method of "
879 "VNF deployed automatically by scenario.")
880 # use vnf started by scenario deployment (e.g. pvp)
881 vnf_index = int(step[0][3:])
883 test_object = self._vnf_list[vnf_index]
885 raise RuntimeError("VNF with index {} is not running.".format(vnf_index))
886 elif step[0] == 'wait':
887 input(os.linesep + "Step {}: Press Enter to continue with "
888 "the next step...".format(i) + os.linesep + os.linesep)
890 elif step[0] == 'sleep':
891 self._logger.debug("Sleep %s seconds", step[1])
892 time.sleep(int(step[1]))
894 elif step[0] == 'log':
895 test_object = self._logger
896 # there isn't a need for validation of log entry
899 elif step[0] == 'pdb':
904 self._logger.error("Unsupported test object %s", step[0])
905 self._step_status = {'status' : False, 'details' : ' '.join(step)}
906 self.step_report_status("Step '{}'".format(' '.join(step)),
907 self._step_status['status'])
910 test_method = getattr(test_object, step[1])
912 test_method_check = getattr(test_object, CHECK_PREFIX + step[1])
914 test_method_check = None
918 # eval parameters, but use only valid step_results
919 # to support negative indexes
920 step_params = self.step_eval_params(step[2:], self._step_result[:i])
921 step_log = '{} {}'.format(' '.join(step[:2]), step_params)
922 step_log += ' filter "{}"'.format(regex) if regex else ''
923 self._logger.debug("Step %s '%s' start", i, step_log)
924 self._step_result[i] = test_method(*step_params)
926 # apply regex to step output
927 self._step_result[i] = functions.filter_output(
928 self._step_result[i], regex)
930 self._logger.debug("Step %s '%s' results '%s'", i,
931 step_log, self._step_result[i])
932 time.sleep(S.getValue('TEST_STEP_DELAY'))
934 step_ok = test_method_check(self._step_result[i], *step_params)
935 except (AssertionError, AttributeError, IndexError) as ex:
937 self._logger.error("Step %s raised %s", i, type(ex).__name__)
940 self.step_report_status("Step {} - '{}'".format(i, step_log), step_ok)
943 self._step_status = {'status' : False, 'details' : step_log}
944 # Stop all VNFs started by TestSteps
945 self.step_stop_vnfs()
948 # all steps processed without any issue
952 # get methods for TestCase members, which needs to be publicly available
954 def get_output_file(self):
955 """Return content of self._output_file member
957 return self._output_file
960 """Return content of self.desc member
964 def get_versions(self):
965 """Return content of self.versions member
967 return self._versions
969 def get_traffic(self):
970 """Return content of self._traffic member
974 def get_tc_results(self):
975 """Return content of self._tc_results member
977 return self._tc_results
979 def get_collector(self):
980 """Return content of self._collector member
982 return self._collector