Add new FT set for tests with separated NFVO

This patch adds new test with separated NFVO such as Grant API.
The test requires additinal config of Tacker, and we cannot use
Tacker with such config for other tests. So we add new FT set for
separated NFVO environment.

Change-Id: I9fb5ef027b9010becc088ca0b66138bcf52512c2
This commit is contained in:
Toshiaki Takahashi 2020-12-09 16:37:27 +09:00
parent 9e0eccac8b
commit 0d412ba7cb
17 changed files with 1405 additions and 0 deletions

View File

@ -214,6 +214,24 @@
controller-tacker:
tox_envlist: dsvm-functional-sol
- job:
name: tacker-functional-devstack-multinode-sol-separated-nfvo
parent: tacker-functional-devstack-multinode-sol
description: |
Multinodes job for SOL devstack-based functional tests
with separated NFVO
host-vars:
controller-tacker:
devstack_local_conf:
post-config:
$TACKER_CONF:
connect_vnf_packages:
base_url: http://127.0.0.1:9990/vnfpkgm/v1/vnf_packages
pipeline: package_content,vnfd,artifacts
connect_grant:
base_url: http://127.0.0.1:9990/grant/v1/grants
tox_envlist: dsvm-functional-sol-separated-nfvo
- project:
templates:
- check-requirements
@ -226,3 +244,4 @@
jobs:
- tacker-functional-devstack-multinode-legacy
- tacker-functional-devstack-multinode-sol
- tacker-functional-devstack-multinode-sol-separated-nfvo

View File

@ -63,6 +63,14 @@ if [ "${TACKER_MODE}" == "all" ]; then
done
fi
elif [ "${TACKER_MODE}" == "standalone" ]; then
# TODO(takahashi-tsc) Remove gawk installation after the following bug is fixed.
# https://bugs.launchpad.net/devstack/+bug/1909041
# post-config requires gawk.
# Generally, gawk is requires by Nova,
# but Nova is not mandatory for Tacker.
# So we install gawk manually until the bug is fixed.
install_package gawk
enable_service dstat
enable_service tacker
enable_service tacker-conductor

View File

@ -0,0 +1,101 @@
heat_template_version: 2013-05-23
description: 'Simple Base HOT for Sample VNF'
parameters:
nfv:
type: json
resources:
VDU1:
type: OS::Heat::AutoScalingGroup
properties:
min_size: 1
max_size: 3
desired_capacity: 1
resource:
type: VDU1.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU1, flavor ] }
image: { get_param: [ nfv, VDU, VDU1, image ] }
zone: { get_param: [ nfv, vdu, VDU1, zone ] }
net1: { get_param: [ nfv, CP, VDU1_CP1, network ] }
net2: { get_param: [ nfv, CP, VDU1_CP2, network ] }
net3: { get_resource: extmanageNW_1 }
net4: { get_resource: extmanageNW_2 }
net5: { get_resource: internalNW_1 }
VDU1_scale_out:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: 1
auto_scaling_group_id:
get_resource: VDU1
adjustment_type: change_in_capacity
VDU1_scale_in:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: -1
auto_scaling_group_id:
get_resource: VDU1
adjustment_type: change_in_capacity
VDU2:
type: OS::Heat::AutoScalingGroup
depends_on: VDU1
properties:
min_size: 2
max_size: 2
desired_capacity: 2
resource:
type: VDU2.yaml
properties:
flavor: { get_param: [ nfv, VDU, VDU2, flavor ] }
image: { get_param: [ nfv, VDU, VDU2, image ] }
zone: { get_param: [ nfv, vdu, VDU2, zone ] }
net1: { get_param: [ nfv, CP, VDU2_CP1, network ] }
net2: { get_param: [ nfv, CP, VDU2_CP2, network ] }
net3: { get_resource: extmanageNW_1 }
net4: { get_resource: extmanageNW_2 }
net5: { get_resource: internalNW_1 }
VDU2_scale_out:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: 1
auto_scaling_group_id:
get_resource: VDU2
adjustment_type: change_in_capacity
VDU2_scale_in:
type: OS::Heat::ScalingPolicy
properties:
scaling_adjustment: -1
auto_scaling_group_id:
get_resource: VDU2
adjustment_type: change_in_capacity
extmanageNW_1:
type: OS::Neutron::Net
extmanageNW_2:
type: OS::Neutron::Net
internalNW_1:
type: OS::Neutron::Net
extmanageNW_1_subnet:
type: OS::Neutron::Subnet
properties:
ip_version: 4
network:
get_resource: extmanageNW_1
cidr: 192.168.3.0/24
extmanageNW_2_subnet:
type: OS::Neutron::Subnet
properties:
ip_version: 4
network:
get_resource: extmanageNW_2
cidr: 192.168.4.0/24
internalNW_1_subnet:
type: OS::Neutron::Subnet
properties:
ip_version: 4
network:
get_resource: internalNW_1
cidr: 192.168.5.0/24
outputs: {}

