Support LCM operation with user data

Implement new method of VNF lifecycle management using LCM operation
user data.

Change-Id: Ib2fbe341b5d26758f0b48dc19e3e05810c2c830f
Blueprint: support-etsi-nfv-specs
changes/12/710012/14
Hiroo Kitamura 2020-04-24 11:37:45 +09:00
parent 428096a247
commit 62813239e1
45 changed files with 2317 additions and 6 deletions

View File

@ -108,6 +108,10 @@ class VNFNotFound(exceptions.NotFound):
message = _('VNF %(vnf_id)s could not be found')
class LCMUserDataFailed(exceptions.TackerException):
message = _('LCM user data %(reason)s')
class ParamYAMLNotWellFormed(exceptions.InvalidInput):
message = _("Parameter YAML not well formed - %(error_msg_details)s")

View File

@ -0,0 +1,94 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: >
Template for test _generate_hot_from_tosca().
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- sample_lcm_with_user_data_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: ntt.nslab.VNF
properties:
flavour_id: simple
requirements:
virtual_link_external: [ CP1, virtual_link ]
node_templates:
VNF:
type: ntt.nslab.VNF
properties:
flavour_description: A simple flavour
interfaces:
Vnflcm:
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_end: []
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU1
description: VDU1 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 1
sw_image_data:
name: Software of VDU1
version: '0.4.0'
checksum:
algorithm: sha-512
hash: 6513f21e44aa3da349f248188a44bc304a3653a04122d8fb4535423c8e1d14cd6a153f735bb0982e2161b5b5186106570c17a9e58b64dd39390617cd5a350f78
container_format: bare
disk_format: qcow2
min_disk: 1 GiB
size: 1 GiB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.4.0-x86_64-disk.img
capabilities:
virtual_compute:
properties:
virtual_memory:
virtual_mem_size: 512 MiB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 1 GiB
CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1

View File

@ -0,0 +1,31 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Sample VNFD for LCM with user data
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- sample_lcm_with_user_data_types.yaml
- sample_lcm_with_user_data_df_simple.yaml
topology_template:
inputs:
selected_flavour:
type: string
description: VNF deployment flavour selected by the consumer. It is provided in the API
node_templates:
VNF:
type: ntt.nslab.VNF
properties:
flavour_id: { get_input: selected_flavour }
descriptor_id: eeeeeeee-ebca-4fa7-95ed-4840d70a1111
provider: NTT NS lab
product_name: Sample VNF for LCM with user data
software_version: '1.0'
descriptor_version: '1.0'
vnfm_info:
- Tacker
requirements:
#- virtual_link_external # mapped in lower-level templates
#- virtual_link_internal # mapped in lower-level templates

View File

@ -0,0 +1,53 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: VNF type definition
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
ntt.nslab.VNF:
derived_from: tosca.nodes.nfv.VNF
properties:
descriptor_id:
type: string
constraints: [ valid_values: [ eeeeeeee-ebca-4fa7-95ed-4840d70a1111 ] ]
default: eeeeeeee-ebca-4fa7-95ed-4840d70a1111
descriptor_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
provider:
type: string
constraints: [ valid_values: [ 'NTT NS lab' ] ]
default: 'NTT NS lab'
product_name:
type: string
constraints: [ valid_values: [ 'Sample VNF for LCM with user data' ] ]
default: 'Sample VNF for LCM with user data'
software_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
vnfm_info:
type: list
entry_schema:
type: string
constraints: [ valid_values: [ Tacker ] ]
default: [ Tacker ]
flavour_id:
type: string
constraints: [ valid_values: [ simple ] ]
default: simple
flavour_description:
type: string
default: "falvour"
requirements:
- virtual_link_external:
capability: tosca.capabilities.nfv.VirtualLinkable
- virtual_link_internal:
capability: tosca.capabilities.nfv.VirtualLinkable
interfaces:
Vnflcm:
type: tosca.interfaces.nfv.Vnflcm

View File

@ -0,0 +1,32 @@
heat_template_version: 2013-05-23
description: 'Template for test _generate_hot_from_tosca().'
parameters:
nfv:
type: json
resources:
VDU1:
type: OS::Nova::Server
properties:
# flavor:
# get_resource: VDU1_flavor
name: VDU1
image: { get_param: [ nfv, VDU, VDU1, image ] }
networks:
- port:
get_resource: CP1
CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, CP1, network ] }
VDU1_flavor:
type: OS::Nova::Flavor
properties:
ram: { get_param: [ nfv, VDU, VDU1, flavor, ram ] }
vcpus: { get_param: [ nfv, VDU, VDU1, flavor, vcpus ] }
disk: { get_param: [ nfv, VDU, VDU1, flavor, disk ] }
outputs: {}

View File

