From 3635aa14762852872c9e7283e87f574923460b98 Mon Sep 17 00:00:00 2001 From: Yi Feng Date: Tue, 15 Feb 2022 15:09:56 +0900 Subject: [PATCH] FT of scale/modify v2 APIs This patch add scale/modify functional tests of v2 APIs to the following test cases: * Test basic lcms for v2 api Max pattern. * Test basic lcms for v2 api Min pattern. * Test rollback update VNF for v2 api. * Test update and scale out's before/after for v2 api. Implements: blueprint support-nfv-solv3-scale-vnf Implements: blueprint support-nfv-solv3-modify-vnf Closes-bug: #1962564 Change-Id: I0826f1c580a56cec860d59ae0eeb197e8d30f344 --- doc/source/user/userdata_script.rst | 4 +- .../conductor/vnflcm_driver_v2.py | 6 +- tacker/tests/functional/sol_v2/base_v2.py | 5 + tacker/tests/functional/sol_v2/paramgen.py | 164 ++-- .../contents/BaseHOT/simple/nested/VDU1.yaml | 0 .../contents/BaseHOT/simple/sample1.yaml | 0 .../Definitions/v2_sample1_df_simple.yaml | 4 +- .../Definitions/v2_sample1_top.vnfd.yaml | 0 .../Definitions/v2_sample1_types.yaml | 0 .../contents/Scripts/sample_script.py | 0 .../contents/TOSCA-Metadata/TOSCA.meta | 0 .../contents/UserData/userdata.py | 0 .../{sample1 => basic_lcms_max}/pkggen.py | 20 +- .../{sample1 => basic_lcms_max}/post.py | 0 .../{sample1 => basic_lcms_max}/pre.py | 0 .../contents/BaseHOT/simple/nested/VDU1.yaml | 0 .../contents/BaseHOT/simple/sample2.yaml | 0 .../Definitions/v2_sample2_df_simple.yaml | 262 ++++++ .../Definitions/v2_sample2_top.vnfd.yaml | 0 .../Definitions/v2_sample2_types.yaml | 0 .../contents/Scripts/sample_script.py | 77 ++ .../contents/TOSCA-Metadata/TOSCA.meta | 0 .../sol_v2/samples/basic_lcms_min/pkggen.py | 58 ++ .../contents/BaseHOT/simple/nested/VDU1.yaml | 30 + .../contents/BaseHOT/simple/sample2.yaml | 61 ++ .../Definitions/v2_sample2_df_simple.yaml | 6 +- .../Definitions/v2_sample2_top.vnfd.yaml | 31 + .../Definitions/v2_sample2_types.yaml | 55 ++ .../contents/Scripts/sample_script.py | 0 .../contents/TOSCA-Metadata/TOSCA.meta | 4 + .../samples/{sample2 => update_vnf}/pkggen.py | 19 +- .../functional/sol_v2/test_vnflcm_basic.py | 845 ++++++++++++++++-- .../sol_v2/test_vnflcm_error_handling.py | 225 ++++- 33 files changed, 1709 insertions(+), 167 deletions(-) rename tacker/tests/functional/sol_v2/samples/{sample1 => basic_lcms_max}/contents/BaseHOT/simple/nested/VDU1.yaml (100%) rename tacker/tests/functional/sol_v2/samples/{sample1 => basic_lcms_max}/contents/BaseHOT/simple/sample1.yaml (100%) rename tacker/tests/functional/sol_v2/samples/{sample1 => basic_lcms_max}/contents/Definitions/v2_sample1_df_simple.yaml (99%) rename tacker/tests/functional/sol_v2/samples/{sample1 => basic_lcms_max}/contents/Definitions/v2_sample1_top.vnfd.yaml (100%) rename tacker/tests/functional/sol_v2/samples/{sample1 => basic_lcms_max}/contents/Definitions/v2_sample1_types.yaml (100%) rename tacker/tests/functional/sol_v2/samples/{sample1 => basic_lcms_max}/contents/Scripts/sample_script.py (100%) rename tacker/tests/functional/sol_v2/samples/{sample1 => basic_lcms_max}/contents/TOSCA-Metadata/TOSCA.meta (100%) rename tacker/tests/functional/sol_v2/samples/{sample1 => basic_lcms_max}/contents/UserData/userdata.py (100%) rename tacker/tests/functional/sol_v2/samples/{sample1 => basic_lcms_max}/pkggen.py (80%) rename tacker/tests/functional/sol_v2/samples/{sample1 => basic_lcms_max}/post.py (100%) rename tacker/tests/functional/sol_v2/samples/{sample1 => basic_lcms_max}/pre.py (100%) rename tacker/tests/functional/sol_v2/samples/{sample2 => basic_lcms_min}/contents/BaseHOT/simple/nested/VDU1.yaml (100%) rename tacker/tests/functional/sol_v2/samples/{sample2 => basic_lcms_min}/contents/BaseHOT/simple/sample2.yaml (100%) create mode 100644 tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/Definitions/v2_sample2_df_simple.yaml rename tacker/tests/functional/sol_v2/samples/{sample2 => basic_lcms_min}/contents/Definitions/v2_sample2_top.vnfd.yaml (100%) rename tacker/tests/functional/sol_v2/samples/{sample2 => basic_lcms_min}/contents/Definitions/v2_sample2_types.yaml (100%) create mode 100644 tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/Scripts/sample_script.py rename tacker/tests/functional/sol_v2/samples/{sample2 => basic_lcms_min}/contents/TOSCA-Metadata/TOSCA.meta (100%) create mode 100644 tacker/tests/functional/sol_v2/samples/basic_lcms_min/pkggen.py create mode 100644 tacker/tests/functional/sol_v2/samples/update_vnf/contents/BaseHOT/simple/nested/VDU1.yaml create mode 100644 tacker/tests/functional/sol_v2/samples/update_vnf/contents/BaseHOT/simple/sample2.yaml rename tacker/tests/functional/sol_v2/samples/{sample2 => update_vnf}/contents/Definitions/v2_sample2_df_simple.yaml (98%) create mode 100644 tacker/tests/functional/sol_v2/samples/update_vnf/contents/Definitions/v2_sample2_top.vnfd.yaml create mode 100644 tacker/tests/functional/sol_v2/samples/update_vnf/contents/Definitions/v2_sample2_types.yaml rename tacker/tests/functional/sol_v2/samples/{sample2 => update_vnf}/contents/Scripts/sample_script.py (100%) create mode 100644 tacker/tests/functional/sol_v2/samples/update_vnf/contents/TOSCA-Metadata/TOSCA.meta rename tacker/tests/functional/sol_v2/samples/{sample2 => update_vnf}/pkggen.py (68%) diff --git a/doc/source/user/userdata_script.rst b/doc/source/user/userdata_script.rst index 0c3b32ed3..e91b6a4a7 100644 --- a/doc/source/user/userdata_script.rst +++ b/doc/source/user/userdata_script.rst @@ -98,12 +98,12 @@ The following is sample Base HOT corresponding to above sample userdata script. **top Base HOT** -.. literalinclude:: ../../../tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/sample1.yaml +.. literalinclude:: ../../../tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/BaseHOT/simple/sample1.yaml :language: yaml **nested Base HOT** -.. literalinclude:: ../../../tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/nested/VDU1.yaml +.. literalinclude:: ../../../tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/BaseHOT/simple/nested/VDU1.yaml :language: yaml diff --git a/tacker/sol_refactored/conductor/vnflcm_driver_v2.py b/tacker/sol_refactored/conductor/vnflcm_driver_v2.py index 5c36ba0e3..1c794d6b8 100644 --- a/tacker/sol_refactored/conductor/vnflcm_driver_v2.py +++ b/tacker/sol_refactored/conductor/vnflcm_driver_v2.py @@ -102,8 +102,10 @@ class VnfLcmDriverV2(object): 'operation': operation, 'request': req.to_dict(), 'vnf_instance': inst.to_dict(), - 'grant_request': grant_req.to_dict(), - 'grant_response': grant.to_dict(), + 'grant_request': (grant_req.to_dict() + if grant_req is not None else None), + 'grant_response': (grant.to_dict() + if grant is not None else None), 'tmp_csar_dir': tmp_csar_dir } # script is relative path to Definitions/xxx.yaml diff --git a/tacker/tests/functional/sol_v2/base_v2.py b/tacker/tests/functional/sol_v2/base_v2.py index 214b42b8f..39c8b4349 100644 --- a/tacker/tests/functional/sol_v2/base_v2.py +++ b/tacker/tests/functional/sol_v2/base_v2.py @@ -313,6 +313,11 @@ class BaseSolV2Test(base.BaseTestCase): return self.tacker_client.do_request( path, "POST", body=req_body, version="2.0.0") + def update_vnf_instance(self, inst_id, req_body): + path = f"/vnflcm/v2/vnf_instances/{inst_id}" + return self.tacker_client.do_request( + path, "PATCH", body=req_body, version="2.0.0") + def terminate_vnf_instance(self, inst_id, req_body): path = "/vnflcm/v2/vnf_instances/{}/terminate".format(inst_id) return self.tacker_client.do_request( diff --git a/tacker/tests/functional/sol_v2/paramgen.py b/tacker/tests/functional/sol_v2/paramgen.py index 52c47ccf1..08fb67722 100644 --- a/tacker/tests/functional/sol_v2/paramgen.py +++ b/tacker/tests/functional/sol_v2/paramgen.py @@ -157,40 +157,6 @@ def sub_create_max(callback_uri): } -def sample1_create(vnfd_id): - # All attributes are set. - # NOTE: All of the following cardinality attributes are set. - # In addition, 0..N or 1..N attributes are set to 2 or more. - # - 0..1 (1) - # - 0..N (2 or more) - # - 1 - # - 1..N (2 or more) - return create_vnf_max(vnfd_id) - - -def sample1_terminate(): - # All attributes are set. - # NOTE: All of the following cardinality attributes are set. - # In addition, 0..N or 1..N attributes are set to 2 or more. - # - 0..1 (1) - # - 0..N (2 or more) - # - 1 - # - 1..N (2 or more) - return terminate_vnf_max() - - -def sample1_instantiate(net_ids, subnets, ports, auth_url): - # All attributes are set. - # NOTE: All of the following cardinality attributes are set. - # In addition, 0..N or 1..N attributes are set to 2 or more. - # - 0..1 (1) - # - 0..N (2 or more) - # - 1 - # - 1..N (2 or more) - - return instantiate_vnf_max(net_ids, subnets, ports, auth_url) - - def create_vnf_max(vnfd_id): # All attributes are set. # NOTE: All of the following cardinality attributes are set. @@ -481,30 +447,6 @@ def instantiate_vnf_max(net_ids, subnets, ports, auth_url): } -def sample2_create(vnfd_id): - # Omit except for required attributes - # NOTE: Only the following cardinality attributes are set. - # - 1 - # - 1..N (1) - return create_vnf_min(vnfd_id) - - -def sample2_terminate(): - # Omit except for required attributes - # NOTE: Only the following cardinality attributes are set. - # - 1 - # - 1..N (1) - return terminate_vnf_min() - - -def sample2_instantiate(): - # Omit except for required attributes - # NOTE: Only the following cardinality attributes are set. - # - 1 - # - 1..N (1) - return instantiate_vnf_min() - - def create_vnf_min(vnfd_id): # Omit except for required attributes # NOTE: Only the following cardinality attributes are set. @@ -549,3 +491,109 @@ def scaleout_vnf_max(): "numberOfSteps": 1, "additionalParams": {"dummy-key": "dummy-value"} } + + +def scaleout_vnf_min(): + # Omit except for required attributes + # NOTE: Only the following cardinality attributes are set. + # - 1 + # - 1..N (1) + return { + "type": "SCALE_OUT", + "aspectId": "VDU1_scale" + } + + +def scalein_vnf_max(): + # All attributes are set. + # NOTE: All of the following cardinality attributes are set. + # In addition, 0..N or 1..N attributes are set to 2 or more. + # - 0..1 (1) + # - 0..N (2 or more) + # - 1 + # - 1..N (2 or more) + return { + "type": "SCALE_IN", + "aspectId": "VDU1_scale", + "numberOfSteps": 1, + "additionalParams": {"dummy-key": "dummy-value"} + } + + +def scalein_vnf_min(): + # Omit except for required attributes + # NOTE: Only the following cardinality attributes are set. + # - 1 + # - 1..N (1) + return { + "type": "SCALE_IN", + "aspectId": "VDU1_scale" + } + + +def update_vnf_max(vnfd_id, vnfc_id_1, vnfc_id_2): + # All attributes are set. + # NOTE: All of the following cardinality attributes are set. + # In addition, 0..N or 1..N attributes are set to 2 or more. + # - 0..1 (1) + # - 0..N (2 or more) + # - 1 + # - 1..N (2 or more) + return { + "vnfInstanceName": "new name", + "vnfInstanceDescription": "new description", + "vnfdId": vnfd_id, + "vnfConfigurableProperties": {"dummy-key": "dummy-value"}, + "metadata": {"dummy-key": "dummy-value"}, + "extensions": {"dummy-key": "dummy-value"}, + "vimConnectionInfo": { + "vim2": { + "vimId": "ac2d2ece-5e49-4b15-b92d-b681e9c096d8", + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "interfaceInfo": { + "endpoint": "http://127.0.0.1/identity/v3" + }, + "accessInfo": { + "username": "dummy_user", + "region": "RegionOne", + "password": "dummy_password", + "project": "dummy_project", + "projectDomain": "Default", + "userDomain": "Default" + }, + "extra": { + "dummy-key": "dummy-val" + } + } + }, + "vnfcInfoModifications": [ + { + "id": vnfc_id_1, + "vnfcConfigurableProperties": {"dummy-key": "dummy-value"} + }, + { + "id": vnfc_id_2, + "vnfcConfigurableProperties": {"dummy-key": "dummy-value"} + } + ] + } + + +def update_vnf_min(): + # Omit except for required attributes + # NOTE: Only the following cardinality attributes are set. + # - 1 + # - 1..N (1) + return { + "vnfInstanceName": "new name" + } + + +def update_vnf_min_with_parameter(vnfd_id): + # Omit except for required attributes + # NOTE: Only the following cardinality attributes are set. + # - 1 + # - 1..N (1) + return { + "vnfdId": vnfd_id + } diff --git a/tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/nested/VDU1.yaml b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/BaseHOT/simple/nested/VDU1.yaml similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/nested/VDU1.yaml rename to tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/BaseHOT/simple/nested/VDU1.yaml diff --git a/tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/sample1.yaml b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/BaseHOT/simple/sample1.yaml similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample1/contents/BaseHOT/simple/sample1.yaml rename to tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/BaseHOT/simple/sample1.yaml diff --git a/tacker/tests/functional/sol_v2/samples/sample1/contents/Definitions/v2_sample1_df_simple.yaml b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/Definitions/v2_sample1_df_simple.yaml similarity index 99% rename from tacker/tests/functional/sol_v2/samples/sample1/contents/Definitions/v2_sample1_df_simple.yaml rename to tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/Definitions/v2_sample1_df_simple.yaml index f6bbbd47b..3453b2bde 100644 --- a/tacker/tests/functional/sol_v2/samples/sample1/contents/Definitions/v2_sample1_df_simple.yaml +++ b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/Definitions/v2_sample1_df_simple.yaml @@ -421,8 +421,8 @@ topology_template: leaf: 1048576 targets: [ internalVL3 ] - - policy_antiaffinity_group: - type: tosca.policies.nfv.AntiAffinityRule + - policy_affinity_group: + type: tosca.policies.nfv.AffinityRule targets: [ affinityOrAntiAffinityGroup1 ] properties: scope: zone diff --git a/tacker/tests/functional/sol_v2/samples/sample1/contents/Definitions/v2_sample1_top.vnfd.yaml b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/Definitions/v2_sample1_top.vnfd.yaml similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample1/contents/Definitions/v2_sample1_top.vnfd.yaml rename to tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/Definitions/v2_sample1_top.vnfd.yaml diff --git a/tacker/tests/functional/sol_v2/samples/sample1/contents/Definitions/v2_sample1_types.yaml b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/Definitions/v2_sample1_types.yaml similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample1/contents/Definitions/v2_sample1_types.yaml rename to tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/Definitions/v2_sample1_types.yaml diff --git a/tacker/tests/functional/sol_v2/samples/sample1/contents/Scripts/sample_script.py b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/Scripts/sample_script.py similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample1/contents/Scripts/sample_script.py rename to tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/Scripts/sample_script.py diff --git a/tacker/tests/functional/sol_v2/samples/sample1/contents/TOSCA-Metadata/TOSCA.meta b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/TOSCA-Metadata/TOSCA.meta similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample1/contents/TOSCA-Metadata/TOSCA.meta rename to tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/TOSCA-Metadata/TOSCA.meta diff --git a/tacker/tests/functional/sol_v2/samples/sample1/contents/UserData/userdata.py b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/UserData/userdata.py similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample1/contents/UserData/userdata.py rename to tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/UserData/userdata.py diff --git a/tacker/tests/functional/sol_v2/samples/sample1/pkggen.py b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/pkggen.py similarity index 80% rename from tacker/tests/functional/sol_v2/samples/sample1/pkggen.py rename to tacker/tests/functional/sol_v2/samples/basic_lcms_max/pkggen.py index b4ecd8d8b..d303203c2 100644 --- a/tacker/tests/functional/sol_v2/samples/sample1/pkggen.py +++ b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/pkggen.py @@ -39,8 +39,10 @@ utils.make_zip(".", tmp_dir, vnfd_id, image_path) shutil.move(os.path.join(tmp_dir, zip_file_name), ".") shutil.rmtree(tmp_dir) -create_req = paramgen.sample1_create(vnfd_id) -terminate_req = paramgen.sample1_terminate() +create_req = paramgen.create_vnf_max(vnfd_id) +scaleout_req = paramgen.scaleout_vnf_max() +scalein_req = paramgen.scalein_vnf_max() +terminate_req = paramgen.terminate_vnf_max() print('#####################################################################\n' '# Run pre.py if an error occurs #\n' @@ -56,14 +58,20 @@ subnet_ids = utils.get_subnet_ids( ['subnet0', 'subnet1', 'ft-ipv4-subnet0', 'ft-ipv6-subnet0']) port_ids = utils.get_port_ids(['VDU2_CP1-1', 'VDU2_CP1-2']) -instantiate_req = paramgen.sample1_instantiate( +instantiate_req = paramgen.instantiate_vnf_max( net_ids, subnet_ids, port_ids, "http://localhost/identity/v3") -with open("create_req", "w") as f: +with open("create_req", "w", encoding='utf-8') as f: f.write(json.dumps(create_req, indent=2)) -with open("terminate_req", "w") as f: +with open("terminate_req", "w", encoding='utf-8') as f: f.write(json.dumps(terminate_req, indent=2)) -with open("instantiate_req", "w") as f: +with open("scaleout_req", "w", encoding='utf-8') as f: + f.write(json.dumps(scaleout_req, indent=2)) + +with open("scalein_req", "w", encoding='utf-8') as f: + f.write(json.dumps(scalein_req, indent=2)) + +with open("instantiate_req", "w", encoding='utf-8') as f: f.write(json.dumps(instantiate_req, indent=2)) diff --git a/tacker/tests/functional/sol_v2/samples/sample1/post.py b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/post.py similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample1/post.py rename to tacker/tests/functional/sol_v2/samples/basic_lcms_max/post.py diff --git a/tacker/tests/functional/sol_v2/samples/sample1/pre.py b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/pre.py similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample1/pre.py rename to tacker/tests/functional/sol_v2/samples/basic_lcms_max/pre.py diff --git a/tacker/tests/functional/sol_v2/samples/sample2/contents/BaseHOT/simple/nested/VDU1.yaml b/tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/BaseHOT/simple/nested/VDU1.yaml similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample2/contents/BaseHOT/simple/nested/VDU1.yaml rename to tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/BaseHOT/simple/nested/VDU1.yaml diff --git a/tacker/tests/functional/sol_v2/samples/sample2/contents/BaseHOT/simple/sample2.yaml b/tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/BaseHOT/simple/sample2.yaml similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample2/contents/BaseHOT/simple/sample2.yaml rename to tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/BaseHOT/simple/sample2.yaml diff --git a/tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/Definitions/v2_sample2_df_simple.yaml b/tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/Definitions/v2_sample2_df_simple.yaml new file mode 100644 index 000000000..06a9a3d65 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/Definitions/v2_sample2_df_simple.yaml @@ -0,0 +1,262 @@ +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 + - v2_sample2_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 + + node_templates: + VNF: + type: company.provider.VNF + properties: + flavour_description: A simple flavour + interfaces: + Vnflcm: + instantiate_start: + implementation: sample-script + instantiate_end: + implementation: sample-script + terminate_start: + implementation: sample-script + terminate_end: + implementation: sample-script + change_external_connectivity_start: + implementation: sample-script + modify_information_start: + implementation: sample-script + artifacts: + sample-script: + description: Sample script + type: tosca.artifacts.Implementation.Python + file: ../Scripts/sample_script.py + + 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.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 + + VDU2: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU2 + description: VDU2 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 1 + 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 + + VDU1_CP1: + 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: 4 + requirements: + - virtual_binding: VDU2 + - virtual_link: internalVL3 + + 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: 192.168.5.0/24 + + groups: + affinityOrAntiAffinityGroup1: + type: tosca.groups.nfv.PlacementGroup + members: [ VDU1, VDU2 ] + + 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: 1 + 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: 1 + instantiation_level_2: + number_of_instances: 1 + targets: [ VDU2 ] + + - 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_affinity_group: + type: tosca.policies.nfv.AffinityRule + targets: [ affinityOrAntiAffinityGroup1 ] + properties: + scope: nfvi_node diff --git a/tacker/tests/functional/sol_v2/samples/sample2/contents/Definitions/v2_sample2_top.vnfd.yaml b/tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/Definitions/v2_sample2_top.vnfd.yaml similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample2/contents/Definitions/v2_sample2_top.vnfd.yaml rename to tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/Definitions/v2_sample2_top.vnfd.yaml diff --git a/tacker/tests/functional/sol_v2/samples/sample2/contents/Definitions/v2_sample2_types.yaml b/tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/Definitions/v2_sample2_types.yaml similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample2/contents/Definitions/v2_sample2_types.yaml rename to tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/Definitions/v2_sample2_types.yaml diff --git a/tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/Scripts/sample_script.py b/tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/Scripts/sample_script.py new file mode 100644 index 000000000..0376d5a1e --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/Scripts/sample_script.py @@ -0,0 +1,77 @@ +# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import pickle +import sys + + +class SampleScript(object): + + def __init__(self, req, inst, grant_req, grant, csar_dir): + self.req = req + self.inst = inst + self.grant_req = grant_req + self.grant = grant + self.csar_dir = csar_dir + + def instantiate_start(self): + pass + + def instantiate_end(self): + pass + + def terminate_start(self): + pass + + def terminate_end(self): + pass + + def change_external_connectivity_start(self): + if os.path.exists('/tmp/change_external_connectivity_start'): + raise Exception("test change_external_connectivity_start error") + pass + + def modify_information_start(self): + if os.path.exists('/tmp/modify_information_start'): + raise Exception("test modify_information_start error") + pass + + +def main(): + script_dict = pickle.load(sys.stdin.buffer) + + operation = script_dict['operation'] + req = script_dict['request'] + inst = script_dict['vnf_instance'] + grant_req = script_dict['grant_request'] + grant = script_dict['grant_response'] + csar_dir = script_dict['tmp_csar_dir'] + + script = SampleScript(req, inst, grant_req, grant, csar_dir) + try: + getattr(script, operation)() + except AttributeError: + raise Exception("{} is not included in the script.".format(operation)) + + +if __name__ == "__main__": + try: + main() + os._exit(0) + except Exception as ex: + sys.stderr.write(str(ex)) + sys.stderr.flush() + os._exit(1) diff --git a/tacker/tests/functional/sol_v2/samples/sample2/contents/TOSCA-Metadata/TOSCA.meta b/tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/TOSCA-Metadata/TOSCA.meta similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample2/contents/TOSCA-Metadata/TOSCA.meta rename to tacker/tests/functional/sol_v2/samples/basic_lcms_min/contents/TOSCA-Metadata/TOSCA.meta diff --git a/tacker/tests/functional/sol_v2/samples/basic_lcms_min/pkggen.py b/tacker/tests/functional/sol_v2/samples/basic_lcms_min/pkggen.py new file mode 100644 index 000000000..b9d9dd0a2 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/basic_lcms_min/pkggen.py @@ -0,0 +1,58 @@ +# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation +# All Rights Reserved. +# +# 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 json +import os +import shutil +import tempfile + +from oslo_utils import uuidutils + +from tacker.tests.functional.sol_v2 import paramgen +from tacker.tests.functional.sol_v2 import utils + + +zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip' +tmp_dir = tempfile.mkdtemp() +vnfd_id = uuidutils.generate_uuid() +utils.make_zip(".", tmp_dir, vnfd_id) + +shutil.copy(os.path.join(tmp_dir, zip_file_name), ".") +shutil.rmtree(tmp_dir) + +create_req = paramgen.create_vnf_min(vnfd_id) +terminate_req = paramgen.terminate_vnf_min() +instantiate_req = paramgen.instantiate_vnf_min() +scaleout_req = paramgen.scaleout_vnf_min() +scalein_req = paramgen.scalein_vnf_min() +update_seq = paramgen.update_vnf_min() + +with open("create_req", "w", encoding='utf-8') as f: + f.write(json.dumps(create_req, indent=2)) + +with open("terminate_req", "w", encoding='utf-8') as f: + f.write(json.dumps(terminate_req, indent=2)) + +with open("instantiate_req", "w", encoding='utf-8') as f: + f.write(json.dumps(instantiate_req, indent=2)) + +with open("scaleout_req", "w", encoding='utf-8') as f: + f.write(json.dumps(scaleout_req, indent=2)) + +with open("scalein_req", "w", encoding='utf-8') as f: + f.write(json.dumps(scalein_req, indent=2)) + +with open("update_seq", "w", encoding='utf-8') as f: + f.write(json.dumps(update_seq, indent=2)) diff --git a/tacker/tests/functional/sol_v2/samples/update_vnf/contents/BaseHOT/simple/nested/VDU1.yaml b/tacker/tests/functional/sol_v2/samples/update_vnf/contents/BaseHOT/simple/nested/VDU1.yaml new file mode 100644 index 000000000..d2959d317 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/update_vnf/contents/BaseHOT/simple/nested/VDU1.yaml @@ -0,0 +1,30 @@ +heat_template_version: 2013-05-23 +description: 'VDU1 HOT for Sample VNF' + +parameters: + flavor: + type: string + image: + type: string + net5: + type: string + affinity: + type: string + +resources: + VDU1: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + image: { get_param: image } + name: VDU1 + networks: + - port: + get_resource: VDU1_CP1 + scheduler_hints: + group: {get_param: affinity } + + VDU1_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net5 } diff --git a/tacker/tests/functional/sol_v2/samples/update_vnf/contents/BaseHOT/simple/sample2.yaml b/tacker/tests/functional/sol_v2/samples/update_vnf/contents/BaseHOT/simple/sample2.yaml new file mode 100644 index 000000000..cef290a67 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/update_vnf/contents/BaseHOT/simple/sample2.yaml @@ -0,0 +1,61 @@ +heat_template_version: 2013-05-23 +description: 'Simple Base HOT for Sample VNF' + +parameters: + nfv: + type: json + +resources: + VDU1_scale_group: + type: OS::Heat::AutoScalingGroup + properties: + min_size: 1 + max_size: 3 + desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] } + resource: + type: VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VDU1, vcImageId ] } + net5: { get_resource: internalVL3 } + affinity: { get_resource: nfvi_node_affinity } + +# NOTE: Resource definition of OS::Heat::ScalingPolicy is omitted. +# It is not used by v2 scale implementation unlike v1. + + VDU2: + type: OS::Nova::Server + properties: + flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } + name: VDU2 + image: { get_param: [ nfv, VDU, VDU2, vcImageId] } + networks: + - port: + get_resource: VDU2_CP1 + scheduler_hints: + group: {get_resource: nfvi_node_affinity } + + VDU2_CP1: + type: OS::Neutron::Port + properties: + network: { get_resource: internalVL3 } + + internalVL3: + type: OS::Neutron::Net + + + internalVL3_subnet: + type: OS::Neutron::Subnet + properties: + ip_version: 4 + network: + get_resource: internalVL3 + cidr: 192.168.5.0/24 + + nfvi_node_affinity: + type: OS::Nova::ServerGroup + properties: + name: nfvi_node_affinity + policies: [ 'affinity' ] + +outputs: {} diff --git a/tacker/tests/functional/sol_v2/samples/sample2/contents/Definitions/v2_sample2_df_simple.yaml b/tacker/tests/functional/sol_v2/samples/update_vnf/contents/Definitions/v2_sample2_df_simple.yaml similarity index 98% rename from tacker/tests/functional/sol_v2/samples/sample2/contents/Definitions/v2_sample2_df_simple.yaml rename to tacker/tests/functional/sol_v2/samples/update_vnf/contents/Definitions/v2_sample2_df_simple.yaml index ea3bb7c41..8d4aca6fb 100644 --- a/tacker/tests/functional/sol_v2/samples/sample2/contents/Definitions/v2_sample2_df_simple.yaml +++ b/tacker/tests/functional/sol_v2/samples/update_vnf/contents/Definitions/v2_sample2_df_simple.yaml @@ -98,7 +98,7 @@ topology_template: min_number_of_instances: 1 max_number_of_instances: 1 sw_image_data: - name: cirros-0.5.2-x86_64-disk + name: cirros-0.5.2-x86_64-disk2 version: '0.5.2' checksum: algorithm: sha-256 @@ -251,8 +251,8 @@ topology_template: leaf: 1048576 targets: [ internalVL3 ] - - policy_antiaffinity_group: - type: tosca.policies.nfv.AntiAffinityRule + - policy_affinity_group: + type: tosca.policies.nfv.AffinityRule targets: [ affinityOrAntiAffinityGroup1 ] properties: scope: nfvi_node diff --git a/tacker/tests/functional/sol_v2/samples/update_vnf/contents/Definitions/v2_sample2_top.vnfd.yaml b/tacker/tests/functional/sol_v2/samples/update_vnf/contents/Definitions/v2_sample2_top.vnfd.yaml new file mode 100644 index 000000000..c61604bb1 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/update_vnf/contents/Definitions/v2_sample2_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 + - v2_sample2_types.yaml + - v2_sample2_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/functional/sol_v2/samples/update_vnf/contents/Definitions/v2_sample2_types.yaml b/tacker/tests/functional/sol_v2/samples/update_vnf/contents/Definitions/v2_sample2_types.yaml new file mode 100644 index 000000000..8fff47c24 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/update_vnf/contents/Definitions/v2_sample2_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: "flavour" + 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/functional/sol_v2/samples/sample2/contents/Scripts/sample_script.py b/tacker/tests/functional/sol_v2/samples/update_vnf/contents/Scripts/sample_script.py similarity index 100% rename from tacker/tests/functional/sol_v2/samples/sample2/contents/Scripts/sample_script.py rename to tacker/tests/functional/sol_v2/samples/update_vnf/contents/Scripts/sample_script.py diff --git a/tacker/tests/functional/sol_v2/samples/update_vnf/contents/TOSCA-Metadata/TOSCA.meta b/tacker/tests/functional/sol_v2/samples/update_vnf/contents/TOSCA-Metadata/TOSCA.meta new file mode 100644 index 000000000..2ccc96c78 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/update_vnf/contents/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/v2_sample2_top.vnfd.yaml diff --git a/tacker/tests/functional/sol_v2/samples/sample2/pkggen.py b/tacker/tests/functional/sol_v2/samples/update_vnf/pkggen.py similarity index 68% rename from tacker/tests/functional/sol_v2/samples/sample2/pkggen.py rename to tacker/tests/functional/sol_v2/samples/update_vnf/pkggen.py index cc0f0d811..a08a6b379 100644 --- a/tacker/tests/functional/sol_v2/samples/sample2/pkggen.py +++ b/tacker/tests/functional/sol_v2/samples/update_vnf/pkggen.py @@ -32,15 +32,14 @@ utils.make_zip(".", tmp_dir, vnfd_id) shutil.copy(os.path.join(tmp_dir, zip_file_name), ".") shutil.rmtree(tmp_dir) -create_req = paramgen.sample2_create(vnfd_id) -terminate_req = paramgen.sample2_terminate() -instantiate_req = paramgen.sample2_instantiate() +update_min_req = paramgen.update_vnf_min_with_parameter(vnfd_id) +# fake vnfc id, shoule be get from show vnf +vnfc_id_1 = "VDU1-9300a3cb-bd3b-45e4-9967-095040caf827" +vnfc_id_2 = "VDU2-39681281-e6e6-4179-8898-d9ec70f1642a" +update_max_req = paramgen.update_vnf_max(vnfd_id, vnfc_id_1, vnfc_id_2) -with open("create_req", "w") as f: - f.write(json.dumps(create_req, indent=2)) +with open("update_min_req", "w", encoding='utf-8') as f: + f.write(json.dumps(update_min_req, indent=2)) -with open("terminate_req", "w") as f: - f.write(json.dumps(terminate_req, indent=2)) - -with open("instantiate_req", "w") as f: - f.write(json.dumps(instantiate_req, indent=2)) +with open("update_max_req", "w", encoding='utf-8') as f: + f.write(json.dumps(update_max_req, indent=2)) diff --git a/tacker/tests/functional/sol_v2/test_vnflcm_basic.py b/tacker/tests/functional/sol_v2/test_vnflcm_basic.py index 9ddd11f4e..5794c5ef0 100644 --- a/tacker/tests/functional/sol_v2/test_vnflcm_basic.py +++ b/tacker/tests/functional/sol_v2/test_vnflcm_basic.py @@ -17,6 +17,7 @@ import ddt import os import time +from tacker.objects import fields from tacker.tests.functional.sol_v2 import base_v2 from tacker.tests.functional.sol_v2 import paramgen @@ -36,13 +37,21 @@ class VnfLcmTest(base_v2.BaseSolV2Test): image_file = "cirros-0.5.2-x86_64-disk.img" image_path = os.path.abspath(os.path.join(image_dir, image_file)) - sample1_path = os.path.join(cur_dir, "samples/sample1") + # for basic lcms tests max pattern + basic_lcms_max_path = os.path.join(cur_dir, "samples/basic_lcms_max") cls.vnf_pkg_1, cls.vnfd_id_1 = cls.create_vnf_package( - sample1_path, image_path=image_path) + basic_lcms_max_path, image_path=image_path) - sample2_path = os.path.join(cur_dir, "samples/sample2") + # for basic lcms tests min pattern + basic_lcms_min_path = os.path.join(cur_dir, "samples/basic_lcms_min") # no image contained - cls.vnf_pkg_2, cls.vnfd_id_2 = cls.create_vnf_package(sample2_path) + cls.vnf_pkg_2, cls.vnfd_id_2 = cls.create_vnf_package( + basic_lcms_min_path) + + # for update vnf test + update_vnf_path = os.path.join(cur_dir, "samples/update_vnf") + # no image contained + cls.vnf_pkg_3, cls.vnfd_id_3 = cls.create_vnf_package(update_vnf_path) @classmethod def tearDownClass(cls): @@ -50,9 +59,10 @@ class VnfLcmTest(base_v2.BaseSolV2Test): cls.delete_vnf_package(cls.vnf_pkg_1) cls.delete_vnf_package(cls.vnf_pkg_2) + cls.delete_vnf_package(cls.vnf_pkg_3) def setUp(self): - super(VnfLcmTest, self).setUp() + super().setUp() def test_api_versions(self): """Test version operations @@ -159,7 +169,7 @@ class VnfLcmTest(base_v2.BaseSolV2Test): self.assertEqual(204, resp.status_code) self.check_resp_headers_in_delete(resp) - def test_sample1(self): + def test_basic_lcms_max(self): """Test LCM operations with all attributes set * About attributes: @@ -173,14 +183,28 @@ class VnfLcmTest(base_v2.BaseSolV2Test): * About LCM operations: This test includes the following operations. - 0. Pre-setting - - 1. Create a new VNF instance resource - - 2. Instantiate a VNF instance - - 3. Show VNF instance - - 4. List VNF instance with attribute-based filtering - - 5. Show VNF LCM operation occurrence - - 6. List VNF LCM operation occurrence with attribute-based filtering - - 7. Terminate a VNF instance - - 8. Delete a VNF instance + - 1. Create subscription + - 2. Test notification + - 3. Show subscription + - 4. List subscriptions with attribute-based filtering + - 5. Create VNF instance + - 6. Instantiate VNF + - 7. Show VNF instance + - 8. List VNF instance with attribute-based filtering + - 9. Show VNF LCM operation occurrence + - 10. List VNF LCM operation occurrence with attribute-based + filtering + - 11. Scale out operation + - 12. Show VNF instance + - 13. Scale in operation + - 14. Show VNF instance + - 15. Update VNF + - 16. Show VNF instance + - 17. Terminate VNF + - 18. Delete VNF instance + - 19. Show VNF instance + - 20. Delete subscription + - 21. Show subscription """ # 0. Pre-setting # Create a new network and subnet to check the IP allocation of @@ -215,7 +239,45 @@ class VnfLcmTest(base_v2.BaseSolV2Test): port_ids[port_name] = port_id self.addCleanup(self.delete_port, port_id) - # 1. Create a new VNF instance resource + # 1. Create subscription + callback_url = os.path.join(base_v2.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) + callback_uri = ('http://localhost:' + f'{base_v2.FAKE_SERVER_MANAGER.SERVER_PORT}' + f'{callback_url}') + + sub_req = paramgen.sub_create_max(callback_uri) + resp, body = self.create_subscription(sub_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + sub_id = body['id'] + + # 2. Test notification + self.assert_notification_get(callback_url) + # check usageState of VNF Package + usage_state = self.get_vnf_package(self.vnf_pkg_1)['usageState'] + self.assertEqual('NOT_IN_USE', usage_state) + + # 3. Show subscription + expected_attrs = [ + 'id', 'callbackUri', 'verbosity', '_links', 'filter' + ] + + resp, body = self.show_subscription(sub_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_attrs) + + # 4. List subscription with attribute-based filtering + filter_expr = {'filter': f'(eq,id,{sub_id})'} + resp, body = self.list_subscriptions(filter_expr) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + for sbsc in body: + self.check_resp_body(sbsc, expected_attrs) + + # 5. Create VNF instance + # ETSI NFV SOL003 v3.3.1 5.5.2.2 VnfInstance # NOTE: extensions and vnfConfigurableProperties are omitted # because they are commented out in etsi_nfv_sol001. expected_inst_attrs = [ @@ -235,7 +297,7 @@ class VnfLcmTest(base_v2.BaseSolV2Test): # 'extensions', # omitted '_links' ] - create_req = paramgen.sample1_create(self.vnfd_id_1) + create_req = paramgen.create_vnf_max(self.vnfd_id_1) resp, body = self.create_vnf_instance(create_req) self.assertEqual(201, resp.status_code) self.check_resp_headers_in_create(resp) @@ -246,8 +308,14 @@ class VnfLcmTest(base_v2.BaseSolV2Test): usage_state = self.get_vnf_package(self.vnf_pkg_1)['usageState'] self.assertEqual('IN_USE', usage_state) - # 2. Instantiate a VNF instance - instantiate_req = paramgen.sample1_instantiate( + # check instantiationState of VNF + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.assertEqual(fields.VnfInstanceState.NOT_INSTANTIATED, + body['instantiationState']) + + # 6. Instantiate VNF instance + instantiate_req = paramgen.instantiate_vnf_max( net_ids, subnet_ids, port_ids, self.auth_url) resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) self.assertEqual(202, resp.status_code) @@ -256,8 +324,9 @@ class VnfLcmTest(base_v2.BaseSolV2Test): lcmocc_id = os.path.basename(resp.headers['Location']) self.wait_lcmocc_complete(lcmocc_id) + # 7. Show VNF instance # check creation of Heat-stack - stack_name = "vnf-{}".format(inst_id) + stack_name = f'vnf-{inst_id}' stack_status, _ = self.heat_client.get_status(stack_name) self.assertEqual("CREATE_COMPLETE", stack_status) @@ -278,7 +347,6 @@ class VnfLcmTest(base_v2.BaseSolV2Test): self.assertEqual('nova', vdu1_az) self.assertEqual('nova', vdu2_az) - # 3. Show VNF instance additional_inst_attrs = [ 'vimConnectionInfo', 'instantiatedVnfInfo' @@ -289,62 +357,71 @@ class VnfLcmTest(base_v2.BaseSolV2Test): self.check_resp_headers_in_get(resp) self.check_resp_body(body, expected_inst_attrs) - # 4. List VNF instance with attribute-based filtering + # check instantiationState of VNF + self.assertEqual(fields.VnfInstanceState.INSTANTIATED, + body['instantiationState']) + + # check vnfState of VNF + self.assertEqual(fields.VnfOperationalStateType.STARTED, + body['instantiatedVnfInfo']['vnfState']) + + # 8. List VNF instance with attribute-based filtering # check attribute-based filtering on VNF instance # NOTE: extensions and vnfConfigurableProperties are omitted # because they are commented out in etsi_nfv_sol001. # * all_fields # -> check the attribute omitted in "exclude_default" is set. - filter_expr = {'filter': '(eq,id,%s)' % inst_id, 'all_fields': ''} + filter_expr = {'filter': f'(eq,id,{inst_id})', 'all_fields': ''} resp, body = self.list_vnf_instance(filter_expr) self.assertEqual(200, resp.status_code) self.check_resp_headers_in_get(resp) for inst in body: - self.assertIsNotNone(inst.get('vnfInstanceName')) - self.assertIsNotNone(inst.get('vnfInstanceDescription')) - self.assertIsNotNone(inst.get('vimConnectionInfo')) - self.assertIsNotNone(inst.get('instantiatedVnfInfo')) - self.assertIsNotNone(inst.get('metadata')) + self.assertIn('vnfInstanceName', inst) + self.assertIn('vnfInstanceDescription', inst) + self.assertIn('vimConnectionInfo', inst) + self.assertIn('instantiatedVnfInfo', inst) + self.assertIn('metadata', inst) # * fields= # -> check the attribute specified in "fields" is set - filter_expr = {'filter': '(eq,id,%s)' % inst_id, + filter_expr = {'filter': f'(eq,id,{inst_id})', 'fields': 'metadata'} resp, body = self.list_vnf_instance(filter_expr) self.assertEqual(200, resp.status_code) self.check_resp_headers_in_get(resp) for inst in body: - self.assertIsNone(inst.get('vnfInstanceName')) - self.assertIsNone(inst.get('vnfInstanceDescription')) - self.assertIsNone(inst.get('vimConnectionInfo')) - self.assertIsNone(inst.get('instantiatedVnfInfo')) - self.assertIsNotNone(inst.get('metadata')) + self.assertNotIn('vnfInstanceName', inst) + self.assertNotIn('vnfInstanceDescription', inst) + self.assertNotIn('vimConnectionInfo', inst) + self.assertNotIn('instantiatedVnfInfo', inst) + self.assertIn('metadata', inst) # * exclude_fields= # -> check the attribute specified in "exclude_fields" is not set - filter_expr = {'filter': '(eq,id,%s)' % inst_id, + filter_expr = {'filter': f'(eq,id,{inst_id})', 'exclude_fields': 'vnfInstanceName'} resp, body = self.list_vnf_instance(filter_expr) self.assertEqual(200, resp.status_code) self.check_resp_headers_in_get(resp) for inst in body: - self.assertIsNone(inst.get('vnfInstanceName')) - self.assertIsNotNone(inst.get('vnfInstanceDescription')) - self.assertIsNotNone(inst.get('vimConnectionInfo')) - self.assertIsNotNone(inst.get('instantiatedVnfInfo')) - self.assertIsNotNone(inst.get('metadata')) + self.assertNotIn('vnfInstanceName', inst) + self.assertIn('vnfInstanceDescription', inst) + self.assertIn('vimConnectionInfo', inst) + self.assertIn('instantiatedVnfInfo', inst) + self.assertIn('metadata', inst) # * exclude_default # -> check the attribute omitted in "exclude_default" is not set. - filter_expr = {'filter': '(eq,id,%s)' % inst_id, 'exclude_default': ''} + filter_expr = {'filter': f'(eq,id,{inst_id})', 'exclude_default': ''} resp, body = self.list_vnf_instance(filter_expr) self.assertEqual(200, resp.status_code) self.check_resp_headers_in_get(resp) for inst in body: - self.assertIsNotNone(inst.get('vnfInstanceName')) - self.assertIsNotNone(inst.get('vnfInstanceDescription')) - self.assertIsNone(inst.get('vimConnectionInfo')) - self.assertIsNone(inst.get('instantiatedVnfInfo')) - self.assertIsNone(inst.get('metadata')) + self.assertIn('vnfInstanceName', inst) + self.assertIn('vnfInstanceDescription', inst) + self.assertNotIn('vimConnectionInfo', inst) + self.assertNotIn('instantiatedVnfInfo', inst) + self.assertNotIn('metadata', inst) - # 5. Show VNF LCM operation occurrence + # 9. Show VNF LCM operation occurrence + # ETSI NFV SOL003 v3.3.1 5.5.2.13 VnfLcmOpOcc # NOTE: omitted values are not supported at that time expected_attrs = [ 'id', @@ -371,52 +448,232 @@ class VnfLcmTest(base_v2.BaseSolV2Test): self.check_resp_headers_in_get(resp) self.check_resp_body(body, expected_attrs) - # 6. List VNF LCM operation occurrence with attribute-based filtering + # 10. List VNF LCM operation occurrence with attribute-based filtering # check attribute-based filtering on vnf_lcm_op_occs # NOTE: error and changedInfo, changedExtConnectivity are omitted # because these values are not supported at that time # * all_fields # -> check the attribute omitted in "exclude_default" is set. - filter_expr = {'filter': '(eq,id,%s)' % lcmocc_id, 'all_fields': ''} + filter_expr = {'filter': f'(eq,id,{lcmocc_id})', 'all_fields': ''} resp, body = self.list_lcmocc(filter_expr) self.assertEqual(200, resp.status_code) self.check_resp_headers_in_get(resp) for lcmocc in body: - self.assertIsNotNone(lcmocc.get('operationParams')) - self.assertIsNotNone(lcmocc.get('resourceChanges')) + self.assertIn('operationParams', lcmocc) + self.assertIn('resourceChanges', lcmocc) # * fields= # -> check the attribute specified in "fields" is set - filter_expr = {'filter': '(eq,id,%s)' % lcmocc_id, + filter_expr = {'filter': f'(eq,id,{lcmocc_id})', 'fields': 'operationParams'} resp, body = self.list_lcmocc(filter_expr) self.assertEqual(200, resp.status_code) self.check_resp_headers_in_get(resp) for lcmocc in body: - self.assertIsNotNone(lcmocc.get('operationParams')) - self.assertIsNone(lcmocc.get('resourceChanges')) + self.assertIn('operationParams', lcmocc) + self.assertNotIn('resourceChanges', lcmocc) # * exclude_fields= # -> check the attribute specified in "exclude_fields" is not set - filter_expr = {'filter': '(eq,id,%s)' % inst_id, + filter_expr = {'filter': f'(eq,id,{inst_id})', 'exclude_fields': 'operationParams'} resp, body = self.list_lcmocc(filter_expr) self.assertEqual(200, resp.status_code) self.check_resp_headers_in_get(resp) for lcmocc in body: - self.assertIsNone(lcmocc.get('operationParams')) - self.assertIsNotNone(lcmocc.get('resourceChanges')) + self.assertNotIn('operationParams', lcmocc) + self.assertIn('resourceChanges', lcmocc) # * exclude_default # -> check the attribute omitted in "exclude_default" is not set. - filter_expr = {'filter': '(eq,id,%s)' % lcmocc_id, + filter_expr = {'filter': f'(eq,id,{lcmocc_id})', 'exclude_default': ''} resp, body = self.list_lcmocc(filter_expr) self.assertEqual(200, resp.status_code) self.check_resp_headers_in_get(resp) for lcmocc in body: - self.assertIsNone(lcmocc.get('operationParams')) - self.assertIsNone(lcmocc.get('resourceChanges')) + self.assertNotIn('operationParams', lcmocc) + self.assertNotIn('resourceChanges', lcmocc) - # 7. Terminate a VNF instance - terminate_req = paramgen.sample1_terminate() + # 11. Scale out operation + # get nested stack count before scale out + nested_stacks = self.heat_client.get_resources(stack_name) + count_before_scaleout = len(nested_stacks) + scaleout_req = paramgen.scaleout_vnf_max() + resp, body = self.scale_vnf_instance(inst_id, scaleout_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 12. Show VNF instance + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_inst_attrs) + + # check vnfState of VNF + self.assertEqual(fields.VnfOperationalStateType.STARTED, + body['instantiatedVnfInfo']['vnfState']) + + # check scaleStatus + scale_status = body['instantiatedVnfInfo']['scaleStatus'] + self.assertGreater(len(scale_status), 0) + for status in scale_status: + self.assertIn('aspectId', status) + self.assertIn('scaleLevel', status) + + # check creation of Heat-stack + stack_status, _ = self.heat_client.get_status(stack_name) + self.assertEqual("UPDATE_COMPLETE", stack_status) + # get nested stack count after scale out + nested_stacks = self.heat_client.get_resources(stack_name) + count_after_scaleout = len(nested_stacks) + # check nested stack was created + # 9 was the sum of 1 VM, 1 Volume, 1 VolumeType, 5 CPs, + # 1 stack(VDU1.yaml) + self.assertEqual(9, count_after_scaleout - count_before_scaleout) + + # 13. Scale in operation + scalein_req = paramgen.scalein_vnf_max() + resp, body = self.scale_vnf_instance(inst_id, scalein_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 14. Show VNF instance + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_inst_attrs) + + # check vnfState of VNF + self.assertEqual(fields.VnfOperationalStateType.STARTED, + body['instantiatedVnfInfo']['vnfState']) + + # check scaleStatus + scale_status = body['instantiatedVnfInfo']['scaleStatus'] + self.assertGreater(len(scale_status), 0) + for status in scale_status: + self.assertIn('aspectId', status) + self.assertIn('scaleLevel', status) + + # check creation of Heat-stack + stack_status, _ = self.heat_client.get_status(stack_name) + self.assertEqual("UPDATE_COMPLETE", stack_status) + # get nested stack count after scale in + nested_stacks = self.heat_client.get_resources(stack_name) + count_after_scalein = len(nested_stacks) + # check nested stack was deleted + # 9 was the sum of 1 VM, 1 Volume, 1 VolumeType, 5 CPs, + # 1 stack(VDU1.yaml) + self.assertEqual(9, count_after_scaleout - count_after_scalein) + + # 15. Update VNF + # check attribute value before update VNF + # check usageState of VNF Package 1 + usage_state = self.get_vnf_package(self.vnf_pkg_1)['usageState'] + self.assertEqual('IN_USE', usage_state) + + # check usageState of VNF Package 3 + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] + self.assertEqual('NOT_IN_USE', usage_state) + + # check vnfd id + self.assertEqual(self.vnfd_id_1, body['vnfdId']) + + # check vnfc info + vnfc_info = body['instantiatedVnfInfo']['vnfcInfo'] + self.assertGreater(len(vnfc_info), 1) + vnfc_ids = [] + for vnfc in vnfc_info: + self.assertIsNotNone(vnfc.get('id')) + self.assertIsNotNone(vnfc.get('vduId')) + self.assertIsNotNone(vnfc.get('vnfcState')) + self.assertIsNone(vnfc.get('vnfcConfigurableProperties')) + vnfc_ids.append(vnfc.get('id')) + + update_req = paramgen.update_vnf_max(self.vnfd_id_3, vnfc_ids[0], + vnfc_ids[1]) + resp, body = self.update_vnf_instance(inst_id, update_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 16. Show VNF instance + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_inst_attrs) + + # check vnfState of VNF + self.assertEqual(fields.VnfOperationalStateType.STARTED, + body['instantiatedVnfInfo']['vnfState']) + + # check usageState of VNF Package 1 + usage_state = self.get_vnf_package(self.vnf_pkg_1)['usageState'] + self.assertEqual('NOT_IN_USE', usage_state) + + # check usageState of VNF Package 3 + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] + self.assertEqual('IN_USE', usage_state) + + # check the specified attribute after update VNF + self.assertEqual(self.vnfd_id_3, body['vnfdId']) + self.assertEqual('new name', body['vnfInstanceName']) + self.assertEqual('new description', body['vnfInstanceDescription']) + dummy_key_value = {'dummy-key': 'dummy-value'} + self.assertEqual(dummy_key_value, body['metadata']) + self.assertEqual(dummy_key_value, body['extensions']) + self.assertEqual(dummy_key_value, body['vnfConfigurableProperties']) + vim_connection_info = { + "vim2": { + "vimId": "ac2d2ece-5e49-4b15-b92d-b681e9c096d8", + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "interfaceInfo": { + "endpoint": "http://127.0.0.1/identity/v3" + }, + "accessInfo": { + "username": "dummy_user", + "region": "RegionOne", + "project": "dummy_project", + "projectDomain": "Default", + "userDomain": "Default" + }, + "extra": { + "dummy-key": "dummy-val" + } + } + } + self.assertEqual(vim_connection_info['vim2'], + body['vimConnectionInfo']['vim2']) + + # check vnfc info + vnfc_info = body['instantiatedVnfInfo']['vnfcInfo'] + self.assertEqual(vnfc_ids[0], vnfc_info[0]['id']) + self.assertEqual(vnfc_ids[1], vnfc_info[1]['id']) + self.assertEqual(dummy_key_value, + vnfc_info[0]['vnfcConfigurableProperties']) + self.assertEqual(dummy_key_value, + vnfc_info[1]['vnfcConfigurableProperties']) + + # 17. Terminate VNF + terminate_req = paramgen.terminate_vnf_max() resp, body = self.terminate_vnf_instance(inst_id, terminate_req) self.assertEqual(202, resp.status_code) self.check_resp_headers_in_operation_task(resp) @@ -437,20 +694,40 @@ class VnfLcmTest(base_v2.BaseSolV2Test): image_id = self.get_image_id(image_name) self.assertIsNone(image_id) - # 8. Delete a VNF instance + # check usageState of VNF Package + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] + self.assertEqual('IN_USE', usage_state) + + # check instantiationState of VNF + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.assertEqual(fields.VnfInstanceState.NOT_INSTANTIATED, + body['instantiationState']) + + # 18. Delete VNF instance resp, body = self.delete_vnf_instance(inst_id) self.assertEqual(204, resp.status_code) self.check_resp_headers_in_delete(resp) + # 19. Show VNF instance # check deletion of VNF instance resp, body = self.show_vnf_instance(inst_id) self.assertEqual(404, resp.status_code) # check usageState of VNF Package - usage_state = self.get_vnf_package(self.vnf_pkg_1)['usageState'] + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] self.assertEqual('NOT_IN_USE', usage_state) - def test_sample2(self): + # 20. Delete subscription + resp, body = self.delete_subscription(sub_id) + self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) + + # 21. Show subscription + resp, body = self.show_subscription(sub_id) + self.assertEqual(404, resp.status_code) + + def test_basic_lcms_min(self): """Test LCM operations with omitting except for required attributes * About attributes: @@ -461,13 +738,42 @@ class VnfLcmTest(base_v2.BaseSolV2Test): * About LCM operations: This test includes the following operations. - - 1. Create a new VNF instance resource - - 2. Instantiate a VNF instance - - 3. Show VNF instance - - 4. Terminate a VNF instance - - 5. Delete a VNF instance + - 1. Create subscription + - 2. Test notification + - 3. Create VNF instance + - 4. Instantiate VNF + - 5. Show VNF instance + - 6. Update VNF + - 7. Show VNF instance + - 8. Scale out operation + - 9. Show VNF instance + - 10. Scale in operation + - 11. Terminate VNF + - 12. Delete VNF instance + - 13. Delete subscription + - 14. Show subscription """ - # 1. Create a new VNF instance resource + # 1. Create subscription + callback_url = os.path.join(base_v2.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) + callback_uri = ('http://localhost:' + f'{base_v2.FAKE_SERVER_MANAGER.SERVER_PORT}' + f'{callback_url}') + + sub_req = paramgen.sub_create_min(callback_uri) + resp, body = self.create_subscription(sub_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + sub_id = body['id'] + + # 2. Test notification + self.assert_notification_get(callback_url) + # check usageState of VNF Package + usage_state = self.get_vnf_package(self.vnf_pkg_2)['usageState'] + self.assertEqual('NOT_IN_USE', usage_state) + + # 3. Create VNF instance + # ETSI NFV SOL003 v3.3.1 5.5.2.2 VnfInstance expected_inst_attrs = [ 'id', # 'vnfInstanceName', # omitted @@ -485,7 +791,7 @@ class VnfLcmTest(base_v2.BaseSolV2Test): # 'extensions', # omitted '_links' ] - create_req = paramgen.sample2_create(self.vnfd_id_2) + create_req = paramgen.create_vnf_min(self.vnfd_id_2) resp, body = self.create_vnf_instance(create_req) self.assertEqual(201, resp.status_code) self.check_resp_headers_in_create(resp) @@ -496,8 +802,14 @@ class VnfLcmTest(base_v2.BaseSolV2Test): usage_state = self.get_vnf_package(self.vnf_pkg_2)['usageState'] self.assertEqual('IN_USE', usage_state) - # 2. Instantiate a VNF instance - instantiate_req = paramgen.sample2_instantiate() + # check instantiationState of VNF + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.assertEqual(fields.VnfInstanceState.NOT_INSTANTIATED, + body['instantiationState']) + + # 4. Instantiate VNF + instantiate_req = paramgen.instantiate_vnf_min() resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) self.assertEqual(202, resp.status_code) self.check_resp_headers_in_operation_task(resp) @@ -506,7 +818,7 @@ class VnfLcmTest(base_v2.BaseSolV2Test): self.wait_lcmocc_complete(lcmocc_id) # check creation of Heat-stack - stack_name = "vnf-{}".format(inst_id) + stack_name = f'vnf-{inst_id}' stack_status, _ = self.heat_client.get_status(stack_name) self.assertEqual("CREATE_COMPLETE", stack_status) @@ -519,7 +831,7 @@ class VnfLcmTest(base_v2.BaseSolV2Test): vdu2_host = vdu2_details['hostId'] self.assertEqual(vdu1_host, vdu2_host) - # 3. Show VNF instance + # 5. Show VNF instance additional_inst_attrs = [ 'vimConnectionInfo', 'instantiatedVnfInfo' @@ -530,8 +842,98 @@ class VnfLcmTest(base_v2.BaseSolV2Test): self.check_resp_headers_in_get(resp) self.check_resp_body(body, expected_inst_attrs) - # 4. Terminate a VNF instance - terminate_req = paramgen.sample2_terminate() + # check instantiationState of VNF + self.assertEqual(fields.VnfInstanceState.INSTANTIATED, + body['instantiationState']) + + # check usageState of VNF Package 2 + usage_state = self.get_vnf_package(self.vnf_pkg_2)['usageState'] + self.assertEqual('IN_USE', usage_state) + + # check usageState of VNF Package 3 + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] + self.assertEqual('NOT_IN_USE', usage_state) + + # check vnfd id + self.assertEqual(self.vnfd_id_2, body['vnfdId']) + + # 6. Update VNF + update_req = paramgen.update_vnf_min_with_parameter(self.vnfd_id_3) + resp, body = self.update_vnf_instance(inst_id, update_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 7. Show VNF instance + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_inst_attrs) + + # check usageState of VNF Package 2 + usage_state = self.get_vnf_package(self.vnf_pkg_2)['usageState'] + self.assertEqual('NOT_IN_USE', usage_state) + + # check usageState of VNF Package 3 + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] + self.assertEqual('IN_USE', usage_state) + + # check vnfd id + self.assertEqual(self.vnfd_id_3, body['vnfdId']) + + # 8. Scale out operation + # get nested stack count before scaleout + nested_stacks = self.heat_client.get_resources(stack_name) + count_before_scaleout = len(nested_stacks) + scaleout_req = paramgen.scaleout_vnf_min() + resp, body = self.scale_vnf_instance(inst_id, scaleout_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 9. Show VNF instance + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_inst_attrs) + + # get nested stack count after scale out + nested_stacks = self.heat_client.get_resources(stack_name) + count_after_scaleout = len(nested_stacks) + # check nested stack was created + # 3 was the sum of 1 VM, 1 CP, 1 stack(VDU1.yaml) + self.assertEqual(3, count_after_scaleout - count_before_scaleout) + + # 10. Scale in operation + scalein_req = paramgen.scalein_vnf_min() + resp, body = self.scale_vnf_instance(inst_id, scalein_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # get nested stack count after scale in + nested_stacks = self.heat_client.get_resources(stack_name) + count_after_scalein = len(nested_stacks) + # check nested stack was deleted + # 3 was the sum of 1 VM, 1 CP, 1 stack(VDU1.yaml) + self.assertEqual(3, count_after_scaleout - count_after_scalein) + + # 11. Terminate a VNF instance + terminate_req = paramgen.terminate_vnf_min() resp, body = self.terminate_vnf_instance(inst_id, terminate_req) self.assertEqual(202, resp.status_code) self.check_resp_headers_in_operation_task(resp) @@ -547,7 +949,17 @@ class VnfLcmTest(base_v2.BaseSolV2Test): stack_status, _ = self.heat_client.get_status(stack_name) self.assertIsNone(stack_status) - # 5. Delete a VNF instance + # check usageState of VNF Package + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] + self.assertEqual('IN_USE', usage_state) + + # check instantiationState of VNF + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.assertEqual(fields.VnfInstanceState.NOT_INSTANTIATED, + body['instantiationState']) + + # 12. Delete a VNF instance resp, body = self.delete_vnf_instance(inst_id) self.assertEqual(204, resp.status_code) self.check_resp_headers_in_delete(resp) @@ -557,5 +969,280 @@ class VnfLcmTest(base_v2.BaseSolV2Test): self.assertEqual(404, resp.status_code) # check usageState of VNF Package - usage_state = self.get_vnf_package(self.vnf_pkg_2)['usageState'] + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] self.assertEqual('NOT_IN_USE', usage_state) + + # 13. Delete subscription + resp, body = self.delete_subscription(sub_id) + self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) + + # 14. Show subscription + resp, body = self.show_subscription(sub_id) + self.assertEqual(404, resp.status_code) + + def test_update_scale_lcm(self): + """Test the sequence of update VNF and scale out + + * About attributes: + All of the following cardinality attributes are set. + In addition, 0..N or 1..N attributes are set to 2 or more. + 0..1 is set to 1. + - 0..1 (1) + - 0..N (2 or more) + - 1..N (2 or more) + + * About LCM operations: + This test includes the following operations. + - 0. Pre-setting + - 1. Create subscription + - 2. Create VNF instance + - 3. Instantiate VNF + - 4. Show VNF instance + - 5. Update VNF + - 6. Show VNF instance + - 7. Scale out operation + - 8. Terminate VNF + - 9. Delete VNF instance + - 10. Delete subscription + """ + # 0. Pre-setting + # Create a new network and subnet to check the IP allocation of + # IPv4 and IPv6 + ft_net0_name = 'ft-net0' + ft_net0_subs = { + 'ft-ipv4-subnet0': { + 'range': '100.100.100.0/24', + 'ip_version': 4 + }, + 'ft-ipv6-subnet0': { + 'range': '1111:2222:3333::/64', + 'ip_version': 6 + } + } + ft_net0_id = self.create_network(ft_net0_name) + self.addCleanup(self.delete_network, ft_net0_id) + for sub_name, val in ft_net0_subs.items(): + # subnet is automatically deleted with network deletion + self.create_subnet( + ft_net0_id, sub_name, val['range'], val['ip_version']) + + net_ids = self.get_network_ids( + ['net0', 'net1', 'net_mgmt', 'ft-net0']) + subnet_ids = self.get_subnet_ids( + ['subnet0', 'subnet1', 'ft-ipv4-subnet0', 'ft-ipv6-subnet0']) + + port_names = ['VDU2_CP1-1', 'VDU2_CP1-2'] + port_ids = {} + for port_name in port_names: + port_id = self.create_port(net_ids['net0'], port_name) + port_ids[port_name] = port_id + self.addCleanup(self.delete_port, port_id) + + # 1. Create subscription + callback_url = os.path.join(base_v2.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) + callback_uri = ('http://localhost:' + f'{base_v2.FAKE_SERVER_MANAGER.SERVER_PORT}' + f'{callback_url}') + + sub_req = paramgen.sub_create_max(callback_uri) + resp, body = self.create_subscription(sub_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + sub_id = body['id'] + + # 2. Create VNF instance + # ETSI NFV SOL003 v3.3.1 5.5.2.2 VnfInstance + # NOTE: extensions and vnfConfigurableProperties are omitted + # because they are commented out in etsi_nfv_sol001. + expected_inst_attrs = [ + 'id', + 'vnfInstanceName', + 'vnfInstanceDescription', + 'vnfdId', + 'vnfProvider', + 'vnfProductName', + 'vnfSoftwareVersion', + 'vnfdVersion', + # 'vnfConfigurableProperties', # omitted + # 'vimConnectionInfo', # omitted + 'instantiationState', + # 'instantiatedVnfInfo', # omitted + 'metadata', + # 'extensions', # omitted + '_links' + ] + create_req = paramgen.create_vnf_max(self.vnfd_id_1) + resp, body = self.create_vnf_instance(create_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + # check instantiationState of VNF + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.assertEqual(fields.VnfInstanceState.NOT_INSTANTIATED, + body['instantiationState']) + + # 3. Instantiate VNF instance + instantiate_req = paramgen.instantiate_vnf_max( + net_ids, subnet_ids, port_ids, self.auth_url) + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 4. Show VNF instance + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_inst_attrs) + + # check instantiationState of VNF + self.assertEqual(fields.VnfInstanceState.INSTANTIATED, + body['instantiationState']) + + # check vnfState of VNF + self.assertEqual(fields.VnfOperationalStateType.STARTED, + body['instantiatedVnfInfo']['vnfState']) + + # 5. Update VNF + # check attribute value before update VNF + # check usageState of VNF Package 1 + usage_state = self.get_vnf_package(self.vnf_pkg_1)['usageState'] + self.assertEqual('IN_USE', usage_state) + + # check usageState of VNF Package 3 + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] + self.assertEqual('NOT_IN_USE', usage_state) + + # check vnfd id + self.assertEqual(self.vnfd_id_1, body['vnfdId']) + + # check vnfc info + vnfc_info = body['instantiatedVnfInfo']['vnfcInfo'] + self.assertGreater(len(vnfc_info), 1) + vnfc_ids = [] + for vnfc in vnfc_info: + self.assertIsNotNone(vnfc.get('id')) + self.assertIsNotNone(vnfc.get('vduId')) + self.assertIsNotNone(vnfc.get('vnfcState')) + self.assertIsNone(vnfc.get('vnfcConfigurableProperties')) + vnfc_ids.append(vnfc.get('id')) + + update_req = paramgen.update_vnf_max(self.vnfd_id_3, vnfc_ids[0], + vnfc_ids[1]) + resp, body = self.update_vnf_instance(inst_id, update_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 6. Show VNF instance + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_inst_attrs) + + # check vnfState of VNF + self.assertEqual(fields.VnfOperationalStateType.STARTED, + body['instantiatedVnfInfo']['vnfState']) + + # check usageState of VNF Package 1 + usage_state = self.get_vnf_package(self.vnf_pkg_1)['usageState'] + self.assertEqual('NOT_IN_USE', usage_state) + + # check usageState of VNF Package 3 + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] + self.assertEqual('IN_USE', usage_state) + + # check the specified attribute after update VNF + self.assertEqual(self.vnfd_id_3, body['vnfdId']) + self.assertEqual('new name', body['vnfInstanceName']) + self.assertEqual('new description', body['vnfInstanceDescription']) + dummy_key_value = {'dummy-key': 'dummy-value'} + self.assertEqual(dummy_key_value, body['metadata']) + self.assertEqual(dummy_key_value, body['extensions']) + self.assertEqual(dummy_key_value, body['vnfConfigurableProperties']) + vim_connection_info = { + "vim2": { + "vimId": "ac2d2ece-5e49-4b15-b92d-b681e9c096d8", + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "interfaceInfo": { + "endpoint": "http://127.0.0.1/identity/v3" + }, + "accessInfo": { + "username": "dummy_user", + "region": "RegionOne", + "project": "dummy_project", + "projectDomain": "Default", + "userDomain": "Default" + }, + "extra": { + "dummy-key": "dummy-val" + } + } + } + self.assertEqual(vim_connection_info['vim2'], + body['vimConnectionInfo']['vim2']) + + # check vnfc info + vnfc_info = body['instantiatedVnfInfo']['vnfcInfo'] + self.assertEqual(vnfc_ids[0], vnfc_info[0]['id']) + self.assertEqual(vnfc_ids[1], vnfc_info[1]['id']) + self.assertEqual(dummy_key_value, + vnfc_info[0]['vnfcConfigurableProperties']) + self.assertEqual(dummy_key_value, + vnfc_info[1]['vnfcConfigurableProperties']) + + # 7. Scale out operation + scaleout_req = paramgen.scaleout_vnf_max() + resp, body = self.scale_vnf_instance(inst_id, scaleout_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 8. Terminate a VNF instance + terminate_req = paramgen.terminate_vnf_max() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # wait a bit because there is a bit time lag between lcmocc DB + # update and terminate completion. + time.sleep(10) + + # check instantiationState of VNF + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.assertEqual(fields.VnfInstanceState.NOT_INSTANTIATED, + body['instantiationState']) + + # 9. Delete a VNF instance + resp, body = self.delete_vnf_instance(inst_id) + self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) + + # 10. Delete subscription + resp, body = self.delete_subscription(sub_id) + self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) diff --git a/tacker/tests/functional/sol_v2/test_vnflcm_error_handling.py b/tacker/tests/functional/sol_v2/test_vnflcm_error_handling.py index 153e1d849..1fb83162b 100644 --- a/tacker/tests/functional/sol_v2/test_vnflcm_error_handling.py +++ b/tacker/tests/functional/sol_v2/test_vnflcm_error_handling.py @@ -48,15 +48,23 @@ class VnfLcmErrorHandlingTest(base_v2.BaseSolV2Test): cls.vnf_pkg_2, cls.vnfd_id_2 = cls.create_vnf_package( error_network_path) + # update VNF or change external VNF connectivity will fail + update_change_ng_path = os.path.join(cur_dir, + "samples/basic_lcms_min") + # no image contained + cls.vnf_pkg_3, cls.vnfd_id_3 = cls.create_vnf_package( + update_change_ng_path) + @classmethod def tearDownClass(cls): super(VnfLcmErrorHandlingTest, cls).tearDownClass() cls.delete_vnf_package(cls.vnf_pkg_1) cls.delete_vnf_package(cls.vnf_pkg_2) + cls.delete_vnf_package(cls.vnf_pkg_3) def setUp(self): - super(VnfLcmErrorHandlingTest, self).setUp() + super().setUp() def test_retry_rollback_scale_out(self): """Test retry and rollback scale out operations @@ -141,6 +149,7 @@ class VnfLcmErrorHandlingTest(base_v2.BaseSolV2Test): self.assertEqual('NOT_IN_USE', usage_state) # 3. Create VNF instance + # ETSI NFV SOL003 v3.3.1 5.5.2.2 VnfInstance expected_inst_attrs = [ 'id', 'vnfInstanceName', @@ -206,7 +215,7 @@ class VnfLcmErrorHandlingTest(base_v2.BaseSolV2Test): # check vnfState of VNF self.assertEqual(fields.VnfOperationalStateType.STARTED, - body['instantiatedVnfInfo'].get('vnfState')) + body['instantiatedVnfInfo']['vnfState']) # 6. Scale out operation(will fail) scaleout_req = paramgen.scaleout_vnf_max() @@ -221,7 +230,6 @@ class VnfLcmErrorHandlingTest(base_v2.BaseSolV2Test): self.assertEqual('IN_USE', usage_state) # 7. Show VNF instance - expected_inst_attrs.extend(additional_inst_attrs) resp, body = self.show_vnf_instance(inst_id) self.assertEqual(200, resp.status_code) self.check_resp_headers_in_get(resp) @@ -240,6 +248,7 @@ class VnfLcmErrorHandlingTest(base_v2.BaseSolV2Test): self.wait_lcmocc_rolled_back(lcmocc_id) # 10. Show VNF LCM operation occurrence + # ETSI NFV SOL003 v3.3.1 5.5.2.13 VnfLcmOpOcc # NOTE: omitted values are not supported at that time expected_attrs = [ 'id', @@ -362,7 +371,7 @@ class VnfLcmErrorHandlingTest(base_v2.BaseSolV2Test): # 1. Create subscription callback_url = os.path.join(base_v2.MOCK_NOTIFY_CALLBACK_URL, self._testMethodName) - callback_uri = (f'http://localhost:' + callback_uri = ('http://localhost:' f'{base_v2.FAKE_SERVER_MANAGER.SERVER_PORT}' f'{callback_url}') @@ -379,6 +388,7 @@ class VnfLcmErrorHandlingTest(base_v2.BaseSolV2Test): self.assertEqual('NOT_IN_USE', usage_state) # 3. Create VNF instance + # ETSI NFV SOL003 v3.3.1 5.5.2.2 VnfInstance expected_inst_attrs = [ 'id', # 'vnfInstanceName', # omitted @@ -441,6 +451,7 @@ class VnfLcmErrorHandlingTest(base_v2.BaseSolV2Test): self.wait_lcmocc_rolled_back(lcmocc_id) # 7. Show VNF LCM operation occurrence + # ETSI NFV SOL003 v3.3.1 5.5.2.13 VnfLcmOpOcc # NOTE: omitted values are not supported at that time expected_attrs = [ 'id', @@ -535,7 +546,7 @@ class VnfLcmErrorHandlingTest(base_v2.BaseSolV2Test): # 1. Create subscription callback_url = os.path.join(base_v2.MOCK_NOTIFY_CALLBACK_URL, self._testMethodName) - callback_uri = (f'http://localhost:' + callback_uri = ('http://localhost:' f'{base_v2.FAKE_SERVER_MANAGER.SERVER_PORT}' f'{callback_url}') @@ -552,6 +563,7 @@ class VnfLcmErrorHandlingTest(base_v2.BaseSolV2Test): self.assertEqual('NOT_IN_USE', usage_state) # 3. Create VNF instance + # ETSI NFV SOL003 v3.3.1 5.5.2.2 VnfInstance expected_inst_attrs = [ 'id', # 'vnfInstanceName', # omitted @@ -635,6 +647,7 @@ class VnfLcmErrorHandlingTest(base_v2.BaseSolV2Test): self.assertEqual('FAILED', body['operationState']) # 7. Show VNF LCM operation occurrence + # ETSI NFV SOL003 v3.3.1 5.5.2.13 VnfLcmOpOcc resp, body = self.show_lcmocc(lcmocc_id) self.assertEqual(200, resp.status_code) self.check_resp_headers_in_get(resp) @@ -684,3 +697,205 @@ class VnfLcmErrorHandlingTest(base_v2.BaseSolV2Test): resp, body = self.delete_subscription(sub_id) self.assertEqual(204, resp.status_code) self.check_resp_headers_in_delete(resp) + + def test_rollback_update(self): + """Test rollback update VNF operation + + * About attributes: + Omit except for required attributes. + Only the following cardinality attributes are set. + - 1 + - 1..N (1) + + * About LCM operations: + This test includes the following operations. + - 1. Create subscription + - 2. Test notification + - 3. Create VNF instance + - 4. Instantiate VNF + - 5. Show VNF instance + - 6. Update VNF(will fail) + - 7. Rollback update operation + - 8. Show VNF LCM operation occurrence + - 9. List VNF LCM operation occurrence + - 10. Terminate VNF + - 11. Delete VNF instance + - 12. Delete subscription + """ + + # 1. Create subscription + callback_url = os.path.join(base_v2.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) + callback_uri = ('http://localhost:' + f'{base_v2.FAKE_SERVER_MANAGER.SERVER_PORT}' + f'{callback_url}') + + sub_req = paramgen.sub_create_min(callback_uri) + resp, body = self.create_subscription(sub_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + sub_id = body['id'] + + # 2. Test notification + self.assert_notification_get(callback_url) + # check usageState of VNF Package + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] + self.assertEqual('NOT_IN_USE', usage_state) + + # 3. Create VNF instance + # ETSI NFV SOL003 v3.3.1 5.5.2.2 VnfInstance + expected_inst_attrs = [ + 'id', + # 'vnfInstanceName', # omitted + # 'vnfInstanceDescription', # omitted + 'vnfdId', + 'vnfProvider', + 'vnfProductName', + 'vnfSoftwareVersion', + 'vnfdVersion', + # 'vnfConfigurableProperties', # omitted + # 'vimConnectionInfo', # omitted + 'instantiationState', + # 'instantiatedVnfInfo', # omitted + # 'metadata', # omitted + # 'extensions', # omitted + '_links' + ] + create_req = paramgen.create_vnf_min(self.vnfd_id_3) + resp, body = self.create_vnf_instance(create_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + # check usageState of VNF Package + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] + self.assertEqual('IN_USE', usage_state) + + # check instantiationState of VNF + self.assertEqual(fields.VnfInstanceState.NOT_INSTANTIATED, + body['instantiationState']) + + # 4. Instantiate VNF + instantiate_req = paramgen.instantiate_vnf_min() + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # check usageState of VNF Package + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] + self.assertEqual('IN_USE', usage_state) + + # 5. Show VNF instance + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_inst_attrs) + + # check instantiationState of VNF + self.assertEqual(fields.VnfInstanceState.INSTANTIATED, + body['instantiationState']) + + # check vnfState of VNF + self.assertEqual(fields.VnfOperationalStateType.STARTED, + body['instantiatedVnfInfo']['vnfState']) + + # 6. Update VNF(will fail) + # NOTE: Create a file so that an error occurs in mgmtDriver + path = '/tmp/modify_information_start' + with open(path, 'w', encoding='utf-8') as f: + f.write('') + self.addCleanup(os.remove, path) + update_req = paramgen.update_vnf_min() + resp, body = self.update_vnf_instance(inst_id, update_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + + # 7. Rollback update operation + resp, body = self.rollback_lcmocc(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_delete(resp) + self.wait_lcmocc_rolled_back(lcmocc_id) + + # 8. Show VNF LCM operation occurrence + # ETSI NFV SOL003 v3.3.1 5.5.2.13 VnfLcmOpOcc + # NOTE: omitted values are not supported at that time + expected_attrs = [ + 'id', + 'operationState', + 'stateEnteredTime', + 'startTime', + 'vnfInstanceId', + # 'grantId', # omitted + 'operation', + 'isAutomaticInvocation', + # 'operationParams', # omitted + 'isCancelPending', + # 'cancelMode', # omitted + # 'error', # omitted + # 'resourceChanges', # omitted + # 'changedInfo', # omitted + # 'changedExtConnectivity', # omitted + # 'modificationsTriggeredByVnfPkgChange', # omitted + # 'vnfSnapshotInfoId', # omitted + '_links' + ] + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.check_resp_body(body, expected_attrs) + + # 9. List VNF LCM operation occurrence + resp, body = self.list_lcmocc() + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + for lcmocc in body: + self.check_resp_body(lcmocc, expected_attrs) + + # 10. Terminate a VNF instance + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # wait a bit because there is a bit time lag between lcmocc DB + # update and terminate completion. + time.sleep(10) + + # check usageState of VNF Package + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] + self.assertEqual('IN_USE', usage_state) + + # check instantiationState of VNF + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.assertEqual(fields.VnfInstanceState.NOT_INSTANTIATED, + body['instantiationState']) + + # 11. Delete VNF instance + resp, body = self.delete_vnf_instance(inst_id) + self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) + + # check usageState of VNF Package + usage_state = self.get_vnf_package(self.vnf_pkg_3)['usageState'] + self.assertEqual('NOT_IN_USE', usage_state) + + # 12. Delete subscription + resp, body = self.delete_subscription(sub_id) + self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp)