From a039fa329e3d5cff7c4c69a56e7986673158b45d Mon Sep 17 00:00:00 2001 From: Manpreet Kaur Date: Mon, 10 Jan 2022 16:31:42 +0530 Subject: [PATCH] FT for multi tenant policy in LCM This patch adds functional test cases to validate the subscription and vnf package functionality in a multi-tenant environment. Validates VNF instantiation is only allowed when VNF and VIM belongs to same tenants. The patch covers only positive test cases of the feature. Validation of negative test cases would require design changes in Fake NFVO server, which could be implemented in the upcoming cycle. Additionally, add missing "domain-name" field while creating VIM config file in tools/gen_vim_config.sh. Implement: blueprint multi-tenant-policy Change-Id: I57d2ec780bd65423820c291bc67d1328bcf9f620 --- .zuul.yaml | 10 + tacker/objects/vnf_lcm_subscriptions.py | 2 +- .../BaseHOT/simple/helloworld3.yaml | 99 ++++ .../BaseHOT/simple/nested/VDU1.yaml | 72 +++ .../BaseHOT/simple/nested/VDU2.yaml | 61 +++ .../Definitions/helloworld3_df_simple.yaml | 403 ++++++++++++++++ .../Definitions/helloworld3_top.vnfd.yaml | 31 ++ .../Definitions/helloworld3_types.yaml | 55 +++ .../mt_functional1/TOSCA-Metadata/TOSCA.meta | 4 + .../nfv/mt_functional1/UserData/__init__.py | 0 .../mt_functional1/UserData/lcm_user_data.py | 35 ++ tacker/tests/functional/base.py | 6 +- tacker/tests/functional/common/fake_server.py | 2 + tacker/tests/functional/sol/vnflcm/base.py | 20 +- .../functional/sol_multi_tenant/__init__.py | 0 .../sol_multi_tenant/vnflcm/__init__.py | 0 .../sol_multi_tenant/vnflcm/base.py | 118 +++++ .../sol_multi_tenant/vnflcm/fake_vnflcm.py | 252 ++++++++++ .../vnflcm/test_vnf_lcm_with_multi_tenant.py | 455 ++++++++++++++++++ tools/gen_vim_config.sh | 1 + tox.ini | 6 + 21 files changed, 1623 insertions(+), 9 deletions(-) create mode 100644 tacker/tests/etc/samples/etsi/nfv/mt_functional1/BaseHOT/simple/helloworld3.yaml create mode 100644 tacker/tests/etc/samples/etsi/nfv/mt_functional1/BaseHOT/simple/nested/VDU1.yaml create mode 100644 tacker/tests/etc/samples/etsi/nfv/mt_functional1/BaseHOT/simple/nested/VDU2.yaml create mode 100644 tacker/tests/etc/samples/etsi/nfv/mt_functional1/Definitions/helloworld3_df_simple.yaml create mode 100644 tacker/tests/etc/samples/etsi/nfv/mt_functional1/Definitions/helloworld3_top.vnfd.yaml create mode 100644 tacker/tests/etc/samples/etsi/nfv/mt_functional1/Definitions/helloworld3_types.yaml create mode 100644 tacker/tests/etc/samples/etsi/nfv/mt_functional1/TOSCA-Metadata/TOSCA.meta create mode 100644 tacker/tests/etc/samples/etsi/nfv/mt_functional1/UserData/__init__.py create mode 100644 tacker/tests/etc/samples/etsi/nfv/mt_functional1/UserData/lcm_user_data.py create mode 100644 tacker/tests/functional/sol_multi_tenant/__init__.py create mode 100644 tacker/tests/functional/sol_multi_tenant/vnflcm/__init__.py create mode 100644 tacker/tests/functional/sol_multi_tenant/vnflcm/base.py create mode 100644 tacker/tests/functional/sol_multi_tenant/vnflcm/fake_vnflcm.py create mode 100644 tacker/tests/functional/sol_multi_tenant/vnflcm/test_vnf_lcm_with_multi_tenant.py 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 c82805803..69f2abe83 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}