@ -0,0 +1,7 @@
TOSCA-Meta-File-Version: 1.0
Created-by: Dummy User
CSAR-Version: 1.1
Entry-Definitions: Definitions/sample_lcm_with_user_data_top.vnfd.yaml
Name: Files/images/cirros-0.4.0-x86_64-disk.img
Content-type: application/x-iso9066-image

View File

@ -0,0 +1,39 @@
#
# 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 tacker.vnfm.lcm_user_data.utils as UserDataUtil
from tacker.vnfm.lcm_user_data.abstract_user_data import AbstractUserData
class SampleUserData(AbstractUserData):
@staticmethod
def instantiate(base_hot_dict=None,
vnfd_dict=None,
inst_req_info=None,
grant_info=None):
# Create HOT input parameter using util functions.
initial_param_dict = UserDataUtil.create_initial_param_dict(
base_hot_dict)
vdu_flavor_dict = UserDataUtil.create_vdu_flavor_dict(vnfd_dict)
vdu_image_dict = UserDataUtil.create_vdu_image_dict(grant_info)
cpd_vl_dict = UserDataUtil.create_cpd_vl_dict(
base_hot_dict, inst_req_info)
final_param_dict = UserDataUtil.create_final_param_dict(
initial_param_dict, vdu_flavor_dict, vdu_image_dict, cpd_vl_dict)
return final_param_dict

View File

@ -0,0 +1,26 @@
#
# 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.
from tacker.vnfm.lcm_user_data.abstract_user_data import AbstractUserData
class SampleUserData(AbstractUserData):
@staticmethod
def instantiate(base_hot_dict=None,
vnfd_dict=None,
inst_req_info=None,
grant_info=None):
# return non dict value
return 'DUMMY'

View File

@ -0,0 +1,32 @@
heat_template_version: 2013-05-23
description: 'Template for test _generate_hot_from_tosca().'
parameters:
nfv:
type: json
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor:
get_resource: VDU1_flavor
name: VDU1
image: { get_param: [ nfv, VDU, VDU1, image ] }
networks:
- port:
get_resource: CP1
CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, CP1, network ] }
VDU1_flavor:
type: OS::Nova::Flavor
properties:
ram: { get_param: [ nfv, VDU, VDU1, flavor, ram ] }
vcpus: { get_param: [ nfv, VDU, VDU1, flavor, vcpus ] }
disk: { get_param: [ nfv, VDU, VDU1, flavor, disk ] }
outputs: {}

View File

@ -0,0 +1,7 @@
TOSCA-Meta-File-Version: 1.0
Created-by: Dummy User
CSAR-Version: 1.1
Entry-Definitions: Definitions/sample_lcm_with_user_data_top.vnfd.yaml
Name: Files/images/cirros-0.4.0-x86_64-disk.img
Content-type: application/x-iso9066-image

View File

@ -0,0 +1,39 @@
#
# 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 tacker.vnfm.lcm_user_data.utils as UserDataUtil
from tacker.vnfm.lcm_user_data.abstract_user_data import AbstractUserData
class SampleUserData(AbstractUserData):
@staticmethod
def instantiate(base_hot_dict=None,
vnfd_dict=None,
inst_req_info=None,
grant_info=None):
# Create HOT input parameter using util functions.
initial_param_dict = UserDataUtil.create_initial_param_dict(
base_hot_dict)
vdu_flavor_dict = UserDataUtil.create_vdu_flavor_dict(vnfd_dict)
vdu_image_dict = UserDataUtil.create_vdu_image_dict(grant_info)
cpd_vl_dict = UserDataUtil.create_cpd_vl_dict(
base_hot_dict, inst_req_info)
final_param_dict = UserDataUtil.create_final_param_dict(
initial_param_dict, vdu_flavor_dict, vdu_image_dict, cpd_vl_dict)
return final_param_dict

View File

@ -0,0 +1,32 @@
heat_template_version: 2013-05-23
description: 'Template for test _generate_hot_from_tosca().'
parameters:
nfv:
type: json
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor:
get_resource: VDU1_flavor
name: VDU1
image: { get_param: [ nfv, VDU, VDU1, image ] }
networks:
- port:
get_resource: CP1
CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, CP1, network ] }
VDU1_flavor:
type: OS::Nova::Flavor
properties:
ram: { get_param: [ nfv, VDU, VDU1, flavor, ram ] }
vcpus: { get_param: [ nfv, VDU, VDU1, flavor, vcpus ] }
disk: { get_param: [ nfv, VDU, VDU1, flavor, disk ] }
outputs: {}

View File

@ -0,0 +1,7 @@
TOSCA-Meta-File-Version: 1.0
Created-by: Dummy User
CSAR-Version: 1.1
Entry-Definitions: Definitions/sample_lcm_with_user_data_top.vnfd.yaml
Name: Files/images/cirros-0.4.0-x86_64-disk.img
Content-type: application/x-iso9066-image

View File

