diff --git a/.zuul.yaml b/.zuul.yaml index e798776a8..b73e31458 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -214,6 +214,24 @@ controller-tacker: tox_envlist: dsvm-functional-sol +- job: + name: tacker-functional-devstack-multinode-sol-separated-nfvo + parent: tacker-functional-devstack-multinode-sol + description: | + Multinodes job for SOL devstack-based functional tests + with separated NFVO + host-vars: + controller-tacker: + devstack_local_conf: + post-config: + $TACKER_CONF: + connect_vnf_packages: + base_url: http://127.0.0.1:9990/vnfpkgm/v1/vnf_packages + pipeline: package_content,vnfd,artifacts + connect_grant: + base_url: http://127.0.0.1:9990/grant/v1/grants + tox_envlist: dsvm-functional-sol-separated-nfvo + - project: templates: - check-requirements @@ -226,3 +244,4 @@ jobs: - tacker-functional-devstack-multinode-legacy - tacker-functional-devstack-multinode-sol + - tacker-functional-devstack-multinode-sol-separated-nfvo diff --git a/devstack/settings b/devstack/settings index d4ac902bc..f0e51d501 100644 --- a/devstack/settings +++ b/devstack/settings @@ -63,6 +63,14 @@ if [ "${TACKER_MODE}" == "all" ]; then done fi elif [ "${TACKER_MODE}" == "standalone" ]; then + # TODO(takahashi-tsc) Remove gawk installation after the following bug is fixed. + # https://bugs.launchpad.net/devstack/+bug/1909041 + # post-config requires gawk. + # Generally, gawk is requires by Nova, + # but Nova is not mandatory for Tacker. + # So we install gawk manually until the bug is fixed. + install_package gawk + enable_service dstat enable_service tacker enable_service tacker-conductor diff --git a/tacker/tests/etc/samples/etsi/nfv/functional6/BaseHOT/simple/helloworld3.yaml b/tacker/tests/etc/samples/etsi/nfv/functional6/BaseHOT/simple/helloworld3.yaml new file mode 100644 index 000000000..6e5fb4992 --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/functional6/BaseHOT/simple/helloworld3.yaml @@ -0,0 +1,101 @@ +heat_template_version: 2013-05-23 +description: 'Simple Base HOT for Sample VNF' + +parameters: + nfv: + type: json + +resources: + VDU1: + type: OS::Heat::AutoScalingGroup + properties: + min_size: 1 + max_size: 3 + desired_capacity: 1 + resource: + type: VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1, flavor ] } + image: { get_param: [ nfv, VDU, VDU1, image ] } + zone: { get_param: [ nfv, vdu, VDU1, zone ] } + net1: { get_param: [ nfv, CP, VDU1_CP1, network ] } + net2: { get_param: [ nfv, CP, VDU1_CP2, network ] } + net3: { get_resource: extmanageNW_1 } + net4: { get_resource: extmanageNW_2 } + net5: { get_resource: internalNW_1 } + VDU1_scale_out: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: 1 + auto_scaling_group_id: + get_resource: VDU1 + adjustment_type: change_in_capacity + VDU1_scale_in: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: -1 + auto_scaling_group_id: + get_resource: VDU1 + adjustment_type: change_in_capacity + VDU2: + type: OS::Heat::AutoScalingGroup + depends_on: VDU1 + properties: + min_size: 2 + max_size: 2 + desired_capacity: 2 + resource: + type: VDU2.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU2, flavor ] } + image: { get_param: [ nfv, VDU, VDU2, image ] } + zone: { get_param: [ nfv, vdu, VDU2, zone ] } + net1: { get_param: [ nfv, CP, VDU2_CP1, network ] } + net2: { get_param: [ nfv, CP, VDU2_CP2, network ] } + net3: { get_resource: extmanageNW_1 } + net4: { get_resource: extmanageNW_2 } + net5: { get_resource: internalNW_1 } + VDU2_scale_out: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: 1 + auto_scaling_group_id: + get_resource: VDU2 + adjustment_type: change_in_capacity + VDU2_scale_in: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: -1 + auto_scaling_group_id: + get_resource: VDU2 + adjustment_type: change_in_capacity + extmanageNW_1: + type: OS::Neutron::Net + extmanageNW_2: + type: OS::Neutron::Net + internalNW_1: + type: OS::Neutron::Net + extmanageNW_1_subnet: + type: OS::Neutron::Subnet + properties: + ip_version: 4 + network: + get_resource: extmanageNW_1 + cidr: 192.168.3.0/24 + extmanageNW_2_subnet: + type: OS::Neutron::Subnet + properties: + ip_version: 4 + network: + get_resource: extmanageNW_2 + cidr: 192.168.4.0/24 + internalNW_1_subnet: + type: OS::Neutron::Subnet + properties: + ip_version: 4 + network: + get_resource: internalNW_1 + cidr: 192.168.5.0/24 +outputs: {} + + diff --git a/tacker/tests/etc/samples/etsi/nfv/functional6/BaseHOT/simple/nested/VDU1.yaml b/tacker/tests/etc/samples/etsi/nfv/functional6/BaseHOT/simple/nested/VDU1.yaml new file mode 100644 index 000000000..b10f7375d --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/functional6/BaseHOT/simple/nested/VDU1.yaml @@ -0,0 +1,63 @@ +heat_template_version: 2013-05-23 +description: 'VDU1 HOT for Sample VNF' + +parameters: + flavor: + type: string + image: + type: string + zone: + type: string + net1: + type: string + net2: + type: string + net3: + type: string + net4: + type: string + net5: + type: string + +resources: + VDU1: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + name: VDU1 + image: { get_param: image } + networks: + - port: + get_resource: VDU1_CP1 + - port: + get_resource: VDU1_CP2 + - port: + get_resource: VDU1_CP3 + - port: + get_resource: VDU1_CP4 + - port: + get_resource: VDU1_CP5 + availability_zone: { get_param: zone } + + VDU1_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net1 } + VDU1_CP2: + type: OS::Neutron::Port + properties: + network: { get_param: net2 } + 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 } + + diff --git a/tacker/tests/etc/samples/etsi/nfv/functional6/BaseHOT/simple/nested/VDU2.yaml b/tacker/tests/etc/samples/etsi/nfv/functional6/BaseHOT/simple/nested/VDU2.yaml new file mode 100644 index 000000000..c4772dad6 --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/functional6/BaseHOT/simple/nested/VDU2.yaml @@ -0,0 +1,61 @@ +heat_template_version: 2013-05-23 +description: 'VDU2 HOT for Sample VNF' + +parameters: + flavor: + type: string + image: + type: string + zone: + type: string + net1: + type: string + net2: + type: string + net3: + type: string + net4: + type: string + net5: + type: string + +resources: + VDU2: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + name: VDU2 + image: { get_param: image } + networks: + - port: + get_resource: VDU2_CP1 + - port: + get_resource: VDU2_CP2 + - port: + get_resource: VDU2_CP3 + - port: + get_resource: VDU2_CP4 + - port: + get_resource: VDU2_CP5 + availability_zone: { get_param: zone } + + VDU2_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net1 } + VDU2_CP2: + type: OS::Neutron::Port + properties: + network: { get_param: net2 } + VDU2_CP3: + type: OS::Neutron::Port + properties: + network: { get_param: net3 } + VDU2_CP4: + type: OS::Neutron::Port + properties: + network: { get_param: net4 } + VDU2_CP5: + type: OS::Neutron::Port + properties: + network: { get_param: net5 } diff --git a/tacker/tests/etc/samples/etsi/nfv/functional6/Definitions/helloworld3_df_simple.yaml b/tacker/tests/etc/samples/etsi/nfv/functional6/Definitions/helloworld3_df_simple.yaml new file mode 100644 index 000000000..a53204c42 --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/functional6/Definitions/helloworld3_df_simple.yaml @@ -0,0 +1,396 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: Simple deployment flavour for Sample VNF + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + - helloworld3_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: company.provider.VNF + properties: + flavour_id: simple + requirements: + virtual_link_external1_1: [ VDU1_CP1, virtual_link ] + virtual_link_external1_2: [ VDU2_CP1, virtual_link ] + virtual_link_external2_1: [ VDU1_CP2, virtual_link ] + virtual_link_external2_2: [ VDU2_CP2, virtual_link ] + + node_templates: + VNF: + type: company.provider.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: 3 + sw_image_data: + name: cirros-0.4.0-x86_64-disk + version: '0.4.0' + checksum: + algorithm: sha-256 + hash: a8dd75ecffd4cdd96072d60c2237b448e0c8b2bc94d57f10fdbc8c481d9005b8 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 12 GB + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + + VDU2: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU2 + description: VDU2 compute node + vdu_profile: + min_number_of_instances: 2 + max_number_of_instances: 2 + sw_image_data: + name: cirros-0.4.0-x86_64-disk + version: '0.4.0' + checksum: + algorithm: sha-256 + hash: a8dd75ecffd4cdd96072d60c2237b448e0c8b2bc94d57f10fdbc8c481d9005b8 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 12 GB + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + + VDU1_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU1 + + VDU1_CP2: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 1 + requirements: + - virtual_binding: VDU1 + + VDU1_CP3: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 2 + requirements: + - virtual_binding: VDU1 + - virtual_link: internalVL1 + + VDU1_CP4: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 3 + requirements: + - virtual_binding: VDU1 + - virtual_link: internalVL2 + + VDU1_CP5: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 4 + requirements: + - virtual_binding: VDU1 + - virtual_link: internalVL3 + + VDU2_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU2 + + VDU2_CP2: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 1 + requirements: + - virtual_binding: VDU2 + + VDU2_CP3: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 2 + requirements: + - virtual_binding: VDU2 + - virtual_link: internalVL1 + + VDU2_CP4: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 3 + requirements: + - virtual_binding: VDU2 + - virtual_link: internalVL2 + + VDU2_CP5: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 4 + requirements: + - virtual_binding: VDU2 + - virtual_link: internalVL3 + + internalVL1: + type: tosca.nodes.nfv.VnfVirtualLink + properties: + connectivity_type: + layer_protocols: [ ipv4 ] + description: External Managed Virtual link in the VNF + vl_profile: + max_bitrate_requirements: + root: 1048576 + leaf: 1048576 + min_bitrate_requirements: + root: 1048576 + leaf: 1048576 + virtual_link_protocol_data: + - associated_layer_protocol: ipv4 + l3_protocol_data: + ip_version: ipv4 + cidr: 33.33.0.0/24 + + internalVL2: + type: tosca.nodes.nfv.VnfVirtualLink + properties: + connectivity_type: + layer_protocols: [ ipv4 ] + description: External Managed Virtual link in the VNF + vl_profile: + max_bitrate_requirements: + root: 1048576 + leaf: 1048576 + min_bitrate_requirements: + root: 1048576 + leaf: 1048576 + virtual_link_protocol_data: + - associated_layer_protocol: ipv4 + l3_protocol_data: + ip_version: ipv4 + cidr: 33.34.0.0/24 + + internalVL3: + type: tosca.nodes.nfv.VnfVirtualLink + properties: + connectivity_type: + layer_protocols: [ ipv4 ] + description: Internal Virtual link in the VNF + vl_profile: + max_bitrate_requirements: + root: 1048576 + leaf: 1048576 + min_bitrate_requirements: + root: 1048576 + leaf: 1048576 + virtual_link_protocol_data: + - associated_layer_protocol: ipv4 + l3_protocol_data: + ip_version: ipv4 + cidr: 33.35.0.0/24 + + policies: + - scaling_aspects: + type: tosca.policies.nfv.ScalingAspects + properties: + aspects: + worker_instance: + name: worker_instance_aspect + description: worker_instance scaling aspect + max_scale_level: 2 + step_deltas: + - delta_1 + + - VDU1_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 1 + targets: [ VDU1 ] + + - VDU2_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 2 + targets: [ VDU2 ] + + - VDU1_scaling_aspect_deltas: + type: tosca.policies.nfv.VduScalingAspectDeltas + properties: + aspect: worker_instance + deltas: + delta_1: + number_of_instances: 1 + targets: [ VDU1 ] + + - instantiation_levels: + type: tosca.policies.nfv.InstantiationLevels + properties: + levels: + instantiation_level_1: + description: Smallest size + scale_info: + worker_instance: + scale_level: 0 + instantiation_level_2: + description: Largest size + scale_info: + worker_instance: + scale_level: 2 + default_level: instantiation_level_1 + + - VDU1_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 3 + targets: [ VDU1 ] + + - VDU2_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 2 + instantiation_level_2: + number_of_instances: 2 + targets: [ VDU2 ] + + - internalVL1_instantiation_levels: + type: tosca.policies.nfv.VirtualLinkInstantiationLevels + properties: + levels: + instantiation_level_1: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + instantiation_level_2: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + targets: [ internalVL1 ] + + - internalVL2_instantiation_levels: + type: tosca.policies.nfv.VirtualLinkInstantiationLevels + properties: + levels: + instantiation_level_1: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + instantiation_level_2: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + targets: [ internalVL2 ] + + - internalVL3_instantiation_levels: + type: tosca.policies.nfv.VirtualLinkInstantiationLevels + properties: + levels: + instantiation_level_1: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + instantiation_level_2: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + targets: [ internalVL3 ] + + - policy_antiaffinity_vdu1: + type: tosca.policies.nfv.AntiAffinityRule + targets: [ VDU1 ] + properties: + scope: zone + + - policy_antiaffinity_vdu2: + type: tosca.policies.nfv.AntiAffinityRule + targets: [ VDU2 ] + properties: + scope: zone + + diff --git a/tacker/tests/etc/samples/etsi/nfv/functional6/Definitions/helloworld3_top.vnfd.yaml b/tacker/tests/etc/samples/etsi/nfv/functional6/Definitions/helloworld3_top.vnfd.yaml new file mode 100644 index 000000000..0be18659b --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/functional6/Definitions/helloworld3_top.vnfd.yaml @@ -0,0 +1,31 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: Sample VNF + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + - helloworld3_types.yaml + - helloworld3_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: company.provider.VNF + properties: + flavour_id: { get_input: selected_flavour } + descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d7000000 + provider: Company + product_name: Sample VNF + 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 \ No newline at end of file diff --git a/tacker/tests/etc/samples/etsi/nfv/functional6/Definitions/helloworld3_types.yaml b/tacker/tests/etc/samples/etsi/nfv/functional6/Definitions/helloworld3_types.yaml new file mode 100644 index 000000000..5c026d6a2 --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/functional6/Definitions/helloworld3_types.yaml @@ -0,0 +1,55 @@ +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: + company.provider.VNF: + derived_from: tosca.nodes.nfv.VNF + properties: + descriptor_id: + type: string + constraints: [ valid_values: [ b1bb0ce7-ebca-4fa7-95ed-4840d7000000 ] ] + default: b1bb0ce7-ebca-4fa7-95ed-4840d7000000 + descriptor_version: + type: string + constraints: [ valid_values: [ '1.0' ] ] + default: '1.0' + provider: + type: string + constraints: [ valid_values: [ 'Company' ] ] + default: 'Company' + product_name: + type: string + constraints: [ valid_values: [ 'Sample VNF' ] ] + default: 'Sample VNF' + 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_external1: + capability: tosca.capabilities.nfv.VirtualLinkable + - virtual_link_external2: + capability: tosca.capabilities.nfv.VirtualLinkable + - virtual_link_internal: + capability: tosca.capabilities.nfv.VirtualLinkable + interfaces: + Vnflcm: + type: tosca.interfaces.nfv.Vnflcm \ No newline at end of file diff --git a/tacker/tests/etc/samples/etsi/nfv/functional6/TOSCA-Metadata/TOSCA.meta b/tacker/tests/etc/samples/etsi/nfv/functional6/TOSCA-Metadata/TOSCA.meta new file mode 100644 index 000000000..cfd7444ac --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/functional6/TOSCA-Metadata/TOSCA.meta @@ -0,0 +1,4 @@ +TOSCA-Meta-File-Version: 1.0 +CSAR-Version: 1.1 +Created-by: Onboarding portal +Entry-Definitions: Definitions/helloworld3_top.vnfd.yaml diff --git a/tacker/tests/etc/samples/etsi/nfv/functional6/UserData/__init__.py b/tacker/tests/etc/samples/etsi/nfv/functional6/UserData/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/tests/etc/samples/etsi/nfv/functional6/UserData/lcm_user_data.py b/tacker/tests/etc/samples/etsi/nfv/functional6/UserData/lcm_user_data.py new file mode 100644 index 000000000..725c5430a --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/functional6/UserData/lcm_user_data.py @@ -0,0 +1,35 @@ +# +# 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 +import tacker.vnfm.lcm_user_data.utils as UserDataUtil + + +class SampleUserData(AbstractUserData): + @staticmethod + def instantiate(base_hot_dict=None, + vnfd_dict=None, + inst_req_info=None, + grant_info=None): + api_param = UserDataUtil.get_diff_base_hot_param_from_api( + base_hot_dict, inst_req_info) + initial_param_dict = \ + UserDataUtil.create_initial_param_server_port_dict( + base_hot_dict) + vdu_flavor_dict = \ + UserDataUtil.create_vdu_flavor_capability_name_dict(vnfd_dict) + vdu_image_dict = UserDataUtil.create_sw_image_dict(vnfd_dict) + cpd_vl_dict = UserDataUtil.create_network_dict( + inst_req_info, initial_param_dict) + final_param_dict = UserDataUtil.create_final_param_dict( + initial_param_dict, vdu_flavor_dict, vdu_image_dict, cpd_vl_dict) + return {**final_param_dict, **api_param} diff --git a/tacker/tests/functional/sol_separated_nfvo/__init__.py b/tacker/tests/functional/sol_separated_nfvo/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/tests/functional/sol_separated_nfvo/vnflcm/__init__.py b/tacker/tests/functional/sol_separated_nfvo/vnflcm/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/tests/functional/sol_separated_nfvo/vnflcm/fake_grant.py b/tacker/tests/functional/sol_separated_nfvo/vnflcm/fake_grant.py new file mode 100644 index 000000000..b390446b0 --- /dev/null +++ b/tacker/tests/functional/sol_separated_nfvo/vnflcm/fake_grant.py @@ -0,0 +1,205 @@ +# +# 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 +from oslo_serialization import jsonutils +from tacker.tests import uuidsentinel + + +class Grant: + GRANT_REQ_PATH = '/grant/v1/grants' + + ZONES = [ + { + "id": uuidsentinel.zone_id, + "zoneId": "nova", + "vimConnectionId": uuidsentinel.vim_connection_id + } + ] + + ADDITIONAL_PARAMS = { + "key": "value" + } + + @staticmethod + def _make_vim_connection_info(tenant_id): + access_info = { + "username": "nfv_user", + "region": "RegionOne", + "password": "devstack", + "tenant": tenant_id + } + + return [{ + "id": uuidsentinel.vim_connection_id, + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.v_2", + "interfaceInfo": { + "endpoint": "http://127.0.0.1/identity" + }, + "accessInfo": access_info + }] + + @staticmethod + def _make_add_resources(req_add_resources): + add_resources = [] + for req_add_resource in req_add_resources: + res_add_resource = { + "resourceDefinitionId": req_add_resource['id'], + "vimConnectionId": uuidsentinel.vim_connection_id + } + + if req_add_resource['type'] == 'COMPUTE': + res_add_resource["zoneId"] = uuidsentinel.zone_id + + add_resources.append(res_add_resource) + + return add_resources + + @staticmethod + def _make_remove_resources(req_remove_resources): + res_remove_resources = [] + for req_remove_resource in req_remove_resources: + res_remove_resource = { + "resourceDefinitionId": req_remove_resource['id'] + } + + res_remove_resources.append(res_remove_resource) + + return res_remove_resources + + @staticmethod + def _make_vim_assets(image_id, flavour_id="1"): + # set m1.tiny="1" for flavour_id + vim_assets = { + "computeResourceFlavours": [ + { + "vimConnectionId": uuidsentinel.vim_connection_id, + "vnfdVirtualComputeDescId": "VDU1", + "vimFlavourId": flavour_id + }, + { + "vimConnectionId": uuidsentinel.vim_connection_id, + "vnfdVirtualComputeDescId": "VDU2", + "vimFlavourId": flavour_id + } + ], + "softwareImages": [ + { + "vimConnectionId": uuidsentinel.vim_connection_id, + "vnfdSoftwareImageId": "VDU1", + "vimSoftwareImageId": image_id + }, + { + "vimConnectionId": uuidsentinel.vim_connection_id, + "vnfdSoftwareImageId": "VDU2", + "vimSoftwareImageId": image_id + } + ] + } + + return vim_assets + + @staticmethod + def _make_response_template(request_body): + res = { + "id": uuidsentinel.__getattr__(request_body['vnfLcmOpOccId']), + "vnfInstanceId": request_body['vnfInstanceId'], + "vnfLcmOpOccId": request_body['vnfLcmOpOccId'], + } + res["_links"] = { + "self": { + # set fake server port. + "href": os.path.join( + 'http://localhost:9990', + Grant.GRANT_REQ_PATH)}, + "vnfLcmOpOcc": { + "href": os.path.join( + 'http://localhost:9890/vnflcm/v1/vnf_lcm_op_occs', + request_body['vnfLcmOpOccId'])}, + "vnfInstance": { + "href": os.path.join( + 'http://localhost:9890/vnflcm/v1/vnf_instances', + request_body['vnfInstanceId'])}} + + return res + + @staticmethod + def _convert_body_to_dict(body): + if isinstance(body, str): + return jsonutils.loads(body) + + return body + + @staticmethod + def make_inst_response_body(request_body, tenant_id, image_id): + request_body = Grant._convert_body_to_dict(request_body) + res = Grant._make_response_template(request_body) + res["vimConnections"] = Grant._make_vim_connection_info(tenant_id) + res["zones"] = Grant.ZONES + if 'addResources' in request_body.keys(): + res["addResources"] = Grant._make_add_resources( + request_body['addResources']) + res["vimAssets"] = Grant._make_vim_assets( + image_id) + res["additionalParams"] = Grant.ADDITIONAL_PARAMS + + return res + + @staticmethod + def make_heal_response_body(request_body, tenant_id, image_id): + request_body = Grant._convert_body_to_dict(request_body) + res = Grant._make_response_template(request_body) + res["vimConnections"] = Grant._make_vim_connection_info(tenant_id) + res["zones"] = Grant.ZONES + if 'addResources' in request_body.keys(): + res["addResources"] = Grant._make_add_resources( + request_body['addResources']) + if 'removeResources' in request_body.keys(): + res["removeResources"] = Grant._make_remove_resources( + request_body['removeResources']) + res["vimAssets"] = Grant._make_vim_assets(image_id) + + return res + + @staticmethod + def make_scaleout_response_body(request_body, tenant_id, image_id): + request_body = Grant._convert_body_to_dict(request_body) + res = Grant._make_response_template(request_body) + res["vimConnections"] = Grant._make_vim_connection_info(tenant_id) + res["zones"] = Grant.ZONES + if 'addResources' in request_body.keys(): + res["addResources"] = Grant._make_add_resources( + request_body['addResources']) + res["vimAssets"] = Grant._make_vim_assets( + image_id) + + return res + + @staticmethod + def make_scalein_response_body(request_body): + request_body = Grant._convert_body_to_dict(request_body) + res = Grant._make_response_template(request_body) + if 'removeResources' in request_body.keys(): + res["removeResources"] = Grant._make_remove_resources( + request_body['removeResources']) + + return res + + @staticmethod + def make_term_response_body(request_body): + request_body = Grant._convert_body_to_dict(request_body) + res = Grant._make_response_template(request_body) + if 'removeResources' in request_body.keys(): + res["removeResources"] = Grant._make_remove_resources( + request_body['removeResources']) + + return res diff --git a/tacker/tests/functional/sol_separated_nfvo/vnflcm/fake_vnfpkgm.py b/tacker/tests/functional/sol_separated_nfvo/vnflcm/fake_vnfpkgm.py new file mode 100644 index 000000000..6f94a8516 --- /dev/null +++ b/tacker/tests/functional/sol_separated_nfvo/vnflcm/fake_vnfpkgm.py @@ -0,0 +1,105 @@ +# +# 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 oslo_utils import uuidutils + + +class VnfPackage: + VNF_PACKAGE_REQ_PATH = "/vnfpkgm/v1/vnf_packages" + + @staticmethod + def make_list_response_body(): + return [VnfPackage.make_individual_response] + + @staticmethod + def make_individual_response_body(vnfd_id, vnf_package_hash): + add_artifact_hash = ( + "6513f21e44aa3da349f248188a44" + + "bc304a3653a04122d8fb4535423c8" + + "e1d14cd6a153f735bb0982e2" + + "161b5b5186106570c17a9" + + "e58b64dd39390617cd5a350f78") + + sw_image_hash = ( + "6513f21e44aa3da349" + + "f248188a44bc304a3653a04" + + "122d8fb4535423c8e1d14c" + + "d6a153f735bb0982e2161b5" + + "b5186106570c17a9e58b6" + + "4dd39390617cd5a350f78") + + data = { + "id": uuidutils.generate_uuid(), + "vnfdId": vnfd_id, + "vnfProvider": "Company", + "vnfProductName": "Sample VNF", + "vnfSoftwareVersion": "1.0", + "vnfdVersion": "1.0", + "checksum": { + "algorithm": "SHA-512", + "hash": vnf_package_hash + }, + "softwareImages": [ + { + "id": "sw_image", + "name": "cirros-0.4.0-x86_64-disk", + "provider": "Company", + "version": "0.4.0", + "checksum": { + "algorithm": "SHA-512", + "hash": sw_image_hash + }, + "containerFormat": "BARE", + "diskFormat": "QCOW2", + "createdAt": "2020-09-01T12:34:56Z", + "minDisk": "2147483648", + "minRam": "268435456", + "size": "1073741824", + "userMetadata": { + "key": "value" + }, + "imagePath": "Files/images/cirros-0.4.0-x86_64-disk.img" + } + ], + "additionalArtifacts": [ + { + "artifactPath": + "Files/images/cirros-0.4.0-x86_64-disk.img", + "checksum": { + "algorithm": "SHA-512", + "hash": add_artifact_hash + }, + "metadata": { + "key": "value" + } + } + ], + "onboardingState": "ONBOARDED", + "operationalState": "ENABLED", + "usageState": "NOT_IN_USE", + "userDefinedData": { + "key": "value" + }, + "_links": { + "self": { + "href": "GetPackage URI" + }, + "vnfd": { + "href": "GetVNFD URI" + }, + "packageContent": { + "href": "GetPackageContent URI" + } + } + } + + return data diff --git a/tacker/tests/functional/sol_separated_nfvo/vnflcm/test_vnf_instance_with_user_data_nfvo_separate.py b/tacker/tests/functional/sol_separated_nfvo/vnflcm/test_vnf_instance_with_user_data_nfvo_separate.py new file mode 100644 index 000000000..0bcafa2c4 --- /dev/null +++ b/tacker/tests/functional/sol_separated_nfvo/vnflcm/test_vnf_instance_with_user_data_nfvo_separate.py @@ -0,0 +1,316 @@ +# +# 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 hashlib +import os + +from tacker.tests.functional.sol.vnflcm import base as vnflcm_base +from tacker.tests.functional.sol.vnflcm import fake_vnflcm +from tacker.tests.functional.sol_separated_nfvo.vnflcm import fake_grant +from tacker.tests.functional.sol_separated_nfvo.vnflcm import fake_vnfpkgm + + +class VnfLcmWithNfvoSeparator(vnflcm_base.BaseVnfLcmTest): + + def _register_vnf_package_mock_response(self): + """Prepare VNF package for test. + + Register VNF package response to fake NFVO server and Cleanups. + + Returns: + Response: VNF Package information + """ + # Pre Setting: Create vnf package. + sample_name = "functional6" + csar_package_path = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../etc/samples/etsi/nfv", + sample_name)) + + # Get VNFD id. + tempname, vnfd_id = vnflcm_base._create_csar_with_unique_vnfd_id( + csar_package_path) + with open(tempname, "rb") as f: + vnf_package_hash = hashlib.sha256(f.read()).hexdigest() + + vnf_package_info = \ + fake_vnfpkgm.VnfPackage.make_individual_response_body( + vnfd_id, vnf_package_hash) + vnf_package_id = vnf_package_info['id'] + + # Post Setting: Reserve deleting vnf package. + self.addCleanup(vnflcm_base._delete_vnf_package, self.tacker_client, + vnf_package_id) + + # Set "VNF Packages" response + vnflcm_base.FAKE_SERVER_MANAGER.set_callback('GET', + fake_vnfpkgm.VnfPackage.VNF_PACKAGE_REQ_PATH, status_code=200, + response_body=[vnf_package_info]) + + # Set "VNF Package content" response + vnflcm_base.FAKE_SERVER_MANAGER.set_callback('GET', + os.path.join( + fake_vnfpkgm.VnfPackage.VNF_PACKAGE_REQ_PATH, + vnf_package_id, + 'package_content'), + status_code=200, + response_headers={"Content-Type": "application/zip"}, + content=tempname) + + # Set "Individual VNF package artifact" response + vnflcm_base.FAKE_SERVER_MANAGER.set_callback('GET', + os.path.join( + fake_vnfpkgm.VnfPackage.VNF_PACKAGE_REQ_PATH, + vnf_package_id, + 'artifacts', + vnf_package_info['additionalArtifacts'][0]['artifactPath']), + status_code=200, + response_headers={"Content-Type": "application/zip"}, + content=tempname) + + # Set "VNFD of individual VNF package" response + vnflcm_base.FAKE_SERVER_MANAGER.set_callback('GET', + os.path.join( + fake_vnfpkgm.VnfPackage.VNF_PACKAGE_REQ_PATH, + vnf_package_id, + 'vnfd'), + status_code=200, + response_headers={"Content-Type": "application/zip"}, + content=tempname) + + return vnf_package_info + + def test_inst_heal_term(self): + """Test basic life cycle operations with sample VNFD with UserData. + + In this test case, we do following steps. + - Create subscription. + - Create VNF instance. + - Instantiate VNF. + - Heal VNF with all VNFc. + - Terminate VNF + - Delete VNF + - Delete subscription + """ + vnf_package_info = self._register_vnf_package_mock_response() + glance_image = self._list_glance_image()[0] + + # Create subscription and register it. + request_body = fake_vnflcm.Subscription.make_create_request_body( + 'http://localhost:{}{}'.format( + vnflcm_base.FAKE_SERVER_MANAGER.SERVER_PORT, + os.path.join( + vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName))) + resp, response_body = self._register_subscription(request_body) + self.assertEqual(201, resp.status_code) + self.assert_http_header_location_for_subscription(resp.headers) + subscription_id = response_body.get('id') + self.addCleanup(self._delete_subscription, subscription_id) + + # Create vnf instance + resp, vnf_instance = self._create_vnf_instance_from_body( + fake_vnflcm.VnfInstances.make_create_request_body( + vnf_package_info['vnfdId'])) + vnf_instance_id = vnf_instance.get('id') + self._wait_lcm_done(vnf_instance_id=vnf_instance_id) + self._assert_create_vnf(resp, vnf_instance) + self.addCleanup(self._delete_vnf_instance, vnf_instance_id) + + # Set Fake server response for Grant-Req(Instantiate) + vnflcm_base.FAKE_SERVER_MANAGER.set_callback('POST', + fake_grant.Grant.GRANT_REQ_PATH, status_code=201, + callback=lambda req_headers, + req_body: fake_grant.Grant.make_inst_response_body(req_body, + self.vim['tenant_id'], glance_image.id)) + + # Instantiate vnf instance + request_body = fake_vnflcm.VnfInstances.make_inst_request_body( + self.vim['tenant_id'], self.ext_networks, self.ext_mngd_networks, + self.ext_link_ports, self.ext_subnets) + resp, _ = self._instantiate_vnf_instance(vnf_instance_id, request_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + self._assert_instantiate_vnf(resp, vnf_instance_id) + + # Show vnf instance + resp, vnf_instance = self._show_vnf_instance(vnf_instance_id) + self.assertEqual(200, resp.status_code) + + # Set Fake server response for Grant-Req(Heal) + vnflcm_base.FAKE_SERVER_MANAGER.set_callback('POST', + fake_grant.Grant.GRANT_REQ_PATH, status_code=201, + callback=lambda req_headers, + req_body: fake_grant.Grant.make_heal_response_body(req_body, + self.vim['tenant_id'], glance_image.id)) + + # Heal vnf (exists vnfc_instace_id) + vnfc_instance_id_list = [ + vnfc.get('id') for vnfc in vnf_instance.get( + 'instantiatedVnfInfo', {}).get( + 'vnfcResourceInfo', [])] + request_body = fake_vnflcm.VnfInstances.make_heal_request_body( + vnfc_instance_id_list) + resp, _ = self._heal_vnf_instance(vnf_instance_id, request_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + self._assert_heal_vnf(resp, vnf_instance_id) + + # Set Fake server response for Grant-Req(Terminate) + vnflcm_base.FAKE_SERVER_MANAGER.set_callback('POST', + fake_grant.Grant.GRANT_REQ_PATH, status_code=201, + callback=lambda req_headers, + req_body: fake_grant.Grant.make_term_response_body(req_body)) + + # Get stack informations to terminate. + stack = self._get_heat_stack(vnf_instance_id) + resources_list = self._get_heat_resource_list(stack.id) + resource_name_list = [r.resource_name for r in resources_list] + glance_image_id_list = self._get_glance_image_list_from_stack_resource( + stack.id, resource_name_list) + + # Terminate VNF + terminate_req_body = fake_vnflcm.VnfInstances.make_term_request_body() + resp, _ = self._terminate_vnf_instance(vnf_instance_id, + terminate_req_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + self._assert_terminate_vnf(resp, vnf_instance_id, stack.id, + resource_name_list, glance_image_id_list) + + # Delete VNF + resp, _ = self._delete_vnf_instance(vnf_instance_id) + self._wait_lcm_done(vnf_instance_id=vnf_instance_id) + self.assert_delete_vnf(resp, vnf_instance_id) + + # Delete Subscription + resp, response_body = self._delete_subscription(subscription_id) + self.assertEqual(204, resp.status_code) + + def _assert_create_vnf(self, resp, vnf_instance): + """Assert that VNF was created via fake server. + + Args: + resp (Response): HTTP response object. + vnf_instance (Dict): VNF instance information. + """ + super().assert_create_vnf(resp, vnf_instance) + + # FT-checkpoint: VnfPkgId + vnf_pkg_mock_responses = vnflcm_base.FAKE_SERVER_MANAGER.get_history( + fake_vnfpkgm.VnfPackage.VNF_PACKAGE_REQ_PATH) + vnflcm_base.FAKE_SERVER_MANAGER.clear_history( + fake_vnfpkgm.VnfPackage.VNF_PACKAGE_REQ_PATH) + + self.assertEqual(1, len(vnf_pkg_mock_responses)) + vnf_pkg_info_list = vnf_pkg_mock_responses[0] + self.assertEqual(vnf_instance['vnfPkgId'], + vnf_pkg_info_list.response_body[0]['id']) + + def _assert_instantiate_vnf(self, resp, vnf_instance_id): + """Assert that VNF was instantiated. + + This method calls same name method of super class and that + checks heat resource status 'CREATE_COMPLETE', then assert + notifications of instantiation. + Then, we check Grant response in this method. + + Args: + resp (Response): HTTP response object. + vnf_instance_id (str): VNF instance id. + """ + super().assert_instantiate_vnf(resp, vnf_instance_id) + + # FT-checkpoint: Grant Response + grant_mock_responses = vnflcm_base.FAKE_SERVER_MANAGER.get_history( + fake_grant.Grant.GRANT_REQ_PATH) + vnflcm_base.FAKE_SERVER_MANAGER.clear_history( + fake_grant.Grant.GRANT_REQ_PATH) + self.assertEqual(1, len(grant_mock_responses)) + self._assert_grant_mock_response(grant_mock_responses[0]) + + def _assert_heal_vnf(self, resp, vnf_instance_id, + expected_stack_status='UPDATE_COMPLETE'): + """Assert that VNF was healed. + + This method calls same name method of super class and that + checks heat resource status 'UPDATE_COMPLETE', then assert + notifications of healing. + Then, we check Grant response in this method. + + Args: + resp (Response): HTTP response object. + vnf_instance_id (str): VNF instance id. + expected_stack_status (str, optional): self explanatory :) + Defaults to 'UPDATE_COMPLETE'. + """ + super().assert_heal_vnf( + resp, vnf_instance_id, expected_stack_status=expected_stack_status) + + # FT-checkpoint: Grant Response + grant_mock_responses = vnflcm_base.FAKE_SERVER_MANAGER.get_history( + fake_grant.Grant.GRANT_REQ_PATH) + vnflcm_base.FAKE_SERVER_MANAGER.clear_history( + fake_grant.Grant.GRANT_REQ_PATH) + self.assertEqual(1, len(grant_mock_responses)) + self._assert_grant_mock_response(grant_mock_responses[0]) + + def _assert_terminate_vnf(self, resp, vnf_instance_id, stack_id, + resource_name_list, glance_image_id_list): + """Assert that VNF was terminated. + + This method calls same name method of super class to check specified + VNF instance is 'NOT_INSTANTIATED' + Then, we check Grant response in this method. + + Args: + resp (Response): HTTP response object. + vnf_instance_id (str): VNF instance id. + stack_id (str): Resource id of heat stack to check. + resource_name_list (list[str]): List of heat stack resources. + glance_image_id_list (list[str]): List of glance image ids. + """ + super().assert_terminate_vnf(resp, vnf_instance_id, stack_id, + resource_name_list, glance_image_id_list) + + # FT-checkpoint: Grant Response + grant_mock_responses = vnflcm_base.FAKE_SERVER_MANAGER.get_history( + fake_grant.Grant.GRANT_REQ_PATH) + vnflcm_base.FAKE_SERVER_MANAGER.clear_history( + fake_grant.Grant.GRANT_REQ_PATH) + self.assertEqual(1, len(grant_mock_responses)) + self._assert_grant_mock_response(grant_mock_responses[0]) + + def _assert_grant_mock_response(self, grant_mock_response, + expected_auth_type=None, expected_token_value=None): + """Assert that HTTP response code is equal to 201 or not. + + This method checks response code of grant request and + authorization result. + + Args: + grant_mock_response (Response): HTTP response object. + expected_auth_type (str, optional): Authentication type. + Defaults to None. + expected_token_value ([type], optional): Authentication token. + Defaults to None. + """ + self.assertEqual(201, grant_mock_response.status_code) + + actual_auth = grant_mock_response.request_headers.get("Authorization") + if expected_auth_type is None: + self.assertIsNone(actual_auth) + return + + self.assertEqual( + '{} {}'.format(expected_auth_type, expected_token_value), + actual_auth) diff --git a/tox.ini b/tox.ini index 9a64b10a3..be96a2488 100644 --- a/tox.ini +++ b/tox.ini @@ -52,6 +52,12 @@ setenv = {[testenv]setenv} commands = stestr --test-path=./tacker/tests/functional/sol run --slowest --concurrency 1 {posargs} +[testenv:dsvm-functional-sol-separated-nfvo] +setenv = {[testenv]setenv} + +commands = + stestr --test-path=./tacker/tests/functional/sol_separated_nfvo run --slowest --concurrency 1 {posargs} + [testenv:debug] commands = oslo_debug_helper {posargs}