UserData 脚本 (VNF LCM v2)

本文档描述了为 VNF LCM 版本 2 创建 userdata 脚本的要求。

UserData 脚本使操作员能够灵活地自定义 LCM 操作中的 VIM 输入参数。

如果您想了解如何使用 userdata 脚本部署 VNF,请查看 ETSI NFV-SOL VNF 部署为使用 LCM 操作用户数据的 VM,或者如果您想了解如何创建包含 userdata 脚本的 VNF 包,请查看 VNF 包手册

需求

UserData 脚本必须根据以下规则进行描述。

必须在 userdata 脚本文件中定义 Userdata 类。文件名和类名都可以接受。

注意

文件和类的名称必须与 LCM API 的以下请求参数相对应:“lcm-operation-user-data”、“lcm-operation-user-data-class”。

userdata 类必须继承 “userdata_utils.AbstractUserData”,然后必须实现函数。

以下是最新 Tacker 支持的方法的要求。

所有方法的输入

所有方法都可以使用以下输入数据。数据类型的详细信息在 ETSI NFV SOL 文档中定义。

  • req: 对应于 API 请求的 operationParams

  • inst: VnfInstance

  • grant_req: GrantRequest

  • grant: Grants

  • tmp_csar_dir: Tacker 展开 csar 的临时路径

方法的输出

所需输出因方法而异。

instantiate()

该方法必须返回以下结构。数据用于 HEAT 中的 stack create API。HEAT API 的要求在 编排服务 API v1 “POST /v1/{tenant_id}/stacks” 的参考中描述。

fields = {‘template’: value, ‘parameters’: value, ‘files’: value}

  • template: 顶部 HOT 文件的转储

  • parameters: Heat API 的输入参数

  • files: 包中所有嵌套 HOT 文件的转储

以下显示示例输出。

fields = {
    'template': yaml.safe_dump(top_hot),
    'parameters': {'nfv': nfv_dict},
    'files': {}
}
for key, value in hot_dict.get('files', {}).items():
    fields['files'][key] = yaml.safe_dump(value)

return fields

scale()

如果需要修改,该方法必须返回以下结构。数据用于 HEAT 中的 update stack API。HEAT API 的要求在 编排服务 API v1 “PATCH /v1/{tenant_id}/stacks/{stack_name}/{stack_id}” 的参考中描述。

fields = {‘parameters’: {‘nfv’: {‘VDU’: new_vdus}}}

  • parameters: Heat API 的输入参数

以下显示示例输出。

fields = {'parameters': {'nfv': {'VDU': new_vdus}}}

return fields

scale_rollback()

如果需要修改,该方法必须返回以下结构。数据用于 HEAT 中的 update stack API。HEAT API 的要求在 编排服务 API v1 “PATCH /v1/{tenant_id}/stacks/{stack_name}/{stack_id}” 的参考中描述。

fields = {‘parameters’: {‘nfv’: {‘VDU’: new_vdus}}}

  • parameters: Heat API 的输入参数

以下显示示例输出。

fields = {'parameters': {'nfv': {'VDU': new_vdus}}}

return fields

change_ext_conn()

该方法必须返回以下结构。数据用于 HEAT 中的 update stack API。HEAT API 的要求在 编排服务 API v1 “PATCH /v1/{tenant_id}/stacks/{stack_name}/{stack_id}” 的参考中描述。

fields = {‘parameters’: {‘nfv’: {‘CP’: new_cps}}}

  • parameters: Heat API 的输入参数

以下显示示例输出。

fields = {'parameters': {'nfv': {'CP': new_cps}}}

return fields

change_ext_conn_rollback()

该方法必须返回以下结构。数据用于 HEAT 中的 update stack API。HEAT API 的要求在 编排服务 API v1 “PATCH /v1/{tenant_id}/stacks/{stack_name}/{stack_id}” 的参考中描述。

fields = {‘parameters’: {‘nfv’: {‘CP’: new_cps}}}

  • parameters: Heat API 的输入参数

以下显示示例输出。

fields = {'parameters': {'nfv': {'CP': new_cps}}}

return fields

heal()

该方法必须返回以下结构。数据用于 HEAT 中的 update stack API。HEAT API 的要求在 编排服务 API v1 “PATCH /v1/{tenant_id}/stacks/{stack_name}/{stack_id}” 的参考中描述。

fields = {‘parameters’: {‘nfv’: {}}}

  • parameters: Heat API 的输入参数

以下显示示例输出。

fields = {'parameters': {'nfv': {}}}

return fields

使用 AutoScalingGroup 的示例 userdata 脚本

如果用户未在 instantiate VNF 请求中指定 userdata,则默认过程将根据以下脚本运行。

该脚本可以用作创建原始 userdata 脚本的示例。它从 VNFD、Instantiate 请求的参数和 Grant 中获取 HEAT 输入参数,例如 *computeFlavourId*、*vcImageId*、*locationConstraints*、*network*、*subnet* 和 *fixed_ips*。

# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation
# All Rights Reserved.
#
#    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
#
#         https://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 yaml

from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored.infra_drivers.openstack import userdata_utils


def _get_new_cps_from_req(cps, req, grant):
    # used by change_ext_conn and change_vnfpkg
    new_cps = {}
    for cp_name, cp_value in cps.items():
        if 'network' in cp_value:
            network = common_script_utils.get_param_network(
                cp_name, grant, req)
            if network is None:
                continue
            new_cps.setdefault(cp_name, {})
            new_cps[cp_name]['network'] = network
        if 'fixed_ips' in cp_value:
            ext_fixed_ips = common_script_utils.get_param_fixed_ips(
                cp_name, grant, req)
            fixed_ips = []
            for i in range(len(ext_fixed_ips)):
                if i not in cp_value['fixed_ips']:
                    break
                ips_i = cp_value['fixed_ips'][i]
                if 'subnet' in ips_i:
                    ips_i['subnet'] = ext_fixed_ips[i].get('subnet')
                if 'ip_address' in ips_i:
                    ips_i['ip_address'] = ext_fixed_ips[i].get(
                        'ip_address')
                fixed_ips.append(ips_i)
            new_cps.setdefault(cp_name, {})
            new_cps[cp_name]['fixed_ips'] = fixed_ips

    return new_cps