View File

@ -0,0 +1,63 @@
heat_template_version: 2013-05-23
description: 'VDU1 HOT for Sample VNF'
parameters:
flavor:
type: string
image:
type: string
zone:
type: string
net1:
type: string
net2:
type: string
net3:
type: string
net4:
type: string
net5:
type: string
resources:
VDU1:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
name: VDU1
image: { get_param: image }
networks:
- port:
get_resource: VDU1_CP1
- port:
get_resource: VDU1_CP2
- port:
get_resource: VDU1_CP3
- port:
get_resource: VDU1_CP4
- port:
get_resource: VDU1_CP5
availability_zone: { get_param: zone }
VDU1_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: net1 }
VDU1_CP2:
type: OS::Neutron::Port
properties:
network: { get_param: net2 }
VDU1_CP3:
type: OS::Neutron::Port
properties:
network: { get_param: net3 }
VDU1_CP4:
type: OS::Neutron::Port
properties:
network: { get_param: net4 }
VDU1_CP5:
type: OS::Neutron::Port
properties:
network: { get_param: net5 }

View File

@ -0,0 +1,61 @@
heat_template_version: 2013-05-23
description: 'VDU2 HOT for Sample VNF'
parameters:
flavor:
type: string
image:
type: string
zone:
type: string
net1:
type: string
net2:
type: string
net3:
type: string
net4:
type: string
net5:
type: string
resources:
VDU2:
type: OS::Nova::Server
properties:
flavor: { get_param: flavor }
name: VDU2
image: { get_param: image }
networks:
- port:
get_resource: VDU2_CP1
- port:
get_resource: VDU2_CP2
- port:
get_resource: VDU2_CP3
- port:
get_resource: VDU2_CP4
- port:
get_resource: VDU2_CP5
availability_zone: { get_param: zone }
VDU2_CP1:
type: OS::Neutron::Port
properties:
network: { get_param: net1 }
VDU2_CP2:
type: OS::Neutron::Port
properties:
network: { get_param: net2 }
VDU2_CP3:
type: OS::Neutron::Port
properties:
network: { get_param: net3 }
VDU2_CP4:
type: OS::Neutron::Port
properties:
network: { get_param: net4 }
VDU2_CP5:
type: OS::Neutron::Port
properties:
network: { get_param: net5 }

View File

