heat_template_version: ocata
description: >
Internal API network. Used for most APIs, Database, RPC.
parameters:
# the defaults here work for static IP assignment (IPAM) only
InternalApiNetCidr:
# OpenStack uses the EUI-64 address format, which requires a /64 prefix
default: 'fd00:fd00:fd00:2000::/64'
description: Cidr for the internal API network.
type: string
InternalApiNetValueSpecs:
default: {'provider:physical_network': 'internal_api', 'provider:network_type': 'flat'}
description: Value specs for the internal API network.
type: json
InternalApiNetAdminStateUp:
default: false
description: This admin state of of the network.
type: boolean
InternalApiNetShared:
default: false
description: Whether this network is shared across all tenants.
type: boolean
InternalApiNetName:
default: internal_api
description: The name of the internal API network.
type: string
InternalApiSubnetName:
default: internal_api_subnet
description: The name of the internal API subnet in Neutron.
type: string
InternalApiAllocationPools:
default: [{'start': 'fd00:fd00:fd00:2000::10', 'end': 'fd00:fd00:fd00:2000:ffff:ffff:ffff:fffe'}]
description: Ip allocation pool range for the internal API network.
type: json
IPv6AddressMode:
default: dhcpv6-stateful
description: Neutron subnet IPv6 address mode
type: string
IPv6RAMode:
default: dhcpv6-stateful
description: Neutron subnet IPv6 router advertisement mode
type: string
resources:
InternalApiNetwork:
type: OS::Neutron::Net
properties:
admin_state_up: {get_param: InternalApiNetAdminStateUp}
name: {get_param: InternalApiNetName}
shared:<# Copyright 2015-2016 Intel Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""TestCase base class
"""
import csv
import os
import time
import logging
import subprocess
import copy
from collections import OrderedDict
import core.component_factory as component_factory
from core.loader import Loader
from core.results.results_constants import ResultsConstants
from tools import tasks
from tools import hugepages
from tools import functions
from tools.pkt_gen.trafficgen.trafficgenhelper import TRAFFIC_DEFAULTS
from conf import settings as S
from conf import get_test_param
class TestCase(object):
"""TestCase base class
In this basic form runs RFC2544 throughput test
"""
def __init__(self, cfg):
"""Pull out fields from test config
:param cfg: A dictionary of string-value pairs describing the test
configuration. Both the key and values strings use well-known
values.
:param results_dir: Where the csv formatted results are written.
"""
self._testcase_start_time = time.time()
self._hugepages_mounted = False
self._traffic_ctl = None
self._vnf_ctl = None
self._vswitch_ctl = None
self._collector = None
self._loadgen = None
self._output_file = None
self._tc_results = None
self.guest_loopback = []
self._settings_original = {}
self._settings_paths_modified = False
self._testcast_run_time = None
self._update_settings('VSWITCH', cfg.get('vSwitch', S.getValue('VSWITCH')))
self._update_settings('VNF', cfg.get('VNF', S.getValue('VNF')))
self._update_settings('TRAFFICGEN', cfg.get('Trafficgen', S.getValue('TRAFFICGEN')))
self._update_settings('TEST_PARAMS', cfg.get('Parameters', S.getValue('TEST_PARAMS')))
# update global settings
guest_loopback = get_test_param('guest_loopback', None)
if guest_loopback:
self._update_settings('GUEST_LOOPBACK', [guest_loopback for dummy in S.getValue('GUEST_LOOPBACK')])
if 'VSWITCH' in self._settings_original or 'VNF' in self._settings_original:
self._settings_original.update({
'RTE_SDK' : S.getValue('RTE_SDK'),
'OVS_DIR' : S.getValue('OVS_DIR'),
})
functions.settings_update_paths()
# set test parameters; CLI options take precedence to testcase settings
self._logger = logging.getLogger(__name__)
self.name = cfg['Name']
self.desc = cfg.get('Description', 'No description given.')
self.test = cfg.get('TestSteps', None)
bidirectional = cfg.get('biDirectional', TRAFFIC_DEFAULTS['bidir'])
bidirectional = get_test_param('bidirectional', bidirectional)
if not isinstance(bidirectional, str):
raise TypeError(
'Bi-dir value must be of type string in testcase configuration')
bidirectional = bidirectional.title() # Keep things consistent
traffic_type = cfg.get('Traffic Type', TRAFFIC_DEFAULTS['traffic_type'])
traffic_type = get_test_param('traffic_type', traffic_type)
framerate = cfg.get('iLoad', TRAFFIC_DEFAULTS['frame_rate'])
framerate = get_test_param('iload', framerate)
self.deployment = cfg['Deployment']
self._frame_mod = cfg.get('Frame Modification', None)
self._tunnel_type = None
self._tunnel_operation = None
if self.deployment == 'op2p':
self._tunnel_operation = cfg['Tunnel Operation']
if 'Tunnel Type' in cfg:
self._tunnel_type = cfg['Tunnel Type']
self._tunnel_type = get_test_param('tunnel_type',
self._tunnel_type)
# identify guest loopback method, so it can be added into reports
if self.deployment == 'pvp':
self.guest_loopback.append(S.getValue('GUEST_LOOPBACK')[0])
else:
self.guest_loopback = S.getValue('GUEST_LOOPBACK').copy()
# read configuration of streams; CLI parameter takes precedence to
# testcase definition
multistream = cfg.get('MultiStream', TRAFFIC_DEFAULTS['multistream'])
multistream = get_test_param('multistream', multistream)
stream_type = cfg.get('Stream Type', TRAFFIC_DEFAULTS['stream_type'])
stream_type = get_test_param('stream_type', stream_type)
pre_installed_flows = cfg.get('Pre-installed Flows', TRAFFIC_DEFAULTS['pre_installed_flows'])
pre_installed_flows = get_test_param('pre-installed_flows', pre_installed_flows)
# check if test requires background load and which generator it uses
self._load_cfg = cfg.get('Load', None)
if self._load_cfg and 'tool' in self._load_cfg:
self._loadgen = self._load_cfg['tool']
else:
# background load is not requested, so use dummy implementation
self._loadgen = "Dummy"
if self._frame_mod:
self._frame_mod = self._frame_mod.lower()
self._results_dir = S.getValue('RESULTS_PATH')
# set traffic details, so they can be passed to vswitch and traffic ctls
self._traffic = copy.deepcopy(TRAFFIC_DEFAULTS)
self._traffic.update({'traffic_type': traffic_type,
'flow_type': cfg.get('Flow Type', TRAFFIC_DEFAULTS['flow_type']),
'bidir': bidirectional,
'tunnel_type': self._tunnel_type,
'multistream': int(multistream),
'stream_type': stream_type,
'pre_installed_flows' : pre_installed_flows,
'frame_rate': int(framerate)})
# Packet Forwarding mode
self._vswitch_none = 'none' == S.getValue('VSWITCH').strip().lower()
# OVS Vanilla requires guest VM MAC address and IPs to work
if 'linux_bridge' in self.guest_loopback:
self._traffic['l2'].update({'srcmac': S.getValue('GUEST_NET2_MAC')[0],
'dstmac': S.getValue('GUEST_NET1_MAC')[0]})
self._traffic['l3'].update({'srcip': S.getValue('VANILLA_TGEN_PORT1_IP'),
'dstip': S.getValue('VANILLA_TGEN_PORT2_IP')})
# trafficgen configuration required for tests of tunneling protocols
if self.deployment == "op2p":
self._traffic['l2'].update({'srcmac':
S.getValue('TRAFFICGEN_PORT1_MAC'),
'dstmac':
S.getValue('TRAFFICGEN_PORT2_MAC')})
self._traffic['l3'].update({'srcip':
S.getValue('TRAFFICGEN_PORT1_IP'),
'dstip':
S.getValue('TRAFFICGEN_PORT2_IP')})
if self._tunnel_operation == "decapsulation":
self._traffic['l2'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L2')
self._traffic['l3'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L3')
self._traffic['l4'] = S.getValue(self._tunnel_type.upper() + '_FRAME_L4')
elif S.getValue('NICS')[0]['type'] == 'vf' or S.getValue('NICS')[1]['type'] == 'vf':
mac1 = S.getValue('NICS')[0]['mac']
mac2 = S.getValue('NICS')[1]['mac']
if mac1 and mac2:
self._traffic['l2'].update({'srcmac': mac2, 'dstmac': mac1})
else:
self._logger.debug("MAC addresses can not be read")
def run_initialize(self):
""" Prepare test execution environment
"""
self._logger.debug(self.name)
# mount hugepages if needed
self._mount_hugepages()
# copy sources of l2 forwarding tools into VM shared dir if needed
self._copy_fwd_tools_for_all_guests()
self._logger.debug("Controllers:")
loader = Loader()
self._traffic_ctl = component_factory.create_traffic(
self._traffic['traffic_type'],
loader.get_trafficgen_class())
self._vnf_ctl = component_factory.create_vnf(
self.deployment,
loader.get_vnf_class())
if self._vswitch_none:
self._vswitch_ctl = component_factory.create_pktfwd(
self.deployment,
loader.get_pktfwd_class())
else:
self._vswitch_ctl = component_factory.create_vswitch(
self.deployment,
loader.get_vswitch_class(),
self._traffic,
self._tunnel_operation)
self._collector = component_factory.create_collector(
loader.get_collector_class(),
self._results_dir, self.name)
self._loadgen = component_factory.create_loadgen(
self._loadgen,
self._load_cfg)
self._output_file = os.path.join(self._results_dir, "result_" + self.name +
"_" + self.deployment + ".csv")
self._logger.debug("Setup:")
def run_finalize(self):
""" Tear down test execution environment and record test results
"""
# umount hugepages if mounted
self._umount_hugepages()
# restore original settings
S.load_from_dict(self._settings_original)
def run_report(self):
""" Report test results
"""
self._logger.debug("self._collector Results:")
self._collector.print_results()
if S.getValue('mode') != 'trafficgen-off':
self._logger.debug("Traffic Results:")
self._traffic_ctl.print_results()
self._tc_results = self._append_results(self._traffic_ctl.get_results())
TestCase._write_result_to_file(self._tc_results, self._output_file)
def run(self):
"""Run the test
All setup and teardown through controllers is included.
"""
# prepare test execution environment
self.run_initialize()
with self._vswitch_ctl, self._loadgen:
with self._vnf_ctl, self._collector:
if not self._vswitch_none:
self._add_flows()
# run traffic generator if requested, otherwise wait for manual termination
if S.getValue('mode') == 'trafficgen-off':
time.sleep(2)
self._logger.debug("All is set. Please run traffic generator manually.")
input(os.linesep + "Press Enter to terminate vswitchperf..." + os.linesep + os.linesep)
else:
if S.getValue('mode') == 'trafficgen-pause':
time.sleep(2)
true_vals = ('yes', 'y', 'ye', None)
while True:
choice = input(os.linesep + 'Transmission paused, should'
' transmission be resumed? ' + os.linesep).lower()
if not choice or choice not in true_vals:
print('Please respond with \'yes\' or \'y\' ', end='')
else:
break
with self._traffic_ctl:
self._traffic_ctl.send_traffic(self._traffic)
# dump vswitch flows before they are affected by VNF termination
if not self._vswitch_none:
self._vswitch_ctl.dump_vswitch_flows()
# tear down test execution environment and log results
self.run_finalize()
self._testcase_run_time = time.strftime("%H:%M:%S",
time.gmtime(time.time() - self._testcase_start_time))
logging.info("Testcase execution time: " + self._testcase_run_time)
# report test results
self.run_report()
def _update_settings(self, param, value):
""" Check value of given configuration parameter
In case that new value is different, then testcase
specific settings is updated and original value stored
:param param: Name of parameter inside settings
:param value: Disired parameter value
"""
orig_value = S.getValue(param)
if orig_value != value:
self._settings_original[param] = orig_value
S.setValue(param, value)
def _append_results(self, results):
"""
Method appends mandatory Test Case results to list of dictionaries.
:param results: list of dictionaries which contains results from
traffic generator.
:returns: modified list of dictionaries.
"""
for item in results:
item[ResultsConstants.ID] = self.name
item[ResultsConstants.DEPLOYMENT] = self.deployment
item[ResultsConstants.TRAFFIC_TYPE] = self._traffic['l3']['proto']
item[ResultsConstants.TEST_RUN_TIME] = self._testcase_run_time
if self._traffic['multistream']:
item[ResultsConstants.SCAL_STREAM_COUNT] = self._traffic['multistream']
item[ResultsConstants.SCAL_STREAM_TYPE] = self._traffic['stream_type']
item[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = self._traffic['pre_installed_flows']
if self.deployment in ['pvp', 'pvvp'] and len(self.guest_loopback):
item[ResultsConstants.GUEST_LOOPBACK] = ' '.join(self.guest_loopback)
if self._tunnel_type:
item[ResultsConstants.TUNNEL_TYPE] = self._tunnel_type
return results
def _copy_fwd_tools_for_all_guests(self):
"""Copy dpdk and l2fwd code to GUEST_SHARE_DIR[s] based on selected deployment.
"""
# data are copied only for pvp and pvvp, so let's count number of 'v'
counter = 1
while counter <= self.deployment.count('v'):
self._copy_fwd_tools_for_guest(counter)
counter += 1
def _copy_fwd_tools_for_guest(self, index):
"""Copy dpdk and l2fwd code to GUEST_SHARE_DIR of VM
:param index: Index of VM starting from 1 (i.e. 1st VM has index 1)
"""
guest_dir = S.getValue('GUEST_SHARE_DIR')[index-1]
# remove shared dir if it exists to avoid issues with file consistency
if os.path.exists(guest_dir):
tasks.run_task(['rm', '-f', '-r', guest_dir], self._logger,
'Removing content of shared directory...', True)
# directory to share files between host and guest
os.makedirs(guest_dir)
# copy sources into shared dir only if neccessary
if 'testpmd' in self.guest_loopback or 'l2fwd' in self.guest_loopback:
try:
tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
os.path.join(S.getValue('RTE_SDK'), ''),
os.path.join(guest_dir, 'DPDK')],
self._logger,
'Copying DPDK to shared directory...',
True)
tasks.run_task(['rsync', '-a', '-r', '-l',
os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/'),
os.path.join(guest_dir, 'l2fwd')],
self._logger,
'Copying l2fwd to shared directory...',
True)
except subprocess.CalledProcessError:
self._logger.error('Unable to copy DPDK and l2fwd to shared directory')
def _mount_hugepages(self):
"""Mount hugepages if usage of DPDK or Qemu is detected
"""
# hugepages are needed by DPDK and Qemu
if not self._hugepages_mounted and \
(self.deployment.count('v') or \
S.getValue('VSWITCH').lower().count('dpdk') or \
self._vswitch_none or \
self.test and 'vnf' in [step[0][0:3] for step in self.test]):
hugepages.mount_hugepages()
self._hugepages_mounted = True
def _umount_hugepages(self):
"""Umount hugepages if they were mounted before
"""
if self._hugepages_mounted:
hugepages.umount_hugepages()
self._hugepages_mounted = False
@staticmethod
def _write_result_to_file(results, output):
"""Write list of dictionaries to a CSV file.
Each element on list will create separate row in output file.
If output file already exists, data will be appended at the end,
otherwise it will be created.
:param results: list of dictionaries.
:param output: path to output file.
"""
with open(output, 'a') as csvfile:
logging.info("Write results to file: " + output)
fieldnames = TestCase._get_unique_keys(results)
writer = csv.DictWriter(csvfile, fieldnames)
if not csvfile.tell(): # file is now empty
writer.writeheader()
for result in results:
writer.writerow(result)
@staticmethod
def _get_unique_keys(list_of_dicts):
"""Gets unique key values as ordered list of strings in given dicts
:param list_of_dicts: list of dictionaries.
:returns: list of unique keys(strings).
"""
result = OrderedDict()
for item in list_of_dicts:
for key in item.keys():
result[key] = ''
return list(result.keys())
def _add_flows(self):
"""Add flows to the vswitch
"""
vswitch = self._vswitch_ctl.get_vswitch()
# TODO BOM 15-08-07 the frame mod code assumes that the
# physical ports are ports 1 & 2. The actual numbers
# need to be retrived from the vSwitch and the metadata value
# updated accordingly.
bridge = S.getValue('VSWITCH_BRIDGE_NAME')
if self._frame_mod == "vlan":
# 0x8100 => VLAN ethertype
self._logger.debug(" **** VLAN ***** ")
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
'actions': ['push_vlan:0x8100', 'goto_table:3']}
vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
'actions': ['push_vlan:0x8100', 'goto_table:3']}
vswitch.add_flow(bridge, flow)
elif self._frame_mod == "mpls":
# 0x8847 => MPLS unicast ethertype
self._logger.debug(" **** MPLS ***** ")
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
'actions': ['push_mpls:0x8847', 'goto_table:3']}
vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
'actions': ['push_mpls:0x8847', 'goto_table:3']}
vswitch.add_flow(bridge, flow)
elif self._frame_mod == "mac":
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
'actions': ['mod_dl_src:22:22:22:22:22:22',
'goto_table:3']}
vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
'actions': ['mod_dl_src:11:11:11:11:11:11',
'goto_table:3']}
vswitch.add_flow(bridge, flow)
elif self._frame_mod == "dscp":
# DSCP 184d == 0x4E<<2 => 'Expedited Forwarding'
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
'dl_type':'0x0800',
'actions': ['mod_nw_tos:184', 'goto_table:3']}
vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
'dl_type':'0x0800',
'actions': ['mod_nw_tos:184', 'goto_table:3']}
vswitch.add_flow(bridge, flow)
elif self._frame_mod == "ttl":
# 251 and 241 are the highest prime numbers < 255
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
'dl_type':'0x0800',
'actions': ['mod_nw_ttl:251', 'goto_table:3']}
vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
'dl_type':'0x0800',
'actions': ['mod_nw_ttl:241', 'goto_table:3']}
vswitch.add_flow(bridge, flow)
elif self._frame_mod == "ip_addr":
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
'dl_type':'0x0800',
'actions': ['mod_nw_src:10.10.10.10',
'mod_nw_dst:20.20.20.20',
'goto_table:3']}
vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
'dl_type':'0x0800',
'actions': ['mod_nw_src:20.20.20.20',
'mod_nw_dst:10.10.10.10',
'goto_table:3']}
vswitch.add_flow(bridge, flow)
elif self._frame_mod == "ip_port":
# TODO BOM 15-08-27 The traffic generated is assumed
# to be UDP (nw_proto 17d) which is the default case but
# we will need to pick up the actual traffic params in use.
flow = {'table':'2', 'priority':'1000', 'metadata':'2',
'dl_type':'0x0800', 'nw_proto':'17',
'actions': ['mod_tp_src:44444',
'mod_tp_dst:44444', 'goto_table:3']}
vswitch.add_flow(bridge, flow)
flow = {'table':'2', 'priority':'1000', 'metadata':'1',
'dl_type':'0x0800', 'nw_proto':'17',
'actions': ['mod_tp_src:44444',
'mod_tp_dst:44444', 'goto_table:3']}
vswitch.add_flow(bridge, flow)
else:
pass