def _get_new_cps_from_inst(cps, inst):
    # used by change_ext_conn and change_vnfpkg
    new_cps = {}
    for cp_name, cp_value in cps.items():
        if 'network' in cp_value:
            network = common_script_utils.get_param_network_from_inst(
                cp_name, inst)
            if network is None:
                continue
            new_cps.setdefault(cp_name, {})
            new_cps[cp_name]['network'] = network
        if 'fixed_ips' in cp_value:
            ext_fixed_ips = (
                common_script_utils.get_param_fixed_ips_from_inst(
                    cp_name, inst))
            fixed_ips = []
            for i in range(len(ext_fixed_ips)):
                if i not in cp_value['fixed_ips']:
                    break
                ips_i = cp_value['fixed_ips'][i]
                if 'subnet' in ips_i:
                    ips_i['subnet'] = ext_fixed_ips[i].get('subnet')
                if 'ip_address' in ips_i:
                    ips_i['ip_address'] = ext_fixed_ips[i].get(
                        'ip_address')
                fixed_ips.append(ips_i)
            new_cps.setdefault(cp_name, {})
            new_cps[cp_name]['fixed_ips'] = fixed_ips

    return new_cps


class DefaultUserData(userdata_utils.AbstractUserData):

    @staticmethod
    def instantiate(req, inst, grant_req, grant, tmp_csar_dir):
        vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
        flavour_id = req['flavourId']

        hot_dict = vnfd.get_base_hot(flavour_id)
        top_hot = hot_dict['template']

        nfv_dict = common_script_utils.init_nfv_dict(top_hot)

        vdus = nfv_dict.get('VDU', {})
        for vdu_name, vdu_value in vdus.items():
            if 'computeFlavourId' in vdu_value:
                vdu_value['computeFlavourId'] = (
                    common_script_utils.get_param_flavor(
                        vdu_name, flavour_id, vnfd, grant))
            if 'vcImageId' in vdu_value:
                vdu_value['vcImageId'] = common_script_utils.get_param_image(
                    vdu_name, flavour_id, vnfd, grant)
            if 'locationConstraints' in vdu_value:
                vdu_value['locationConstraints'] = (
                    common_script_utils.get_param_zone(
                        vdu_name, grant_req, grant))
            if 'desired_capacity' in vdu_value:
                vdu_value['desired_capacity'] = (
                    common_script_utils.get_param_capacity(
                        vdu_name, inst, grant_req))

        cps = nfv_dict.get('CP', {})
        for cp_name, cp_value in cps.items():
            if 'network' in cp_value:
                cp_value['network'] = common_script_utils.get_param_network(
                    cp_name, grant, req)
            if 'fixed_ips' in cp_value:
                ext_fixed_ips = common_script_utils.get_param_fixed_ips(
                    cp_name, grant, req)
                fixed_ips = []
                for i in range(len(ext_fixed_ips)):
                    if i not in cp_value['fixed_ips']:
                        break
                    ips_i = cp_value['fixed_ips'][i]
                    if 'subnet' in ips_i:
                        ips_i['subnet'] = ext_fixed_ips[i].get('subnet')
                    if 'ip_address' in ips_i:
                        ips_i['ip_address'] = ext_fixed_ips[i].get(
                            'ip_address')
                    fixed_ips.append(ips_i)
                cp_value['fixed_ips'] = fixed_ips

        common_script_utils.apply_ext_managed_vls(top_hot, req, grant)

        if 'nfv' in req.get('additionalParams', {}):
            nfv_dict = inst_utils.json_merge_patch(nfv_dict,
                    req['additionalParams']['nfv'])
        if 'nfv' in grant.get('additionalParams', {}):
            nfv_dict = inst_utils.json_merge_patch(nfv_dict,
                    grant['additionalParams']['nfv'])

        fields = {
            'template': yaml.safe_dump(top_hot),
            'parameters': {'nfv': nfv_dict},
            'files': {}
        }
        for key, value in hot_dict.get('files', {}).items():
            fields['files'][key] = yaml.safe_dump(value)

        return fields

    @staticmethod
    def scale(req, inst, grant_req, grant, tmp_csar_dir):
        # scale is interested in 'desired_capacity' only.
        # This method returns only 'desired_capacity' part in the
        # 'nfv' dict. It is applied to json merge patch against
        # the existing 'nfv' dict by the caller.
        # NOTE: complete 'nfv' dict can not be made at the moment
        # since InstantiateVnfRequest is necessary to make it.

        vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
        flavour_id = inst['instantiatedVnfInfo']['flavourId']

        hot_dict = vnfd.get_base_hot(flavour_id)
        top_hot = hot_dict['template']

        nfv_dict = common_script_utils.init_nfv_dict(top_hot)

        vdus = nfv_dict.get('VDU', {})
        new_vdus = {}
        for vdu_name, vdu_value in vdus.items():
            if 'desired_capacity' in vdu_value:
                capacity = common_script_utils.get_param_capacity(
                    vdu_name, inst, grant_req)
                new_vdus[vdu_name] = {'desired_capacity': capacity}

        fields = {'parameters': {'nfv': {'VDU': new_vdus}}}

        return fields

    @staticmethod
    def scale_rollback(req, inst, grant_req, grant, tmp_csar_dir):
        # NOTE: This method is not called by a userdata script but
        # is called by the openstack infra_driver directly now.
        # It is thought that it is suitable that this method defines
        # here since it is very likely to scale method above.

        vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
        flavour_id = inst['instantiatedVnfInfo']['flavourId']

        hot_dict = vnfd.get_base_hot(flavour_id)
        top_hot = hot_dict['template']

        nfv_dict = common_script_utils.init_nfv_dict(top_hot)

        vdus = nfv_dict.get('VDU', {})
        new_vdus = {}
        for vdu_name, vdu_value in vdus.items():
            if 'desired_capacity' in vdu_value:
                capacity = common_script_utils.get_current_capacity(
                    vdu_name, inst)
                new_vdus[vdu_name] = {'desired_capacity': capacity}

        fields = {'parameters': {'nfv': {'VDU': new_vdus}}}

        return fields

    @staticmethod
    def change_ext_conn(req, inst, grant_req, grant, tmp_csar_dir):
        # change_ext_conn is interested in 'CP' only.
        # This method returns only 'CP' part in the 'nfv' dict from
        # ChangeExtVnfConnectivityRequest.
        # It is applied to json merge patch against the existing 'nfv'
        # dict by the caller.
        # NOTE: complete 'nfv' dict can not be made at the moment
        # since InstantiateVnfRequest is necessary to make it.

        vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
        flavour_id = inst['instantiatedVnfInfo']['flavourId']

        hot_dict = vnfd.get_base_hot(flavour_id)
        top_hot = hot_dict['template']

        nfv_dict = common_script_utils.init_nfv_dict(top_hot)

        cps = nfv_dict.get('CP', {})
        new_cps = _get_new_cps_from_req(cps, req, grant)

        fields = {'parameters': {'nfv': {'CP': new_cps}}}

        return fields

    @staticmethod
    def change_ext_conn_rollback(req, inst, grant_req, grant, tmp_csar_dir):
        # NOTE: This method is not called by a userdata script but
        # is called by the openstack infra_driver directly now.
        # It is thought that it is suitable that this method defines
        # here since it is very likely to scale method above.

        vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
        flavour_id = inst['instantiatedVnfInfo']['flavourId']

        hot_dict = vnfd.get_base_hot(flavour_id)
        top_hot = hot_dict['template']

        nfv_dict = common_script_utils.init_nfv_dict(top_hot)

        cps = nfv_dict.get('CP', {})
        new_cps = _get_new_cps_from_inst(cps, inst)

        fields = {'parameters': {'nfv': {'CP': new_cps}}}

        return fields

    @staticmethod
    def heal(req, inst, grant_req, grant, tmp_csar_dir):
        # It is not necessary to change parameters at heal basically.

        fields = {'parameters': {'nfv': {}}}

        return fields

    @staticmethod
    def change_vnfpkg(req, inst, grant_req, grant, tmp_csar_dir):
        vnfd = common_script_utils.get_vnfd(grant_req['dstVnfdId'],
                                            tmp_csar_dir)
        flavour_id = inst['instantiatedVnfInfo']['flavourId']

        hot_dict = vnfd.get_base_hot(flavour_id)
        top_hot = hot_dict['template']

        nfv_dict = common_script_utils.init_nfv_dict(top_hot)

        vdus = nfv_dict.get('VDU', {})
        new_vdus = {}
        for vdu_name, vdu_value in vdus.items():
            if 'computeFlavourId' in vdu_value:
                flavor = common_script_utils.get_param_flavor(
                    vdu_name, flavour_id, vnfd, grant)
                new_vdus.setdefault(vdu_name, {})
                new_vdus[vdu_name]['computeFlavourId'] = flavor
            if 'vcImageId' in vdu_value:
                image = common_script_utils.get_param_image(
                    vdu_name, flavour_id, vnfd, grant)
                new_vdus.setdefault(vdu_name, {})
                new_vdus[vdu_name]['vcImageId'] = image

        cps = nfv_dict.get('CP', {})
        new_cps = _get_new_cps_from_req(cps, req, grant)

        fields = {
            'parameters': {'nfv': {'VDU': new_vdus, 'CP': new_cps}}
        }

        return fields

    @staticmethod
    def change_vnfpkg_rollback(req, inst, grant_req, grant, tmp_csar_dir):
        images = {}
        flavors = {}
        for vnfc in inst.get('instantiatedVnfInfo', {}).get(
                'vnfcResourceInfo', []):
            vdu_name = vnfc['vduId']
            if vdu_name in flavors:
                continue
            for key, value in vnfc['metadata'].items():
                if key == 'flavor':
                    flavors[vdu_name] = value
                elif key.startswith('image-'):
                    image_vdu = key.replace('image-', '')
                    images[image_vdu] = value

        vnfd = common_script_utils.get_vnfd(inst['vnfdId'],
                                            tmp_csar_dir)
        flavour_id = inst['instantiatedVnfInfo']['flavourId']

        hot_dict = vnfd.get_base_hot(flavour_id)
        top_hot = hot_dict['template']

        nfv_dict = common_script_utils.init_nfv_dict(top_hot)

        vdus = nfv_dict.get('VDU', {})
        new_vdus = {}
        for vdu_name, vdu_value in vdus.items():
            if 'computeFlavourId' in vdu_value:
                new_vdus.setdefault(vdu_name, {})
                new_vdus[vdu_name]['computeFlavourId'] = flavors.get(vdu_name)
            if 'vcImageId' in vdu_value:
                new_vdus.setdefault(vdu_name, {})
                new_vdus[vdu_name]['vcImageId'] = images.get(vdu_name)

        cps = nfv_dict.get('CP', {})
        new_cps = _get_new_cps_from_inst(cps, inst)

        fields = {
            'parameters': {'nfv': {'VDU': new_vdus, 'CP': new_cps}}
        }

        return fields