@ -0,0 +1,396 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Simple deployment flavour for Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- helloworld3_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: company.provider.VNF
properties:
flavour_id: simple
requirements:
virtual_link_external1_1: [ VDU1_CP1, virtual_link ]
virtual_link_external1_2: [ VDU2_CP1, virtual_link ]
virtual_link_external2_1: [ VDU1_CP2, virtual_link ]
virtual_link_external2_2: [ VDU2_CP2, virtual_link ]
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_description: A simple flavour
interfaces:
Vnflcm:
instantiate: []
instantiate_start: []
instantiate_end: []
terminate: []
terminate_start: []
terminate_end: []
modify_information: []
modify_information_start: []
modify_information_end: []
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU1
description: VDU1 compute node
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 3
sw_image_data:
name: cirros-0.4.0-x86_64-disk
version: '0.4.0'
checksum:
algorithm: sha-256
hash: a8dd75ecffd4cdd96072d60c2237b448e0c8b2bc94d57f10fdbc8c481d9005b8
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 12 GB
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
VDU2:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: VDU2
description: VDU2 compute node
vdu_profile:
min_number_of_instances: 2
max_number_of_instances: 2
sw_image_data:
name: cirros-0.4.0-x86_64-disk
version: '0.4.0'
checksum:
algorithm: sha-256
hash: a8dd75ecffd4cdd96072d60c2237b448e0c8b2bc94d57f10fdbc8c481d9005b8
container_format: bare
disk_format: qcow2
min_disk: 0 GB
min_ram: 256 MB
size: 12 GB
capabilities:
virtual_compute:
properties:
requested_additional_capabilities:
properties:
requested_additional_capability_name: m1.tiny
support_mandatory: true
target_performance_parameters:
entry_schema: test
virtual_memory:
virtual_mem_size: 512 MB
virtual_cpu:
num_virtual_cpu: 1
virtual_local_storage:
- size_of_storage: 3 GB
VDU1_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU1
VDU1_CP2:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 1
requirements:
- virtual_binding: VDU1
VDU1_CP3:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 2
requirements:
- virtual_binding: VDU1
- virtual_link: internalVL1
VDU1_CP4:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 3
requirements:
- virtual_binding: VDU1
- virtual_link: internalVL2
VDU1_CP5:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 4
requirements:
- virtual_binding: VDU1
- virtual_link: internalVL3
VDU2_CP1:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 0
requirements:
- virtual_binding: VDU2
VDU2_CP2:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 1
requirements:
- virtual_binding: VDU2
VDU2_CP3:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 2
requirements:
- virtual_binding: VDU2
- virtual_link: internalVL1
VDU2_CP4:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 3
requirements:
- virtual_binding: VDU2
- virtual_link: internalVL2
VDU2_CP5:
type: tosca.nodes.nfv.VduCp
properties:
layer_protocols: [ ipv4 ]
order: 4
requirements:
- virtual_binding: VDU2
- virtual_link: internalVL3
internalVL1:
type: tosca.nodes.nfv.VnfVirtualLink
properties:
connectivity_type:
layer_protocols: [ ipv4 ]
description: External Managed Virtual link in the VNF
vl_profile:
max_bitrate_requirements:
root: 1048576
leaf: 1048576
min_bitrate_requirements:
root: 1048576
leaf: 1048576
virtual_link_protocol_data:
- associated_layer_protocol: ipv4
l3_protocol_data:
ip_version: ipv4
cidr: 33.33.0.0/24
internalVL2:
type: tosca.nodes.nfv.VnfVirtualLink
properties:
connectivity_type:
layer_protocols: [ ipv4 ]
description: External Managed Virtual link in the VNF
vl_profile:
max_bitrate_requirements:
root: 1048576
leaf: 1048576
min_bitrate_requirements:
root: 1048576
leaf: 1048576
virtual_link_protocol_data:
- associated_layer_protocol: ipv4
l3_protocol_data:
ip_version: ipv4
cidr: 33.34.0.0/24
internalVL3:
type: tosca.nodes.nfv.VnfVirtualLink
properties:
connectivity_type:
layer_protocols: [ ipv4 ]
description: Internal Virtual link in the VNF
vl_profile:
max_bitrate_requirements:
root: 1048576
leaf: 1048576
min_bitrate_requirements:
root: 1048576
leaf: 1048576
virtual_link_protocol_data:
- associated_layer_protocol: ipv4
l3_protocol_data:
ip_version: ipv4
cidr: 33.35.0.0/24
policies:
- scaling_aspects:
type: tosca.policies.nfv.ScalingAspects
properties:
aspects:
worker_instance:
name: worker_instance_aspect
description: worker_instance scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- VDU1_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU1 ]
- VDU2_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 2
targets: [ VDU2 ]
- VDU1_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: worker_instance
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU1 ]
- instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels
properties:
levels:
instantiation_level_1:
description: Smallest size
scale_info:
worker_instance:
scale_level: 0
instantiation_level_2:
description: Largest size
scale_info:
worker_instance:
scale_level: 2
default_level: instantiation_level_1
- VDU1_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU1 ]
- VDU2_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 2
instantiation_level_2:
number_of_instances: 2
targets: [ VDU2 ]
- internalVL1_instantiation_levels:
type: tosca.policies.nfv.VirtualLinkInstantiationLevels
properties:
levels:
instantiation_level_1:
bitrate_requirements:
root: 1048576
leaf: 1048576
instantiation_level_2:
bitrate_requirements:
root: 1048576
leaf: 1048576
targets: [ internalVL1 ]
- internalVL2_instantiation_levels:
type: tosca.policies.nfv.VirtualLinkInstantiationLevels
properties:
levels:
instantiation_level_1:
bitrate_requirements:
root: 1048576
leaf: 1048576
instantiation_level_2:
bitrate_requirements:
root: 1048576
leaf: 1048576
targets: [ internalVL2 ]
- internalVL3_instantiation_levels:
type: tosca.policies.nfv.VirtualLinkInstantiationLevels
properties:
levels:
instantiation_level_1:
bitrate_requirements:
root: 1048576
leaf: 1048576
instantiation_level_2:
bitrate_requirements:
root: 1048576
leaf: 1048576
targets: [ internalVL3 ]
- policy_antiaffinity_vdu1:
type: tosca.policies.nfv.AntiAffinityRule
targets: [ VDU1 ]
properties:
scope: zone
- policy_antiaffinity_vdu2:
type: tosca.policies.nfv.AntiAffinityRule
targets: [ VDU2 ]
properties:
scope: zone

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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}