@ -0,0 +1,39 @@
#
# 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 tacker.vnfm.lcm_user_data.utils as UserDataUtil
from tacker.vnfm.lcm_user_data.abstract_user_data import AbstractUserData
class SampleUserData(AbstractUserData):
@staticmethod
def instantiate(base_hot_dict=None,
vnfd_dict=None,
inst_req_info=None,
grant_info=None):
# Create HOT input parameter using util functions.
initial_param_dict = UserDataUtil.create_initial_param_dict(
base_hot_dict)
# vdu_flavor_dict = UserDataUtil.create_vdu_flavor_dict(vnfd_dict)
# vdu_image_dict = UserDataUtil.create_vdu_image_dict(grant_info)
# cpd_vl_dict = UserDataUtil.create_cpd_vl_dict(
# base_hot_dict, inst_req_info)
#
# final_param_dict = UserDataUtil.create_final_param_dict(
# initial_param_dict, vdu_flavor_dict, vdu_image_dict, cpd_vl_dict)
return initial_param_dict

View File

@ -0,0 +1,32 @@
heat_template_version: 2013-05-23
description: 'Template for test _generate_hot_from_tosca().'
parameters:
nfv:
type: json
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor:
get_resource: VDU1_flavor
name: VDU1
image: { get_param: [ nfv, VDU, VDU1, image ] }
networks:
- port:
get_resource: CP1
CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, CP1, network ] }
VDU1_flavor:
type: OS::Nova::Flavor
properties:
ram: { get_param: [ nfv, VDU, VDU1, flavor, ram ] }
vcpus: { get_param: [ nfv, VDU, VDU1, flavor, vcpus ] }
disk: { get_param: [ nfv, VDU, VDU1, flavor, disk ] }
outputs: {}

View File

@ -0,0 +1,7 @@
TOSCA-Meta-File-Version: 1.0
Created-by: Dummy User
CSAR-Version: 1.1
Entry-Definitions: Definitions/sample_lcm_with_user_data_top.vnfd.yaml
Name: Files/images/cirros-0.4.0-x86_64-disk.img
Content-type: application/x-iso9066-image

View File

@ -0,0 +1,28 @@
#
# 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.
from tacker.extensions import vnfm
from tacker.vnfm.lcm_user_data.abstract_user_data import AbstractUserData
class SampleUserData(AbstractUserData):
@staticmethod
def instantiate(base_hot_dict=None,
vnfd_dict=None,
inst_req_info=None,
grant_info=None):
error_reason = _(
"invalid user data script.")
raise vnfm.LCMUserDataFailed(reason=error_reason)

View File

@ -0,0 +1,32 @@
heat_template_version: 2013-05-23
description: 'Template for test _generate_hot_from_tosca().'
parameters:
nfv:
type: json
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor:
get_resource: VDU1_flavor
name: VDU1
image: { get_param: [ nfv, VDU, VDU1, image ] }
networks:
- port:
get_resource: CP1
CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, CP1, network ] }
VDU1_flavor:
type: OS::Nova::Flavor
properties:
ram: { get_param: [ nfv, VDU, VDU1, flavor, ram ] }
vcpus: { get_param: [ nfv, VDU, VDU1, flavor, vcpus ] }
disk: { get_param: [ nfv, VDU, VDU1, flavor, disk ] }
outputs: {}

View File

@ -0,0 +1,7 @@
TOSCA-Meta-File-Version: 1.0
Created-by: Dummy User
CSAR-Version: 1.1
Entry-Definitions: Definitions/sample_lcm_with_user_data_top.vnfd.yaml
Name: Files/images/cirros-0.4.0-x86_64-disk.img
Content-type: application/x-iso9066-image

View File

@ -0,0 +1,32 @@
heat_template_version: 2013-05-23
description: 'Template for test _generate_hot_from_tosca().'
parameters:
nfv:
type: json
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor:
get_resource: VDU1_flavor
name: VDU1
image: { get_param: [ nfv, VDU, VDU1, image ] }
networks:
- port:
get_resource: CP1
CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, CP1, network ] }
VDU1_flavor:
type: OS::Nova::Flavor
properties:
ram: { get_param: [ nfv, VDU, VDU1, flavor, ram ] }
vcpus: { get_param: [ nfv, VDU, VDU1, flavor, vcpus ] }
disk: { get_param: [ nfv, VDU, VDU1, flavor, disk ] }
outputs: {}

View File

@ -0,0 +1,7 @@
TOSCA-Meta-File-Version: 1.0
Created-by: Dummy User
CSAR-Version: 1.1
Entry-Definitions: Definitions/sample_lcm_with_user_data_top.vnfd.yaml
Name: Files/images/cirros-0.4.0-x86_64-disk.img
Content-type: application/x-iso9066-image

View File