以下是与上述示例 userdata 脚本对应的示例 Base HOT。

顶部 Base HOT

heat_template_version: 2013-05-23
description: 'Simple Base HOT for Sample VNF'

parameters:
  nfv:
    type: json

resources:
  VDU1_scale_group:
    type: OS::Heat::AutoScalingGroup
    properties:
      min_size: 1
      max_size: 3
      desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] }
      resource:
        type: VDU1.yaml
        properties:
          flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
          image-VDU1-VirtualStorage: { get_param: [ nfv, VDU, VDU1-VirtualStorage, vcImageId ] }
          zone: { get_param: [ nfv, VDU, VDU1, locationConstraints] }
          net1: { get_param: [ nfv, CP, VDU1_CP1, network] }
          net2: { get_param: [ nfv, CP, VDU1_CP2, network ] }
          subnet1: { get_param: [nfv, CP, VDU1_CP1, fixed_ips, 0, subnet ]}
          subnet2: { get_param: [nfv, CP, VDU1_CP2, fixed_ips, 0, subnet ]}
          net3: { get_resource: internalVL1 }
          net4: { get_resource: internalVL2 }
          net5: { get_resource: internalVL3 }
          volume_type: { get_resource: VDU1-VolumeType }

# NOTE: Resource definition of OS::Heat::ScalingPolicy is omitted.
# It is not used by v2 scale implementation unlike v1.

  VDU1-VolumeType:
    type: OS::Cinder::VolumeType
    properties:
      name: VDU1-multi
      metadata: { multiattach: "<is> True" }

  VDU2:
    type: OS::Nova::Server
    properties:
      flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
      name: VDU2
      availability_zone: { get_param: [ nfv, VDU, VDU2, locationConstraints ] }
      block_device_mapping_v2: [{"volume_id": { get_resource: VDU2-VirtualStorage }}]
      networks:
      - port: { get_param: [ nfv, CP, VDU2_CP1-1, port ]  }
      - port: { get_param: [ nfv, CP, VDU2_CP1-2, port ]  }
      - port:
          get_resource: VDU2_CP2
      - port:
          get_resource: VDU2_CP3
      - port:
          get_resource: VDU2_CP4
      - port:
          get_resource: VDU2_CP5

  VDU2-VirtualStorage:
    type: OS::Cinder::Volume
    properties:
      image: { get_param: [ nfv, VDU, VDU2-VirtualStorage, vcImageId] }
      size: 1
      volume_type: { get_resource: VDU2-VolumeType }
  VDU2-VolumeType:
    type: OS::Cinder::VolumeType
    properties:
      name: VDU2-multi
      metadata: { multiattach: "<is> True" }