View File

@ -0,0 +1,205 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from oslo_serialization import jsonutils
from tacker.tests import uuidsentinel
class Grant:
GRANT_REQ_PATH = '/grant/v1/grants'
ZONES = [
{
"id": uuidsentinel.zone_id,
"zoneId": "nova",
"vimConnectionId": uuidsentinel.vim_connection_id
}
]
ADDITIONAL_PARAMS = {
"key": "value"
}
@staticmethod
def _make_vim_connection_info(tenant_id):
access_info = {
"username": "nfv_user",
"region": "RegionOne",
"password": "devstack",
"tenant": tenant_id
}
return [{
"id": uuidsentinel.vim_connection_id,
"vimType": "ETSINFV.OPENSTACK_KEYSTONE.v_2",
"interfaceInfo": {
"endpoint": "http://127.0.0.1/identity"
},
"accessInfo": access_info
}]
@staticmethod
def _make_add_resources(req_add_resources):
add_resources = []
for req_add_resource in req_add_resources:
res_add_resource = {
"resourceDefinitionId": req_add_resource['id'],
"vimConnectionId": uuidsentinel.vim_connection_id
}
if req_add_resource['type'] == 'COMPUTE':
res_add_resource["zoneId"] = uuidsentinel.zone_id
add_resources.append(res_add_resource)
return add_resources
@staticmethod
def _make_remove_resources(req_remove_resources):
res_remove_resources = []
for req_remove_resource in req_remove_resources:
res_remove_resource = {
"resourceDefinitionId": req_remove_resource['id']
}
res_remove_resources.append(res_remove_resource)
return res_remove_resources
@staticmethod
def _make_vim_assets(image_id, flavour_id="1"):
# set m1.tiny="1" for flavour_id
vim_assets = {
"computeResourceFlavours": [
{
"vimConnectionId": uuidsentinel.vim_connection_id,
"vnfdVirtualComputeDescId": "VDU1",
"vimFlavourId": flavour_id
},
{
"vimConnectionId": uuidsentinel.vim_connection_id,
"vnfdVirtualComputeDescId": "VDU2",
"vimFlavourId": flavour_id
}
],
"softwareImages": [
{
"vimConnectionId": uuidsentinel.vim_connection_id,
"vnfdSoftwareImageId": "VDU1",
"vimSoftwareImageId": image_id
},
{
"vimConnectionId": uuidsentinel.vim_connection_id,
"vnfdSoftwareImageId": "VDU2",
"vimSoftwareImageId": image_id
}
]
}
return vim_assets
@staticmethod
def _make_response_template(request_body):
res = {
"id": uuidsentinel.__getattr__(request_body['vnfLcmOpOccId']),
"vnfInstanceId": request_body['vnfInstanceId'],
"vnfLcmOpOccId": request_body['vnfLcmOpOccId'],
}
res["_links"] = {
"self": {
# set fake server port.
"href": os.path.join(
'http://localhost:9990',
Grant.GRANT_REQ_PATH)},
"vnfLcmOpOcc": {
"href": os.path.join(
'http://localhost:9890/vnflcm/v1/vnf_lcm_op_occs',
request_body['vnfLcmOpOccId'])},
"vnfInstance": {
"href": os.path.join(
'http://localhost:9890/vnflcm/v1/vnf_instances',
request_body['vnfInstanceId'])}}
return res
@staticmethod
def _convert_body_to_dict(body):
if isinstance(body, str):
return jsonutils.loads(body)
return body
@staticmethod
def make_inst_response_body(request_body, tenant_id, image_id):
request_body = Grant._convert_body_to_dict(request_body)
res = Grant._make_response_template(request_body)
res["vimConnections"] = Grant._make_vim_connection_info(tenant_id)
res["zones"] = Grant.ZONES
if 'addResources' in request_body.keys():
res["addResources"] = Grant._make_add_resources(
request_body['addResources'])
res["vimAssets"] = Grant._make_vim_assets(
image_id)
res["additionalParams"] = Grant.ADDITIONAL_PARAMS
return res
@staticmethod
def make_heal_response_body(request_body, tenant_id, image_id):
request_body = Grant._convert_body_to_dict(request_body)
res = Grant._make_response_template(request_body)
res["vimConnections"] = Grant._make_vim_connection_info(tenant_id)
res["zones"] = Grant.ZONES
if 'addResources' in request_body.keys():
res["addResources"] = Grant._make_add_resources(
request_body['addResources'])
if 'removeResources' in request_body.keys():
res["removeResources"] = Grant._make_remove_resources(
request_body['removeResources'])
res["vimAssets"] = Grant._make_vim_assets(image_id)
return res
@staticmethod
def make_scaleout_response_body(request_body, tenant_id, image_id):
request_body = Grant._convert_body_to_dict(request_body)
res = Grant._make_response_template(request_body)
res["vimConnections"] = Grant._make_vim_connection_info(tenant_id)
res["zones"] = Grant.ZONES
if 'addResources' in request_body.keys():
res["addResources"] = Grant._make_add_resources(
request_body['addResources'])
res["vimAssets"] = Grant._make_vim_assets(
image_id)
return res
@staticmethod
def make_scalein_response_body(request_body):
request_body = Grant._convert_body_to_dict(request_body)
res = Grant._make_response_template(request_body)
if 'removeResources' in request_body.keys():
res["removeResources"] = Grant._make_remove_resources(
request_body['removeResources'])
return res
@staticmethod
def make_term_response_body(request_body):
request_body = Grant._convert_body_to_dict(request_body)
res = Grant._make_response_template(request_body)
if 'removeResources' in request_body.keys():
res["removeResources"] = Grant._make_remove_resources(
request_body['removeResources'])
return res

