498 lines
20 KiB
Python
498 lines
20 KiB
Python
#
|
|
# 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 copy
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_serialization import jsonutils
|
|
from tacker.common.utils import MemoryUnit
|
|
from tacker.tosca import utils as tosca_utils
|
|
|
|
"""Define util functions that can be used in UserData.
|
|
|
|
As for how to use the function (`create _ * _ dict`), dict can be obtained
|
|
as a return value by setting the required arguments and calling.
|
|
For detailed usage, check the docstring of each function.
|
|
|
|
NOTE: The functions defined here are limited to common and basic ones.
|
|
Please note that not all conversion logics are offered in Tacker,
|
|
different from Heat-Translator.
|
|
"""
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
HOT_NOVA_SERVER = 'OS::Nova::Server'
|
|
HOT_NOVA_FLAVOR = 'OS::Nova::Flavor'
|
|
HOT_NEUTRON_PORT = 'OS::Neutron::Port'
|
|
AUTO_SCALING_GROUP = 'OS::Heat::AutoScalingGroup'
|
|
HOT_CINDER_VOLUME = 'OS::Cinder::Volume'
|
|
SUPPORTED_HOT_TYPE = [HOT_NOVA_SERVER, HOT_NOVA_FLAVOR,
|
|
HOT_NEUTRON_PORT, HOT_CINDER_VOLUME]
|
|
PORT_SERVER_TYPE = [HOT_NOVA_SERVER, HOT_NEUTRON_PORT, HOT_CINDER_VOLUME]
|
|
|
|
|
|
def create_initial_param_dict(base_hot_dict):
|
|
"""Create initial dict containing information about get_param resources.
|
|
|
|
:param base_hot_dict: dict(Base HOT dict format)
|
|
:return: dict('nfv', Initial HOT resource dict)
|
|
|
|
NOTE: 'nfv' is a fixed value for 1st element.
|
|
'VDU' and 'CP' are supported for 2nd element.
|
|
3rd and 4th element are mandatory.
|
|
"""
|
|
initial_param_dict = {
|
|
'nfv': {
|
|
'VDU': {
|
|
},
|
|
'CP': {
|
|
}
|
|
}
|
|
}
|
|
|
|
for hot_dict in base_hot_dict.values():
|
|
param_nfv = hot_dict.get('parameters', {}).get('nfv')
|
|
if not param_nfv:
|
|
continue
|
|
resources = hot_dict.get('resources', {})
|
|
for resource_name, resource_val in resources.items():
|
|
resource_type = resource_val.get('type')
|
|
if resource_type in SUPPORTED_HOT_TYPE:
|
|
resource_props = resource_val.get('properties', {})
|
|
for prop_key, prop_val in resource_props.items():
|
|
if isinstance(prop_val, dict) and 'get_param' in prop_val:
|
|
param_list = prop_val['get_param']
|
|
if len(param_list) == 4:
|
|
resource_info = initial_param_dict.get(
|
|
param_list[0], {}).get(
|
|
param_list[1], {})
|
|
if param_list[2] not in resource_info:
|
|
resource_info[param_list[2]] = {}
|
|
if resource_type == HOT_NOVA_SERVER:
|
|
cinder_boot = resource_props.get('block_device_mapping_v2',
|
|
{})
|
|
if cinder_boot:
|
|
for boot in cinder_boot:
|
|
b_param = boot.get('image')
|
|
if b_param and \
|
|
isinstance(b_param, dict) and \
|
|
'get_param' in b_param:
|
|
param_list = b_param['get_param']
|
|
if len(param_list) == 4:
|
|
resource_info = initial_param_dict.get(
|
|
param_list[0], {}).get(
|
|
param_list[1], {})
|
|
if param_list[2] not in resource_info:
|
|
resource_info[param_list[2]] = {}
|
|
elif resource_type == AUTO_SCALING_GROUP:
|
|
resource_nest = resource_val.get('properties').get('resource')
|
|
resource_props = resource_nest.get('properties', {})
|
|
for prop_key, prop_val in resource_props.items():
|
|
if isinstance(prop_val, dict) and 'get_param' in prop_val:
|
|
param_list = prop_val['get_param']
|
|
if len(param_list) == 4:
|
|
resource_info = initial_param_dict.get(
|
|
param_list[0], {}).get(
|
|
param_list[1], {})
|
|
if param_list[2] not in resource_info:
|
|
resource_info[param_list[2]] = {}
|
|
|
|
LOG.info('initial_param_dict: %s', initial_param_dict)
|
|
return initial_param_dict
|
|
|
|
|
|
def create_initial_param_server_port_dict(base_hot_dict):
|
|
"""Create initial dict containing information about get_param resources.
|
|
|
|
:param base_hot_dict: dict(Base HOT dict format)
|
|
:return: dict('nfv', Initial HOT resource dict)
|
|
|
|
NOTE: 'nfv' is a fixed value for 1st element.
|
|
'VDU' and 'CP' are supported for 2nd element.
|
|
3rd and 4th element are mandatory.
|
|
"""
|
|
initial_param_dict = {
|
|
'nfv': {
|
|
'VDU': {
|
|
},
|
|
'CP': {
|
|
}
|
|
}
|
|
}
|
|
|
|
for hot_dict in base_hot_dict.values():
|
|
LOG.debug("init hot_dict: %s", hot_dict)
|
|
param_nfv = hot_dict.get('parameters', {}).get('nfv')
|
|
if not param_nfv:
|
|
continue
|
|
resources = hot_dict.get('resources', {})
|
|
for resource_name, resource_val in resources.items():
|
|
resource_type = resource_val.get('type')
|
|
if resource_type in PORT_SERVER_TYPE:
|
|
resource_props = resource_val.get('properties', {})
|
|
for prop_key, prop_val in resource_props.items():
|
|
if isinstance(prop_val, dict) and 'get_param' in prop_val:
|
|
param_list = prop_val['get_param']
|
|
if len(param_list) == 4:
|
|
resource_info = initial_param_dict.get(
|
|
param_list[0], {}).get(
|
|
param_list[1], {})
|
|
if param_list[2] not in resource_info:
|
|
resource_info[param_list[2]] = {}
|
|
if resource_type == HOT_NOVA_SERVER:
|
|
cinder_boot = resource_props.get('block_device_mapping_v2',
|
|
{})
|
|
if cinder_boot:
|
|
for boot in cinder_boot:
|
|
b_param = boot.get('image')
|
|
if b_param and \
|
|
isinstance(b_param, dict) and \
|
|
'get_param' in b_param:
|
|
param_list = b_param['get_param']
|
|
if len(param_list) == 4:
|
|
resource_info = initial_param_dict.get(
|
|
param_list[0], {}).get(
|
|
param_list[1], {})
|
|
if param_list[2] not in resource_info:
|
|
resource_info[param_list[2]] = {}
|
|
elif resource_type == AUTO_SCALING_GROUP:
|
|
resource_nest = resource_val.get('properties').get('resource')
|
|
resource_props = resource_nest.get('properties', {})
|
|
for prop_key, prop_val in resource_props.items():
|
|
if isinstance(prop_val, dict) and 'get_param' in prop_val:
|
|
param_list = prop_val['get_param']
|
|
if len(param_list) == 4:
|
|
resource_info = initial_param_dict.get(
|
|
param_list[0], {}).get(
|
|
param_list[1], {})
|
|
if param_list[2] not in resource_info:
|
|
resource_info[param_list[2]] = {}
|
|
|
|
LOG.info('initial_param_dict: %s', initial_param_dict)
|
|
return initial_param_dict
|
|
|
|
|
|
def create_final_param_dict(param_dict, vdu_flavor_dict,
|
|
vdu_image_dict, cpd_vl_dict):
|
|
"""Create final dict containing information about HOT input parameter.
|
|
|
|
:param param_dict: dict('nfv', Initial HOT resource dict)
|
|
:param vdu_flavor_dict: dict(VDU name, VDU flavor dict)
|
|
:param vdu_iamge_dict: dict(VDU name, Glance-image uuid)
|
|
:param cpd_vl_dict: dict(external CPD ID, Neutron-network uuid)
|
|
:return: dict('nfv', Final HOT resource dict)
|
|
"""
|
|
final_param_dict = copy.deepcopy(param_dict)
|
|
|
|
vdus = final_param_dict.get('nfv', {}).get('VDU', {})
|
|
for target_vdu in vdus:
|
|
vdus[target_vdu]['flavor'] = vdu_flavor_dict.get(target_vdu)
|
|
vdus[target_vdu]['image'] = vdu_image_dict.get(target_vdu)
|
|
|
|
cps = final_param_dict.get('nfv', {}).get('CP', {})
|
|
if cpd_vl_dict:
|
|
for target_cp in cps:
|
|
if 'network' in cpd_vl_dict.get(target_cp):
|
|
cps[target_cp]['network'] = cpd_vl_dict.get(
|
|
target_cp).get('network')
|
|
if 'fixed_ips' in cpd_vl_dict.get(target_cp):
|
|
cps[target_cp]['fixed_ips'] = []
|
|
for fixed_ip in cpd_vl_dict.get(target_cp).get("fixed_ips"):
|
|
cps[target_cp]['fixed_ips'].append(fixed_ip)
|
|
|
|
LOG.info('final_param_dict: %s', final_param_dict)
|
|
return final_param_dict
|
|
|
|
|
|
def create_vdu_flavor_dict(vnfd_dict):
|
|
"""Create a dict containing information about VDU's flavor.
|
|
|
|
:param vnfd_dict: dict(VNFD dict format)
|
|
:return: dict(VDU name, VDU flavor dict)
|
|
"""
|
|
vdu_flavor_dict = {}
|
|
node_templates = vnfd_dict.get(
|
|
'topology_template', {}).get(
|
|
'node_templates', {})
|
|
|
|
for vdu_name, val in node_templates.items():
|
|
vdu_flavor_props = val.get(
|
|
'capabilities', {}).get(
|
|
'virtual_compute', {}).get('properties', {})
|
|
if vdu_flavor_props:
|
|
flavor_dict = {}
|
|
for key, val in vdu_flavor_props.items():
|
|
if key == 'virtual_cpu':
|
|
flavor_dict['vcpus'] = val['num_virtual_cpu']
|
|
elif key == 'virtual_memory':
|
|
# Convert to MiB
|
|
flavor_dict['ram'] = MemoryUnit.convert_unit_size_to_num(
|
|
val['virtual_mem_size'], 'MiB')
|
|
elif key == 'virtual_local_storage':
|
|
# Convert to GiB
|
|
flavor_dict['disk'] = MemoryUnit.convert_unit_size_to_num(
|
|
val[0]['size_of_storage'], 'GiB')
|
|
vdu_flavor_dict[vdu_name] = flavor_dict
|
|
|
|
LOG.info('vdu_flavor_dict: %s', vdu_flavor_dict)
|
|
return vdu_flavor_dict
|
|
|
|
|
|
def create_vdu_image_dict(grant_info):
|
|
"""Create a dict containing information about VDU's image.
|
|
|
|
:param grant_info: dict(Grant information format)
|
|
:return: dict(VDU name, Glance-image uuid)
|
|
"""
|
|
vdu_image_dict = {}
|
|
for vdu_name, resources in grant_info.items():
|
|
for vnf_resource in resources:
|
|
vdu_image_dict[vdu_name] = vnf_resource.resource_identifier
|
|
|
|
LOG.info('vdu_image_dict: %s', vdu_image_dict)
|
|
return vdu_image_dict
|
|
|
|
|
|
def create_cpd_vl_dict(base_hot_dict, inst_req_info):
|
|
"""Create a dict containing information about CPD and VL.
|
|
|
|
:param base_hot_dict: dict(Base HOT dict format)
|
|
:param inst_req_info: dict(Instantiation request information format)
|
|
:return: dict(external CPD ID, Neutron-network uuid)
|
|
"""
|
|
cpd_vl_dict = {}
|
|
ext_vls = inst_req_info.ext_virtual_links
|
|
if ext_vls is not None:
|
|
for ext_vl in ext_vls:
|
|
ext_cps = ext_vl.ext_cps
|
|
vl_uuid = ext_vl.resource_id
|
|
for ext_cp in ext_cps:
|
|
for hot_dict in base_hot_dict.values():
|
|
cp_resource = hot_dict['resources'].get(
|
|
ext_cp.cpd_id)
|
|
if cp_resource is None:
|
|
continue
|
|
cpd_vl_dict[ext_cp.cpd_id] = vl_uuid
|
|
break
|
|
|
|
LOG.info('cpd_vl_dict: %s', cpd_vl_dict)
|
|
return cpd_vl_dict
|
|
|
|
|
|
def get_diff_base_hot_param_from_api(base_hot_dict, inst_req_info):
|
|
"""Compare base hot param from API param.
|
|
|
|
:param base_hot_dict: dict(Base HOT dict format)
|
|
:param inst_req_info: dict(Instantiation request information format)
|
|
:return: dict(Parameters)
|
|
"""
|
|
param_value = {}
|
|
additional_param = inst_req_info.additional_params
|
|
|
|
if additional_param is None:
|
|
additional_param = {}
|
|
input_attributes = base_hot_dict['heat_template'].get('parameters')
|
|
|
|
for input_attr, value in input_attributes.items():
|
|
if additional_param.get(input_attr):
|
|
param_value.update({input_attr: additional_param.get(
|
|
input_attr)})
|
|
|
|
return param_value
|
|
|
|
|
|
def create_vdu_flavor_capability_name_dict(vnfd_dict):
|
|
"""Create a dict containing information about VDU's flavor.
|
|
|
|
:param vnfd_dict: dict(VNFD dict format)
|
|
:return: dict(VDU name, VDU Capability Name dict)
|
|
"""
|
|
vdu_flavor_dict = {}
|
|
node_templates = vnfd_dict.get(
|
|
'topology_template', {}).get(
|
|
'node_templates', {})
|
|
|
|
for vdu_name, val in node_templates.items():
|
|
vdu_flavor_props = val.get(
|
|
'capabilities', {}).get(
|
|
'virtual_compute', {}).get('properties', {})
|
|
if vdu_flavor_props:
|
|
for key, val in vdu_flavor_props.items():
|
|
if key == 'requested_additional_capabilities':
|
|
capability_props = val.get('properties', {})
|
|
if 'requested_additional_capability_name'\
|
|
in capability_props.keys():
|
|
vdu_flavor_dict[vdu_name] = \
|
|
capability_props[
|
|
"requested_additional"
|
|
"_capability_name"]
|
|
|
|
LOG.info('vdu_flavor_dict: %s', vdu_flavor_dict)
|
|
return vdu_flavor_dict
|
|
|
|
|
|
def create_sw_image_dict(vnfd_dict):
|
|
"""Create a dict containing information about VDU's flavor.
|
|
|
|
:param vnfd_dict: dict(VNFD dict format)
|
|
:return: dict(VDU name, VDU SW Image data dict)
|
|
"""
|
|
sw_image_data = {}
|
|
node_templates = vnfd_dict.get(
|
|
'topology_template', {}).get(
|
|
'node_templates', {})
|
|
|
|
for vdu_name, val in node_templates.items():
|
|
sw_image_data_props = val.get(
|
|
'properties', {}).get('sw_image_data', {})
|
|
if sw_image_data_props:
|
|
if 'name' in sw_image_data_props.keys():
|
|
sw_image_data[vdu_name] = sw_image_data_props['name']
|
|
|
|
LOG.info('sw_image_data: %s', sw_image_data)
|
|
return sw_image_data
|
|
|
|
|
|
def create_network_dict(inst_req_info, param_dict):
|
|
"""Create a dict containing information about VDU's network.
|
|
|
|
:param inst_req_info: dict(Instantiation request information format)
|
|
:param param_dict: dict('nfv', Initial HOT resource dict)
|
|
:return: dict(VDU name, VDU SW Image data dict)
|
|
"""
|
|
cp_data = {}
|
|
ext_vl_param = inst_req_info.ext_virtual_links
|
|
cp_param = param_dict.get('nfv', {}).get('CP')
|
|
|
|
for ext_vl in ext_vl_param:
|
|
for ext_cp in ext_vl.ext_cps:
|
|
if ext_cp.cpd_id in cp_param.keys():
|
|
cp_data[ext_cp.cpd_id] = {}
|
|
cp_data[ext_cp.cpd_id]["network"] = ext_vl.resource_id
|
|
cp_data[ext_cp.cpd_id]["fixed_ips"] =\
|
|
_create_fixed_ips_list(ext_cp)
|
|
|
|
LOG.info('cp_data: %s', cp_data)
|
|
return cp_data
|
|
|
|
|
|
def _create_fixed_ips_list(ext_cp):
|
|
"""Create a list containing information about IP information.
|
|
|
|
:param ext_cp: dict(ExtCp data of Instantiation request)
|
|
:return: list(List structured of fixed_ips)
|
|
"""
|
|
fixed_ips_lst = []
|
|
for cp_config in ext_cp.cp_config:
|
|
for cp_protocol_data in cp_config.cp_protocol_data:
|
|
for ip_address in cp_protocol_data.ip_over_ethernet.ip_addresses:
|
|
|
|
# TODO(w-juso):
|
|
# Currently, I have not found a way to specify multiple
|
|
# numDynamicAddresses to HOT, so I create a list of fixed_ips
|
|
# with numDynamicAddress=1.
|
|
# Needs to be considered in the future.
|
|
fixed_ips = {}
|
|
if ip_address.subnet_id:
|
|
fixed_ips['subnet'] = ip_address.subnet_id
|
|
if ip_address.fixed_addresses:
|
|
for fixed_address in ip_address.fixed_addresses:
|
|
fixed_ips['ip_address'] = fixed_address
|
|
|
|
if fixed_ips:
|
|
fixed_ips_lst.append(fixed_ips)
|
|
|
|
return fixed_ips_lst
|
|
|
|
|
|
def _create_scale_group_dict(base_hot_dict, vnfd_dict, inst_req_info):
|
|
|
|
base_hot = base_hot_dict['heat_template']
|
|
LOG.debug("base_hot: %s", base_hot)
|
|
|
|
scaling_group_dict = {}
|
|
for name, rsc in base_hot.get('resources').items():
|
|
if rsc['type'] == 'OS::Heat::AutoScalingGroup':
|
|
key_name = name.replace('_group', '')
|
|
scaling_group_dict[key_name] = name
|
|
LOG.debug("scaling_group_dict: %s", scaling_group_dict)
|
|
|
|
if scaling_group_dict:
|
|
vnf = {'attributes': {'scaling_group_names':
|
|
jsonutils.dump_as_bytes(scaling_group_dict)}}
|
|
scale_group_dict = tosca_utils.get_scale_group(
|
|
vnf, vnfd_dict, inst_req_info)
|
|
LOG.debug("scale_group_dict: %s", scale_group_dict)
|
|
return scale_group_dict
|
|
else:
|
|
LOG.debug("no scale_group_dict")
|
|
return {}
|
|
|
|
|
|
def create_desired_capacity_dict(base_hot_dict, vnfd_dict, inst_req_info):
|
|
"""Create a dict containing information about desired capacity.
|
|
|
|
:param base_hot_dict: dict(Base HOT dict format)
|
|
:param vnfd_dict: dict(VNFD dict format)
|
|
:param inst_req_info: dict(Instantiation request information format)
|
|
:return: dict(Scaling aspect name, Desired capacity value)
|
|
"""
|
|
scale_group_dict = _create_scale_group_dict(
|
|
base_hot_dict, vnfd_dict, inst_req_info)
|
|
|
|
param_dict = {}
|
|
if scale_group_dict.get('scaleGroupDict'):
|
|
for name, value in scale_group_dict['scaleGroupDict'].items():
|
|
param_dict[name] = value['default']
|
|
|
|
LOG.info("desired_capacity dict: %s", param_dict)
|
|
return param_dict
|
|
|
|
|
|
def _calc_desired_capacity(inst_vnf_info, name, value):
|
|
|
|
for scale_status in inst_vnf_info.scale_status:
|
|
if scale_status.aspect_id == name:
|
|
LOG.debug("scale_level of %s: %d",
|
|
name, scale_status.scale_level)
|
|
increase = value['num'] * scale_status.scale_level
|
|
desired_capacity = value['initialNum'] + increase
|
|
LOG.debug("desired_capacity: %d", desired_capacity)
|
|
return desired_capacity
|
|
|
|
LOG.debug("scale_level of %s: None", name)
|
|
return None
|
|
|
|
|
|
def get_desired_capacity_dict(base_hot_dict, vnfd_dict, inst_vnf_info):
|
|
"""Get a dict containing information about desired capacity.
|
|
|
|
:param base_hot_dict: dict(Base HOT dict format)
|
|
:param vnfd_dict: dict(VNFD dict format)
|
|
:param inst_vnf_info: dict(Instantiated VNF Info dict format)
|
|
:return: dict(Scaling aspect name, Desired capacity value)
|
|
"""
|
|
scale_group_dict = _create_scale_group_dict(
|
|
base_hot_dict, vnfd_dict, {})
|
|
|
|
param_dict = {}
|
|
if scale_group_dict.get('scaleGroupDict'):
|
|
for name, value in scale_group_dict['scaleGroupDict'].items():
|
|
desired_capacity = _calc_desired_capacity(
|
|
inst_vnf_info, name, value)
|
|
if desired_capacity is not None:
|
|
param_dict[name] = desired_capacity
|
|
|
|
LOG.info("desired_capacity dict: %s", param_dict)
|
|
return param_dict
|