# extVL with FixedIP and Subnet
  VDU2_CP2:
    type: OS::Neutron::Port
    properties:
      network: { get_param: [ nfv, CP, VDU2_CP2, network ]  }
      fixed_ips:
      - ip_address: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 0, ip_address]}
        subnet: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 0, subnet]}
      - subnet: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 1, subnet]}

  VDU2_CP3:
    type: OS::Neutron::Port
    properties:
# replace the following line to VL's ID when extmanagedVLs are specified in instantiatevnfrequest
      network: { get_resource: internalVL1 }

  VDU2_CP4:
    type: OS::Neutron::Port
    properties:
# replace the following line to VL's ID when extmanagedVLs are specified in instantiatevnfrequest
      network: { get_resource: internalVL2 }

  VDU2_CP5:
    type: OS::Neutron::Port
    properties:
# replace the following line to VL's ID when extmanagedVLs are specified in instantiatevnfrequest
      network: { get_resource: internalVL3 }

# delete the following lines when extmanagedVLs are specified in instantiatevnfrequest
  internalVL1:
    type: OS::Neutron::Net
  internalVL2:
    type: OS::Neutron::Net
  internalVL3:
    type: OS::Neutron::Net

  internalVL1_subnet:
    type: OS::Neutron::Subnet
    properties:
      ip_version: 4
      network:
        get_resource: internalVL1
      cidr: 192.168.3.0/24
  internalVL2_subnet:
    type: OS::Neutron::Subnet
    properties:
      ip_version: 4
      network:
        get_resource: internalVL2
      cidr: 192.168.4.0/24
  internalVL3_subnet:
    type: OS::Neutron::Subnet
    properties:
      ip_version: 4
      network:
        get_resource: internalVL3
      cidr: 192.168.5.0/24
outputs: {}

嵌套 Base HOT

heat_template_version: 2013-05-23
description: 'VDU1 HOT for Sample VNF'

parameters:
  flavor:
    type: string
  image-VDU1-VirtualStorage:
    type: string
  zone:
    type: string
  net1:
    type: string
  net2:
    type: string
  net3:
    type: string
  net4:
    type: string
  net5:
    type: string
  subnet1:
    type: string
  subnet2:
    type: string
  volume_type:
    type: string

resources:
  VDU1:
    type: OS::Nova::Server
    properties:
      flavor: { get_param: flavor }
      name: VDU1
      block_device_mapping_v2: [{"volume_id": { get_resource: VDU1-VirtualStorage }}]
      networks:
      - port:
          get_resource: VDU1_CP1
      - port:
          get_resource: VDU1_CP2
# replace the following line to Port ID when extmanagedVLs' Ports are specified in instantiatevnfrequest
      - port:
          get_resource: VDU1_CP3
      - port:
          get_resource: VDU1_CP4
      - port:
          get_resource: VDU1_CP5
      availability_zone: { get_param: zone }

  VDU1-VirtualStorage:
    type: OS::Cinder::Volume
    properties:
      image: { get_param: image-VDU1-VirtualStorage }
      size: 1
      volume_type: { get_param: volume_type }

# extVL without FixedIP or with numDynamicAddresses
  VDU1_CP1:
    type: OS::Neutron::Port
    properties:
      network: { get_param: net1 }
      fixed_ips:
      - subnet: { get_param: subnet1}

# extVL with numDynamicAddresses and subnet
  VDU1_CP2:
    type: OS::Neutron::Port
    properties:
      network: { get_param: net2 }
      fixed_ips:
      - subnet: { get_param: subnet2}

# delete the following line when extmanagedVLs' Ports are specified in instantiatevnfrequest
  VDU1_CP3:
    type: OS::Neutron::Port
    properties:
      network: { get_param: net3 }

  VDU1_CP4:
    type: OS::Neutron::Port
    properties:
      network: { get_param: net4 }

  VDU1_CP5:
    type: OS::Neutron::Port
    properties:
      network: { get_param: net5 }

不使用 AutoScalingGroup 的示例 userdata 脚本

即使 HOT 中未指定 OS::Heat::AutoScalingGroup,Tacker 也可以根据 VNFD 创建所需的 VNFC 资源数量作为单独的资源。此配置使用户能够处理单个 VNFC 资源,例如用户可以更改指定 VNFC 的镜像或网络。

以下显示了用于处理不使用 AutoScalingGroup 的 VNFC 的示例 userdata 脚本。

# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation
# All Rights Reserved.
#
#    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
#
#         https://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
import yaml

from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored.infra_drivers.openstack import userdata_utils


def add_idx(name, index):
    return f'{name}-{index}'


def rm_idx(name_idx):
    return name_idx.rpartition('-')[0]


def add_idx_to_vdu_template(vdu_template, vdu_idx):
    """Add index to the third element of get_param

    ex. input VDU template:
    ---
    VDU1:
      type: VDU1.yaml
      properties:
        flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
        image-VDU1: { get_param: [ nfv, VDU, VDU1, vcImageId ] }
        net1: { get_param: [ nfv, CP, VDU1_CP1, network ] }
    ---

    output VDU template:
    ---
    VDU1:
      type: VDU1.yaml
      properties:
        flavor: { get_param: [ nfv, VDU, VDU1-1, computeFlavourId ] }
        image-VDU1: { get_param: [ nfv, VDU, VDU1-1, vcImageId ] }
        net1: { get_param: [ nfv, CP, VDU1_CP1-1, network ] }
    ---
    """
    res = copy.deepcopy(vdu_template)
    for prop_value in res.get('properties', {}).values():
        get_param = prop_value.get('get_param')
        if (get_param is not None and
                isinstance(get_param, list) and len(get_param) >= 4):
            get_param[2] = add_idx(get_param[2], vdu_idx)
    return res


def _get_new_cps_from_req(cps, req, grant):
    # used by change_ext_conn and change_vnfpkg
    new_cps = {}
    for cp_name_idx, cp_value in cps.items():
        cp_name = rm_idx(cp_name_idx)
        if 'network' in cp_value:
            network = common_script_utils.get_param_network(
                cp_name, grant, req)
            if network is None:
                continue
            new_cps.setdefault(cp_name_idx, {})
            new_cps[cp_name_idx]['network'] = network
        if 'fixed_ips' in cp_value:
            ext_fixed_ips = common_script_utils.get_param_fixed_ips(
                cp_name, grant, req)
            fixed_ips = []
            for i in range(len(ext_fixed_ips)):
                if i not in cp_value['fixed_ips']:
                    break
                ips_i = cp_value['fixed_ips'][i]
                if 'subnet' in ips_i:
                    ips_i['subnet'] = ext_fixed_ips[i].get('subnet')
                if 'ip_address' in ips_i:
                    ips_i['ip_address'] = ext_fixed_ips[i].get(
                        'ip_address')
                fixed_ips.append(ips_i)
            new_cps.setdefault(cp_name_idx, {})
            new_cps[cp_name_idx]['fixed_ips'] = fixed_ips

    return new_cps


