# # 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