tacker/tacker/tosca/utils.py
LiangLu a8516bb079 Fix bug only last vdu will be saved to nested_tpl
When instantiating multiple VDU using `OS::Heat::AutoScalingGroup`
type in vnfd with SOL format, an error about not being able to
find the specified type occurred.

In current `for loop` of update_nested_scaling_resources(),
only the last data will be stored into the nested_tpl[],
the other data will be lost.

This modification changed the logic of the `for loop`,
it will store all types of data configuration into nested_tpl[]
according to the `aspect id`.

Closes-Bug: 1917314
Change-Id: I40eb711b7d0e8e2cf61f0210151be60febd08407
2021-03-23 09:27:03 +00:00

1391 lines
55 KiB
Python

# Copyright 2016 - Nokia
# 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.
import collections
import os
import re
import sys
import yaml
from collections import OrderedDict
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from tacker._i18n import _
from tacker.common import exceptions
from tacker.common import log
from tacker.common import utils
from tacker.extensions import vnfm
from tacker.plugins.common import constants
from toscaparser import properties
from toscaparser.utils import yamlparser
FAILURE = 'tosca.policies.tacker.Failure'
LOG = logging.getLogger(__name__)
MONITORING = 'tosca.policies.Monitoring'
SCALING = 'tosca.policies.Scaling'
RESERVATION = 'tosca.policies.Reservation'
PLACEMENT = 'tosca.policies.tacker.Placement'
TACKERCP = 'tosca.nodes.nfv.CP.Tacker'
TACKERVDU = 'tosca.nodes.nfv.VDU.Tacker'
BLOCKSTORAGE = 'tosca.nodes.BlockStorage.Tacker'
BLOCKSTORAGE_ATTACHMENT = 'tosca.nodes.BlockStorageAttachment'
TOSCA_BINDS_TO = 'tosca.relationships.network.BindsTo'
VDU = 'tosca.nodes.nfv.VDU'
IMAGE = 'tosca.artifacts.Deployment.Image.VM'
ETSI_INST_LEVEL = 'tosca.policies.nfv.InstantiationLevels'
ETSI_SCALING_ASPECT = 'tosca.policies.nfv.ScalingAspects'
ETSI_SCALING_ASPECT_DELTA = 'tosca.policies.nfv.VduScalingAspectDeltas'
ETSI_INITIAL_DELTA = 'tosca.policies.nfv.VduInitialDelta'
HEAT_SOFTWARE_CONFIG = 'OS::Heat::SoftwareConfig'
OS_RESOURCES = {
'flavor': 'get_flavor_dict',
'image': 'get_image_dict'
}
FLAVOR_PROPS = {
"num_cpus": ("vcpus", 1, None),
"disk_size": ("disk", 1, "GB"),
"mem_size": ("ram", 512, "MB")
}
CPU_PROP_MAP = (('hw:cpu_policy', 'cpu_affinity'),
('hw:cpu_threads_policy', 'thread_allocation'),
('hw:cpu_sockets', 'socket_count'),
('hw:cpu_threads', 'thread_count'),
('hw:cpu_cores', 'core_count'))
CPU_PROP_VAL_MAP = {'cpu_affinity': ('shared', 'dedicated')}
CPU_PROP_KEY_SET = {'cpu_affinity', 'thread_allocation', 'socket_count',
'thread_count', 'core_count'}
FLAVOR_EXTRA_SPECS_LIST = ('cpu_allocation',
'mem_page_size',
'numa_node_count',
'numa_nodes')
delpropmap = {TACKERVDU: ('mgmt_driver', 'config', 'service_type',
'placement_policy', 'monitoring_policy',
'metadata', 'failure_policy'),
TACKERCP: ('management',)}
convert_prop = {TACKERCP: {'anti_spoofing_protection':
'port_security_enabled',
'type':
'binding:vnic_type'}}
convert_prop_values = {TACKERCP: {'type': {'sriov': 'direct',
'vnic': 'normal'}}}
deletenodes = (MONITORING, FAILURE, PLACEMENT)
HEAT_RESOURCE_MAP = {
"flavor": "OS::Nova::Flavor",
"image": "OS::Glance::WebImage",
"maintenance": "OS::Aodh::EventAlarm"
}
SCALE_GROUP_RESOURCE = "OS::Heat::AutoScalingGroup"
SCALE_POLICY_RESOURCE = "OS::Heat::ScalingPolicy"
PLACEMENT_POLICY_RESOURCE = "OS::Nova::ServerGroup"
@log.log
def updateimports(template):
path = os.path.dirname(os.path.abspath(__file__)) + '/lib/'
defsfile = path + 'tacker_defs.yaml'
if 'imports' in template:
template['imports'].append(defsfile)
else:
template['imports'] = [defsfile]
if 'nfv' in template.get('tosca_definitions_version', {}):
nfvfile = path + 'tacker_nfv_defs.yaml'
template['imports'].append(nfvfile)
LOG.debug(path)
@log.log
def check_for_substitution_mappings(template, params):
sm_dict = params.get('substitution_mappings', {})
requirements = sm_dict.get('requirements')
node_tpl = template['topology_template']['node_templates']
req_dict_tpl = template['topology_template']['substitution_mappings'].get(
'requirements')
# Check if substitution_mappings and requirements are empty in params but
# not in template. If True raise exception
if (not sm_dict or not requirements) and req_dict_tpl:
raise vnfm.InvalidParamsForSM()
# Check if requirements are present for SM in template, if True then return
elif (not sm_dict or not requirements) and not req_dict_tpl:
return
del params['substitution_mappings']
for req_name, req_val in (req_dict_tpl).items():
if req_name not in requirements:
raise vnfm.SMRequirementMissing(requirement=req_name)
if not isinstance(req_val, list):
raise vnfm.InvalidSubstitutionMapping(requirement=req_name)
try:
node_name = req_val[0]
node_req = req_val[1]
node_tpl[node_name]['requirements'].append({
node_req: {
'node': requirements[req_name]
}
})
node_tpl[requirements[req_name]] = \
sm_dict[requirements[req_name]]
except Exception:
raise vnfm.InvalidSubstitutionMapping(requirement=req_name)
@log.log
def get_vdu_monitoring(template):
monitoring_dict = dict()
policy_dict = dict()
policy_dict['vdus'] = collections.OrderedDict()
for nt in template.nodetemplates:
if nt.type_definition.is_derived_from(TACKERVDU):
mon_policy = nt.get_property_value('monitoring_policy') or 'noop'
if mon_policy != 'noop':
if 'parameters' in mon_policy:
mon_policy['monitoring_params'] = mon_policy['parameters']
policy_dict['vdus'][nt.name] = {}
policy_dict['vdus'][nt.name][mon_policy['name']] = mon_policy
if policy_dict.get('vdus'):
monitoring_dict = policy_dict
return monitoring_dict
def get_vdu_applicationmonitoring(template):
tpl_temp = "topology_template"
n_temp = "node_templates"
poly = "app_monitoring_policy"
monitoring_dict = dict()
policy_dict = dict()
policy_dict['vdus'] = collections.OrderedDict()
node_list = template[tpl_temp][n_temp].keys()
for node in node_list:
nt = template[tpl_temp][n_temp][node]
if nt['type'] == TACKERVDU:
if poly in nt['properties']:
mon_policy = nt['properties'][poly]
if mon_policy != 'noop':
policy_dict['vdus'][node] = {}
policy_dict['vdus'][node] = mon_policy
del template[tpl_temp][n_temp][node]['properties'][poly]
if policy_dict.get('vdus'):
monitoring_dict = policy_dict
return monitoring_dict
@log.log
def get_vdu_metadata(template, unique_id=None):
metadata = dict()
metadata.setdefault('vdus', {})
for nt in template.nodetemplates:
if nt.type_definition.is_derived_from(TACKERVDU):
metadata_dict = nt.get_property_value('metadata') or None
if metadata_dict:
metadata_dict['metering.server_group'] = \
(metadata_dict['metering.server_group'] + '-'
+ unique_id)[:15]
metadata['vdus'][nt.name] = {}
metadata['vdus'][nt.name].update(metadata_dict)
return metadata
@log.log
def get_metadata_for_reservation(template, metadata):
"""Method used to add lease_id in metadata
So that it can be used further while creating query_metadata
:param template: ToscaTemplate object
:param metadata: metadata dict
:return: dictionary contains lease_id
"""
metadata.setdefault('reservation', {})
input_param_list = template.parsed_params.keys()
# if lease_id is passed in the parameter file,
# get it from template parsed_params.
if 'lease_id' in input_param_list:
metadata['reservation']['lease_id'] = template.parsed_params[
'lease_id']
else:
for policy in template.policies:
if policy.entity_tpl['type'] == constants.POLICY_RESERVATION:
metadata['reservation']['lease_id'] = policy.entity_tpl[
'reservation']['properties']['lease_id']
break
if not uuidutils.is_uuid_like(metadata['reservation']['lease_id']):
raise exceptions.Invalid('Invalid UUID for lease_id')
return metadata
@log.log
def pre_process_alarm_resources(vnf, template, vdu_metadata, unique_id=None):
alarm_resources = dict()
query_metadata = dict()
alarm_actions = dict()
for policy in template.policies:
if policy.type_definition.is_derived_from(MONITORING):
query_metadata.update(_process_query_metadata(
vdu_metadata, policy, unique_id))
alarm_actions.update(_process_alarm_actions(vnf, policy))
if policy.type_definition.is_derived_from(RESERVATION):
query_metadata.update(_process_query_metadata_reservation(
vdu_metadata, policy))
alarm_actions.update(_process_alarm_actions_for_reservation(
vnf, policy))
alarm_resources['event_types'] = {
'start_actions': {'event_type': 'lease.event.start_lease'},
'before_end_actions': {
'event_type': 'lease.event.before_end_lease'},
'end_actions': {'event_type': 'lease.event.end_lease'}}
maintenance_actions = _process_alarm_actions_for_maintenance(vnf)
if maintenance_actions:
alarm_actions.update(maintenance_actions)
alarm_resources['event_types'] = {}
alarm_resources['event_types'].update({
'ALL_maintenance': {'event_type': 'maintenance.scheduled'}})
alarm_resources['query_metadata'] = query_metadata
alarm_resources['alarm_actions'] = alarm_actions
return alarm_resources
def _process_query_metadata(metadata, policy, unique_id):
query_mtdata = dict()
triggers = policy.entity_tpl['triggers']
for trigger_name, trigger_dict in triggers.items():
resource_type = trigger_dict.get('condition').get('resource_type')
# TODO(phuoc): currently, Tacker only supports resource_type with
# instance value. Other types such as instance_network_interface,
# instance_disk can be supported in the future.
if resource_type == 'instance':
if not (trigger_dict.get('metadata') and metadata):
raise vnfm.MetadataNotMatched()
is_matched = False
for vdu_name, metadata_dict in metadata['vdus'].items():
trigger_dict['metadata'] = \
(trigger_dict['metadata'] + '-' + unique_id)[:15]
if trigger_dict['metadata'] == \
metadata_dict['metering.server_group']:
is_matched = True
if not is_matched:
raise vnfm.MetadataNotMatched()
query_template = dict()
query_template['str_replace'] = dict()
query_template['str_replace']['template'] = \
'{"=": {"server_group": "scaling_group_id"}}'
scaling_group_param = \
{'scaling_group_id': trigger_dict['metadata']}
query_template['str_replace']['params'] = scaling_group_param
else:
raise vnfm.InvalidResourceType(resource_type=resource_type)
query_mtdata[trigger_name] = query_template
return query_mtdata
def _process_query_metadata_reservation(metadata, policy):
query_metadata = dict()
policy_actions = list(policy.entity_tpl['reservation'].keys())
policy_actions.remove('properties')
for action in policy_actions:
query_template = [{
"field": 'traits.lease_id', "op": "eq",
"value": metadata['reservation']['lease_id']}]
query_metadata[action] = query_template
return query_metadata
def _process_alarm_actions(vnf, policy):
# process alarm url here
triggers = policy.entity_tpl['triggers']
alarm_actions = dict()
for trigger_name, trigger_dict in triggers.items():
alarm_url = vnf['attributes'].get(trigger_name)
if alarm_url:
alarm_url = str(alarm_url)
LOG.debug('Alarm url in heat %s', alarm_url)
alarm_actions[trigger_name] = dict()
alarm_actions[trigger_name]['alarm_actions'] = [alarm_url]
return alarm_actions
def _process_alarm_actions_for_reservation(vnf, policy):
# process alarm url here
alarm_actions = dict()
policy_actions = list(policy.entity_tpl['reservation'].keys())
policy_actions.remove('properties')
for action in policy_actions:
alarm_url = vnf['attributes'].get(action)
if alarm_url:
LOG.debug('Alarm url in heat %s', alarm_url)
alarm_actions[action] = dict()
alarm_actions[action]['alarm_actions'] = [alarm_url]
return alarm_actions
def _process_alarm_actions_for_maintenance(vnf):
# process alarm url here
alarm_actions = dict()
maintenance_props = vnf['attributes'].get('maintenance', '{}')
maintenance_props = jsonutils.loads(maintenance_props)
maintenance_url = vnf['attributes'].get('maintenance_url', '')
for vdu, access_key in maintenance_props.items():
action = '%s_maintenance' % vdu
alarm_url = '%s/%s' % (maintenance_url.rstrip('/'), access_key)
if alarm_url:
LOG.debug('Alarm url in heat %s', alarm_url)
alarm_actions[action] = dict()
alarm_actions[action]['alarm_actions'] = [alarm_url]
return alarm_actions
def get_volumes(template):
volume_dict = dict()
node_tpl = template['topology_template']['node_templates']
for node_name in list(node_tpl.keys()):
node_value = node_tpl[node_name]
if node_value['type'] != BLOCKSTORAGE:
continue
volume_dict[node_name] = dict()
block_properties = node_value.get('properties', {})
if 'volume_id' in block_properties:
volume_dict[node_name]['volume_id'] = block_properties['volume_id']
del node_tpl[node_name]
continue
for prop_name, prop_value in block_properties.items():
if prop_name == 'size':
prop_value = \
re.compile(r'(\d+)\s*(\w+)').match(prop_value).groups()[0]
volume_dict[node_name][prop_name] = prop_value
del node_tpl[node_name]
return volume_dict
@log.log
def get_vol_attachments(template, volume_dict):
vol_attach_dict = dict()
node_tpl = template['topology_template']['node_templates']
valid_properties = {
'location': 'mountpoint'
}
for node_name in list(node_tpl.keys()):
node_value = node_tpl[node_name]
if node_value['type'] != BLOCKSTORAGE_ATTACHMENT:
continue
vol_attach_dict[node_name] = dict()
vol_attach_properties = node_value.get('properties', {})
# parse properties
for prop_name, prop_value in vol_attach_properties.items():
if prop_name in valid_properties:
vol_attach_dict[node_name][valid_properties[prop_name]] = \
prop_value
# parse requirements to get mapping of cinder volume <-> Nova instance
for req in node_value.get('requirements', {}):
if 'virtualBinding' in req:
vol_attach_dict[node_name]['instance_uuid'] = \
{'get_resource': req['virtualBinding']['node']}
elif 'virtualAttachment' in req:
node = req['virtualAttachment']['node']
if 'volume_id' in volume_dict.get(node, {}):
value = {'get_param': volume_dict[node]['volume_id']}
else:
value = {'get_resource': node}
vol_attach_dict[node_name]['volume_id'] = value
del node_tpl[node_name]
return vol_attach_dict
@log.log
def get_block_storage_details(template):
block_storage_details = dict()
volume_dict = get_volumes(template)
block_storage_details['volumes'] = volume_dict
block_storage_details['volume_attachments'] = \
get_vol_attachments(template, volume_dict)
return block_storage_details
@log.log
def get_mgmt_ports(tosca):
mgmt_ports = {}
for nt in tosca.nodetemplates:
if nt.type_definition.is_derived_from(TACKERCP):
mgmt = nt.get_property_value('management') or None
if mgmt:
vdu = None
for rel, node in nt.relationships.items():
if rel.is_derived_from(TOSCA_BINDS_TO):
vdu = node.name
break
if vdu is not None:
name = 'mgmt_ip-%s' % vdu
mgmt_ports[name] = nt.name
LOG.debug('mgmt_ports: %s', mgmt_ports)
return mgmt_ports
@log.log
def add_resources_tpl(heat_dict, hot_res_tpl):
for res, res_dict in (hot_res_tpl).items():
for vdu, vdu_dict in (res_dict).items():
res_name = vdu + "_" + res
heat_dict["resources"][res_name] = {
"type": HEAT_RESOURCE_MAP[res],
"properties": {}
}
if res == "maintenance":
continue
for prop, val in (vdu_dict).items():
# change from 'get_input' to 'get_param' to meet HOT template
if isinstance(val, dict):
if 'get_input' in val:
val['get_param'] = val.pop('get_input')
heat_dict["resources"][res_name]["properties"][prop] = val
if heat_dict["resources"].get(vdu):
heat_dict["resources"][vdu]["properties"][res] = {
"get_resource": res_name
}
@log.log
def convert_unsupported_res_prop(heat_dict, unsupported_res_prop):
res_dict = heat_dict['resources']
for res, attr in (res_dict).items():
res_type = attr['type']
if res_type in unsupported_res_prop:
prop_dict = attr['properties']
unsupported_prop_dict = unsupported_res_prop[res_type]
unsupported_prop = set(prop_dict.keys()) & set(
unsupported_prop_dict.keys())
for prop in unsupported_prop:
# some properties are just punted to 'value_specs'
# property if they are incompatible
new_prop = unsupported_prop_dict[prop]
if new_prop == 'value_specs':
prop_dict.setdefault(new_prop, {})[
prop] = prop_dict.pop(prop)
else:
prop_dict[new_prop] = prop_dict.pop(prop)
@log.log
def represent_odict(dump, tag, mapping, flow_style=None):
value = []
node = yaml.MappingNode(tag, value, flow_style=flow_style)
if dump.alias_key is not None:
dump.represented_objects[dump.alias_key] = node
best_style = True
if hasattr(mapping, 'items'):
mapping = mapping.items()
for item_key, item_value in mapping:
node_key = dump.represent_data(item_key)
node_value = dump.represent_data(item_value)
if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style):
best_style = False
if not (isinstance(node_value, yaml.ScalarNode)
and not node_value.style):
best_style = False
value.append((node_key, node_value))
if flow_style is None:
if dump.default_flow_style is not None:
node.flow_style = dump.default_flow_style
else:
node.flow_style = best_style
return node
@log.log
def post_process_heat_template(heat_tpl, mgmt_ports, metadata,
alarm_resources, res_tpl, vol_res={},
unsupported_res_prop=None, unique_id=None,
inst_req_info=None, grant_info=None,
tosca=None):
#
# TODO(bobh) - remove when heat-translator can support literal strings.
#
def fix_user_data(user_data_string):
user_data_string = re.sub('user_data: #', 'user_data: |\n #',
user_data_string, re.MULTILINE)
return re.sub('\n\n', '\n', user_data_string, re.MULTILINE)
heat_tpl = fix_user_data(heat_tpl)
#
# End temporary workaround for heat-translator
#
heat_dict = yamlparser.simple_ordered_parse(heat_tpl)
for outputname, portname in mgmt_ports.items():
ipval = {'get_attr': [portname, 'fixed_ips', 0, 'ip_address']}
output = {outputname: {'value': ipval}}
if 'outputs' in heat_dict:
heat_dict['outputs'].update(output)
else:
heat_dict['outputs'] = output
LOG.debug('Added output for %s', outputname)
if metadata.get('vdus'):
for vdu_name, metadata_dict in metadata['vdus'].items():
metadata_dict['metering.server_group'] = \
(metadata_dict['metering.server_group'] + '-' + unique_id)[:15]
if heat_dict['resources'].get(vdu_name):
heat_dict['resources'][vdu_name]['properties']['metadata'] =\
metadata_dict
add_resources_tpl(heat_dict, res_tpl)
query_metadata = alarm_resources.get('query_metadata')
alarm_actions = alarm_resources.get('alarm_actions')
event_types = alarm_resources.get('event_types')
if query_metadata:
for trigger_name, matching_metadata_dict in query_metadata.items():
if heat_dict['resources'].get(trigger_name):
query_mtdata = dict()
query_mtdata['query'] = \
query_metadata[trigger_name]
heat_dict['resources'][trigger_name][
'properties'].update(query_mtdata)
if alarm_actions:
for trigger_name, alarm_actions_dict in alarm_actions.items():
if heat_dict['resources'].get(trigger_name):
heat_dict['resources'][trigger_name]['properties'].update(
alarm_actions_dict)
if event_types:
for trigger_name, event_type in event_types.items():
if heat_dict['resources'].get(trigger_name):
heat_dict['resources'][trigger_name]['properties'].update(
event_type)
for res in heat_dict["resources"].values():
if not res['type'] == HEAT_SOFTWARE_CONFIG:
continue
config = res["properties"]["config"]
if 'get_file' in config:
res["properties"]["config"] = open(config["get_file"]).read()
if vol_res.get('volumes'):
add_volume_resources(heat_dict, vol_res)
if unsupported_res_prop:
convert_unsupported_res_prop(heat_dict, unsupported_res_prop)
if grant_info:
convert_grant_info(heat_dict, grant_info)
if inst_req_info:
convert_inst_req_info(heat_dict, inst_req_info, tosca)
if heat_dict.get('parameters') and heat_dict.get(
'parameters', {}).get('vnfm_info'):
heat_dict.get('parameters').get('vnfm_info').update(
{'type': 'comma_delimited_list'})
yaml.SafeDumper.add_representer(OrderedDict,
lambda dumper, value: represent_odict(dumper,
'tag:yaml.org,2002:map', value))
return yaml.safe_dump(heat_dict)
@log.log
def post_process_heat_template_for_scaling(
heat_tpl, mgmt_ports, metadata,
alarm_resources, res_tpl, vol_res={},
unsupported_res_prop=None, unique_id=None,
inst_req_info=None, grant_info=None,
tosca=None):
heat_dict = yamlparser.simple_ordered_parse(heat_tpl)
if inst_req_info:
check_inst_req_info_for_scaling(heat_dict, inst_req_info)
convert_inst_req_info(heat_dict, inst_req_info, tosca)
if grant_info:
convert_grant_info(heat_dict, grant_info)
yaml.SafeDumper.add_representer(OrderedDict,
lambda dumper, value: represent_odict(dumper,
'tag:yaml.org,2002:map', value))
return yaml.safe_dump(heat_dict)
@log.log
def check_inst_req_info_for_scaling(heat_dict, inst_req_info):
# Check whether fixed ip_address or mac_address is set in CP,
# because CP with fixed IP address cannot be scaled.
if not inst_req_info.ext_virtual_links:
return
def _get_mac_ip(exp_cp):
mac = None
ip = None
for cp_conf in ext_cp.cp_config:
if cp_conf.cp_protocol_data is None:
continue
for cp_protocol in cp_conf.cp_protocol_data:
if cp_protocol.ip_over_ethernet is None:
continue
mac = cp_protocol.ip_over_ethernet.mac_address
for ip_address in \
cp_protocol.ip_over_ethernet.ip_addresses:
if ip_address.fixed_addresses:
ip = ip_address.fixed_addresses
return mac, ip
for ext_vl in inst_req_info.ext_virtual_links:
ext_cps = ext_vl.ext_cps
for ext_cp in ext_cps:
if not ext_cp.cp_config:
continue
mac, ip = _get_mac_ip(ext_cp)
cp_resource = heat_dict['resources'].get(ext_cp.cpd_id)
if cp_resource is not None:
if mac or ip:
raise vnfm.InvalidInstReqInfoForScaling()
@log.log
def convert_inst_req_info(heat_dict, inst_req_info, tosca):
# Case which extVls is defined.
ext_vl_infos = inst_req_info.ext_virtual_links
if ext_vl_infos is not None:
for ext_vl in ext_vl_infos:
_convert_ext_vls(heat_dict, ext_vl)
# Case which extMngVls is defined.
ext_mng_vl_infos = inst_req_info.ext_managed_virtual_links
if ext_mng_vl_infos is not None:
for ext_mng_vl in ext_mng_vl_infos:
_convert_ext_mng_vl(
heat_dict, ext_mng_vl.vnf_virtual_link_desc_id,
ext_mng_vl.resource_id)
# Check whether instLevelId is defined.
# Extract the initial number of scalable VDUs from the instantiation
# policy.
inst_level_id = inst_req_info.instantiation_level_id
# The format of dict required to calculate desired_capacity are
# shown below.
# { aspectId: { deltaId: deltaNum }}
aspect_delta_dict = {}
# { aspectId: [ vduId ]}
aspect_vdu_dict = {}
# { instLevelId: { aspectId: levelNum }}
inst_level_dict = {}
# { aspectId: deltaId }
aspect_id_dict = {}
# { vduId: initialDelta }
vdu_delta_dict = {}
# { aspectId: maxScaleLevel }
aspect_max_level_dict = {}
tosca_policies = tosca.topology_template.policies
default_inst_level_id = _extract_policy_info(
tosca_policies, inst_level_dict,
aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict,
aspect_max_level_dict)
if inst_level_id is not None:
# Case which instLevelId is defined.
_convert_desired_capacity(inst_level_id, inst_level_dict,
aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict,
heat_dict)
elif inst_level_id is None and default_inst_level_id is not None:
# Case which instLevelId is not defined.
# In this case, use the default instLevelId.
_convert_desired_capacity(default_inst_level_id, inst_level_dict,
aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict,
heat_dict)
else:
LOG.info('Because instLevelId is not defined and '
'there is no default level in TOSCA, '
'the conversion of desired_capacity is skipped.')
@log.log
def convert_grant_info(heat_dict, grant_info):
# Case which grant_info is defined.
if not grant_info:
return
for vdu_name, vnf_resources in grant_info.items():
_convert_grant_info_vdu(heat_dict, vdu_name, vnf_resources)
def _convert_ext_vls(heat_dict, ext_vl):
ext_cps = ext_vl.ext_cps
vl_id = ext_vl.resource_id
defined_ext_link_ports = [ext_link_port.resource_handle.resource_id
for ext_link_port in ext_vl.ext_link_ports]
def _replace_external_network_port(link_port_id, cpd_id):
for ext_link_port in ext_vl.ext_link_ports:
if ext_link_port.id == link_port_id:
if heat_dict['resources'].get(cpd_id) is not None:
_convert_ext_link_port(heat_dict, cpd_id,
ext_link_port.resource_handle.resource_id)
for ext_cp in ext_cps:
cp_resource = heat_dict['resources'].get(ext_cp.cpd_id)
if cp_resource is None:
return
# Update CP network properties to NEUTRON NETWORK-UUID
# defined in extVls.
cp_resource['properties']['network'] = vl_id
# Check whether extLinkPorts is defined.
for cp_config in ext_cp.cp_config:
for cp_protocol in cp_config.cp_protocol_data:
# Update the following CP properties to the values defined
# in extVls.
# - subnet
# - ip_address
# - mac_address
ip_over_ethernet = cp_protocol.ip_over_ethernet
if ip_over_ethernet:
if ip_over_ethernet.mac_address or\
ip_over_ethernet.ip_addresses:
if ip_over_ethernet.mac_address:
cp_resource['properties']['mac_address'] =\
ip_over_ethernet.mac_address
if ip_over_ethernet.ip_addresses:
_convert_fixed_ips_list(
'ip_address',
ip_over_ethernet.ip_addresses,
cp_resource)
elif defined_ext_link_ports:
_replace_external_network_port(cp_config.link_port_id,
ext_cp.cpd_id)
def _convert_fixed_ips_list(cp_key, cp_val, cp_resource):
for val in cp_val:
new_dict = {}
if val.fixed_addresses:
new_dict['ip_address'] = ''.join(val.fixed_addresses)
if val.subnet_id:
new_dict['subnet'] = val.subnet_id
fixed_ips_list = cp_resource['properties'].get('fixed_ips')
# Add if it doesn't exist yet.
if fixed_ips_list is None:
cp_resource['properties']['fixed_ips'] = [new_dict]
# Update if it already exists.
else:
for index, fixed_ips in enumerate(fixed_ips_list):
if fixed_ips.get(cp_key) is not None:
fixed_ips_list[index] = new_dict
else:
fixed_ips_list.append(new_dict)
sorted_list = sorted(fixed_ips_list)
cp_resource['properties']['fixed_ips'] = sorted_list
def _convert_ext_link_port(heat_dict, cp_name, ext_link_port):
# Delete CP resource and update VDU's properties
# related to CP defined in extLinkPorts.
del heat_dict['resources'][cp_name]
for rsrc_info in heat_dict['resources'].values():
if rsrc_info['type'] == 'OS::Nova::Server':
vdu_networks = rsrc_info['properties']['networks']
for index, vdu_network in enumerate(vdu_networks):
if isinstance(vdu_network['port'], dict) and\
vdu_network['port'].get('get_resource') == cp_name:
new_dict = {'port': ext_link_port}
rsrc_info['properties']['networks'][index] = new_dict
def _convert_ext_mng_vl(heat_dict, vl_name, vl_id):
# Delete resources related to VL defined in extMngVLs.
if heat_dict['resources'].get(vl_name) is not None:
del heat_dict['resources'][vl_name]
del heat_dict['resources'][vl_name + '_subnet']
del heat_dict['resources'][vl_name + '_qospolicy']
del heat_dict['resources'][vl_name + '_bandwidth']
for rsrc_info in heat_dict['resources'].values():
# Update CP's properties related to VL defined in extMngVls.
if rsrc_info['type'] == 'OS::Neutron::Port':
cp_network = rsrc_info['properties']['network']
if isinstance(cp_network, dict) and\
cp_network.get('get_resource') == vl_name:
rsrc_info['properties']['network'] = vl_id
# Update AutoScalingGroup's properties related to VL defined
# in extMngVls.
elif rsrc_info['type'] == 'OS::Heat::AutoScalingGroup':
asg_rsrc_props = \
rsrc_info['properties']['resource'].get('properties')
for vl_key, vl_val in asg_rsrc_props.items():
if vl_val.get('get_resource') == vl_name:
asg_rsrc_props[vl_key] = vl_id
def _extract_policy_info(tosca_policies, inst_level_dict,
aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict,
aspect_max_level_dict):
default_inst_level_id = None
if tosca_policies:
for p in tosca_policies:
if p.type == ETSI_SCALING_ASPECT_DELTA:
vdu_list = p.targets
aspect_id = p.properties['aspect']
deltas = p.properties['deltas']
delta_id_dict = {}
for delta_id, delta_val in deltas.items():
delta_num = delta_val['number_of_instances']
delta_id_dict[delta_id] = delta_num
aspect_delta_dict[aspect_id] = delta_id_dict
aspect_vdu_dict[aspect_id] = vdu_list
elif p.type == ETSI_INST_LEVEL:
inst_levels = p.properties['levels']
for level_id, inst_val in inst_levels.items():
scale_info = inst_val['scale_info']
aspect_level_dict = {}
for aspect_id, scale_level in scale_info.items():
aspect_level_dict[aspect_id] = \
scale_level['scale_level']
inst_level_dict[level_id] = aspect_level_dict
default_inst_level_id = p.properties.get('default_level')
# On TOSCA definitions, step_deltas is list and
# multiple description is possible,
# but only single description is supported.
# (first win)
# Like heat-translator.
elif p.type == ETSI_SCALING_ASPECT:
aspects = p.properties['aspects']
for aspect_id, aspect_val in aspects.items():
delta_names = aspect_val['step_deltas']
delta_name = delta_names[0]
aspect_id_dict[aspect_id] = delta_name
aspect_max_level_dict[aspect_id] = \
aspect_val['max_scale_level']
elif p.type == ETSI_INITIAL_DELTA:
vdus = p.targets
initial_delta = \
p.properties['initial_delta']['number_of_instances']
for vdu in vdus:
vdu_delta_dict[vdu] = initial_delta
return default_inst_level_id
def _convert_desired_capacity(inst_level_id, inst_level_dict,
aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict,
heat_dict):
al_dict = inst_level_dict.get(inst_level_id)
if al_dict is not None:
# Get level_num.
for aspect_id, level_num in al_dict.items():
delta_id = aspect_id_dict.get(aspect_id)
# Get delta_num.
if delta_id is not None:
delta_num = \
aspect_delta_dict.get(aspect_id).get(delta_id)
# Get initial_delta.
vdus = aspect_vdu_dict.get(aspect_id)
initial_delta = None
for vdu in vdus:
initial_delta = vdu_delta_dict.get(vdu)
if initial_delta is not None:
# Calculate desired_capacity.
desired_capacity = initial_delta + delta_num * level_num
# Convert desired_capacity on HOT.
for rsrc_key, rsrc_info in heat_dict['resources'].items():
if rsrc_info['type'] == 'OS::Heat::AutoScalingGroup' and \
rsrc_key == aspect_id:
rsrc_info['properties']['desired_capacity'] = \
desired_capacity
else:
LOG.warning('Because target instLevelId is not defined in TOSCA, '
'the conversion of desired_capacity is skipped.')
pass
def _convert_grant_info_vdu(heat_dict, vdu_name, vnf_resources):
for vnf_resource in vnf_resources:
if vnf_resource.resource_type == "image":
# Update VDU's properties related to
# image defined in grant_info.
vdu_info = heat_dict.get('resources').get(vdu_name)
if vdu_info is not None:
vdu_props = vdu_info.get('properties')
if vdu_props.get('image') is None:
vdu_props.update({'image':
vnf_resource.resource_identifier})
@log.log
def add_volume_resources(heat_dict, vol_res):
# Add cinder volumes
for res_name, cinder_vol in vol_res['volumes'].items():
if 'volume_id' in cinder_vol:
continue
heat_dict['resources'][res_name] = {
'type': 'OS::Cinder::Volume',
'properties': {}
}
for prop_name, prop_val in cinder_vol.items():
heat_dict['resources'][res_name]['properties'][prop_name] = \
prop_val
# Add cinder volume attachments
for res_name, cinder_vol in vol_res['volume_attachments'].items():
heat_dict['resources'][res_name] = {
'type': 'OS::Cinder::VolumeAttachment',
'properties': {}
}
for prop_name, prop_val in cinder_vol.items():
heat_dict['resources'][res_name]['properties'][prop_name] = \
prop_val
@log.log
def post_process_template(template):
def _add_scheduler_hints_property(nt):
hints = nt.get_property_value('scheduler_hints')
if hints is None:
hints = OrderedDict()
hints_schema = {'type': 'map', 'required': False,
'entry_schema': {'type': 'string'}}
hints_prop = properties.Property('scheduler_hints',
hints,
hints_schema)
nt.get_properties_objects().append(hints_prop)
return hints
for nt in template.nodetemplates:
if (nt.type_definition.is_derived_from(MONITORING) or
nt.type_definition.is_derived_from(FAILURE) or
nt.type_definition.is_derived_from(PLACEMENT)):
template.nodetemplates.remove(nt)
continue
if nt.type in delpropmap:
for prop in delpropmap[nt.type]:
for p in nt.get_properties_objects():
if prop == p.name:
nt.get_properties_objects().remove(p)
# change the property value first before the property key
if nt.type in convert_prop_values:
for prop in convert_prop_values[nt.type]:
for p in nt.get_properties_objects():
if (prop == p.name and
p.value in
convert_prop_values[nt.type][prop]):
v = convert_prop_values[nt.type][prop][p.value]
p.value = v
if nt.type in convert_prop:
for prop in convert_prop[nt.type]:
for p in nt.get_properties_objects():
if prop == p.name:
schema_dict = {'type': p.type}
v = nt.get_property_value(p.name)
newprop = properties.Property(
convert_prop[nt.type][prop], v, schema_dict)
nt.get_properties_objects().append(newprop)
nt.get_properties_objects().remove(p)
if nt.type_definition.is_derived_from(TACKERVDU):
reservation_metadata = nt.get_property_value(
'reservation_metadata')
if reservation_metadata is not None:
hints = _add_scheduler_hints_property(nt)
input_resource_type = reservation_metadata.get(
'resource_type')
input_id = reservation_metadata.get('id')
# Checking if 'resource_type' and 'id' is passed through a
# input parameter file or not. If it's then get the value
# from input parameter file.
if (isinstance(input_resource_type, OrderedDict) and
input_resource_type.get('get_input')):
input_resource_type = template.parsed_params.get(
input_resource_type.get('get_input'))
# TODO(niraj-singh): Remove this validation once bug
# 1815755 is fixed.
if input_resource_type not in (
'physical_host', 'virtual_instance'):
raise exceptions.Invalid(
'resoure_type must be physical_host'
' or virtual_instance')
if (isinstance(input_id, OrderedDict) and
input_id.get('get_input')):
input_id = template.parsed_params.get(
input_id.get('get_input'))
if input_resource_type == 'physical_host':
hints['reservation'] = input_id
elif input_resource_type == 'virtual_instance':
hints['group'] = input_id
nt.get_properties_objects().remove(nt.get_properties().get(
'reservation_metadata'))
@log.log
def get_mgmt_driver(template):
mgmt_driver = None
for nt in template.nodetemplates:
if nt.type_definition.is_derived_from(TACKERVDU):
if (mgmt_driver and nt.get_property_value('mgmt_driver') !=
mgmt_driver):
raise vnfm.MultipleMGMTDriversSpecified()
else:
mgmt_driver = nt.get_property_value('mgmt_driver')
return mgmt_driver
def findvdus(template):
vdus = []
for nt in template.nodetemplates:
if nt.type_definition.is_derived_from(TACKERVDU):
vdus.append(nt)
return vdus
def find_maintenance_vdus(template):
maintenance_vdu_names = list()
vdus = findvdus(template)
for nt in vdus:
if nt.get_properties().get('maintenance'):
maintenance_vdu_names.append(nt.name)
return maintenance_vdu_names
def get_flavor_dict(template, flavor_extra_input=None):
flavor_dict = {}
vdus = findvdus(template)
for nt in vdus:
flavor_tmp = nt.get_properties().get('flavor')
if flavor_tmp:
continue
if nt.get_capabilities().get("nfv_compute"):
flavor_dict[nt.name] = {}
properties = nt.get_capabilities()["nfv_compute"].get_properties()
for prop, (hot_prop, default, unit) in \
(FLAVOR_PROPS).items():
hot_prop_val = (properties[prop].value
if properties.get(prop, None) else None)
if unit and hot_prop_val:
hot_prop_val = \
utils.change_memory_unit(hot_prop_val, unit)
flavor_dict[nt.name][hot_prop] = \
hot_prop_val if hot_prop_val else default
if any(p in properties for p in FLAVOR_EXTRA_SPECS_LIST):
flavor_dict[nt.name]['extra_specs'] = {}
es_dict = flavor_dict[nt.name]['extra_specs']
populate_flavor_extra_specs(es_dict, properties,
flavor_extra_input)
return flavor_dict
def populate_flavor_extra_specs(es_dict, properties, flavor_extra_input):
if 'mem_page_size' in properties:
mval = properties['mem_page_size'].value
if str(mval).isdigit():
mval = mval * 1024
elif mval not in ('small', 'large', 'any'):
raise vnfm.HugePageSizeInvalidInput(
error_msg_details=(mval + ":Invalid Input"))
es_dict['hw:mem_page_size'] = mval
if 'numa_nodes' in properties and 'numa_node_count' in properties:
LOG.warning('Both numa_nodes and numa_node_count have been '
'specified; numa_node definitions will be ignored and '
'numa_node_count will be applied')
if 'numa_node_count' in properties:
es_dict['hw:numa_nodes'] = \
properties['numa_node_count'].value
if 'numa_nodes' in properties and 'numa_node_count' not in properties:
nodes_dict = dict(properties['numa_nodes'].value)
dval = list(nodes_dict.values())
ncount = 0
for ndict in dval:
invalid_input = set(ndict.keys()) - {'id', 'vcpus', 'mem_size'}
if invalid_input:
raise vnfm.NumaNodesInvalidKeys(
error_msg_details=(', '.join(invalid_input)),
valid_keys="id, vcpus and mem_size")
if 'id' in ndict and 'vcpus' in ndict:
vk = "hw:numa_cpus." + str(ndict['id'])
vval = ",".join([str(x) for x in ndict['vcpus']])
es_dict[vk] = vval
if 'id' in ndict and 'mem_size' in ndict:
mk = "hw:numa_mem." + str(ndict['id'])
es_dict[mk] = ndict['mem_size']
ncount += 1
es_dict['hw:numa_nodes'] = ncount
if 'cpu_allocation' in properties:
cpu_dict = dict(properties['cpu_allocation'].value)
invalid_input = set(cpu_dict.keys()) - CPU_PROP_KEY_SET
if invalid_input:
raise vnfm.CpuAllocationInvalidKeys(
error_msg_details=(', '.join(invalid_input)),
valid_keys=(', '.join(CPU_PROP_KEY_SET)))
for(k, v) in CPU_PROP_MAP:
if v not in cpu_dict:
continue
if CPU_PROP_VAL_MAP.get(v, None):
if cpu_dict[v] not in CPU_PROP_VAL_MAP[v]:
raise vnfm.CpuAllocationInvalidValues(
error_msg_details=cpu_dict[v],
valid_values=CPU_PROP_VAL_MAP[v])
es_dict[k] = cpu_dict[v]
if flavor_extra_input:
es_dict.update(flavor_extra_input)
def get_image_dict(template):
image_dict = {}
vdus = findvdus(template)
for vdu in vdus:
if not vdu.entity_tpl.get("artifacts"):
continue
artifacts = vdu.entity_tpl["artifacts"]
for name, artifact in (artifacts).items():
if ('type' in artifact and
artifact["type"] == IMAGE):
if 'file' not in artifact:
raise vnfm.FilePathMissing()
image_dict[vdu.name] = {
"location": artifact["file"],
"container_format": "bare",
"disk_format": "raw",
"name": name
}
return image_dict
def get_resources_dict(template, flavor_extra_input=None):
res_dict = dict()
for res, method in (OS_RESOURCES).items():
res_method = getattr(sys.modules[__name__], method)
if res == 'flavor':
res_dict[res] = res_method(template, flavor_extra_input)
else:
res_dict[res] = res_method(template)
return res_dict
def add_maintenance_resources(template, res_tpl):
res_dict = {}
maintenance_vdus = find_maintenance_vdus(template)
maintenance_vdus.append('ALL')
if maintenance_vdus:
for vdu_name in maintenance_vdus:
res_dict[vdu_name] = {}
res_tpl['maintenance'] = res_dict
@log.log
def get_policy_dict(template, policy_type):
policy_dict = dict()
for policy in template.policies:
if (policy.type_definition.is_derived_from(policy_type)):
policy_attrs = dict()
policy_attrs['targets'] = policy.targets
policy_dict[policy.name] = policy_attrs
return policy_dict
@log.log
def get_scaling_policy(template):
scaling_policy_names = list()
for policy in template.policies:
if (policy.type_definition.is_derived_from(SCALING)):
scaling_policy_names.append(policy.name)
return scaling_policy_names
@log.log
def get_scaling_group_dict(ht_template, scaling_policy_names):
scaling_group_dict = dict()
scaling_group_names = list()
heat_dict = yamlparser.simple_ordered_parse(ht_template)
for resource_name, resource_dict in heat_dict['resources'].items():
if resource_dict['type'] == SCALE_GROUP_RESOURCE:
scaling_group_names.append(resource_name)
if scaling_group_names:
scaling_group_dict[scaling_policy_names[0]] = scaling_group_names[0]
return scaling_group_dict
def get_nested_resources_name(hot):
nested_resource_names = []
hot_yaml = yaml.safe_load(hot)
for r_key, r_val in hot_yaml.get('resources').items():
if r_val.get('type') == 'OS::Heat::AutoScalingGroup':
nested_resource_name = r_val.get('properties', {}).get(
'resource', {}).get('type', None)
nested_resource_names.append(nested_resource_name)
return nested_resource_names
def get_sub_heat_tmpl_name(tmpl_name):
return uuidutils.generate_uuid() + tmpl_name
def update_nested_scaling_resources(nested_resources, mgmt_ports, metadata,
res_tpl, unsupported_res_prop=None,
grant_info=None, inst_req_info=None):
nested_tpl = dict()
yaml.SafeDumper.add_representer(
OrderedDict, lambda dumper, value: represent_odict(
dumper, 'tag:yaml.org,2002:map', value))
for nested_resource_name, nested_resources_yaml in \
nested_resources.items():
nested_resources_dict =\
yamlparser.simple_ordered_parse(nested_resources_yaml)
if metadata.get('vdus'):
for vdu_name, metadata_dict in metadata['vdus'].items():
if nested_resources_dict['resources'].get(vdu_name):
vdu_dict = nested_resources_dict['resources'][vdu_name]
vdu_dict['properties']['metadata'] = metadata_dict
convert_grant_info(nested_resources_dict, grant_info)
# Replace external virtual links if specified in the inst_req_info
if inst_req_info is not None:
for ext_vl in inst_req_info.ext_virtual_links:
_convert_ext_vls(nested_resources_dict, ext_vl)
add_resources_tpl(nested_resources_dict, res_tpl)
for res in nested_resources_dict["resources"].values():
if not res['type'] == HEAT_SOFTWARE_CONFIG:
continue
config = res["properties"]["config"]
if 'get_file' in config:
res["properties"]["config"] = open(config["get_file"]).read()
if unsupported_res_prop:
convert_unsupported_res_prop(nested_resources_dict,
unsupported_res_prop)
if mgmt_ports:
for outputname, portname in mgmt_ports.items():
ipval = {'get_attr': [portname, 'fixed_ips', 0, 'ip_address']}
output = {outputname: {'value': ipval}}
if 'outputs' in nested_resources_dict:
nested_resources_dict['outputs'].update(output)
else:
nested_resources_dict['outputs'] = output
LOG.debug(_('Added output for %s'), outputname)
nested_tpl[nested_resource_name] =\
yaml.safe_dump(nested_resources_dict)
return nested_tpl
def get_policies_from_dict(vnfd_dict, policy_type=None):
final_policies = dict()
policies = vnfd_dict.get('topology_template', {}).get('policies', {})
for policy in policies:
for policy_name, policy_dict in policy.items():
if policy_type:
if policy_dict.get('type') == policy_type:
final_policies.update({policy_name: policy_dict})
else:
final_policies.update({policy_name: policy_dict})
return final_policies
@log.log
def get_scale_group(vnf_dict, vnfd_dict, inst_req_info):
scaling_group_dict = dict()
data_dict = dict()
if vnf_dict['attributes'].get('scaling_group_names'):
for policy_name, policy_dict in \
get_policies_from_dict(vnfd_dict,
ETSI_SCALING_ASPECT_DELTA).items():
aspect = policy_dict['properties']['aspect']
vdu = policy_dict['targets']
deltas = policy_dict['properties']['deltas']
for delta_key, delta_dict in deltas.items():
num = delta_dict['number_of_instances']
data_dict.update({
aspect: {
'vdu': vdu,
'num': num
}
})
for aspect_name, aspect_dict in data_dict.items():
aspect_policy = \
get_policies_from_dict(vnfd_dict, ETSI_SCALING_ASPECT)
for policy_name, policy_dict in aspect_policy.items():
aspect = policy_dict['properties']['aspects'][aspect_name]
max_level = aspect.get('max_scale_level')
data_dict[aspect_name].update({'maxLevel': max_level})
delta_policy = \
get_policies_from_dict(vnfd_dict, ETSI_INITIAL_DELTA)
for policy_name, policy_dict in delta_policy.items():
for target in policy_dict['targets']:
if target in aspect_dict['vdu']:
delta = policy_dict['properties']['initial_delta']
number_of_instances = delta['number_of_instances']
data_dict[aspect_name].update(
{'initialNum': number_of_instances})
level_policy = \
get_policies_from_dict(vnfd_dict, ETSI_INST_LEVEL)
for policy_name, policy_dict in level_policy.items():
instantiation_level_id = ""
if hasattr(inst_req_info, 'instantiation_level_id'):
instantiation_level_id = \
inst_req_info.instantiation_level_id
if not instantiation_level_id:
instantiation_level_id = \
policy_dict['properties']['default_level']
levels = policy_dict['properties']['levels']
scale_info = levels[instantiation_level_id]['scale_info']
initial_level = scale_info[aspect_name]['scale_level']
increase = aspect_dict['num'] * initial_level
default = aspect_dict['initialNum'] + increase
data_dict[aspect_name].update({'initialLevel': initial_level,
'default': default})
scaling_group_dict.update({'scaleGroupDict': data_dict})
return scaling_group_dict