@ -0,0 +1,51 @@
#
# 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 time
from oslo_log import log as logging
import tacker.vnfm.lcm_user_data.utils as UserDataUtil
from tacker.vnfm.lcm_user_data.abstract_user_data import AbstractUserData
from tacker.vnfm.lcm_user_data.constants import USER_DATA_TIMEOUT
LOG = logging.getLogger(__name__)
class SampleUserData(AbstractUserData):
@staticmethod
def instantiate(base_hot_dict=None,
vnfd_dict=None,
inst_req_info=None,
grant_info=None):
# Sleep more than timeout.
LOG.debug('Sleep start.')
time.sleep(USER_DATA_TIMEOUT + 60)
LOG.debug('Sleep end.')
# Create HOT input parameter using util functions.
initial_param_dict = UserDataUtil.create_initial_param_dict(
base_hot_dict)
vdu_flavor_dict = UserDataUtil.create_vdu_flavor_dict(vnfd_dict)
vdu_image_dict = UserDataUtil.create_vdu_image_dict(grant_info)
cpd_vl_dict = UserDataUtil.create_cpd_vl_dict(
base_hot_dict, inst_req_info)
final_param_dict = UserDataUtil.create_final_param_dict(
initial_param_dict, vdu_flavor_dict, vdu_image_dict, cpd_vl_dict)
return final_param_dict

View File

@ -0,0 +1,32 @@
heat_template_version: 2013-05-23
description: 'Template for test _generate_hot_from_tosca().'
parameters:
nfv:
type: json
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor:
get_resource: VDU1_flavor
name: VDU1
image: { get_param: [ nfv, VDU, VDU1, image ] }
networks:
- port:
get_resource: CP1
CP1:
type: OS::Neutron::Port
properties:
network: { get_param: [ nfv, CP, CP1, network ] }
VDU1_flavor:
type: OS::Nova::Flavor
properties:
ram: { get_param: [ nfv, VDU, VDU1, flavor, ram ] }
vcpus: { get_param: [ nfv, VDU, VDU1, flavor, vcpus ] }
disk: { get_param: [ nfv, VDU, VDU1, flavor, disk ] }
outputs: {}

View File

@ -0,0 +1,34 @@
{
"flavourId": "simple",
"instantiationLevelId": "instantiation_level_1",
"extVirtualLinks": [
{
"id": "ext-vl-uuid-VL1",
"resourceId": "neutron-network-uuid_VL1",
"extCps": [
{
"cpdId": "CP1",
"cpConfig": [
{
"cpProtocolData": [
{
"layerProtocol": "IP_OVER_ETHERNET"
}
]
}
]
}
]
}
],
"vimConnectionInfo": [
{
"id": "vim-uuid",
"vimType": "openstack"
}
],
"additionalParams":{
"lcm-operation-user-data":"./UserData/lcm_user_data.py",
"lcm-operation-user-data-class":"SampleUserData"
}
}

View File

@ -0,0 +1,94 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: >
Template for test _generate_hot_from_tosca().
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- sample_lcm_with_user_data_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: ntt.nslab.VNF
properties:
flavour_id: simple
requirements:
virtual_link_external: [ CP1, virtual_link ]
node_templates:
VNF:
type: ntt.nslab.VNF
properties:
flavour_description: A simple flavour
interfaces:
Vnflcm:
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_end: []
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU1
description: VDU1 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 1
sw_image_data:
name: Software of VDU1
version: '0.4.0'
checksum:
algorithm: sha-256
hash: cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
container_format: bare
disk_format: qcow2
min_disk: 1 GiB
size: 1 GiB
artifacts:
sw_image:
type: tosca.artifacts.nfv.SwImage
file: ../Files/images/cirros-0.4.0-x86_64-disk.img
capabilities:
virtual_compute:
properties:
virtual_memory:
virtual_mem_size: 512 MiB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 1 GiB
CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1

View File