View File

@ -0,0 +1,105 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import uuidutils
class VnfPackage:
VNF_PACKAGE_REQ_PATH = "/vnfpkgm/v1/vnf_packages"
@staticmethod
def make_list_response_body():
return [VnfPackage.make_individual_response]
@staticmethod
def make_individual_response_body(vnfd_id, vnf_package_hash):
add_artifact_hash = (
"6513f21e44aa3da349f248188a44" +
"bc304a3653a04122d8fb4535423c8" +
"e1d14cd6a153f735bb0982e2" +
"161b5b5186106570c17a9" +
"e58b64dd39390617cd5a350f78")
sw_image_hash = (
"6513f21e44aa3da349" +
"f248188a44bc304a3653a04" +
"122d8fb4535423c8e1d14c" +
"d6a153f735bb0982e2161b5" +
"b5186106570c17a9e58b6" +
"4dd39390617cd5a350f78")
data = {
"id": uuidutils.generate_uuid(),
"vnfdId": vnfd_id,
"vnfProvider": "Company",
"vnfProductName": "Sample VNF",
"vnfSoftwareVersion": "1.0",
"vnfdVersion": "1.0",
"checksum": {
"algorithm": "SHA-512",
"hash": vnf_package_hash
},
"softwareImages": [
{
"id": "sw_image",
"name": "cirros-0.4.0-x86_64-disk",
"provider": "Company",
"version": "0.4.0",
"checksum": {
"algorithm": "SHA-512",
"hash": sw_image_hash
},
"containerFormat": "BARE",
"diskFormat": "QCOW2",
"createdAt": "2020-09-01T12:34:56Z",
"minDisk": "2147483648",
"minRam": "268435456",
"size": "1073741824",
"userMetadata": {
"key": "value"
},
"imagePath": "Files/images/cirros-0.4.0-x86_64-disk.img"
}
],
"additionalArtifacts": [
{
"artifactPath":
"Files/images/cirros-0.4.0-x86_64-disk.img",
"checksum": {
"algorithm": "SHA-512",
"hash": add_artifact_hash
},
"metadata": {
"key": "value"
}
}
],
"onboardingState": "ONBOARDED",
"operationalState": "ENABLED",
"usageState": "NOT_IN_USE",
"userDefinedData": {
"key": "value"
},
"_links": {
"self": {
"href": "GetPackage URI"
},
"vnfd": {
"href": "GetVNFD URI"
},
"packageContent": {
"href": "GetPackageContent URI"
}
}
}
return data