def _merge_additional_params(nfv_dict, req, grant):
    if 'nfv' in req.get('additionalParams', {}):
        nfv_dict = inst_utils.json_merge_patch(
            nfv_dict, req['additionalParams']['nfv'])
    if 'nfv' in grant.get('additionalParams', {}):
        nfv_dict = inst_utils.json_merge_patch(
            nfv_dict, grant['additionalParams']['nfv'])
    return nfv_dict


class StandardUserData(userdata_utils.AbstractUserData):

    @staticmethod
    def instantiate(req, inst, grant_req, grant, tmp_csar_dir):
        vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
        flavour_id = req['flavourId']

        hot_dict = vnfd.get_base_hot(flavour_id)
        top_hot = hot_dict['template']

        # first modify VDU resources
        popped_vdu = {}
        vdu_idxes = {}
        for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys():
            popped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name)
            vdu_idxes[vdu_name] = 0
        zones = {}
        for res in grant_req['addResources']:
            if res['type'] != 'COMPUTE':
                continue
            vdu_name = res['resourceTemplateId']
            if vdu_name not in popped_vdu:
                continue
            vdu_idx = vdu_idxes[vdu_name]
            vdu_idxes[vdu_name] += 1
            zones[add_idx(vdu_name, vdu_idx)] = (
                common_script_utils.get_param_zone_by_vnfc(
                    res['id'], grant))
            res = add_idx_to_vdu_template(popped_vdu[vdu_name], vdu_idx)
            top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res

        nfv_dict = common_script_utils.init_nfv_dict(top_hot)

        vdus = nfv_dict.get('VDU', {})
        for vdu_name_idx, vdu_value in vdus.items():
            vdu_name = rm_idx(vdu_name_idx)
            if 'computeFlavourId' in vdu_value:
                vdu_value['computeFlavourId'] = (
                    common_script_utils.get_param_flavor(
                        vdu_name, flavour_id, vnfd, grant))
            if 'vcImageId' in vdu_value:
                vdu_value['vcImageId'] = common_script_utils.get_param_image(
                    vdu_name, flavour_id, vnfd, grant)
            if 'locationConstraints' in vdu_value:
                vdu_value['locationConstraints'] = zones[vdu_name_idx]

        cps = nfv_dict.get('CP', {})
        for cp_name, cp_value in cps.items():
            cp_name = rm_idx(cp_name)
            if 'network' in cp_value:
                cp_value['network'] = common_script_utils.get_param_network(
                    cp_name, grant, req)
            if 'fixed_ips' in cp_value:
                ext_fixed_ips = common_script_utils.get_param_fixed_ips(
                    cp_name, grant, req)
                fixed_ips = []
                for i in range(len(ext_fixed_ips)):
                    if i not in cp_value['fixed_ips']:
                        break
                    ips_i = cp_value['fixed_ips'][i]
                    if 'subnet' in ips_i:
                        ips_i['subnet'] = ext_fixed_ips[i].get('subnet')
                    if 'ip_address' in ips_i:
                        ips_i['ip_address'] = ext_fixed_ips[i].get(
                            'ip_address')
                    fixed_ips.append(ips_i)
                cp_value['fixed_ips'] = fixed_ips

        common_script_utils.apply_ext_managed_vls(top_hot, req, grant)
        nfv_dict = _merge_additional_params(nfv_dict, req, grant)

        fields = {
            'template': yaml.safe_dump(top_hot),
            'parameters': {'nfv': nfv_dict},
            'files': {}
        }
        for key, value in hot_dict.get('files', {}).items():
            fields['files'][key] = yaml.safe_dump(value)

        return fields

    @staticmethod
    def scale(req, inst, grant_req, grant, tmp_csar_dir):
        if req['type'] == 'SCALE_OUT':
            return StandardUserData._scale_out(req, inst, grant_req, grant,
                                              tmp_csar_dir)
        else:
            return StandardUserData._scale_in(req, inst, grant_req, grant,
                                             tmp_csar_dir)

    @staticmethod
    def _scale_out(req, inst, grant_req, grant, tmp_csar_dir):
        vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
        flavour_id = inst['instantiatedVnfInfo']['flavourId']

        hot_dict = vnfd.get_base_hot(flavour_id)
        top_hot = hot_dict['template']

        # first modify VDU resources
        popped_vdu = {}
        vdu_idxes = {}
        for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys():
            popped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name)
            vdu_idxes[vdu_name] = common_script_utils.get_current_capacity(
                vdu_name, inst)

        zones = {}
        for res in grant_req['addResources']:
            if res['type'] != 'COMPUTE':
                continue
            vdu_name = res['resourceTemplateId']
            if vdu_name not in popped_vdu:
                continue
            vdu_idx = vdu_idxes[vdu_name]
            vdu_idxes[vdu_name] += 1
            zones[add_idx(vdu_name, vdu_idx)] = (
                common_script_utils.get_param_zone_by_vnfc(
                    res['id'], grant))
            res = add_idx_to_vdu_template(popped_vdu[vdu_name], vdu_idx)
            top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res

        nfv_dict = common_script_utils.init_nfv_dict(top_hot)

        vdus = nfv_dict.get('VDU', {})
        for vdu_name_idx, vdu_value in vdus.items():
            vdu_name = rm_idx(vdu_name_idx)
            if 'computeFlavourId' in vdu_value:
                vdu_value['computeFlavourId'] = (
                    common_script_utils.get_param_flavor(
                        vdu_name, flavour_id, vnfd, grant))
            if 'vcImageId' in vdu_value:
                vdu_value['vcImageId'] = common_script_utils.get_param_image(
                    vdu_name, flavour_id, vnfd, grant)
            if 'locationConstraints' in vdu_value:
                vdu_value['locationConstraints'] = zones[vdu_name_idx]
            exclude_params = [param for param, value in vdu_value.items()
                              if value is None]
            for exclude_param in exclude_params:
                del vdu_value[exclude_param]

        cps = nfv_dict.get('CP', {})
        for cp_name, cp_value in cps.items():
            cp_name = rm_idx(cp_name)
            if 'network' in cp_value:
                cp_value['network'] = (
                    common_script_utils.get_param_network_from_inst(
                        cp_name, inst))
            if 'fixed_ips' in cp_value:
                ext_fixed_ips = (
                    common_script_utils.get_param_fixed_ips_from_inst(
                        cp_name, inst))
                fixed_ips = []
                for i in range(len(ext_fixed_ips)):
                    if i not in cp_value['fixed_ips']:
                        break
                    ips_i = cp_value['fixed_ips'][i]
                    if 'subnet' in ips_i:
                        ips_i['subnet'] = ext_fixed_ips[i].get('subnet')
                    if 'ip_address' in ips_i:
                        ips_i['ip_address'] = ext_fixed_ips[i].get(
                            'ip_address')
                    fixed_ips.append(ips_i)
                cp_value['fixed_ips'] = fixed_ips
            exclude_params = [param for param, value in cp_value.items()
                              if value is None]
            for exclude_param in exclude_params:
                del cp_value[exclude_param]

        common_script_utils.apply_ext_managed_vls_from_inst(top_hot, inst)
        nfv_dict = _merge_additional_params(nfv_dict, req, grant)

        fields = {
            'template': yaml.safe_dump(top_hot),
            'parameters': {'nfv': nfv_dict}
        }

        return fields

    @staticmethod
    def _scale_in(req, inst, grant_req, grant, tmp_csar_dir):
        template = {'resources': {}}

        for res in grant_req['removeResources']:
            if res['type'] != 'COMPUTE':
                continue
            for inst_vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']:
                if (inst_vnfc['computeResource']['resourceId'] ==
                        res['resource']['resourceId']):
                    # must be found
                    vdu_idx = inst_vnfc['metadata']['vdu_idx']
                    break
            vdu_name = res['resourceTemplateId']
            template['resources'][add_idx(vdu_name, vdu_idx)] = None

        fields = {
            'template': yaml.safe_dump(template),
        }

        return fields

    @staticmethod
    def scale_rollback(req, inst, grant_req, grant, tmp_csar_dir):
        vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
        flavour_id = inst['instantiatedVnfInfo']['flavourId']

        vdu_nodes = vnfd.get_vdu_nodes(flavour_id)
        vdu_idxes = {}
        for vdu_name in vdu_nodes.keys():
            vdu_idxes[vdu_name] = common_script_utils.get_current_capacity(
                vdu_name, inst)

        template = {'resources': {}}
        for res in grant_req['addResources']:
            if res['type'] != 'COMPUTE':
                continue
            vdu_name = res['resourceTemplateId']
            vdu_idx = vdu_idxes[vdu_name]
            vdu_idxes[vdu_name] += 1
            template['resources'][add_idx(vdu_name, vdu_idx)] = None

        fields = {
            'template': yaml.safe_dump(template),
        }

        return fields

    @staticmethod
    def change_ext_conn(req, inst, grant_req, grant, tmp_csar_dir):
        # change_ext_conn is interested in 'CP' only.
        # This method returns only 'CP' part in the 'nfv' dict from
        # ChangeExtVnfConnectivityRequest.
        # It is applied to json merge patch against the existing 'nfv'
        # dict by the caller.
        # NOTE: complete 'nfv' dict can not be made at the moment
        # since InstantiateVnfRequest is necessary to make it.

        vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
        flavour_id = inst['instantiatedVnfInfo']['flavourId']

        hot_dict = vnfd.get_base_hot(flavour_id)
        top_hot = hot_dict['template']

        # first modify VDU resources
        popped_vdu = {}
        for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys():
            popped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name)

        for inst_vnfc in inst['instantiatedVnfInfo'].get(
                'vnfcResourceInfo', []):
            vdu_idx = inst_vnfc['metadata'].get('vdu_idx')
            if vdu_idx is None:
                continue
            vdu_name = inst_vnfc['vduId']
            res = add_idx_to_vdu_template(popped_vdu[vdu_name], vdu_idx)
            top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res

        nfv_dict = common_script_utils.init_nfv_dict(top_hot)

        cps = nfv_dict.get('CP', {})
        new_cps = _get_new_cps_from_req(cps, req, grant)

        nfv_dict = _merge_additional_params({'CP': new_cps}, req, grant)
        fields = {'parameters': {'nfv': nfv_dict}}

        return fields

    @staticmethod
    def change_ext_conn_rollback(req, inst, grant_req, grant, tmp_csar_dir):
        fields = {
            'parameters': {
                'nfv': inst['instantiatedVnfInfo']['metadata']['nfv']
            }
        }

        return fields

    @staticmethod
    def heal(req, inst, grant_req, grant, tmp_csar_dir):
        vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
        flavour_id = inst['instantiatedVnfInfo']['flavourId']

        vdus = inst['instantiatedVnfInfo']['metadata']['nfv'].get('VDU', {})
        for res in grant_req['removeResources']:
            if res['type'] != 'COMPUTE':
                continue
            for inst_vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']:
                if (inst_vnfc['computeResource']['resourceId'] ==
                        res['resource']['resourceId']):
                    # must be found
                    vdu_name = inst_vnfc['vduId']
                    vdu_idx = inst_vnfc['metadata']['vdu_idx']
                    image = common_script_utils.get_param_image(
                        vdu_name, flavour_id, vnfd, grant, fallback_vnfd=False)
                    if image is not None:
                        vdus[add_idx(vdu_name, vdu_idx)]['vcImageId'] = image
                    break

        nfv_dict = _merge_additional_params({'VDU': vdus}, req, grant)
        fields = {'parameters': {'nfv': nfv_dict}}

        return fields

    @staticmethod
    def change_vnfpkg(req, inst, grant_req, grant, tmp_csar_dir):
        vnfd = common_script_utils.get_vnfd(grant_req['dstVnfdId'],
                                            tmp_csar_dir)
        flavour_id = inst['instantiatedVnfInfo']['flavourId']

        hot_dict = vnfd.get_base_hot(flavour_id)
        top_hot = hot_dict['template']

        # first modify VDU resources
        popped_vdu = {}
        for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys():
            popped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name)

        target_vnfc_res_ids = [
            res['resource']['resourceId']
            for res in grant_req['removeResources']
            if res['type'] == 'COMPUTE'
        ]

        cur_hot_reses = {}
        new_hot_reses = {}
        for inst_vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']:
            vdu_idx = inst_vnfc['metadata'].get('vdu_idx')
            if vdu_idx is None:
                # should not be None. just check for consistency.
                continue
            vdu_name = inst_vnfc['vduId']
            vdu_name_idx = add_idx(vdu_name, vdu_idx)

            res = add_idx_to_vdu_template(popped_vdu[vdu_name], vdu_idx)
            top_hot['resources'][vdu_name_idx] = res

            if (inst_vnfc['computeResource']['resourceId'] in
                    target_vnfc_res_ids):
                new_hot_reses[vdu_name_idx] = res
            else:
                cur_hot_reses[vdu_name_idx] = res

        cur_nfv_dict = common_script_utils.init_nfv_dict(
            {'resources': cur_hot_reses})
        new_nfv_dict = common_script_utils.init_nfv_dict(
            {'resources': new_hot_reses})
        new_vdus = new_nfv_dict.get('VDU', {})
        vdus = inst['instantiatedVnfInfo']['metadata']['nfv'].get('VDU', {})

        for vdu_name_idx, vdu_value in new_vdus.items():
            vdu_name = rm_idx(vdu_name_idx)
            if 'computeFlavourId' in vdu_value:
                vdus[vdu_name_idx]['computeFlavourId'] = (
                    common_script_utils.get_param_flavor(
                        vdu_name, flavour_id, vnfd, grant))
            if 'vcImageId' in vdu_value:
                vdus[vdu_name_idx]['vcImageId'] = (
                    common_script_utils.get_param_image(
                        vdu_name, flavour_id, vnfd, grant))

        cps = cur_nfv_dict.get('CP', {})
        cps.update(new_nfv_dict.get('CP', {}))
        # NOTE: req includes only different part. some CPs in new_nfv_dict
        # may be necessary to get from inst.
        cur_cps = inst['instantiatedVnfInfo']['metadata']['nfv'].get('CP', {})
        req_cps = _get_new_cps_from_req(cps, req, grant)
        for cp_name in cps.keys():
            if cp_name in req_cps:
                cps[cp_name] = req_cps[cp_name]
            else:
                cps[cp_name] = cur_cps[cp_name]

        common_script_utils.apply_ext_managed_vls(top_hot, req, grant)

        nfv_dict = _merge_additional_params({'VDU': vdus, 'CP': cps},
                                            req, grant)

        fields = {
            'template': yaml.safe_dump(top_hot),
            'parameters': {'nfv': nfv_dict},
            'files': {},
            'existing': False
        }
        for key, value in hot_dict.get('files', {}).items():
            fields['files'][key] = yaml.safe_dump(value)

        return fields

    @staticmethod
    def change_vnfpkg_rollback(req, inst, grant_req, grant, tmp_csar_dir):
        vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir)
        flavour_id = inst['instantiatedVnfInfo']['flavourId']

        hot_dict = vnfd.get_base_hot(flavour_id)
        top_hot = hot_dict['template']

        # first modify VDU resources
        popped_vdu = {}
        for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys():
            popped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name)

        for inst_vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']:
            vdu_idx = inst_vnfc['metadata'].get('vdu_idx')
            if vdu_idx is None:
                # should not be None. just check for consistency.
                continue
            vdu_name = inst_vnfc['vduId']
            vdu_name_idx = add_idx(vdu_name, vdu_idx)

            res = add_idx_to_vdu_template(popped_vdu[vdu_name], vdu_idx)
            top_hot['resources'][vdu_name_idx] = res

        common_script_utils.apply_ext_managed_vls(top_hot, req, grant)

        fields = {
            'template': yaml.safe_dump(top_hot),
            'parameters': {
                'nfv': inst['instantiatedVnfInfo']['metadata']['nfv']},
            'files': {},
            'existing': False
        }
        for key, value in hot_dict.get('files', {}).items():
            fields['files'][key] = yaml.safe_dump(value)

        return fields

