# 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