View File

@ -0,0 +1,316 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import hashlib
import os
from tacker.tests.functional.sol.vnflcm import base as vnflcm_base
from tacker.tests.functional.sol.vnflcm import fake_vnflcm
from tacker.tests.functional.sol_separated_nfvo.vnflcm import fake_grant
from tacker.tests.functional.sol_separated_nfvo.vnflcm import fake_vnfpkgm
class VnfLcmWithNfvoSeparator(vnflcm_base.BaseVnfLcmTest):
def _register_vnf_package_mock_response(self):
"""Prepare VNF package for test.
Register VNF package response to fake NFVO server and Cleanups.
Returns:
Response: VNF Package information
"""
# Pre Setting: Create vnf package.
sample_name = "functional6"
csar_package_path = os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"../../../etc/samples/etsi/nfv",
sample_name))
# Get VNFD id.
tempname, vnfd_id = vnflcm_base._create_csar_with_unique_vnfd_id(
csar_package_path)
with open(tempname, "rb") as f:
vnf_package_hash = hashlib.sha256(f.read()).hexdigest()
vnf_package_info = \
fake_vnfpkgm.VnfPackage.make_individual_response_body(
vnfd_id, vnf_package_hash)
vnf_package_id = vnf_package_info['id']
# Post Setting: Reserve deleting vnf package.
self.addCleanup(vnflcm_base._delete_vnf_package, self.tacker_client,
vnf_package_id)
# Set "VNF Packages" response
vnflcm_base.FAKE_SERVER_MANAGER.set_callback('GET',
fake_vnfpkgm.VnfPackage.VNF_PACKAGE_REQ_PATH, status_code=200,
response_body=[vnf_package_info])
# Set "VNF Package content" response
vnflcm_base.FAKE_SERVER_MANAGER.set_callback('GET',
os.path.join(
fake_vnfpkgm.VnfPackage.VNF_PACKAGE_REQ_PATH,
vnf_package_id,
'package_content'),
status_code=200,
response_headers={"Content-Type": "application/zip"},
content=tempname)
# Set "Individual VNF package artifact" response
vnflcm_base.FAKE_SERVER_MANAGER.set_callback('GET',
os.path.join(
fake_vnfpkgm.VnfPackage.VNF_PACKAGE_REQ_PATH,
vnf_package_id,
'artifacts',
vnf_package_info['additionalArtifacts'][0]['artifactPath']),
status_code=200,
response_headers={"Content-Type": "application/zip"},
content=tempname)
# Set "VNFD of individual VNF package" response
vnflcm_base.FAKE_SERVER_MANAGER.set_callback('GET',
os.path.join(
fake_vnfpkgm.VnfPackage.VNF_PACKAGE_REQ_PATH,
vnf_package_id,
'vnfd'),
status_code=200,
response_headers={"Content-Type": "application/zip"},
content=tempname)
return vnf_package_info
def test_inst_heal_term(self):
"""Test basic life cycle operations with sample VNFD with UserData.
In this test case, we do following steps.
- Create subscription.
- Create VNF instance.
- Instantiate VNF.
- Heal VNF with all VNFc.
- Terminate VNF
- Delete VNF
- Delete subscription
"""
vnf_package_info = self._register_vnf_package_mock_response()
glance_image = self._list_glance_image()[0]
# Create subscription and register it.
request_body = fake_vnflcm.Subscription.make_create_request_body(
'http://localhost:{}{}'.format(
vnflcm_base.FAKE_SERVER_MANAGER.SERVER_PORT,
os.path.join(
vnflcm_base.MOCK_NOTIFY_CALLBACK_URL,
self._testMethodName)))
resp, response_body = self._register_subscription(request_body)
self.assertEqual(201, resp.status_code)
self.assert_http_header_location_for_subscription(resp.headers)
subscription_id = response_body.get('id')
self.addCleanup(self._delete_subscription, subscription_id)
# Create vnf instance
resp, vnf_instance = self._create_vnf_instance_from_body(
fake_vnflcm.VnfInstances.make_create_request_body(
vnf_package_info['vnfdId']))
vnf_instance_id = vnf_instance.get('id')
self._wait_lcm_done(vnf_instance_id=vnf_instance_id)
self._assert_create_vnf(resp, vnf_instance)
self.addCleanup(self._delete_vnf_instance, vnf_instance_id)
# Set Fake server response for Grant-Req(Instantiate)
vnflcm_base.FAKE_SERVER_MANAGER.set_callback('POST',
fake_grant.Grant.GRANT_REQ_PATH, status_code=201,
callback=lambda req_headers,
req_body: fake_grant.Grant.make_inst_response_body(req_body,
self.vim['tenant_id'], glance_image.id))
# Instantiate vnf instance
request_body = fake_vnflcm.VnfInstances.make_inst_request_body(
self.vim['tenant_id'], self.ext_networks, self.ext_mngd_networks,
self.ext_link_ports, self.ext_subnets)
resp, _ = self._instantiate_vnf_instance(vnf_instance_id, request_body)
self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id)
self._assert_instantiate_vnf(resp, vnf_instance_id)
# Show vnf instance
resp, vnf_instance = self._show_vnf_instance(vnf_instance_id)
self.assertEqual(200, resp.status_code)
# Set Fake server response for Grant-Req(Heal)
vnflcm_base.FAKE_SERVER_MANAGER.set_callback('POST',
fake_grant.Grant.GRANT_REQ_PATH, status_code=201,
callback=lambda req_headers,
req_body: fake_grant.Grant.make_heal_response_body(req_body,
self.vim['tenant_id'], glance_image.id))
# Heal vnf (exists vnfc_instace_id)
vnfc_instance_id_list = [
vnfc.get('id') for vnfc in vnf_instance.get(
'instantiatedVnfInfo', {}).get(
'vnfcResourceInfo', [])]
request_body = fake_vnflcm.VnfInstances.make_heal_request_body(
vnfc_instance_id_list)
resp, _ = self._heal_vnf_instance(vnf_instance_id, request_body)
self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id)
self._assert_heal_vnf(resp, vnf_instance_id)
# Set Fake server response for Grant-Req(Terminate)
vnflcm_base.FAKE_SERVER_MANAGER.set_callback('POST',
fake_grant.Grant.GRANT_REQ_PATH, status_code=201,
callback=lambda req_headers,
req_body: fake_grant.Grant.make_term_response_body(req_body))
# Get stack informations to terminate.
stack = self._get_heat_stack(vnf_instance_id)
resources_list = self._get_heat_resource_list(stack.id)
resource_name_list = [r.resource_name for r in resources_list]
glance_image_id_list = self._get_glance_image_list_from_stack_resource(
stack.id, resource_name_list)
# Terminate VNF
terminate_req_body = fake_vnflcm.VnfInstances.make_term_request_body()
resp, _ = self._terminate_vnf_instance(vnf_instance_id,
terminate_req_body)
self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id)
self._assert_terminate_vnf(resp, vnf_instance_id, stack.id,
resource_name_list, glance_image_id_list)
# Delete VNF
resp, _ = self._delete_vnf_instance(vnf_instance_id)
self._wait_lcm_done(vnf_instance_id=vnf_instance_id)
self.assert_delete_vnf(resp, vnf_instance_id)
# Delete Subscription
resp, response_body = self._delete_subscription(subscription_id)
self.assertEqual(204, resp.status_code)
def _assert_create_vnf(self, resp, vnf_instance):
"""Assert that VNF was created via fake server.
Args:
resp (Response): HTTP response object.
vnf_instance (Dict): VNF instance information.
"""
super().assert_create_vnf(resp, vnf_instance)
# FT-checkpoint: VnfPkgId
vnf_pkg_mock_responses = vnflcm_base.FAKE_SERVER_MANAGER.get_history(
fake_vnfpkgm.VnfPackage.VNF_PACKAGE_REQ_PATH)
vnflcm_base.FAKE_SERVER_MANAGER.clear_history(
fake_vnfpkgm.VnfPackage.VNF_PACKAGE_REQ_PATH)
self.assertEqual(1, len(vnf_pkg_mock_responses))
vnf_pkg_info_list = vnf_pkg_mock_responses[0]
self.assertEqual(vnf_instance['vnfPkgId'],
vnf_pkg_info_list.response_body[0]['id'])
def _assert_instantiate_vnf(self, resp, vnf_instance_id):
"""Assert that VNF was instantiated.
This method calls same name method of super class and that
checks heat resource status 'CREATE_COMPLETE', then assert
notifications of instantiation.
Then, we check Grant response in this method.
Args:
resp (Response): HTTP response object.
vnf_instance_id (str): VNF instance id.
"""
super().assert_instantiate_vnf(resp, vnf_instance_id)
# FT-checkpoint: Grant Response
grant_mock_responses = vnflcm_base.FAKE_SERVER_MANAGER.get_history(
fake_grant.Grant.GRANT_REQ_PATH)
vnflcm_base.FAKE_SERVER_MANAGER.clear_history(
fake_grant.Grant.GRANT_REQ_PATH)
self.assertEqual(1, len(grant_mock_responses))
self._assert_grant_mock_response(grant_mock_responses[0])
def _assert_heal_vnf(self, resp, vnf_instance_id,
expected_stack_status='UPDATE_COMPLETE'):
"""Assert that VNF was healed.
This method calls same name method of super class and that
checks heat resource status 'UPDATE_COMPLETE', then assert
notifications of healing.
Then, we check Grant response in this method.
Args:
resp (Response): HTTP response object.
vnf_instance_id (str): VNF instance id.
expected_stack_status (str, optional): self explanatory :)
Defaults to 'UPDATE_COMPLETE'.
"""
super().assert_heal_vnf(
resp, vnf_instance_id, expected_stack_status=expected_stack_status)
# FT-checkpoint: Grant Response
grant_mock_responses = vnflcm_base.FAKE_SERVER_MANAGER.get_history(
fake_grant.Grant.GRANT_REQ_PATH)
vnflcm_base.FAKE_SERVER_MANAGER.clear_history(
fake_grant.Grant.GRANT_REQ_PATH)
self.assertEqual(1, len(grant_mock_responses))
self._assert_grant_mock_response(grant_mock_responses[0])
def _assert_terminate_vnf(self, resp, vnf_instance_id, stack_id,
resource_name_list, glance_image_id_list):
"""Assert that VNF was terminated.
This method calls same name method of super class to check specified
VNF instance is 'NOT_INSTANTIATED'
Then, we check Grant response in this method.
Args:
resp (Response): HTTP response object.
vnf_instance_id (str): VNF instance id.
stack_id (str): Resource id of heat stack to check.
resource_name_list (list[str]): List of heat stack resources.
glance_image_id_list (list[str]): List of glance image ids.
"""
super().assert_terminate_vnf(resp, vnf_instance_id, stack_id,
resource_name_list, glance_image_id_list)
# FT-checkpoint: Grant Response
grant_mock_responses = vnflcm_base.FAKE_SERVER_MANAGER.get_history(
fake_grant.Grant.GRANT_REQ_PATH)
vnflcm_base.FAKE_SERVER_MANAGER.clear_history(
fake_grant.Grant.GRANT_REQ_PATH)
self.assertEqual(1, len(grant_mock_responses))
self._assert_grant_mock_response(grant_mock_responses[0])
def _assert_grant_mock_response(self, grant_mock_response,
expected_auth_type=None, expected_token_value=None):
"""Assert that HTTP response code is equal to 201 or not.
This method checks response code of grant request and
authorization result.
Args:
grant_mock_response (Response): HTTP response object.
expected_auth_type (str, optional): Authentication type.
Defaults to None.
expected_token_value ([type], optional): Authentication token.
Defaults to None.
"""
self.assertEqual(201, grant_mock_response.status_code)
actual_auth = grant_mock_response.request_headers.get("Authorization")
if expected_auth_type is None:
self.assertIsNone(actual_auth)
return
self.assertEqual(
'{} {}'.format(expected_auth_type, expected_token_value),
actual_auth)

View File

@ -52,6 +52,12 @@ setenv = {[testenv]setenv}
commands =
stestr --test-path=./tacker/tests/functional/sol run --slowest --concurrency 1 {posargs}
[testenv:dsvm-functional-sol-separated-nfvo]
setenv = {[testenv]setenv}
commands =
stestr --test-path=./tacker/tests/functional/sol_separated_nfvo run --slowest --concurrency 1 {posargs}
[testenv:debug]
commands = oslo_debug_helper {posargs}