@ -0,0 +1,501 @@
#
# 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 os
import tempfile
import time
import yaml
import zipfile
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from tacker.objects import fields
from tacker.tests.functional import base
from tacker.tests import utils
VNF_PACKAGE_UPLOAD_TIMEOUT = 60
VNF_INSTANTIATE_TIMEOUT = 60
VNF_TERMINATE_TIMEOUT = 60
VNF_INSTANTIATE_ERROR_WAIT = 80
VNF_DELETE_COMPLETION_WAIT = 60
def _get_external_virtual_links(net0_id):
return [
{
"id": "net0",
"resourceId": net0_id,
"extCps": [{
"cpdId": "CP1",
"cpConfig": [{
"cpProtocolData": [{
"layerProtocol": "IP_OVER_ETHERNET",
}]
}]
}]
}
]
def _create_csar_with_unique_vnfd_id(csar_dir):
unique_id = uuidutils.generate_uuid()
tempfd, tempname = tempfile.mkstemp(suffix=".zip",
dir=os.path.dirname(csar_dir))
os.close(tempfd)
common_dir = os.path.join(csar_dir, "../common/")
ud_common_dir = os.path.join(csar_dir, "../user_data_common/")
zcsar = zipfile.ZipFile(tempname, 'w')
target_dir_list = [csar_dir, common_dir, ud_common_dir]
_write_zipfile(zcsar, unique_id, target_dir_list)
zcsar.close()
return tempname, unique_id
def _write_zipfile(zcsar, unique_id, target_dir_list):
for target_dir in target_dir_list:
for (dpath, _, fnames) in os.walk(target_dir):
if not fnames:
continue
for fname in fnames:
src_file = os.path.join(dpath, fname)
dst_file = os.path.relpath(
os.path.join(dpath, fname), target_dir)
if fname.endswith('.yaml') or fname.endswith('.yml'):
with open(src_file, 'rb') as yfile:
data = yaml.safe_load(yfile)
utils._update_unique_id_in_yaml(data, unique_id)
zcsar.writestr(dst_file, yaml.dump(
data, default_flow_style=False,
allow_unicode=True))
else:
zcsar.write(src_file, dst_file)
def _create_and_upload_vnf_package(tacker_client, csar_package_name,
user_defined_data):
# create vnf package
body = jsonutils.dumps({"userDefinedData": user_defined_data})
resp, vnf_package = tacker_client.do_request(
'/vnfpkgm/v1/vnf_packages', "POST", body=body)
# upload vnf package
csar_package_path = \
"../../etc/samples/etsi/nfv/%s" % csar_package_name
file_path = os.path.abspath(os.path.join(os.path.dirname(__file__),
csar_package_path))
# Generating unique vnfd id. This is required when multiple workers
# are running concurrently. The call below creates a new temporary
# CSAR with unique vnfd id.
file_path, uniqueid = _create_csar_with_unique_vnfd_id(file_path)
with open(file_path, 'rb') as file_object:
resp, resp_body = tacker_client.do_request(
'/vnfpkgm/v1/vnf_packages/{id}/package_content'.format(
id=vnf_package['id']),
"PUT", body=file_object, content_type='application/zip')
# wait for onboard
timeout = VNF_PACKAGE_UPLOAD_TIMEOUT
start_time = int(time.time())
show_url = os.path.join('/vnfpkgm/v1/vnf_packages', vnf_package['id'])
vnfd_id = None
while True:
resp, body = tacker_client.do_request(show_url, "GET")
if body['onboardingState'] == "ONBOARDED":
vnfd_id = body['vnfdId']
break
if ((int(time.time()) - start_time) > timeout):
raise Exception("Failed to onboard vnf package")
time.sleep(1)
# remove temporarily created CSAR file
os.remove(file_path)
return vnf_package['id'], vnfd_id
class VnfLcmWithUserDataTest(base.BaseTackerTest):
def setUp(self):
super(VnfLcmWithUserDataTest, self).setUp()
self.tacker_client = base.BaseTackerTest.tacker_http_client()
self.base_url = "/vnflcm/v1/vnf_instances"
vim_list = self.client.list_vims()
self.vim = self.get_vim(vim_list, 'VIM0')
if not self.vim:
assert False, "vim_list is Empty: Default VIM is missing"
neutron_client = self.neutronclient()
net = neutron_client.list_networks()
networks = {}
for network in net['networks']:
networks[network['name']] = network['id']
net0_id = networks.get('net0')
if not net0_id:
self.fail("net0 network is not available")
self.ext_vl = _get_external_virtual_links(net0_id)
def _create_instantiate_vnf_request_body(self, flavour_id,
instantiation_level_id=None, vim_id=None, ext_vl=None,
add_params=None):
request_body = {"flavourId": flavour_id}
if instantiation_level_id:
request_body["instantiationLevelId"] = instantiation_level_id
if ext_vl:
request_body["extVirtualLinks"] = ext_vl
if vim_id:
request_body["vimConnectionInfo"] = [
{"id": uuidutils.generate_uuid(),
"vimId": vim_id,
"vimType": "ETSINFV.OPENSTACK_KEYSTONE.v_2"}]
if add_params:
request_body["additionalParams"] = add_params
return request_body
def _create_vnf_instance(self, vnfd_id, vnf_instance_name=None,
vnf_instance_description=None):
request_body = {'vnfdId': vnfd_id}
if vnf_instance_name:
request_body['vnfInstanceName'] = vnf_instance_name
if vnf_instance_description:
request_body['vnfInstanceDescription'] = vnf_instance_description
resp, response_body = self.http_client.do_request(
self.base_url, "POST", body=jsonutils.dumps(request_body))
return resp, response_body
def _delete_vnf_instance(self, id):
url = os.path.join(self.base_url, id)
resp, body = self.http_client.do_request(url, "DELETE")
self.assertEqual(204, resp.status_code)
# verify vnf instance is deleted
url = os.path.join(self.base_url, id)
resp, body = self.http_client.do_request(url, "GET")
self.assertEqual(404, resp.status_code)
def _show_vnf_instance(self, id, expected_result=None):
show_url = os.path.join(self.base_url, id)
resp, vnf_instance = self.http_client.do_request(show_url, "GET")
self.assertEqual(200, resp.status_code)
if expected_result:
self.assertDictSupersetOf(expected_result, vnf_instance)
return vnf_instance
def _vnf_instance_wait(self, id,
instantiation_state=fields.VnfInstanceState.INSTANTIATED,
timeout=VNF_INSTANTIATE_TIMEOUT):
show_url = os.path.join(self.base_url, id)
start_time = int(time.time())
while True:
resp, body = self.http_client.do_request(show_url, "GET")
if body['instantiationState'] == instantiation_state:
break
if ((int(time.time()) - start_time) > timeout):
error = ("Vnf instance %(id)s status is %(current)s, "
"expected status should be %(expected)s")
self.fail(error % {"id": id,
"current": body['instantiationState'],
"expected": instantiation_state})
time.sleep(5)
def _vnf_instance_wait_until_fail_detected(self, id,
instantiation_state=fields.VnfInstanceState.NOT_INSTANTIATED,
timeout=VNF_INSTANTIATE_ERROR_WAIT):
show_url = os.path.join(self.base_url, id)
time.sleep(VNF_INSTANTIATE_ERROR_WAIT)
resp, body = self.http_client.do_request(show_url, "GET")
if body['instantiationState'] != instantiation_state:
error = ("Vnf instance %(id)s status is %(current)s, "
"expected status should be %(expected)s")
self.fail(error % {"id": id,
"current": body['instantiationState'],
"expected": instantiation_state})
def _instantiate_vnf_instance(self, id, request_body):
url = os.path.join(self.base_url, id, "instantiate")
resp, body = self.http_client.do_request(url, "POST",
body=jsonutils.dumps(request_body))
self.assertEqual(202, resp.status_code)
self._vnf_instance_wait(id)
def _instantiate_vnf_instance_fail(self, id, request_body):
url = os.path.join(self.base_url, id, "instantiate")
resp, body = self.http_client.do_request(url, "POST",
body=jsonutils.dumps(request_body))
self.assertEqual(202, resp.status_code)
# Confirm that the state doesn't change from NOT_INSTANTIATED.
self._vnf_instance_wait_until_fail_detected(id)
def _terminate_vnf_instance(self, id, request_body):
url = os.path.join(self.base_url, id, "terminate")
resp, body = self.http_client.do_request(url, "POST",
body=jsonutils.dumps(request_body))
self.assertEqual(202, resp.status_code)
timeout = request_body.get('gracefulTerminationTimeout')
start_time = int(time.time())
self._vnf_instance_wait(id,
instantiation_state=fields.VnfInstanceState.NOT_INSTANTIATED,
timeout=VNF_TERMINATE_TIMEOUT)
# If gracefulTerminationTimeout is set, check whether vnf
# instantiation_state is set to NOT_INSTANTIATED after
# gracefulTerminationTimeout seconds.
if timeout and int(time.time()) - start_time < timeout:
self.fail("Vnf is terminated before graceful termination"
"timeout period")
# wait for status completion
time.sleep(VNF_DELETE_COMPLETION_WAIT)
def _delete_vnf_package(self, vnf_package_id):
url = '/vnfpkgm/v1/vnf_packages/%s' % vnf_package_id
# Update vnf package before delete
req_body = jsonutils.dumps({"operationalState": "DISABLED"})
self.tacker_client.do_request(url, "PATCH", body=req_body)
# Delete vnf package before delete
self.tacker_client.do_request(url, "DELETE")
def test_instantiate_vnf_normal(self):
# Create vnf package
sample_name = "user_data_sample_normal"
vnf_package_id, vnfd_id = _create_and_upload_vnf_package(
self.tacker_client, sample_name, {"key": sample_name})
# Reserve deleting vnf package
self.addCleanup(self._delete_vnf_package, vnf_package_id)
# Settings
vnf_instance_name = "vnf_with_user_data-%s" % \
uuidutils.generate_uuid()
vnf_instance_description = "vnf_with_user_data_normal"
add_params = {
"lcm-operation-user-data": "./UserData/lcm_user_data.py",
"lcm-operation-user-data-class": "SampleUserData"}
# Create vnf instance
resp, vnf_instance = self._create_vnf_instance(vnfd_id,
vnf_instance_name=vnf_instance_name,
vnf_instance_description=vnf_instance_description)
self.assertIsNotNone(vnf_instance['id'])
self.assertEqual(201, resp.status_code)
# Reserve deleting vnf instance
self.addCleanup(self._delete_vnf_instance, vnf_instance['id'])
request_body = self._create_instantiate_vnf_request_body("simple",
vim_id=self.vim['id'], ext_vl=self.ext_vl, add_params=add_params)
self._instantiate_vnf_instance(vnf_instance['id'], request_body)
vnf_instance = self._show_vnf_instance(vnf_instance['id'])
vdu_count = len(vnf_instance['instantiatedVnfInfo']
['vnfcResourceInfo'])
self.assertEqual(1, vdu_count)
# Terminate vnf forcefully
terminate_req_body = {
"terminationType": fields.VnfInstanceTerminationType.FORCEFUL
}
self._terminate_vnf_instance(vnf_instance['id'], terminate_req_body)
def test_instantiate_vnf_basehot_invalid(self):
# Create vnf package
sample_name = "user_data_sample_basehot_invalid"
vnf_package_id, vnfd_id = _create_and_upload_vnf_package(
self.tacker_client, sample_name, {"key": sample_name})
# Reserve deleting vnf package
self.addCleanup(self._delete_vnf_package, vnf_package_id)
# Settings
vnf_instance_name = "vnf_with_user_data-%s" % \
uuidutils.generate_uuid()
vnf_instance_description = "vnf_with_user_data_basehot_invalid"
add_params = {
"lcm-operation-user-data": "./UserData/lcm_user_data.py",
"lcm-operation-user-data-class": "SampleUserData"}
# Create vnf instance
resp, vnf_instance = self._create_vnf_instance(vnfd_id,
vnf_instance_name=vnf_instance_name,
vnf_instance_description=vnf_instance_description)
self.assertIsNotNone(vnf_instance['id'])
self.assertEqual(201, resp.status_code)
# Reserve deleting vnf instance
self.addCleanup(self._delete_vnf_instance, vnf_instance['id'])
request_body = self._create_instantiate_vnf_request_body("simple",
vim_id=self.vim['id'], ext_vl=self.ext_vl, add_params=add_params)
self._instantiate_vnf_instance_fail(vnf_instance['id'], request_body)
def test_instantiate_vnf_userdata_timeout(self):
# Create vnf package
sample_name = "user_data_sample_userdata_timeout"
vnf_package_id, vnfd_id = _create_and_upload_vnf_package(
self.tacker_client, sample_name, {"key": sample_name})
# Reserve deleting vnf package
self.addCleanup(self._delete_vnf_package, vnf_package_id)
# Settings
vnf_instance_name = "vnf_with_user_data-%s" % \
uuidutils.generate_uuid()
vnf_instance_description = "vnf_with_user_data_timeout"
add_params = {
"lcm-operation-user-data": "./UserData/lcm_user_data_sleeping.py",
"lcm-operation-user-data-class": "SampleUserData"}
# Create vnf instance
resp, vnf_instance = self._create_vnf_instance(vnfd_id,
vnf_instance_name=vnf_instance_name,
vnf_instance_description=vnf_instance_description)
self.assertIsNotNone(vnf_instance['id'])
self.assertEqual(201, resp.status_code)
# Reserve deleting vnf instance
self.addCleanup(self._delete_vnf_instance, vnf_instance['id'])
request_body = self._create_instantiate_vnf_request_body("simple",
vim_id=self.vim['id'], ext_vl=self.ext_vl, add_params=add_params)
self._instantiate_vnf_instance_fail(vnf_instance['id'], request_body)
def test_instantiate_vnf_userdata_invalid_hot_param(self):
# Create vnf package
sample_name = "user_data_sample_userdata_invalid_hot_param"
vnf_package_id, vnfd_id = _create_and_upload_vnf_package(
self.tacker_client, sample_name, {"key": sample_name})
# Reserve deleting vnf package
self.addCleanup(self._delete_vnf_package, vnf_package_id)
# Settings
vnf_instance_name = "vnf_with_user_data-%s" % \
uuidutils.generate_uuid()
vnf_instance_description = "vnf_with_user_data_timeout"
add_params = {
"lcm-operation-user-data": "./UserData/"
"lcm_user_data_invalid_hot_param.py",
"lcm-operation-user-data-class": "SampleUserData"}
# Create vnf instance
resp, vnf_instance = self._create_vnf_instance(vnfd_id,
vnf_instance_name=vnf_instance_name,
vnf_instance_description=vnf_instance_description)
self.assertIsNotNone(vnf_instance['id'])
self.assertEqual(201, resp.status_code)
# Reserve deleting vnf instance
self.addCleanup(self._delete_vnf_instance, vnf_instance['id'])
request_body = self._create_instantiate_vnf_request_body("simple",
vim_id=self.vim['id'], ext_vl=self.ext_vl, add_params=add_params)
self._instantiate_vnf_instance_fail(vnf_instance['id'], request_body)
def test_instantiate_vnf_userdata_none(self):
# Create vnf package
sample_name = "user_data_sample_userdata_none"
vnf_package_id, vnfd_id = _create_and_upload_vnf_package(
self.tacker_client, sample_name, {"key": sample_name})
# Reserve deleting vnf package
self.addCleanup(self._delete_vnf_package, vnf_package_id)
# Settings
vnf_instance_name = "vnf_with_user_data-%s" % \
uuidutils.generate_uuid()
vnf_instance_description = "vnf_with_user_data_timeout"
add_params = {
"lcm-operation-user-data": "./UserData/lcm_user_data.py",
"lcm-operation-user-data-class": "SampleUserData"}
# Create vnf instance
resp, vnf_instance = self._create_vnf_instance(vnfd_id,
vnf_instance_name=vnf_instance_name,
vnf_instance_description=vnf_instance_description)
self.assertIsNotNone(vnf_instance['id'])
self.assertEqual(201, resp.status_code)
# Reserve deleting vnf instance
self.addCleanup(self._delete_vnf_instance, vnf_instance['id'])
request_body = self._create_instantiate_vnf_request_body("simple",
vim_id=self.vim['id'], ext_vl=self.ext_vl, add_params=add_params)
self._instantiate_vnf_instance_fail(vnf_instance['id'], request_body)
def test_instantiate_vnf_userdata_invalid_script(self):
# Create vnf package
sample_name = "user_data_sample_userdata_invalid_script"
vnf_package_id, vnfd_id = _create_and_upload_vnf_package(
self.tacker_client, sample_name, {"key": sample_name})
# Reserve deleting vnf package
self.addCleanup(self._delete_vnf_package, vnf_package_id)
# Settings
vnf_instance_name = "vnf_with_user_data-%s" % \
uuidutils.generate_uuid()
vnf_instance_description = "vnf_with_user_data_timeout"
add_params = {
"lcm-operation-user-data": "./UserData/"
"lcm_user_data_invalid_script.py",
"lcm-operation-user-data-class": "SampleUserData"}
# Create vnf instance
resp, vnf_instance = self._create_vnf_instance(vnfd_id,
vnf_instance_name=vnf_instance_name,
vnf_instance_description=vnf_instance_description)
self.assertIsNotNone(vnf_instance['id'])
self.assertEqual(201, resp.status_code)
# Reserve deleting vnf instance
self.addCleanup(self._delete_vnf_instance, vnf_instance['id'])
request_body = self._create_instantiate_vnf_request_body("simple",
vim_id=self.vim['id'], ext_vl=self.ext_vl, add_params=add_params)
self._instantiate_vnf_instance_fail(vnf_instance['id'], request_body)

