diff --git a/.zuul.yaml b/.zuul.yaml index 981325236..196072b7d 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -275,6 +275,15 @@ controller-tacker: tox_envlist: dsvm-functional-sol-v2 +- job: + name: tacker-functional-devstack-multinode-sol-multi-tenant + parent: tacker-functional-devstack-multinode-sol + description: | + Multinodes job for SOL Multi tenant devstack-based functional tests + host-vars: + controller-tacker: + tox_envlist: dsvm-functional-sol-multi-tenant + - job: name: tacker-functional-devstack-multinode-sol-separated-nfvo parent: tacker-functional-devstack-multinode-sol @@ -522,3 +531,4 @@ - tacker-functional-devstack-multinode-sol-kubernetes - tacker-functional-devstack-multinode-libs-master - tacker-functional-devstack-multinode-sol-v2 + - tacker-functional-devstack-multinode-sol-multi-tenant diff --git a/tacker/objects/vnf_lcm_subscriptions.py b/tacker/objects/vnf_lcm_subscriptions.py index aa1151857..50458c85c 100644 --- a/tacker/objects/vnf_lcm_subscriptions.py +++ b/tacker/objects/vnf_lcm_subscriptions.py @@ -163,7 +163,7 @@ def _vnf_lcm_subscriptions_show(context, subscriptionId): sql = text( "select " - "t1.id,t1.callback_uri,t2.filter " + "t1.id,t1.callback_uri,t1.tenant_id,t2.filter " "from vnf_lcm_subscriptions t1, " "(select distinct subscription_uuid,filter from vnf_lcm_filters) t2 " "where t1.id = t2.subscription_uuid " diff --git a/tacker/tests/etc/samples/etsi/nfv/mt_functional1/BaseHOT/simple/helloworld3.yaml b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/BaseHOT/simple/helloworld3.yaml new file mode 100644 index 000000000..1f0d451b9 --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/BaseHOT/simple/helloworld3.yaml @@ -0,0 +1,99 @@ +heat_template_version: 2013-05-23 +description: 'Simple Base HOT for Sample VNF' + +parameters: + nfv: + type: json + +resources: + VDU1_scale: + 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, VirtualStorage, 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_scale_out: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: 1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + VDU1_scale_scale_in: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: -1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + VDU2_scale: + type: OS::Heat::AutoScalingGroup + depends_on: VDU1_scale + 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_scale_out: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: 1 + auto_scaling_group_id: + get_resource: VDU2_scale + adjustment_type: change_in_capacity + VDU2_scale_scale_in: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: -1 + auto_scaling_group_id: + get_resource: VDU2_scale + 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/mt_functional1/BaseHOT/simple/nested/VDU1.yaml b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/BaseHOT/simple/nested/VDU1.yaml new file mode 100644 index 000000000..60fc60313 --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/BaseHOT/simple/nested/VDU1.yaml @@ -0,0 +1,72 @@ +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 + block_device_mapping_v2: [{"volume_id": { get_resource: VirtualStorage }}] + 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 } + + VirtualStorage: + type: OS::Cinder::Volume + properties: + image: { get_param: image } + size: 1 + volume_type: { get_resource: multi } + multi: + type: OS::Cinder::VolumeType + properties: + name: { get_resource: VDU1_CP1 } + metadata: { multiattach: " True" } + 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/mt_functional1/BaseHOT/simple/nested/VDU2.yaml b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/BaseHOT/simple/nested/VDU2.yaml new file mode 100644 index 000000000..d777c76ea --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/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: errornetwork + VDU2_CP5: + type: OS::Neutron::Port + properties: + network: errornetwork diff --git a/tacker/tests/etc/samples/etsi/nfv/mt_functional1/Definitions/helloworld3_df_simple.yaml b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/Definitions/helloworld3_df_simple.yaml new file mode 100644 index 000000000..f108b7f46 --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/Definitions/helloworld3_df_simple.yaml @@ -0,0 +1,403 @@ +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 + 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 + requirements: + - virtual_storage: VirtualStorage + + 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.5.2-x86_64-disk + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + 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 + + VirtualStorage: + type: tosca.nodes.nfv.Vdu.VirtualBlockStorage + properties: + virtual_block_storage_data: + size_of_storage: 1 GB + rdma_enabled: true + sw_image_data: + name: cirros-0.5.2-x86_64-disk + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 12 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: + VDU1_scale: + name: VDU1_scale + description: VDU1 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: VDU1_scale + 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: + VDU1_scale: + scale_level: 0 + instantiation_level_2: + description: Largest size + scale_info: + VDU1_scale: + 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/mt_functional1/Definitions/helloworld3_top.vnfd.yaml b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/Definitions/helloworld3_top.vnfd.yaml new file mode 100644 index 000000000..0be18659b --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/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/mt_functional1/Definitions/helloworld3_types.yaml b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/Definitions/helloworld3_types.yaml new file mode 100644 index 000000000..5c026d6a2 --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/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/mt_functional1/TOSCA-Metadata/TOSCA.meta b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/TOSCA-Metadata/TOSCA.meta new file mode 100644 index 000000000..cfd7444ac --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/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/mt_functional1/UserData/__init__.py b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/UserData/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/tests/etc/samples/etsi/nfv/mt_functional1/UserData/lcm_user_data.py b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/UserData/lcm_user_data.py new file mode 100644 index 000000000..725c5430a --- /dev/null +++ b/tacker/tests/etc/samples/etsi/nfv/mt_functional1/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/base.py b/tacker/tests/functional/base.py index fa12f0dd5..8e4bbd04c 100644 --- a/tacker/tests/functional/base.py +++ b/tacker/tests/functional/base.py @@ -454,10 +454,12 @@ class BaseTackerTest(base.BaseTestCase): return vnf_instance, tosca_dict - def _list_op_occs(self, filter_string=''): + def _list_op_occs(self, filter_string='', http_client=None): + if http_client is None: + http_client = self.http_client show_url = os.path.join( self.base_vnf_lcm_op_occs_url) - resp, response_body = self.http_client.do_request( + resp, response_body = http_client.do_request( show_url + filter_string, "GET") return resp, response_body diff --git a/tacker/tests/functional/common/fake_server.py b/tacker/tests/functional/common/fake_server.py index 6766a09ef..ff701f2e2 100644 --- a/tacker/tests/functional/common/fake_server.py +++ b/tacker/tests/functional/common/fake_server.py @@ -206,6 +206,8 @@ class FakeServerManager(object): """Manager class to manage dummy server setting and control""" SERVER_PORT = 9990 + SERVER_PORT_T1 = 9995 + SERVER_PORT_T2 = 9996 SERVER_INVOKE_CHECK_INTERVAL = 10 def __init__(self): diff --git a/tacker/tests/functional/sol/vnflcm/base.py b/tacker/tests/functional/sol/vnflcm/base.py index 3847cfd4b..082d062ce 100644 --- a/tacker/tests/functional/sol/vnflcm/base.py +++ b/tacker/tests/functional/sol/vnflcm/base.py @@ -209,6 +209,12 @@ def _create_instantiate_vnf_request_body(flavour_id, class BaseVnfLcmTest(base.BaseTackerTest): is_setup_error = False + # NOTE: If prepare_fake_server is set(by default) then this base + # class will prepare(create and start) the fake http server which + # takes time and can add up more time in gate CI. Any child class + # can set it false. For example, BaseVnfLcmMultiTenantTest which + # create their own servers for two different tenants. + prepare_fake_server = True @classmethod def setUpClass(cls): @@ -217,13 +223,15 @@ class BaseVnfLcmTest(base.BaseTackerTest): we set up fake NFVO server for test at here. ''' super(BaseVnfLcmTest, cls).setUpClass() - cls._prepare_start_fake_server(FAKE_SERVER_MANAGER, + if cls.prepare_fake_server: + cls._prepare_start_fake_server(FAKE_SERVER_MANAGER, FAKE_SERVER_PORT) @classmethod def tearDownClass(cls): super(BaseVnfLcmTest, cls).tearDownClass() - FAKE_SERVER_MANAGER.stop_server() + if cls.prepare_fake_server: + FAKE_SERVER_MANAGER.stop_server() def setUp(self): super(BaseVnfLcmTest, self).setUp() @@ -231,10 +239,10 @@ class BaseVnfLcmTest(base.BaseTackerTest): if self.is_setup_error: self.fail("Faild, not exists pre-registered image.") - callback_url = os.path.join( - MOCK_NOTIFY_CALLBACK_URL, - self._testMethodName) - self._clear_history_and_set_callback(FAKE_SERVER_MANAGER, + if self.prepare_fake_server: + callback_url = os.path.join(MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) + self._clear_history_and_set_callback(FAKE_SERVER_MANAGER, callback_url) self.tacker_client = base.BaseTackerTest.tacker_http_client() diff --git a/tacker/tests/functional/sol_multi_tenant/__init__.py b/tacker/tests/functional/sol_multi_tenant/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/tests/functional/sol_multi_tenant/vnflcm/__init__.py b/tacker/tests/functional/sol_multi_tenant/vnflcm/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/tests/functional/sol_multi_tenant/vnflcm/base.py b/tacker/tests/functional/sol_multi_tenant/vnflcm/base.py new file mode 100644 index 000000000..fd239caf3 --- /dev/null +++ b/tacker/tests/functional/sol_multi_tenant/vnflcm/base.py @@ -0,0 +1,118 @@ +# +# 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 tacker.tests.functional import base +from tacker.tests.functional.common.fake_server import FakeServerManager +from tacker.tests.functional.sol.vnflcm import base as vnflcm_base + + +FAKE_SERVER_MANAGER_T1 = FakeServerManager() +FAKE_SERVER_PORT_T1 = 9995 +FAKE_SERVER_MANAGER_T2 = FakeServerManager() +FAKE_SERVER_PORT_T2 = 9996 + + +class BaseVnfLcmMultiTenantTest(vnflcm_base.BaseVnfLcmTest): + + prepare_fake_server = False + + @classmethod + def setUpClass(cls): + super(BaseVnfLcmMultiTenantTest, cls).setUpClass() + + result = cls.get_openstack_client_session( + vim_conf_file='local-tenant1-vim.yaml') + cls.client_tenant1 = result.get('client') + cls.http_client_tenant1 = result.get('http_client') + cls.h_client_tenant1 = result.get('h_client') + cls.glance_client_tenant1 = result.get('glance_client') + + result = cls.get_openstack_client_session( + vim_conf_file='local-tenant2-vim.yaml') + cls.client_tenant2 = result.get('client') + cls.http_client_tenant2 = result.get('http_client') + cls.h_client_tenant2 = result.get('h_client') + cls.glance_client_tenant2 = result.get('glance_client') + + cls.tacker_client_t1 = base.BaseTackerTest.tacker_http_client( + 'local-tenant1-vim.yaml') + cls.tacker_client_t2 = base.BaseTackerTest.tacker_http_client( + 'local-tenant2-vim.yaml') + + # Set up fake NFVO server for tenant1 and tenant2 + cls.servers = {FAKE_SERVER_PORT_T1: FAKE_SERVER_MANAGER_T1, + FAKE_SERVER_PORT_T2: FAKE_SERVER_MANAGER_T2} + # NOTE: Create both server in parallel, otherwise they can + # cause (especially server start) job timeout. + for port, manager in cls.servers.items(): + cls._prepare_start_fake_server(manager, port) + + @classmethod + def tearDownClass(cls): + super(BaseVnfLcmMultiTenantTest, cls).tearDownClass() + for _, manager in cls.servers.items(): + manager.stop_server() + + def setUp(self): + super(BaseVnfLcmMultiTenantTest, self).setUp() + self.base_url = "/vnfpkgm/v1/vnf_packages" + + callback_url = os.path.join( + vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) + self._clear_history_and_set_callback(FAKE_SERVER_MANAGER_T1, + callback_url) + self._clear_history_and_set_callback(FAKE_SERVER_MANAGER_T2, + callback_url) + + vim_list = self.client.list_vims() + self.vim_tenant1 = self.get_vim(vim_list, 'VIM_TEST') + if not self.vim_tenant1: + assert False, "vim_list is Empty: Tenant VIM is missing" + self.vim_tenant2 = self.get_vim(vim_list, 'VIM_DEMO') + if not self.vim_tenant2: + assert False, "vim_list is Empty: Tenant VIM is missing" + + result = self._create_network_settings() + self.ext_networks_tenant1 = result.get('ext_networks') + self.ext_vl_tenant1 = result.get('ext_vl') + self.ext_mngd_networks_tenant1 = result.get('ext_mngd_networks') + self.ext_link_ports_tenant1 = result.get('ext_link_ports') + self.ext_subnets_tenant1 = result.get('ext_subnets') + self.changed_ext_networks_tenant1 = result.get( + 'changed_ext_networks') + self.changed_ext_subnets_tenant1 = result.get( + 'changed_ext_subnets') + + result = self._create_network_settings() + self.ext_networks_tenant2 = result.get('ext_networks') + self.ext_vl_tenant2 = result.get('ext_vl') + self.ext_mngd_networks_tenant2 = result.get('ext_mngd_networks') + self.ext_link_ports_tenant2 = result.get('ext_link_ports') + self.ext_subnets_tenant2 = result.get('ext_subnets') + self.changed_ext_networks_tenant2 = result.get( + 'changed_ext_networks') + self.changed_ext_subnets_tenant2 = result.get( + 'changed_ext_subnets') + + @classmethod + def get_openstack_client_session(cls, vim_conf_file): + client = base.BaseTackerTest.tackerclient(vim_conf_file) + http_client = base.BaseTackerTest.tacker_http_client(vim_conf_file) + h_client = base.BaseTackerTest.heatclient(vim_conf_file) + glance_client = base.BaseTackerTest.glanceclient(vim_conf_file) + return {'client': client, + 'http_client': http_client, + 'h_client': h_client, + 'glance_client': glance_client} diff --git a/tacker/tests/functional/sol_multi_tenant/vnflcm/fake_vnflcm.py b/tacker/tests/functional/sol_multi_tenant/vnflcm/fake_vnflcm.py new file mode 100644 index 000000000..1dc868449 --- /dev/null +++ b/tacker/tests/functional/sol_multi_tenant/vnflcm/fake_vnflcm.py @@ -0,0 +1,252 @@ +# +# 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.tests import uuidsentinel + + +class Subscription: + + @staticmethod + def make_create_request_body(callback_uri): + """Parameter selection policy. + + Set all Notification types and all life cycle types for filter. + Specify OAuth2 for authentication → do not set authentication. + + Args: + callback_uri (str): Notification URI. + + Returns: + dict: Request body + """ + return { + "filter": { + "vnfInstanceSubscriptionFilter": { + "vnfdIds": ["b1bb0ce7-ebca-4fa7-95ed-4840d7000000"], + "vnfProductsFromProviders": [{ + "vnfProvider": "Company", + "vnfProducts": [ + { + "vnfProductName": "Sample VNF", + "versions": [ + { + "vnfSoftwareVersion": "1.0", + "vnfdVersions": ["1.0"] + } + ] + } + ] + }] + }, + "notificationTypes": [ + "VnfLcmOperationOccurrenceNotification", + "VnfIdentifierCreationNotification", + "VnfIdentifierDeletionNotification" + ], + "operationTypes": [ + "INSTANTIATE", + "SCALE", + "TERMINATE", + "HEAL", + "MODIFY_INFO", + "CHANGE_EXT_CONN" + ], + "operationStates": ["STARTING"] + }, + "callbackUri": callback_uri + } + + +ext_vdu1_cp1 = { + "cpdId": "VDU1_CP1", + "cpConfig": [{ + "linkPortId": uuidsentinel.elp1_id + }], +} +ext_vdu2_cp1 = { + "cpdId": "VDU2_CP1", + "cpConfig": [{ + "linkPortId": uuidsentinel.elp2_id + }] +} + + +def _set_ext_link_port1(external_ports_id): + ext_link_port1 = { + "id": uuidsentinel.elp1_id, + "resourceHandle": { + "vimConnectionId": uuidsentinel.vim_connection_id, + "resourceId": external_ports_id[0] + } + } + return ext_link_port1 + + +def _set_ext_link_port2(external_ports_id): + ext_link_port2 = { + "id": uuidsentinel.elp2_id, + "resourceHandle": { + "vimConnectionId": uuidsentinel.vim_connection_id, + "resourceId": external_ports_id[1] + } + } + return ext_link_port2 + + +def _set_ext_virtual_link_cp1(networks_id, external_ports_id): + ext_virtual_link_cp1 = { + "id": uuidsentinel.evl1_id, + "resourceId": networks_id[0], + "vimConnectionId": uuidsentinel.vim_connection_id, + "extCps": [ext_vdu1_cp1, ext_vdu2_cp1], + "extLinkPorts": [ + _set_ext_link_port1(external_ports_id), + _set_ext_link_port2(external_ports_id)] + } + return ext_virtual_link_cp1 + + +def _set_ext_cps_vdu1_cp2(external_subnets_id): + ext_cps_vdu1_cp2 = { + "cpdId": "VDU1_CP2", + "cpConfig": [{ + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "fixedAddresses": ["22.22.1.10"], + "subnetId": external_subnets_id[1] + }] + } + }] + }] + } + return ext_cps_vdu1_cp2 + + +def _set_ext_cps_vdu2_cp2(external_subnets_id): + ext_cps_vdu2_cp2 = { + "cpdId": "VDU2_CP2", + "cpConfig": [{ + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "fixedAddresses": ["22.22.1.20"], + "subnetId": external_subnets_id[1] + }] + } + }] + }] + } + return ext_cps_vdu2_cp2 + + +def _set_ext_virtual_link_cp2(networks_id, external_subnets_id): + ext_virtual_link_cp2 = { + "id": uuidsentinel.evl2_id, + "resourceId": networks_id[1], + "vimConnectionId": uuidsentinel.vim_connection_id, + "extCps": [ + _set_ext_cps_vdu1_cp2(external_subnets_id), + _set_ext_cps_vdu2_cp2(external_subnets_id) + ] + } + return ext_virtual_link_cp2 + + +def _set_ext_mng_vtl_lnks(ext_mngd_networks_id): + ext_mng_vtl_lnks = [{ + "id": uuidsentinel.emvl1_id, + "vnfVirtualLinkDescId": "internalVL1", + "resourceId": ext_mngd_networks_id[0] + }, { + "id": uuidsentinel.emvl2_id, + "vnfVirtualLinkDescId": "internalVL2", + "resourceId": ext_mngd_networks_id[1] + }] + return ext_mng_vtl_lnks + + +class VnfInstances: + + @staticmethod + def make_create_request_body(vnfd_id): + return { + "vnfdId": vnfd_id, + "vnfInstanceName": "", + "vnfInstanceDescription": "Sample VNF", + "metadata": { + "samplekey": "samplevalue" + } + } + + @staticmethod + def make_inst_request_body( + user_name, + tenant_id, + networks_id, + ext_mngd_networks_id, + external_ports_id, + external_subnets_id): + data = { + "flavourId": "simple", + "instantiationLevelId": "instantiation_level_1", + "extVirtualLinks": [ + _set_ext_virtual_link_cp1( + networks_id, external_ports_id), + _set_ext_virtual_link_cp2( + networks_id, external_subnets_id) + ], + "extManagedVirtualLinks": _set_ext_mng_vtl_lnks( + ext_mngd_networks_id), + "vimConnectionInfo": [{ + "id": uuidsentinel.vim_connection_id, + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.v_2", + "vimConnectionId": uuidsentinel.vim_connection_id, + "interfaceInfo": { + "endpoint": "http://127.0.0.1/identity" + }, + "accessInfo": { + "username": user_name, + "region": "RegionOne", + "password": "devstack", + "tenant": tenant_id + } + }], + "additionalParams": { + "lcm-operation-user-data": "./UserData/lcm_user_data.py", + "lcm-operation-user-data-class": "SampleUserData" + } + } + + return data + + @staticmethod + def make_term_request_body(): + """Parameter selection policy. + + As all parameters are set, GRACEFUL is specified for terminationType. + (to specify gracefulTerminationTimeout) + + Returns: + dict: Request body + """ + return { + "terminationType": "GRACEFUL", + "gracefulTerminationTimeout": 1, + "additionalParams": { + "samplekey": "samplevalue" + } + } diff --git a/tacker/tests/functional/sol_multi_tenant/vnflcm/test_vnf_lcm_with_multi_tenant.py b/tacker/tests/functional/sol_multi_tenant/vnflcm/test_vnf_lcm_with_multi_tenant.py new file mode 100644 index 000000000..799e00174 --- /dev/null +++ b/tacker/tests/functional/sol_multi_tenant/vnflcm/test_vnf_lcm_with_multi_tenant.py @@ -0,0 +1,455 @@ +# +# 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.objects import fields +from tacker.tests.functional.sol.vnflcm import base as vnflcm_base +from tacker.tests.functional.sol_multi_tenant.vnflcm import base +from tacker.tests.functional.sol_multi_tenant.vnflcm import fake_vnflcm +import tempfile +import time + + +class VnfLcmWithMultiTenant(base.BaseVnfLcmMultiTenantTest): + + VNF_PACKAGE_DELETE_TIMEOUT = 120 + + @classmethod + def setUpClass(cls): + super(VnfLcmWithMultiTenant, cls).setUpClass() + + # ModifyVNF specific image create. + is_setup_error_tenant1 = cls._modify_vnf_specific_image_create( + cls.glance_client_tenant1) + if is_setup_error_tenant1: + cls.is_setup_error = True + return + + is_setup_error_tenant2 = cls._modify_vnf_specific_image_create( + cls.glance_client_tenant2) + if is_setup_error_tenant2: + cls.is_setup_error = True + return + + @classmethod + def _modify_vnf_specific_image_create(cls, glance_clt): + is_setup_error = False + images = cls._list_glance_image() + if len(images) == 0: + is_setup_error = True + return is_setup_error + + for image in images: + specific_image_name = f'{image.name}{2}' + image_data = { + "min_disk": image.min_disk, + "min_ram": image.min_ram, + "disk_format": image.disk_format, + "container_format": image.container_format, + "visibility": image.visibility, + "name": specific_image_name} + + try: + images = cls._list_glance_image(specific_image_name) + if len(images) == 1: + break + + _, body = glance_clt.http_client.get( + f'{glance_clt.http_client.get_endpoint()}{image.file}') + + with tempfile.TemporaryFile('w+b') as f: + for content in body: + f.write(content) + cls._create_glance_image(image_data, f.read()) + except Exception as e: + print("Fail, Modify-VNF specific image create.", e, flush=True) + is_setup_error = True + return is_setup_error + + return is_setup_error + + def _wait_show_subscription(self, subscription_id, tacker_client): + # wait for subscription creation + timeout = vnflcm_base.VNF_SUBSCRIPTION_TIMEOUT + start_time = int(time.time()) + while True: + resp, body = self._show_subscription(subscription_id, + tacker_client) + if resp.ok or resp.status_code == 404: + return resp, body + + if ((int(time.time()) - start_time) > timeout): + if resp: + resp.raise_for_status() + raise TimeoutError("Failed to show_subscription") + + time.sleep(1) + + def _delete_vnf_package(self, package_uuid, http_client): + url = os.path.join(self.base_url, package_uuid) + resp, _ = http_client.do_request(url, "DELETE") + self.assertEqual(204, resp.status_code) + + def _wait_for_delete(self, package_uuid, http_client): + show_url = os.path.join(self.base_url, package_uuid) + timeout = self.VNF_PACKAGE_DELETE_TIMEOUT + start_time = int(time.time()) + while True: + resp, body = http_client.do_request(show_url, "GET") + if (404 == resp.status_code): + return resp, body + + if (int(time.time()) - start_time) > timeout: + raise Exception("Failed to delete package") + time.sleep(1) + + def _disable_operational_state(self, package_uuid, http_client): + update_req_body = jsonutils.dumps({ + "operationalState": "DISABLED"}) + + resp, _ = http_client.do_request( + '{base_path}/{id}'.format(id=package_uuid, + base_path=self.base_url), + "PATCH", content_type='application/json', + body=update_req_body) + self.assertEqual(200, resp.status_code) + + def assert_vnf_package_usage_state( + self, + vnf_package_info, + expected_usage_state=fields.PackageUsageStateType.IN_USE): + self.assertEqual( + expected_usage_state, + vnf_package_info['usageState']) + + def assert_create_vnf(self, resp, vnf_instance, vnf_pkg_id, + tacker_client, fake_server_manager): + super().assert_create_vnf(resp, vnf_instance, + fake_server_manager) + + resp, vnf_pkg_info = vnflcm_base._show_vnf_package( + tacker_client, vnf_pkg_id) + self.assert_vnf_package_usage_state(vnf_pkg_info) + + def _vnf_instance_wait_until_fail_detected(self, id, + instantiation_state=fields.VnfInstanceState.NOT_INSTANTIATED, + timeout=vnflcm_base.VNF_INSTANTIATE_ERROR_WAIT): + time.sleep(timeout) + _, body = self._show_vnf_instance(id) + 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 test_subscription_functionality(self): + """Test subscription operations with member role users. + + In this test case, we do following steps. + Note: User A belongs to Tenant 1(t1). + User B belongs to Tenant 2(t2). + - Create subscription. + - User A registers Subscription A(Notification Server A). + - User B registers Subscription B(Notification Server B). + - Show Subscription + - User A only gets information about Subscription A. + - User B only gets information about Subscription B. + - List Subscription + - User A gets subscription list and confirms only + Subscription A is output. + - User B gets subscription list and confirms only + Subscription B is output. + - Delete Subscription + - User A deletes Subscription A. + - User B deletes Subscription B. + TODO(manpreetk): Only positive test cases are validated in + Y-release. + Negative test cases + - User A fails to delete Subscription B. + - User B fails to delete Subscription A. + Validation of negative test cases would require design changes + in Fake NFVO server, which could be implemented in the upcoming + cycle. + """ + # Create subscription + # User A registers Subscription A. + callback_url = os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) + req_body = fake_vnflcm.Subscription.make_create_request_body( + 'http://localhost:{}{}'.format( + base.FAKE_SERVER_MANAGER_T1.SERVER_PORT_T1, + callback_url)) + resp_t1, resp_body_t1 = self._register_subscription(req_body, + self.http_client_tenant1) + self.assertEqual(201, resp_t1.status_code) + self.assert_http_header_location_for_subscription( + resp_t1.headers) + self.assert_notification_get(callback_url, + base.FAKE_SERVER_MANAGER_T1) + subscription_id_t1 = resp_body_t1.get('id') + + # User B registers Subscription B + callback_url = os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) + req_body_t2 = fake_vnflcm.Subscription.make_create_request_body( + 'http://localhost:{}{}'.format( + base.FAKE_SERVER_MANAGER_T2.SERVER_PORT_T2, + callback_url)) + resp_t2, resp_body_t2 = self._register_subscription( + req_body_t2, self.http_client_tenant2) + self.assertEqual(201, resp_t2.status_code) + self.assert_http_header_location_for_subscription( + resp_t2.headers) + self.assert_notification_get(callback_url, + base.FAKE_SERVER_MANAGER_T2) + subscription_id_t2 = resp_body_t2.get('id') + + # Show Subscription + # User A gets information for Subscription A + resp_t1, resp_body_show_t1 = self._wait_show_subscription( + subscription_id_t1, self.tacker_client_t1) + self.assert_subscription_show(resp_t1, resp_body_show_t1) + + # User B gets information for Subscription B + resp_t2, resp_body_show_t2 = self._wait_show_subscription( + subscription_id_t2, self.tacker_client_t2) + self.assert_subscription_show(resp_t2, resp_body_show_t2) + + # List Subscription + # User A gets subscription list + resp, _ = self._list_subscription(self.tacker_client_t1) + self.assertEqual(200, resp_t1.status_code) + + # Confirm subscription A + filter_expr = { + 'filter': "filter=(eq,id,{})".format( + resp_body_show_t1.get('id'))} + resp, subscription_body_t1 = self._list_subscription_filter( + self.http_client_tenant1, + params=filter_expr) + self.assertEqual(200, resp.status_code) + self.assertEqual(1, len(subscription_body_t1)) + + # User B gets subscription list + resp_t2, _ = self._list_subscription( + self.tacker_client_t2) + self.assertEqual(200, resp_t2.status_code) + + # Confirm subscription B + filter_expr = { + 'filter': "filter=(eq,id,{})".format( + resp_body_show_t2.get('id'))} + resp, subscription_body_t2 = self._list_subscription_filter( + self.http_client_tenant2, + params=filter_expr) + self.assertEqual(200, resp.status_code) + self.assertEqual(1, len(subscription_body_t2)) + + # Delete subscription + # User A deletes Subscription A + self.addCleanup(self._delete_subscription, + subscription_id_t1, self.tacker_client_t1) + + # User B deletes Subscription B + self.addCleanup(self._delete_subscription, + subscription_id_t2, self.tacker_client_t2) + + def test_vnf_package_functionality(self): + """Test VNF package operations with member role users. + + In this test case, we do following steps. + Note: User A belongs to Tenant 1. + User B belongs to Tenant 2. + - Create and Upload VNF Package + - User A creates VNF Package A. + - User A uploads VNF Package A. + - User B creates VNF Package B. + - User B uploads VNF Package B. + - List VNF Package + - User A gets VNF package list and confirms only + VNF Package A is output. + - User B gets VNF package list and confirms only + VNF Package B is output. + - Show VNF Package + - User A only gets information about VNF Package A. + - User B only gets information about VNF Package B. + - Delete VNF Package + - User A deletes VNF Package A. + - User B deletes VNF Package B. + TODO(manpreetk): Only positive test cases are validated in + Y-release. + Negative test cases + - User A fails to delete VNF Package B. + - User B fails to delete VNF Package A. + Validation of negative test cases would require design changes + in Fake NFVO server, which could be implemented in the upcoming + cycle. + """ + # Create and Upload VNF Package + # User A creates VNF Package A + sample_name = 'mt_functional1' + csar_package_path = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../etc/samples/etsi/nfv", + sample_name)) + tempname, _ = vnflcm_base._create_csar_with_unique_vnfd_id( + csar_package_path) + + # User A uploads VNF Package A + vnf_pkg_id, vnfd_id = vnflcm_base._create_and_upload_vnf_package( + self.tacker_client_t1, user_defined_data={ + "key": sample_name}, temp_csar_path=tempname) + + # User B creates VNF Package B + sample_name_t2 = 'mt_functional1' + csar_package_path_t2 = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../etc/samples/etsi/nfv", + sample_name_t2)) + tempname_t2, _ = vnflcm_base._create_csar_with_unique_vnfd_id( + csar_package_path_t2) + + # User B uploads VNF Package B + vnf_pkg_id2, vnfd_id2 = vnflcm_base._create_and_upload_vnf_package( + self.tacker_client_t2, user_defined_data={ + "key": sample_name_t2}, temp_csar_path=tempname_t2) + + # List VNF Package + # User A gets VNF package list and confirms only VNF Package A + # is output. + resp, body = self.http_client_tenant1.do_request( + self.base_url, "GET") + self.assertEqual(200, resp.status_code) + + package_id = [obj['id'] for obj in body] + self.assertIn(vnf_pkg_id, package_id) + + # User B gets VNF package list and confirms only VNF Package B + # is output. + resp_t2, body_t2 = self.http_client_tenant2.do_request( + self.base_url, "GET") + self.assertEqual(200, resp_t2.status_code) + + package_id = [obj['id'] for obj in body_t2] + self.assertIn(vnf_pkg_id2, package_id) + + # Show VNF Package + # User A only gets information about VNF Package A + show_url = os.path.join(self.base_url, vnf_pkg_id) + resp, body = self.http_client_tenant1.do_request( + show_url, "GET") + self.assertEqual(200, resp.status_code) + + # User B only gets information about VNF Package B + show_url_t2 = os.path.join(self.base_url, vnf_pkg_id2) + resp_t2, body_t2 = self.http_client_tenant2.do_request( + show_url_t2, "GET") + self.assertEqual(200, resp_t2.status_code) + + # Delete VNF Package + # User A deletes VNF Package A + self._disable_operational_state(vnf_pkg_id, + self.http_client_tenant1) + self._delete_vnf_package(vnf_pkg_id, self.http_client_tenant1) + self._wait_for_delete(vnf_pkg_id, self.http_client_tenant1) + + # User B deletes VNF Package B + self._disable_operational_state(vnf_pkg_id2, + self.http_client_tenant2) + self._delete_vnf_package(vnf_pkg_id2, self.http_client_tenant2) + self._wait_for_delete(vnf_pkg_id2, self.http_client_tenant2) + + def test_vnf_instantiation_by_vim_of_different_tenant_and_role(self): + """Test VNF instantiation by VIM of differnt tenant. + + In this test case, we do following steps. + Note: User A is an admin role user belongs to Tenant 1. + User B is a non-admin role user belongs to Tenant 2. + - Create VNF Instance + - User B creates VNF Instance B using VNF Package B. + - Instantiate VNF + - User A fails to instantiates VNF Instance B, both + VNF and VIM belong to different tenant. + """ + # Pre-Setting + # User B registers Subscription B. + callback_url = os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) + req_body_t2 = fake_vnflcm.Subscription.make_create_request_body( + 'http://localhost:{}{}'.format( + base.FAKE_SERVER_MANAGER_T2.SERVER_PORT_T2, + callback_url)) + resp_t2, resp_body_t2 = self._register_subscription( + req_body_t2, self.http_client_tenant2) + self.assertEqual(201, resp_t2.status_code) + self.assert_http_header_location_for_subscription( + resp_t2.headers) + self.assert_notification_get(callback_url, + base.FAKE_SERVER_MANAGER_T2) + subscription_id_t2 = resp_body_t2.get('id') + self.addCleanup( + self._delete_subscription, + subscription_id_t2, + self.tacker_client_t2) + + # Create and Upload VNF Package + # User B creates VNF Package B + sample_name_t2 = 'mt_functional1' + csar_package_path_t2 = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../etc/samples/etsi/nfv", + sample_name_t2)) + tempname_t2, _ = vnflcm_base._create_csar_with_unique_vnfd_id( + csar_package_path_t2) + + # User B uploads VNF Package B + vnf_pkg_id, vnfd_id = vnflcm_base._create_and_upload_vnf_package( + self.tacker_client_t2, user_defined_data={ + "key": sample_name_t2}, temp_csar_path=tempname_t2) + + # Post Setting: Reserve deleting VNF package. + self.addCleanup(vnflcm_base._delete_vnf_package, + self.tacker_client_t2, vnf_pkg_id) + + # Create VNF Instance + # User B creates VNF Instance B using VNF Package B + resp_t2, vnf_instance_t2 = self._create_vnf_instance_from_body( + fake_vnflcm.VnfInstances.make_create_request_body(vnfd_id), + self.http_client_tenant2) + vnf_instance_id_t2 = vnf_instance_t2.get('id') + self._wait_lcm_done(vnf_instance_id=vnf_instance_id_t2, + fake_server_manager=base.FAKE_SERVER_MANAGER_T2) + self.assert_create_vnf(resp_t2, vnf_instance_t2, + vnf_pkg_id, + self.tacker_client_t2, + base.FAKE_SERVER_MANAGER_T2) + + # Instantiate VNF instance + # User A fails to instantiate VNF Instance B as both VIM and VNF + # belongs to differernt tenants + request_body = fake_vnflcm.VnfInstances.make_inst_request_body( + 'nfv_user', + 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_t2, + request_body, self.http_client) + self.assertEqual(202, resp.status_code) + self._vnf_instance_wait_until_fail_detected(vnf_instance_id_t2) diff --git a/tools/gen_vim_config.sh b/tools/gen_vim_config.sh index fa4ade5eb..99f5c8839 100755 --- a/tools/gen_vim_config.sh +++ b/tools/gen_vim_config.sh @@ -88,6 +88,7 @@ auth_url: "${VIMC_ENDPOINT}" username: "${VIMC_OS_USER}" password: "${VIMC_OS_PASSWORD}" project_name: "${VIMC_PROJ}" +domain_name: "${VIMC_OS_PROJ_DOMAIN}" project_domain_name: "${VIMC_OS_PROJ_DOMAIN}" user_domain_name: "${VIMC_OS_USER_DOMAIN}" cert_verify: "${_cert_verify}" diff --git a/tox.ini b/tox.ini index 42fb774fd..e3ad0942f 100644 --- a/tox.ini +++ b/tox.ini @@ -61,6 +61,12 @@ setenv = {[testenv]setenv} commands = stestr --test-path=./tacker/tests/functional/sol_v2 run --slowest --concurrency 1 {posargs} +[testenv:dsvm-functional-sol-multi-tenant] +setenv = {[testenv]setenv} + +commands = + stestr --test-path=./tacker/tests/functional/sol_multi_tenant run --slowest --concurrency 1 {posargs} + [testenv:debug] commands = oslo_debug_helper {posargs}