以下是示例 UserData 脚本的规范。

  • UserData 脚本根据 VnfInstance.instantiatedVnfInfo.vnfcResourceInfoGrant.addResourcesGrant.removeResources 的数量计算 VNFC 的数量,类似于计算 desired_capacity 的方法。UserData 类的一个实用函数 get_param_capacity 可用于获取资源数量。

  • UserData 脚本描述与调整后的 HOT 资源数量相同。

    • UserData 脚本创建 VNFC 的资源 ID(例如 VDU1-0、VDU-1-1)。

    • 资源的属性从 BaseHOT 复制。

  • UserData 脚本创建与 Adjusted HOT 对应的输入参数。

注意

使用和不使用 AutoScalingGroup 的 scale-in 操作存在差异。在 scale-in 操作中,VNFC 按照最新顺序删除。在使用 AutoScalingGroup 的情况下,最新的资源是根据 OpenStack Nova 的 creation_time 确定的。由于 creation_time 由 heal 操作更新,因此 VNFC 的顺序会动态更改。另一方面,如果不使用 AutoScalingGroup,则最新的资源由资源 ID(例如 VDU1-0、VDU1-1)确定。因此,不使用 AutoScalingGroup 时,heal 操作不会更改 VNFC 的顺序。

此 userdata 脚本在 VNF 包中从 BaseHOT 创建调整后的 HOT,并将其用作 HEAT 模板。