View File

@ -14,16 +14,20 @@
# under the License.
import ddt
import importlib
import json
import mock
import os
import requests
import tempfile
import yaml
from tacker.common import exceptions
from tacker import context
from tacker.extensions import vnfm
from tacker import objects
from tacker.tests.common import helpers
from tacker.tests import constants
from tacker.tests.unit import base
from tacker.tests.unit.db import utils
from tacker.tests.unit.vnfm.infra_drivers.openstack.fixture_data import client
@ -46,6 +50,8 @@ class TestOpenStack(base.FixturedTestCase):
self.glance_url = client.GLANCE_URL
self.instance_uuid = uuidsentinel.instance_id
self.stack_id = uuidsentinel.stack_id
self.auth_attr = None
self.plugin = None
self.json_headers = {'content-type': 'application/json',
'location': 'http://heat-api/stacks/'
+ self.instance_uuid + '/myStack/60f83b5e'}
@ -111,6 +117,438 @@ class TestOpenStack(base.FixturedTestCase):
else:
self.requests_mock.register_uri('PATCH', url)
def _json_load(self, input_file):
json_file = os.path.abspath(os.path.join(os.path.dirname(__file__),
"../../../../etc/samples/lcm_instantiate_request/",
str(input_file)))
with open(json_file) as f:
json_dict = json.load(f)
return json_dict
def _read_file(self):
yaml_file = os.path.abspath(os.path.join(os.path.dirname(__file__),
"../../../../etc/samples/",
"hot_lcm_user_data.yaml"))
with open(yaml_file, 'r') as f:
yaml_file_dict = yaml.safe_load(f)
return yaml_file_dict
@mock.patch('tacker.common.clients.OpenstackClients')
def test_create_normal(self, mock_OpenstackClients_heat):
vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid)
vnf['placement_attr'] = {'region_name': 'dummy_region'}
base_hot_dict_test = self._read_file()
vnf_package_path_test = os.path.abspath(
os.path.join(os.path.dirname(__file__),
"../../../../etc/samples/etsi/nfv",
"user_data_sample_normal"))
inst_req_info_test = type('', (), {})
test_json = self._json_load(
'instantiate_vnf_request_lcm_userdata.json')
inst_req_info_test.additional_params = test_json['additionalParams']
inst_req_info_test.ext_virtual_links = None
vnf_resource = type('', (), {})
vnf_resource.resource_identifier = constants.INVALID_UUID
grant_info_test = {'vdu_name': {vnf_resource}}
self.openstack.create(self.plugin, self.context, vnf,