以下显示了一个示例 BaseHOT 和调整后的 HOT。

BaseHOT

  • 顶部 HOT

    heat_template_version: 2013-05-23
    description: Test Base HOT
    
    parameters:
      nfv:
        type: json
    
    resources:
      VDU1:
        type: VDU1.yaml
        properties:
          name: { get_param: [ nfv, VDU, VDU1, computeName ] }
          flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] }
          image: { get_param: [ nfv, VDU, VDU1, vcImageId ] }
          zone: { get_param: [ nfv, VDU, VDU1, locationConstraints] }
          net: { get_param: [ nfv, CP, VDU1_CP1, network] }
    
  • 嵌套 HOT(上述顶部 HOT 中指定的 VDU1.yaml)

    heat_template_version: 2013-05-23
    description: 'VDU1 HOT for Sample VNF'
    
    parameters:
      name:
        type: string
      flavor:
        type: string
      image:
        type: string
      zone:
        type: string
      net:
        type: string
    
    resources:
      VDU1:
        type: OS::Nova::Server
        properties:
          name: { get_param: name }
          flavor: { get_param: flavor }
          image: { get_param: image }
          networks:
          - port:
              get_resource: VDU1_CP1
    
          availability_zone: { get_param: zone }
    
      VDU1_CP1:
        type: OS::Neutron::Port
        properties:
          network: { get_param: net }
    
  • 输入参数

    "nfv": {
      "VDU": {
        "VDU1": {
          "computeName": "VDU1",
          "computeFlavourId": "m1.tiny",
          "vcImageId": "6b8a14f0-1b40-418a-b650-ae4a0378daa5",
          "locationConstraints": "zone-x"
        }
      },
      "CP": {
        "VDU1_CP1": {
          "network": "67c837dc-c247-4a3e-ac0f-5603bfef1ba3"
        }
      }
    }
    

Adjusted HOT

  • 顶部 HOT

    heat_template_version: 2013-05-23
    description: Test Base HOT
    
    parameters:
      nfv:
        type: json
    
    resources:
      VDU1-0:
        type: VDU1.yaml
        properties:
          name: { get_param: [ nfv, VDU, VDU1-0, computeName ] }
          flavor: { get_param: [ nfv, VDU, VDU1-0, computeFlavourId ] }
          image: { get_param: [ nfv, VDU, VDU1-0, vcImageId ] }
          zone: { get_param: [ nfv, VDU, VDU1-0, locationConstraints ] }
          net: { get_param: [ nfv, CP, VDU1_CP1-0, network ] }
      VDU1-1:
        type: VDU1.yaml
        properties:
          name: { get_param: [ nfv, VDU, VDU1-1, computeName ] }
          flavor: { get_param: [ nfv, VDU,VDU1-1, computeFlavourId ] }
          image: { get_param: [ nfv, VDU,VDU1-1, vcImageId ] }
          zone: { get_param: [ nfv, VDU,VDU1-1, locationConstraints ] }
          net: { get_param: [ nfv, CP, VDU1_CP1-1,network ] }
    
  • 嵌套 HOT

    只有顶部 HOT 会更改为调整后的 HOT。嵌套 HOT 不会从 BaseHOT 更改。

  • 输入参数

    "nfv": {
      "VDU": {
        "VDU1-0": {
          "computeName": "VDU1-0",
          "computeFlavourId": "m1.tiny",
          "vcImageId": "6b8a14f0-1b40-418a-b650-ae4a0378daa5",
          "locationConstraints": "zone-x"
        },
        "VDU1-1": {
          "computeName": "VDU1-1",
          "computeFlavourId": "m1.large",
          "vcImageId": "0ef0597c-4aab-4235-8513-bf5d8304fe64",
          "locationConstraints": "zone-y"
        }
      },
      "CP": {
        "VDU1_CP1-0": {
          "network": "67c837dc-c247-4a3e-ac0f-5603bfef1ba3"
        },
        "VDU1_CP1-1": {
          "network": "4d8aa289-21eb-4997-86f2-49a884f78d0b"
        }
      }
    }
    

userdata 类的实用函数

Tacker 为 userdata 脚本提供以下实用函数。以下函数可以在 userdata 类中调用。

def get_vnfd(vnfd_id, csar_dir)

以 yaml 格式获取 vnfd。

vnf_id: vnfid,csar_dir: csar 的路径

它返回 Vnfd 类 的实例。

def init_nfv_dict(hot_template)

查找 HOT 模板中由 get_param 指定的参数,并获取 HEAT 输入参数的 nfv 结构的字典。

hot_template: yaml 格式的 HOT。

它返回 nfv 结构的字典

def get_param_flavor(vdu_name, req, vnfd, grant)

获取 VDU 的 flavor。如果 Grant 包含 flavor,则返回它。否则,从 vnfd 获取 flavor 并返回。

vdu_name: VDU 的名称,req: 对应于 API 请求的 operationParams,vnfd: vnfd,grant: Grants

它返回 vimFlavourId

def get_param_image(vdu_name, req, vnfd, grant)

获取 VDU 的软件镜像。如果 Grant 包含对应于 VDU 的 glance-imageId,则返回它。否则,从 vnfd 获取软件镜像的名称并返回。

vdu_name: VDU 的名称,req: 对应于 API 请求的 operationParams,vnfd: vnfd,grant: Grants

它返回 image IDimage name

def get_param_zone(vdu_name, grant_req, grant)

获取 VDU 的区域 ID。

vdu_name: VDU 的名称,req: 对应于 API 请求的 operationParams,vnfd: vnfd,grant: Grants

它返回 区域 ID

def get_current_capacity(vdu_name, inst)

获取期望的容量。

vdu_name: VDU 的名称,inst: VnfInstance

它返回 期望的容量

def get_param_capacity(vdu_name, inst, grant_req)

参考 grant 请求中的 addResources 和 removeResources,并获取期望的容量。

vdu_name: VDU 的名称,inst: VnfInstance,grant_req: GrantRequest

它返回 期望的容量

def _get_fixed_ips_from_extcp(extcp)

从 extcp 获取固定的地址和子网。extcp 是 instantiateVnfRequest > extVirtualLinks > extcps,定义在 ETSI NFV SOL003 中。

它返回固定地址和子网的列表。

def get_param_network(cp_name, grant, req)

获取 CP 的网络 resourceId。

cp_name: CP 的名称,grant: Grants,req: 对应于 API 请求的 operationParams

它返回网络 resourceId。

def get_param_fixed_ips(cp_name, grant, req)

获取 CP 的固定 IP 地址。

cp_name: CP 的名称,grant: Grants,req: 对应于 API 请求的 operationParams

它返回 CP 的固定 IP 地址。

def get_param_network_from_inst(cp_name, inst)

从 VnfInstance 获取网络 resourceId。

cp_name: CP 的名称,inst: VnfInstance

它返回 VnfInstance 中的网络 resourceId。

def get_param_fixed_ips_from_inst(cp_name, inst)

从 VnfInstance 获取 CP 的固定 IP 地址。

cp_name: CP 的名称,inst: VnfInstance

它返回 VnfInstance 中的 CP 的固定 IP 地址。

def apply_ext_managed_vls(hot_dict, req, grant)

修改 HOT 以应用外部提供的 extmanaged 内部虚拟链路 (extmanagedVL)。

ExtmanagedVL 由 VNFM 在实例化 VNF 时创建,或由 Grants 或 InstantiateVnfRequest 外部创建并指定。由于一个 HOT 只能对应于其中一种情况,因此此函数将前者的情况修改为后者的情况。

以下显示示例 HOT 描述。

  • 输入 HOT

heat_template_version: 2013-05-23
description: 'Simple Base HOT for Sample VNF'

resources:
  VDU1:
    type: OS::Nova::Server
    properties:
      flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
      image: { get_param: [ nfv, VDU, VDU2, vcImageId] }
      networks:
      - port:
          get_resource: VDU1_CP1

  VDU1_CP1:
    type: OS::Neutron::Port
    properties:
      network: { get_resource: internalVL1 }

  internalVL1:
    type: OS::Neutron::Net

outputs: {}
  • 输出 HOT

heat_template_version: 2013-05-23
description: 'Simple Base HOT for Sample VNF'

resources:
  VDU1:
    type: OS::Nova::Server
    properties:
      flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] }
      image: { get_param: [ nfv, VDU, VDU2, vcImageId] }
      networks:
      - port:
          get_resource: VDU1_CP1


  VDU1_CP1:
    type: OS::Neutron::Port
    properties:
      network: network_id

outputs: {}

vnfd.get_base_hot(flavour_id)

获取 HOT 字典。

flavour_id: vnf 实例的 flavour_id。

它返回具有以下结构的 HOT 字典:dict = {‘template’:tophot, ‘Files’{file 1:, file2:…}}

vnf_instance_utils.json_merge_patch(target, patch)

获取 json_merge_patch (IETF RFC 7396) 的结果。

target: 合并目标,patch: 应用的补丁

它返回 json_merge_patch (IETF RFC 7396) 的结果。