From f2adf542e878c96895210f97ebf1ebb763b2f465 Mon Sep 17 00:00:00 2001 From: Michal Jagiello Date: Mon, 17 Oct 2022 12:46:49 +0000 Subject: Release ONAP SDK Issue-ID: INT-2150 Signed-off-by: Michal Jagiello Change-Id: I650047c599a5aae6de7c6b42d38e34aea88578e2 --- .coveragerc | 26 + .dockerignore | 134 ++ .gitignore | 139 ++ .gitlab-ci.yml | 145 ++ .pyup.yml | 37 + .pyup.yml.old | 37 + CHANGELOG.md | 12 + CODEOWNER | 10 + Dockerfile | 17 + LICENSE | 201 +++ MANIFEST.in | 1 + README.md | 119 ++ dev-requirements.txt | 11 + doc-requirements.txt | 4 + docs/Makefile | 20 + docs/_static/css/custom.css | 3 + docs/architecture.rst | 2 + docs/conf.py | 75 + docs/description.rst | 13 + docs/development.rst | 122 ++ docs/examples/e2e_artifact_upload.rst | 58 + .../e2e_basicvm_nomulticloud_instantiation.rst | 414 ++++++ docs/examples/e2e_closed_loop_instantiation.rst | 108 ++ docs/examples/e2e_net_instantiation.rst | 332 +++++ docs/examples/e2e_vfw_instantiation.rst | 429 ++++++ docs/examples/e2e_vfw_macro_instantiation.rst | 493 +++++++ docs/examples/examples.rst | 13 + docs/examples/k8s_plugin_usage.rst | 94 ++ docs/glossary.rst | 20 + docs/index.rst | 34 + docs/modules/modules.rst | 7 + docs/modules/onapsdk.aai.business.rst | 109 ++ docs/modules/onapsdk.aai.cloud_infrastructure.rst | 37 + docs/modules/onapsdk.aai.rst | 46 + docs/modules/onapsdk.cds.rst | 53 + docs/modules/onapsdk.clamp.rst | 29 + docs/modules/onapsdk.configuration.rst | 29 + docs/modules/onapsdk.cps.rst | 45 + docs/modules/onapsdk.dmaap.rst | 29 + docs/modules/onapsdk.msb.k8s.rst | 37 + docs/modules/onapsdk.msb.rst | 45 + docs/modules/onapsdk.nbi.rst | 21 + docs/modules/onapsdk.rst | 65 + docs/modules/onapsdk.sdc.rst | 101 ++ docs/modules/onapsdk.sdnc.rst | 29 + docs/modules/onapsdk.so.rst | 45 + docs/modules/onapsdk.utils.rst | 53 + docs/modules/onapsdk.ves.rst | 29 + docs/modules/onapsdk.vid.rst | 21 + docs/usage/installation.rst | 32 + docs/usage/intro.rst | 16 + docs/usage/usage.rst | 14 + docs/usage/usage/cds.rst | 173 +++ docs/usage/usage/cloud_configuration.rst | 210 +++ docs/usage/usage/cps.rst | 32 + docs/usage/usage/deletion.rst | 28 + docs/usage/usage/design_time.rst | 351 +++++ docs/usage/usage/dmaap.rst | 47 + docs/usage/usage/instantiation.rst | 485 +++++++ docs/usage/usage/ves.rst | 42 + integration_tests/__init__.py | 21 + integration_tests/docker-compose.yml | 53 + integration_tests/local_urls.py | 23 + integration_tests/tca_clampnode.yaml | 171 +++ integration_tests/test_01_vendor.py | 42 + integration_tests/test_02_vsp.py | 63 + integration_tests/test_03_vf.py | 90 ++ integration_tests/test_04_service.py | 122 ++ integration_tests/test_05_cloud_infrastructure.py | 88 ++ integration_tests/test_06_customer.py | 85 ++ integration_tests/test_07_instantiation.py | 427 ++++++ integration_tests/test_08_cds.py | 65 + integration_tests/test_09_clamp.py | 78 + integration_tests/test_10_msb_k8s.py | 125 ++ integration_tests/test_11_ves.py | 109 ++ integration_tests/test_12_dmaap.py | 31 + integration_tests/test_files/test_dd.json | 48 + .../test_files/test_vLB_CBA_Python.zip | Bin 0 -> 25559 bytes integration_tests/ubuntu16.zip | Bin 0 -> 1641 bytes integration_tests/urls.py | 23 + requirements.txt | 8 + scripts/build_all_branches_in.sh | 36 + setup.cfg | 53 + setup.py | 7 + src/onapsdk/__init__.py | 14 + src/onapsdk/aai/__init__.py | 14 + src/onapsdk/aai/aai_element.py | 192 +++ src/onapsdk/aai/bulk.py | 90 ++ src/onapsdk/aai/business/__init__.py | 27 + src/onapsdk/aai/business/customer.py | 603 ++++++++ src/onapsdk/aai/business/instance.py | 55 + src/onapsdk/aai/business/line_of_business.py | 123 ++ src/onapsdk/aai/business/network.py | 223 +++ src/onapsdk/aai/business/owning_entity.py | 154 ++ src/onapsdk/aai/business/platform.py | 123 ++ src/onapsdk/aai/business/pnf.py | 267 ++++ src/onapsdk/aai/business/project.py | 123 ++ src/onapsdk/aai/business/service.py | 484 +++++++ src/onapsdk/aai/business/sp_partner.py | 176 +++ src/onapsdk/aai/business/vf_module.py | 199 +++ src/onapsdk/aai/business/vnf.py | 536 +++++++ src/onapsdk/aai/cloud_infrastructure/__init__.py | 18 + .../aai/cloud_infrastructure/cloud_region.py | 621 ++++++++ src/onapsdk/aai/cloud_infrastructure/complex.py | 300 ++++ src/onapsdk/aai/cloud_infrastructure/geo_region.py | 191 +++ src/onapsdk/aai/cloud_infrastructure/tenant.py | 101 ++ src/onapsdk/aai/network/__init__.py | 16 + src/onapsdk/aai/network/site_resource.py | 244 ++++ src/onapsdk/aai/service_design_and_creation.py | 186 +++ .../aai/templates/aai_add_relationship.json.j2 | 11 + src/onapsdk/aai/templates/aai_bulk.json.j2 | 11 + .../templates/aai_line_of_business_create.json.j2 | 3 + .../aai/templates/aai_owning_entity_create.json.j2 | 4 + .../aai/templates/aai_platform_create.json.j2 | 3 + .../aai/templates/aai_project_create.json.j2 | 3 + .../aai/templates/aai_service_create.json.j2 | 4 + .../templates/aai_service_instance_create.json.j2 | 22 + .../aai/templates/aai_sp_partner_create.json.j2 | 21 + .../cloud_region_add_availability_zone.json.j2 | 7 + .../cloud_region_add_esr_system_info.json.j2 | 54 + .../aai/templates/cloud_region_add_tenant.json.j2 | 5 + .../aai/templates/cloud_region_create.json.j2 | 16 + src/onapsdk/aai/templates/complex_create.json.j2 | 23 + src/onapsdk/aai/templates/customer_create.json.j2 | 15 + .../customer_service_subscription_create.json.j2 | 3 + .../aai/templates/geo_region_create.json.j2 | 10 + .../aai/templates/site_resource_create.json.j2 | 16 + src/onapsdk/cds/README.md | 71 + src/onapsdk/cds/__init__.py | 18 + src/onapsdk/cds/blueprint.py | 830 +++++++++++ src/onapsdk/cds/blueprint_model.py | 222 +++ src/onapsdk/cds/blueprint_processor.py | 53 + src/onapsdk/cds/cds_element.py | 47 + src/onapsdk/cds/data_dictionary.py | 266 ++++ .../cds_blueprintprocessor_bootstrap.json.j2 | 5 + .../cds/templates/data_dictionary_base.json.j2 | 52 + .../templates/data_dictionary_source_rest.json.j2 | 13 + src/onapsdk/clamp/__init__.py | 14 + src/onapsdk/clamp/clamp_element.py | 79 ++ src/onapsdk/clamp/loop_instance.py | 349 +++++ src/onapsdk/clamp/schema_details.json | 138 ++ .../clamp/templates/clamp_MinMax_config.json.j2 | 94 ++ .../templates/clamp_add_drools_policy.json.j2 | 325 +++++ .../clamp/templates/clamp_add_frequency.json.j2 | 102 ++ .../clamp/templates/clamp_add_tca_config.json.j2 | 30 + src/onapsdk/configuration/__init__.py | 18 + src/onapsdk/configuration/global_settings.py | 71 + src/onapsdk/configuration/loader.py | 115 ++ src/onapsdk/constants.py | 61 + src/onapsdk/cps/__init__.py | 18 + src/onapsdk/cps/anchor.py | 193 +++ src/onapsdk/cps/cps_element.py | 24 + src/onapsdk/cps/dataspace.py | 193 +++ src/onapsdk/cps/schemaset.py | 73 + src/onapsdk/dmaap/__init__.py | 14 + src/onapsdk/dmaap/dmaap.py | 87 ++ src/onapsdk/dmaap/dmaap_service.py | 26 + src/onapsdk/exceptions.py | 109 ++ src/onapsdk/msb/__init__.py | 17 + src/onapsdk/msb/esr.py | 85 ++ src/onapsdk/msb/k8s/__init__.py | 17 + src/onapsdk/msb/k8s/connectivity_info.py | 105 ++ src/onapsdk/msb/k8s/definition.py | 424 ++++++ src/onapsdk/msb/k8s/instance.py | 190 +++ src/onapsdk/msb/msb_service.py | 24 + src/onapsdk/msb/multicloud.py | 55 + .../msb/templates/msb_esr_vim_registration.json.j2 | 31 + .../multicloud_k8s_add_connectivity_info.json.j2 | 8 + .../multicloud_k8s_add_definition.json.j2 | 7 + ...cloud_k8s_create_configuration_template.json.j2 | 4 + ...cloud_k8s_create_profile_for_definition.json.j2 | 8 + .../templates/multicloud_k8s_instantiate.json.j2 | 18 + src/onapsdk/nbi/__init__.py | 16 + src/onapsdk/nbi/nbi.py | 490 +++++++ .../nbi/templates/nbi_service_order_create.json.j2 | 28 + src/onapsdk/onap_service.py | 327 +++++ src/onapsdk/sdc/__init__.py | 486 +++++++ src/onapsdk/sdc/category_management.py | 285 ++++ src/onapsdk/sdc/component.py | 162 +++ src/onapsdk/sdc/pnf.py | 74 + src/onapsdk/sdc/properties.py | 202 +++ src/onapsdk/sdc/sdc_element.py | 227 +++ src/onapsdk/sdc/sdc_resource.py | 960 +++++++++++++ src/onapsdk/sdc/service.py | 932 ++++++++++++ .../sdc/templates/add_artifact_to_vf.json.j2 | 9 + .../sdc/templates/add_resource_to_service.json.j2 | 10 + .../sdc/templates/component_declare_input.json.j2 | 37 + src/onapsdk/sdc/templates/pnf_create.json.j2 | 29 + .../sdc/templates/sdc_element_action.json.j2 | 6 + .../sdc/templates/sdc_resource_action.json.j2 | 3 + .../sdc_resource_add_deployment_artifact.json.j2 | 8 + .../sdc/templates/sdc_resource_add_input.json.j2 | 39 + .../sdc_resource_add_nested_input.json.j2 | 35 + .../templates/sdc_resource_add_property.json.j2 | 17 + .../sdc/templates/sdc_resource_category.json.j2 | 13 + ...c_resource_component_set_property_value.json.j2 | 13 + .../sdc_resource_set_input_default_value.json.j2 | 8 + .../sdc_resource_set_property_value.json.j2 | 13 + src/onapsdk/sdc/templates/service_create.json.j2 | 29 + src/onapsdk/sdc/templates/vendor_create.json.j2 | 5 + src/onapsdk/sdc/templates/vf_create.json.j2 | 27 + src/onapsdk/sdc/templates/vf_vsp_update.json.j2 | 61 + src/onapsdk/sdc/templates/vsp_create.json.j2 | 11 + src/onapsdk/sdc/vendor.py | 108 ++ src/onapsdk/sdc/vf.py | 164 +++ src/onapsdk/sdc/vfc.py | 46 + src/onapsdk/sdc/vl.py | 46 + src/onapsdk/sdc/vsp.py | 370 +++++ src/onapsdk/sdnc/__init__.py | 15 + src/onapsdk/sdnc/preload.py | 148 ++ src/onapsdk/sdnc/sdnc_element.py | 48 + ...network_ala_carte_upload_preload_gr_api.json.j2 | 42 + ..._module_ala_carte_upload_preload_gr_api.json.j2 | 41 + src/onapsdk/so/__init__.py | 14 + src/onapsdk/so/deletion.py | 167 +++ src/onapsdk/so/instantiation.py | 957 +++++++++++++ src/onapsdk/so/so_db_adapter.py | 94 ++ src/onapsdk/so/so_element.py | 223 +++ .../add_cloud_site_with_identity_service.json.j2 | 22 + src/onapsdk/so/templates/deletion_network.json.j2 | 22 + src/onapsdk/so/templates/deletion_service.json.j2 | 26 + .../so/templates/deletion_vf_module.json.j2 | 27 + src/onapsdk/so/templates/deletion_vnf.json.j2 | 28 + .../instantiate_multi_vnf_service_macro.json.j2 | 121 ++ .../instantiate_network_ala_carte.json.j2 | 10 + .../instantiate_network_vnf_ala_carte_base.json.j2 | 44 + .../instantiate_service_ala_carte.json.j2 | 45 + .../so/templates/instantiate_service_macro.json.j2 | 173 +++ .../instantiate_vf_module_ala_carte.json.j2 | 66 + .../so/templates/instantiate_vnf_ala_carte.json.j2 | 18 + .../so/templates/instantiate_vnf_macro.json.j2 | 153 ++ .../templates/instantiate_vnf_macro_so_vnf.json.j2 | 151 ++ .../templates/service_instance_model_info.json.j2 | 7 + src/onapsdk/so/templates/vf_model_info.json.j2 | 15 + src/onapsdk/so/templates/vnf_model_info.json.j2 | 9 + src/onapsdk/utils/__init__.py | 40 + src/onapsdk/utils/configuration.py | 25 + src/onapsdk/utils/gui.py | 35 + src/onapsdk/utils/headers_creator.py | 245 ++++ src/onapsdk/utils/jinja.py | 50 + src/onapsdk/utils/mixins.py | 99 ++ src/onapsdk/utils/tosca_file_handler.py | 106 ++ src/onapsdk/version.py | 16 + src/onapsdk/ves/__init__.py | 14 + .../ves7_batch_with_stndDefined_valid.json.j2 | 109 ++ src/onapsdk/ves/templates/ves_stnd_event.json.j2 | 54 + .../ves/templates/ves_stnd_valid_event.json.j2 | 54 + src/onapsdk/ves/ves.py | 84 ++ src/onapsdk/ves/ves_service.py | 27 + src/onapsdk/vid/__init__.py | 16 + .../vid/templates/vid_declare_resource.json.j2 | 3 + src/onapsdk/vid/vid.py | 134 ++ test-requirements.txt | 7 + tests/__init__.py | 21 + tests/data/__init__.py | 13 + tests/data/bad.csar | 1 + tests/data/bad_no_service.csar | Bin 0 -> 60062 bytes tests/data/csar.meta | 2 + tests/data/service-Foo-template.yml | 1228 ++++++++++++++++ tests/data/service-TestPnfVsp-template.yml | 146 ++ tests/data/service-TestServiceFyx-template.yml | 646 +++++++++ tests/data/service-Ubuntu16-template.yml | 543 +++++++ tests/data/service-VfwcdsService-template.yml | 1439 +++++++++++++++++++ tests/data/test.csar | Bin 0 -> 58950 bytes tests/data/test_so_service_data.yaml | 35 + tests/data/tests_settings.py | 15 + tests/data/utils_load_json_file_test.json | 1 + tests/data/vLB_CBA_Python.zip | Bin 0 -> 25559 bytes tests/test_aai_bulk.py | 90 ++ tests/test_aai_cloud_region.py | 71 + tests/test_aai_complex.py | 138 ++ tests/test_aai_customer.py | 580 ++++++++ tests/test_aai_geo_region.py | 54 + tests/test_aai_line_of_business.py | 82 ++ tests/test_aai_network.py | 159 +++ tests/test_aai_owning_entity.py | 87 ++ tests/test_aai_platform.py | 82 ++ tests/test_aai_pnf.py | 140 ++ tests/test_aai_project.py | 82 ++ tests/test_aai_resource.py | 60 + tests/test_aai_service.py | 770 ++++++++++ tests/test_aai_service_instance.py | 275 ++++ tests/test_aai_service_subscription.py | 191 +++ tests/test_aai_site_resource.py | 57 + tests/test_aai_vf_module.py | 83 ++ tests/test_aai_vnf.py | 449 ++++++ tests/test_cds.py | 435 ++++++ tests/test_cds_blueprint_models.py | 201 +++ tests/test_clamp.py | 532 +++++++ tests/test_configuration.py | 24 + tests/test_cps.py | 240 ++++ tests/test_dmaap.py | 47 + tests/test_esr.py | 39 + tests/test_exceptions.py | 23 + tests/test_generic_instance.txt | 0 tests/test_gui.py | 55 + tests/test_headers_creator.py | 84 ++ tests/test_jinja.py | 26 + tests/test_msb_k8s.py | 378 +++++ tests/test_multicloud.py | 38 + tests/test_nbi.py | 563 ++++++++ tests/test_onap_service.py | 380 +++++ tests/test_pnf.py | 485 +++++++ tests/test_preload.py | 150 ++ tests/test_sdc_category_management.py | 349 +++++ tests/test_sdc_component.py | 44 + tests/test_sdc_element.py | 109 ++ tests/test_sdc_resource.py | 487 +++++++ tests/test_sdc_resource_properties.py | 1080 ++++++++++++++ tests/test_sdc_vfc.py | 82 ++ tests/test_sdc_vl.py | 82 ++ tests/test_sdnc_element.py | 26 + tests/test_service.py | 1498 ++++++++++++++++++++ tests/test_settings.py | 79 ++ tests/test_so_db_adapter.py | 136 ++ tests/test_so_deletion.py | 74 + tests/test_so_element.py | 28 + tests/test_so_instantiation.py | 1002 +++++++++++++ tests/test_so_orchestration_request.py | 103 ++ tests/test_sp_partner.py | 84 ++ tests/test_subnet.py | 49 + tests/test_tosca_file_handler.py | 88 ++ tests/test_utils.py | 46 + tests/test_vendor.py | 316 +++++ tests/test_version.py | 20 + tests/test_ves.py | 56 + tests/test_vf.py | 564 ++++++++ tests/test_vid.py | 54 + tests/test_vsp.py | 799 +++++++++++ tox.ini | 14 + upload-requirements.txt | 3 + 331 files changed, 44777 insertions(+) create mode 100644 .coveragerc create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .pyup.yml create mode 100644 .pyup.yml.old create mode 100644 CHANGELOG.md create mode 100644 CODEOWNER create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 dev-requirements.txt create mode 100644 doc-requirements.txt create mode 100644 docs/Makefile create mode 100644 docs/_static/css/custom.css create mode 100644 docs/architecture.rst create mode 100644 docs/conf.py create mode 100644 docs/description.rst create mode 100644 docs/development.rst create mode 100644 docs/examples/e2e_artifact_upload.rst create mode 100644 docs/examples/e2e_basicvm_nomulticloud_instantiation.rst create mode 100644 docs/examples/e2e_closed_loop_instantiation.rst create mode 100644 docs/examples/e2e_net_instantiation.rst create mode 100644 docs/examples/e2e_vfw_instantiation.rst create mode 100644 docs/examples/e2e_vfw_macro_instantiation.rst create mode 100644 docs/examples/examples.rst create mode 100644 docs/examples/k8s_plugin_usage.rst create mode 100644 docs/glossary.rst create mode 100644 docs/index.rst create mode 100644 docs/modules/modules.rst create mode 100644 docs/modules/onapsdk.aai.business.rst create mode 100644 docs/modules/onapsdk.aai.cloud_infrastructure.rst create mode 100644 docs/modules/onapsdk.aai.rst create mode 100644 docs/modules/onapsdk.cds.rst create mode 100644 docs/modules/onapsdk.clamp.rst create mode 100644 docs/modules/onapsdk.configuration.rst create mode 100644 docs/modules/onapsdk.cps.rst create mode 100644 docs/modules/onapsdk.dmaap.rst create mode 100644 docs/modules/onapsdk.msb.k8s.rst create mode 100644 docs/modules/onapsdk.msb.rst create mode 100644 docs/modules/onapsdk.nbi.rst create mode 100644 docs/modules/onapsdk.rst create mode 100644 docs/modules/onapsdk.sdc.rst create mode 100644 docs/modules/onapsdk.sdnc.rst create mode 100644 docs/modules/onapsdk.so.rst create mode 100644 docs/modules/onapsdk.utils.rst create mode 100644 docs/modules/onapsdk.ves.rst create mode 100644 docs/modules/onapsdk.vid.rst create mode 100644 docs/usage/installation.rst create mode 100644 docs/usage/intro.rst create mode 100644 docs/usage/usage.rst create mode 100644 docs/usage/usage/cds.rst create mode 100644 docs/usage/usage/cloud_configuration.rst create mode 100644 docs/usage/usage/cps.rst create mode 100644 docs/usage/usage/deletion.rst create mode 100644 docs/usage/usage/design_time.rst create mode 100644 docs/usage/usage/dmaap.rst create mode 100644 docs/usage/usage/instantiation.rst create mode 100644 docs/usage/usage/ves.rst create mode 100644 integration_tests/__init__.py create mode 100644 integration_tests/docker-compose.yml create mode 100644 integration_tests/local_urls.py create mode 100644 integration_tests/tca_clampnode.yaml create mode 100644 integration_tests/test_01_vendor.py create mode 100644 integration_tests/test_02_vsp.py create mode 100644 integration_tests/test_03_vf.py create mode 100644 integration_tests/test_04_service.py create mode 100644 integration_tests/test_05_cloud_infrastructure.py create mode 100644 integration_tests/test_06_customer.py create mode 100644 integration_tests/test_07_instantiation.py create mode 100644 integration_tests/test_08_cds.py create mode 100644 integration_tests/test_09_clamp.py create mode 100644 integration_tests/test_10_msb_k8s.py create mode 100644 integration_tests/test_11_ves.py create mode 100644 integration_tests/test_12_dmaap.py create mode 100644 integration_tests/test_files/test_dd.json create mode 100644 integration_tests/test_files/test_vLB_CBA_Python.zip create mode 100644 integration_tests/ubuntu16.zip create mode 100644 integration_tests/urls.py create mode 100644 requirements.txt create mode 100755 scripts/build_all_branches_in.sh create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 src/onapsdk/__init__.py create mode 100644 src/onapsdk/aai/__init__.py create mode 100644 src/onapsdk/aai/aai_element.py create mode 100644 src/onapsdk/aai/bulk.py create mode 100644 src/onapsdk/aai/business/__init__.py create mode 100644 src/onapsdk/aai/business/customer.py create mode 100644 src/onapsdk/aai/business/instance.py create mode 100644 src/onapsdk/aai/business/line_of_business.py create mode 100644 src/onapsdk/aai/business/network.py create mode 100644 src/onapsdk/aai/business/owning_entity.py create mode 100644 src/onapsdk/aai/business/platform.py create mode 100644 src/onapsdk/aai/business/pnf.py create mode 100644 src/onapsdk/aai/business/project.py create mode 100644 src/onapsdk/aai/business/service.py create mode 100644 src/onapsdk/aai/business/sp_partner.py create mode 100644 src/onapsdk/aai/business/vf_module.py create mode 100644 src/onapsdk/aai/business/vnf.py create mode 100644 src/onapsdk/aai/cloud_infrastructure/__init__.py create mode 100644 src/onapsdk/aai/cloud_infrastructure/cloud_region.py create mode 100644 src/onapsdk/aai/cloud_infrastructure/complex.py create mode 100644 src/onapsdk/aai/cloud_infrastructure/geo_region.py create mode 100644 src/onapsdk/aai/cloud_infrastructure/tenant.py create mode 100644 src/onapsdk/aai/network/__init__.py create mode 100644 src/onapsdk/aai/network/site_resource.py create mode 100644 src/onapsdk/aai/service_design_and_creation.py create mode 100644 src/onapsdk/aai/templates/aai_add_relationship.json.j2 create mode 100644 src/onapsdk/aai/templates/aai_bulk.json.j2 create mode 100644 src/onapsdk/aai/templates/aai_line_of_business_create.json.j2 create mode 100644 src/onapsdk/aai/templates/aai_owning_entity_create.json.j2 create mode 100644 src/onapsdk/aai/templates/aai_platform_create.json.j2 create mode 100644 src/onapsdk/aai/templates/aai_project_create.json.j2 create mode 100644 src/onapsdk/aai/templates/aai_service_create.json.j2 create mode 100644 src/onapsdk/aai/templates/aai_service_instance_create.json.j2 create mode 100644 src/onapsdk/aai/templates/aai_sp_partner_create.json.j2 create mode 100644 src/onapsdk/aai/templates/cloud_region_add_availability_zone.json.j2 create mode 100644 src/onapsdk/aai/templates/cloud_region_add_esr_system_info.json.j2 create mode 100644 src/onapsdk/aai/templates/cloud_region_add_tenant.json.j2 create mode 100644 src/onapsdk/aai/templates/cloud_region_create.json.j2 create mode 100644 src/onapsdk/aai/templates/complex_create.json.j2 create mode 100644 src/onapsdk/aai/templates/customer_create.json.j2 create mode 100644 src/onapsdk/aai/templates/customer_service_subscription_create.json.j2 create mode 100644 src/onapsdk/aai/templates/geo_region_create.json.j2 create mode 100644 src/onapsdk/aai/templates/site_resource_create.json.j2 create mode 100644 src/onapsdk/cds/README.md create mode 100644 src/onapsdk/cds/__init__.py create mode 100644 src/onapsdk/cds/blueprint.py create mode 100644 src/onapsdk/cds/blueprint_model.py create mode 100644 src/onapsdk/cds/blueprint_processor.py create mode 100644 src/onapsdk/cds/cds_element.py create mode 100644 src/onapsdk/cds/data_dictionary.py create mode 100644 src/onapsdk/cds/templates/cds_blueprintprocessor_bootstrap.json.j2 create mode 100644 src/onapsdk/cds/templates/data_dictionary_base.json.j2 create mode 100644 src/onapsdk/cds/templates/data_dictionary_source_rest.json.j2 create mode 100644 src/onapsdk/clamp/__init__.py create mode 100644 src/onapsdk/clamp/clamp_element.py create mode 100644 src/onapsdk/clamp/loop_instance.py create mode 100644 src/onapsdk/clamp/schema_details.json create mode 100644 src/onapsdk/clamp/templates/clamp_MinMax_config.json.j2 create mode 100644 src/onapsdk/clamp/templates/clamp_add_drools_policy.json.j2 create mode 100644 src/onapsdk/clamp/templates/clamp_add_frequency.json.j2 create mode 100644 src/onapsdk/clamp/templates/clamp_add_tca_config.json.j2 create mode 100644 src/onapsdk/configuration/__init__.py create mode 100644 src/onapsdk/configuration/global_settings.py create mode 100644 src/onapsdk/configuration/loader.py create mode 100644 src/onapsdk/constants.py create mode 100644 src/onapsdk/cps/__init__.py create mode 100644 src/onapsdk/cps/anchor.py create mode 100644 src/onapsdk/cps/cps_element.py create mode 100644 src/onapsdk/cps/dataspace.py create mode 100644 src/onapsdk/cps/schemaset.py create mode 100644 src/onapsdk/dmaap/__init__.py create mode 100644 src/onapsdk/dmaap/dmaap.py create mode 100644 src/onapsdk/dmaap/dmaap_service.py create mode 100644 src/onapsdk/exceptions.py create mode 100644 src/onapsdk/msb/__init__.py create mode 100644 src/onapsdk/msb/esr.py create mode 100644 src/onapsdk/msb/k8s/__init__.py create mode 100644 src/onapsdk/msb/k8s/connectivity_info.py create mode 100644 src/onapsdk/msb/k8s/definition.py create mode 100644 src/onapsdk/msb/k8s/instance.py create mode 100644 src/onapsdk/msb/msb_service.py create mode 100644 src/onapsdk/msb/multicloud.py create mode 100644 src/onapsdk/msb/templates/msb_esr_vim_registration.json.j2 create mode 100644 src/onapsdk/msb/templates/multicloud_k8s_add_connectivity_info.json.j2 create mode 100644 src/onapsdk/msb/templates/multicloud_k8s_add_definition.json.j2 create mode 100644 src/onapsdk/msb/templates/multicloud_k8s_create_configuration_template.json.j2 create mode 100644 src/onapsdk/msb/templates/multicloud_k8s_create_profile_for_definition.json.j2 create mode 100644 src/onapsdk/msb/templates/multicloud_k8s_instantiate.json.j2 create mode 100644 src/onapsdk/nbi/__init__.py create mode 100644 src/onapsdk/nbi/nbi.py create mode 100644 src/onapsdk/nbi/templates/nbi_service_order_create.json.j2 create mode 100644 src/onapsdk/onap_service.py create mode 100644 src/onapsdk/sdc/__init__.py create mode 100644 src/onapsdk/sdc/category_management.py create mode 100644 src/onapsdk/sdc/component.py create mode 100644 src/onapsdk/sdc/pnf.py create mode 100644 src/onapsdk/sdc/properties.py create mode 100644 src/onapsdk/sdc/sdc_element.py create mode 100644 src/onapsdk/sdc/sdc_resource.py create mode 100644 src/onapsdk/sdc/service.py create mode 100644 src/onapsdk/sdc/templates/add_artifact_to_vf.json.j2 create mode 100644 src/onapsdk/sdc/templates/add_resource_to_service.json.j2 create mode 100644 src/onapsdk/sdc/templates/component_declare_input.json.j2 create mode 100644 src/onapsdk/sdc/templates/pnf_create.json.j2 create mode 100644 src/onapsdk/sdc/templates/sdc_element_action.json.j2 create mode 100644 src/onapsdk/sdc/templates/sdc_resource_action.json.j2 create mode 100644 src/onapsdk/sdc/templates/sdc_resource_add_deployment_artifact.json.j2 create mode 100644 src/onapsdk/sdc/templates/sdc_resource_add_input.json.j2 create mode 100644 src/onapsdk/sdc/templates/sdc_resource_add_nested_input.json.j2 create mode 100644 src/onapsdk/sdc/templates/sdc_resource_add_property.json.j2 create mode 100644 src/onapsdk/sdc/templates/sdc_resource_category.json.j2 create mode 100644 src/onapsdk/sdc/templates/sdc_resource_component_set_property_value.json.j2 create mode 100644 src/onapsdk/sdc/templates/sdc_resource_set_input_default_value.json.j2 create mode 100644 src/onapsdk/sdc/templates/sdc_resource_set_property_value.json.j2 create mode 100644 src/onapsdk/sdc/templates/service_create.json.j2 create mode 100644 src/onapsdk/sdc/templates/vendor_create.json.j2 create mode 100644 src/onapsdk/sdc/templates/vf_create.json.j2 create mode 100644 src/onapsdk/sdc/templates/vf_vsp_update.json.j2 create mode 100644 src/onapsdk/sdc/templates/vsp_create.json.j2 create mode 100644 src/onapsdk/sdc/vendor.py create mode 100644 src/onapsdk/sdc/vf.py create mode 100644 src/onapsdk/sdc/vfc.py create mode 100644 src/onapsdk/sdc/vl.py create mode 100644 src/onapsdk/sdc/vsp.py create mode 100644 src/onapsdk/sdnc/__init__.py create mode 100644 src/onapsdk/sdnc/preload.py create mode 100644 src/onapsdk/sdnc/sdnc_element.py create mode 100644 src/onapsdk/sdnc/templates/instantiate_network_ala_carte_upload_preload_gr_api.json.j2 create mode 100644 src/onapsdk/sdnc/templates/instantiate_vf_module_ala_carte_upload_preload_gr_api.json.j2 create mode 100644 src/onapsdk/so/__init__.py create mode 100644 src/onapsdk/so/deletion.py create mode 100644 src/onapsdk/so/instantiation.py create mode 100644 src/onapsdk/so/so_db_adapter.py create mode 100644 src/onapsdk/so/so_element.py create mode 100644 src/onapsdk/so/templates/add_cloud_site_with_identity_service.json.j2 create mode 100644 src/onapsdk/so/templates/deletion_network.json.j2 create mode 100644 src/onapsdk/so/templates/deletion_service.json.j2 create mode 100644 src/onapsdk/so/templates/deletion_vf_module.json.j2 create mode 100644 src/onapsdk/so/templates/deletion_vnf.json.j2 create mode 100644 src/onapsdk/so/templates/instantiate_multi_vnf_service_macro.json.j2 create mode 100644 src/onapsdk/so/templates/instantiate_network_ala_carte.json.j2 create mode 100644 src/onapsdk/so/templates/instantiate_network_vnf_ala_carte_base.json.j2 create mode 100644 src/onapsdk/so/templates/instantiate_service_ala_carte.json.j2 create mode 100644 src/onapsdk/so/templates/instantiate_service_macro.json.j2 create mode 100644 src/onapsdk/so/templates/instantiate_vf_module_ala_carte.json.j2 create mode 100644 src/onapsdk/so/templates/instantiate_vnf_ala_carte.json.j2 create mode 100644 src/onapsdk/so/templates/instantiate_vnf_macro.json.j2 create mode 100644 src/onapsdk/so/templates/instantiate_vnf_macro_so_vnf.json.j2 create mode 100644 src/onapsdk/so/templates/service_instance_model_info.json.j2 create mode 100644 src/onapsdk/so/templates/vf_model_info.json.j2 create mode 100644 src/onapsdk/so/templates/vnf_model_info.json.j2 create mode 100644 src/onapsdk/utils/__init__.py create mode 100644 src/onapsdk/utils/configuration.py create mode 100644 src/onapsdk/utils/gui.py create mode 100644 src/onapsdk/utils/headers_creator.py create mode 100644 src/onapsdk/utils/jinja.py create mode 100644 src/onapsdk/utils/mixins.py create mode 100644 src/onapsdk/utils/tosca_file_handler.py create mode 100644 src/onapsdk/version.py create mode 100644 src/onapsdk/ves/__init__.py create mode 100644 src/onapsdk/ves/templates/ves7_batch_with_stndDefined_valid.json.j2 create mode 100644 src/onapsdk/ves/templates/ves_stnd_event.json.j2 create mode 100644 src/onapsdk/ves/templates/ves_stnd_valid_event.json.j2 create mode 100644 src/onapsdk/ves/ves.py create mode 100644 src/onapsdk/ves/ves_service.py create mode 100644 src/onapsdk/vid/__init__.py create mode 100644 src/onapsdk/vid/templates/vid_declare_resource.json.j2 create mode 100644 src/onapsdk/vid/vid.py create mode 100644 test-requirements.txt create mode 100644 tests/__init__.py create mode 100644 tests/data/__init__.py create mode 100644 tests/data/bad.csar create mode 100644 tests/data/bad_no_service.csar create mode 100644 tests/data/csar.meta create mode 100644 tests/data/service-Foo-template.yml create mode 100644 tests/data/service-TestPnfVsp-template.yml create mode 100644 tests/data/service-TestServiceFyx-template.yml create mode 100644 tests/data/service-Ubuntu16-template.yml create mode 100644 tests/data/service-VfwcdsService-template.yml create mode 100644 tests/data/test.csar create mode 100644 tests/data/test_so_service_data.yaml create mode 100644 tests/data/tests_settings.py create mode 100644 tests/data/utils_load_json_file_test.json create mode 100755 tests/data/vLB_CBA_Python.zip create mode 100644 tests/test_aai_bulk.py create mode 100644 tests/test_aai_cloud_region.py create mode 100644 tests/test_aai_complex.py create mode 100644 tests/test_aai_customer.py create mode 100644 tests/test_aai_geo_region.py create mode 100644 tests/test_aai_line_of_business.py create mode 100644 tests/test_aai_network.py create mode 100644 tests/test_aai_owning_entity.py create mode 100644 tests/test_aai_platform.py create mode 100644 tests/test_aai_pnf.py create mode 100644 tests/test_aai_project.py create mode 100644 tests/test_aai_resource.py create mode 100644 tests/test_aai_service.py create mode 100644 tests/test_aai_service_instance.py create mode 100644 tests/test_aai_service_subscription.py create mode 100644 tests/test_aai_site_resource.py create mode 100644 tests/test_aai_vf_module.py create mode 100644 tests/test_aai_vnf.py create mode 100644 tests/test_cds.py create mode 100644 tests/test_cds_blueprint_models.py create mode 100644 tests/test_clamp.py create mode 100644 tests/test_configuration.py create mode 100644 tests/test_cps.py create mode 100644 tests/test_dmaap.py create mode 100644 tests/test_esr.py create mode 100644 tests/test_exceptions.py create mode 100644 tests/test_generic_instance.txt create mode 100644 tests/test_gui.py create mode 100644 tests/test_headers_creator.py create mode 100644 tests/test_jinja.py create mode 100644 tests/test_msb_k8s.py create mode 100644 tests/test_multicloud.py create mode 100644 tests/test_nbi.py create mode 100644 tests/test_onap_service.py create mode 100644 tests/test_pnf.py create mode 100644 tests/test_preload.py create mode 100644 tests/test_sdc_category_management.py create mode 100644 tests/test_sdc_component.py create mode 100644 tests/test_sdc_element.py create mode 100644 tests/test_sdc_resource.py create mode 100644 tests/test_sdc_resource_properties.py create mode 100644 tests/test_sdc_vfc.py create mode 100644 tests/test_sdc_vl.py create mode 100644 tests/test_sdnc_element.py create mode 100755 tests/test_service.py create mode 100644 tests/test_settings.py create mode 100644 tests/test_so_db_adapter.py create mode 100644 tests/test_so_deletion.py create mode 100644 tests/test_so_element.py create mode 100644 tests/test_so_instantiation.py create mode 100644 tests/test_so_orchestration_request.py create mode 100644 tests/test_sp_partner.py create mode 100644 tests/test_subnet.py create mode 100644 tests/test_tosca_file_handler.py create mode 100644 tests/test_utils.py create mode 100644 tests/test_vendor.py create mode 100644 tests/test_version.py create mode 100644 tests/test_ves.py create mode 100644 tests/test_vf.py create mode 100644 tests/test_vid.py create mode 100644 tests/test_vsp.py create mode 100644 tox.ini create mode 100644 upload-requirements.txt diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..42c57d4 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,26 @@ +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: + +show_missing = True + +[html] +directory = coverage + +[xml] +output = pytest-coverage.xml + diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9a2ed9a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,134 @@ +## VSCode +.vscode/ +## Python +# tests as they're not needed +tests/ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +coverage/ +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ +pytest-coverage.xml + +# tests +pytest-unit.xml +pytest.xml +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don’t work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8afe550 --- /dev/null +++ b/.gitignore @@ -0,0 +1,139 @@ +## VSCode +.vscode/ + +## IntelliJ +.idea/ + +## Python + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +coverage/ +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ +pytest-coverage.xml + +# tests +pytest-unit.xml +pytest-integration.xml +pytest.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don’t work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..5ed14e3 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,145 @@ +--- + stages: + - linting + - unit_test + - build + - test + - deploy + + image: docker:git + services: + - docker:dind + variables: + DOCKER_DRIVER: overlay + # Variables for pytest.gitlab-ci.yml + PYTHON_VERSIONS: "v3.7 v3.8 v3.9 v3.10" + COVERAGE_FILE: sdk-tests-cov.xml + # Variables for Container-Scanning.gitlab-ci.yml + CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE #/$CI_COMMIT_REF_SLUG + CI_APPLICATION_TAG: $CI_COMMIT_REF_SLUG #$CI_COMMIT_SHA + # Variable for pylint/pydocstyle/SAST/Code-Quality.gitlab-ci.yml + SRC_PATH: '/src' + DOC_PATH: '/docs' + # Variable for SAST + SAST_EXCLUDED_PATHS: "docs,integration_tests,scripts,tests" + SAST_BANDIT_EXCLUDED_PATHS: "docs,integration_tests,scripts,tests" + + .before_script_docker: &before_script_docker + before_script: + - docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY" + + build_master: + stage: build + <<: *before_script_docker + script: + - docker build -t "$CI_REGISTRY_IMAGE:latest" . + - docker push "$CI_REGISTRY_IMAGE:latest" + rules: + - if: '$CI_COMMIT_BRANCH == "master"' + + build_testing: + stage: build + <<: *before_script_docker + script: + - docker build -t "$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}" . + - docker push "$CI_REGISTRY_IMAGE:${CI_COMMIT_REF_SLUG}" + rules: + - if: '$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != "master"' + + build_stable: + stage: build + <<: *before_script_docker + script: + - docker build -t "$CI_REGISTRY_IMAGE:${CI_COMMIT_TAG}" . + - docker push "$CI_REGISTRY_IMAGE:${CI_COMMIT_TAG}" + rules: + - if: '$CI_COMMIT_TAG' + + .integration_tests: &integration_tests + stage: test + variables: + FF_NETWORK_PER_BUILD: 1 # Enable https://docs.gitlab.com/runner/executors/docker.html#network-per-build feature + services: + - name: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-sdc:develop + alias: sdc.api.fe.simpledemo.onap.org + - name: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-sdnc:latest + alias: sdnc.api.simpledemo.onap.org + - name: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-so:latest + alias: so.api.simpledemo.onap.org + - name: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-aai:latest + alias: aai.api.sparky.simpledemo.onap.org + - name: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-clamp:develop + alias: clamp.api.simpledemo.onap.org + - name: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-cds:latest + alias: cds.api.simpledemo.onap.org + - name: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-msb-k8s:latest + alias: msb.k8s.api.simpledemo.onap.org + - name: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-dcae/mock-ves:latest + alias: ves.api.simpledemo.onap.org + - name: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-dmaap:latest + alias: dmaap.api.simpledemo.onap.org + script: + - pip install . + - pip install pytest mock # mock is needed as pytest parse all files before selection + - PYTHONPATH=$PYTHONPATH:integration_tests/ ONAP_PYTHON_SDK_SETTINGS="urls" pytest --verbose -c /dev/null --junitxml=pytest-integration.xml integration_tests + artifacts: + reports: + junit: pytest-*.xml + + integration_tests:3.7: + image: python:3.7 + <<: *integration_tests + + integration_tests:3.8: + image: python:3.8 + <<: *integration_tests + + integration_tests:3.9: + image: python:3.9 + <<: *integration_tests + + integration_tests:3.10: + image: python:3.10 + <<: *integration_tests + + pages: + stage: deploy + image: + name: python:3.7 + script: + - chmod +x scripts/build_all_branches_in.sh + - scripts/build_all_branches_in.sh + artifacts: + paths: + - public + except: + variables: + - $JOBS_DISABLED + + upload: + stage: deploy + image: + name: python:3.8 + script: + - pip install -r upload-requirements.txt + - python setup.py sdist bdist_wheel + - twine upload --non-interactive dist/* + rules: + - if: '$CI_COMMIT_TAG' + + # https://docs.gitlab.com/ee/update/deprecations.html#dependency-scanning-python-39-and-36-image-deprecation + gemnasium-python-dependency_scanning: + image: + name: registry.gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python:2-python-3.9 + + include: + - remote: 'https://gitlab.com/Orange-OpenSource/lfn/ci_cd/gitlab-ci-templates/raw/master/pylint.gitlab-ci.yml' + - remote: 'https://gitlab.com/Orange-OpenSource/lfn/ci_cd/gitlab-ci-templates/-/raw/master/pytest.gitlab-ci.yml' + - remote: 'https://gitlab.com/Orange-OpenSource/lfn/ci_cd/gitlab-ci-templates/raw/master/pydocstyle.gitlab-ci.yml' + - remote: 'https://gitlab.com/Orange-OpenSource/lfn/ci_cd/gitlab-ci-templates/raw/master/doc8.gitlab-ci.yml' + - remote: 'https://gitlab.com/Orange-OpenSource/lfn/ci_cd/gitlab-ci-templates/raw/master/pyup.gitlab-ci.yml' + - template: License-Scanning.gitlab-ci.yml + - template: Dependency-Scanning.gitlab-ci.yml + - template: Jobs/Code-Quality.gitlab-ci.yml + - template: SAST.gitlab-ci.yml + - template: Container-Scanning.gitlab-ci.yml diff --git a/.pyup.yml b/.pyup.yml new file mode 100644 index 0000000..b4adf18 --- /dev/null +++ b/.pyup.yml @@ -0,0 +1,37 @@ +# configure updates globally +# default: all +# allowed: all, insecure, False +update: all + +# configure dependency pinning globally +# default: True +# allowed: True, False +pin: True + +# set the default branch +# default: empty, the default branch on GitHub +branch: develop + +# update schedule +# default: empty +# allowed: "every day", "every week", .. +schedule: "every day" + +# search for requirement files +# default: True +# allowed: True, False +search: True + +# add a label to pull requests, default is not set +# requires private repo permissions, even on public repos +# default: empty +label_prs: update + +# assign users to pull requests, default is not set +# requires private repo permissions, even on public repos +# default: empty +assignees: + - sylvainOL + +gitlab: + should_remove_source_branch: True diff --git a/.pyup.yml.old b/.pyup.yml.old new file mode 100644 index 0000000..b4adf18 --- /dev/null +++ b/.pyup.yml.old @@ -0,0 +1,37 @@ +# configure updates globally +# default: all +# allowed: all, insecure, False +update: all + +# configure dependency pinning globally +# default: True +# allowed: True, False +pin: True + +# set the default branch +# default: empty, the default branch on GitHub +branch: develop + +# update schedule +# default: empty +# allowed: "every day", "every week", .. +schedule: "every day" + +# search for requirement files +# default: True +# allowed: True, False +search: True + +# add a label to pull requests, default is not set +# requires private repo permissions, even on public repos +# default: empty +label_prs: update + +# assign users to pull requests, default is not set +# requires private repo permissions, even on public repos +# default: empty +assignees: + - sylvainOL + +gitlab: + should_remove_source_branch: True diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..bc392e5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Python ONAP SDK Changelog + +## v1.0 + +[Documentation](https://readthedocs.org/dashboard/python-onapsdk/version/v1.0) + +Main new features: + +- Onboard a simple service via SDC +- Instantiate a simple service via SO using GR API +- Instantiate a simple service via NBI +- create business objects in AAI diff --git a/CODEOWNER b/CODEOWNER new file mode 100644 index 0000000..82ab28a --- /dev/null +++ b/CODEOWNER @@ -0,0 +1,10 @@ +# Rules defined later in the file take precedence over the rules +# defined before. + +# For everything except doc +* @davidblaisonneau-orange @Rene_ROBERT @morganrOL @sylvainOL @jardellos + +# For doc +# This will match all files for which the file name ends in `.rst` +*.rst @eric_debeau +/docs/ @eric_debeau diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..43a897b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.9-alpine3.12 + +ARG PIP_TAG=21.2.4 + +WORKDIR /opt/chained-ci-mqtt-trigger-master + +COPY . . + +ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 + +RUN apk add --no-cache --virtual .build-deps gcc \ + musl-dev \ + libffi-dev \ + openssl-dev && \ + pip install --no-cache-dir --upgrade pip==$PIP_TAG && \ + pip install --no-cache-dir . && \ + apk del .build-deps diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5b656e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Orange-OpenSource / lfn / onap + + 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. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..117c0c7 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include src *.j2 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0eb8fd8 --- /dev/null +++ b/README.md @@ -0,0 +1,119 @@ +# Python ONAP SDK + +an SDK to use ONAP programmatically with Python code + +[![Maintainability](https://api.codeclimate.com/v1/badges/858bb5b1aed4b42da2d2/maintainability)](https://codeclimate.com/github/Orange-OpenSource/python-onapsdk/maintainability) +[![Code Coverage](https://gitlab.com/Orange-OpenSource/lfn/onap/python-onapsdk/badges/master/coverage.svg)](https://gitlab.com/Orange-OpenSource/lfn/onap/python-onapsdk/) +[![Documentation Status](https://readthedocs.org/projects/python-onapsdk/badge/?version=latest)](https://python-onapsdk.readthedocs.io/en/latest/?badge=latest) + +## Description + +ONAP SDK is a client library written in Python for building applications to work with ONAP. The project aims to provide a consistent and complete set of interactions with ONAP’s many services, along with complete documentation, examples, and tools. + +Using few python commands, you should be able to onboard, distribute models, instantiate xNFs and many others. Check [doc](https://python-onapsdk.readthedocs.io/en/latest/index.html) site to find out all the features. + +## Installation + +You can install it using `pip` tool + +``` +$ pip install onapsdk +``` + +## Development + +Before you start, ensure you have Python installation in version 3.7 or higher. +Please see [the official Python documentation](https://docs.python.org/3/using/index.html) +in case you have to upgrade or install certain Python version. + +### Setting up development environment + +Clone the project. Inside the project folder create a new virtual environment and activate +it: + +``` +$ python -m venv env +$ source env/bin/activate +``` +On Windows, activate by executing the following: + +``` +$ .\env\Scripts\activate +``` + +When your virtual environment is ready, install required dependencies: + +``` +$ pip install -r requirements.txt +``` + +### Developing + +To use library functions directly from the source code, execute the following +to point to the source folder in *PYTHONPATH* variable and run the interpreter: + + +``` +$ PYTHONPATH=$PYTHONPATH:src/ python +``` + +On Windows: + +``` +$ $env:PYTHONPATH='src\';python +``` + +Verify that packages are accessible: + +``` +>>> import onapsdk +``` +You can then start working with library functions as needed. + +### Testing + +Install [tox](https://tox.readthedocs.io/en/latest/index.html): + +``` +$ pip install tox +``` + +To run all unit test, lint and docstyle checks, inside the project folder simply +execute *tox*: + +``` +$ tox +``` + +Please note that the above runs unit tests on all major versions of Python available on your +OS (3.7, 3.8, 3.9). To limit execution to only specific version of Python interpreter, +use the following example: + +``` +$ tox -e py37 +``` + +### Integration testing + +It is possible to run integration tests using [mock-servers](https://gitlab.com/Orange-OpenSource/lfn/onap/mock_servers) +project. +Make sure Docker Compose is available on your system. Install required dependencies: +``` +$ pip install pytest mock +``` + +Go to *integration_tests/* directory and execute: +``` +$ docker-compose up +``` +Please note that *docker-compose* attempts to create subnet 172.20.0.0/24, so it can not be run if the scope is already allocated. +Also, containers are not reachable by their IP addresses on Windows host since +Docker for Windows [does not support](https://docs.docker.com/docker-for-windows/networking/#known-limitations-use-cases-and-workarounds) +bridged network interface for Linux containers. + +Once containers are running, execute the following in the project's directory: +``` +$ PYTHONPATH=$PYTHONPATH:integration_tests/:src/ ONAP_PYTHON_SDK_SETTINGS="local_urls" pytest -c /dev/null --verbose --junitxml=pytest-integration.xml integration_tests +``` + +Please make sure all the test are passing before creating merge request. \ No newline at end of file diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..8e6ca20 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,11 @@ +requests==2.24.0 +jinja2==2.11.3 +simplejson==3.17.2 +mock==4.0.2 +pytest==6.1.1 +pytest-cov==2.10.1 +pytest-mock==3.3.1 +yapf==0.30.0 +coverage==5.3 +pyOpenSSL==19.1.0 +jsonschema==3.2.0 diff --git a/doc-requirements.txt b/doc-requirements.txt new file mode 100644 index 0000000..c33ef9c --- /dev/null +++ b/doc-requirements.txt @@ -0,0 +1,4 @@ +sphinx==3.2.1 +sphinx_rtd_theme==0.5.0 +sphinx-autodoc-typehints==1.11.1 +Pygments==2.7.4 diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..733775b --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# SPDX-License-Identifier: Apache-2.0 +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css new file mode 100644 index 0000000..b07bdb1 --- /dev/null +++ b/docs/_static/css/custom.css @@ -0,0 +1,3 @@ +.wy-nav-content { + max-width: none; +} diff --git a/docs/architecture.rst b/docs/architecture.rst new file mode 100644 index 0000000..ece5cb2 --- /dev/null +++ b/docs/architecture.rst @@ -0,0 +1,2 @@ +Architecture +############ diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..b6dd084 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: Apache-2.0 +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('../src/onapsdk')) + + +# -- Project information ----------------------------------------------------- + +project = 'ONAP SDK' +copyright = '2019, Sylvain Desbureaux' +author = 'Sylvain Desbureaux' + +# The full version, including alpha/beta/rc tags +package_version = {} +with open("../src/onapsdk/version.py") as fp: + exec(fp.read(), package_version) +release = package_version['__version__'] + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', + 'sphinx_autodoc_typehints' +] + +intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} + +# These folders are copied to the documentation's HTML output +html_static_path = ['_static'] + +# These paths are either relative to html_static_path +# or fully qualified paths (eg. https://...) +html_css_files = [ + 'css/custom.css', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] +master_doc = 'index' diff --git a/docs/description.rst b/docs/description.rst new file mode 100644 index 0000000..39cf761 --- /dev/null +++ b/docs/description.rst @@ -0,0 +1,13 @@ +Description +########### + +ONAP SDK is a client library written in Python for building applications to +work with ONAP. The project aims to provide a consistent and complete set of +interactions with ONAP’s many services, along with complete documentation, +examples, and tools. + +Using few python commands, you should be able to onboard, distribute models and +instantiate xNFs. + +First beta release deals with ONAP "Legacy" APIs but new +APIs, CDS and policy integration is planned for next releases. diff --git a/docs/development.rst b/docs/development.rst new file mode 100644 index 0000000..d1029b3 --- /dev/null +++ b/docs/development.rst @@ -0,0 +1,122 @@ +Development +############ + + + +Setting up development environment +---------------------------------- +Before you start, ensure you have Python installation in version 3.7 or higher. +Please see the official Python documentation_ in case you have to upgrade or install +certain Python version. + +.. _documentation: https://docs.python.org/3/using/index.html + +Clone the project. Inside the project folder create a new virtual environment and activate +it: + +.. code:: shell + + $ python -m venv env + $ source env/bin/activate + +On Windows, activate by executing the following: + +.. code:: powershell + + $ .\env\Scripts\activate + +When your virtual environment is ready, install required dependencies: + +.. code:: shell + + $ pip install -r requirements.txt + +Developing +---------- + +To use library functions directly from the source code, execute the following +to point to the source folder in *PYTHONPATH* variable and run the interpreter: + + +.. code:: shell + + $ PYTHONPATH=$PYTHONPATH:src/ python + + +On Windows: + +.. code:: powershell + + $ $env:PYTHONPATH='src\';python + +Verify that packages are accessible: + +.. code:: python + + >>> import onapsdk + +You can then start working with library functions as needed. + +New ONAP component package +-------------------------- + +When you create a new ONAP component package and wants to use Jinja templates you need to create `templates` directory +to store them in a newly created package. Furthermore you need to add a `PackageLoader` in `utils.jinja` module. + +Testing +------- + +Install tox: + +.. code:: shell + + $ pip install tox + +To run all unit test, lint and docstyle checks, inside the project folder simply +execute *tox*: + +.. code:: shell + + $ tox + +Please note that the above runs unit tests on all major versions of Python available on your +OS (3.7, 3.8, 3.9). To limit execution to only specific version of Python Interpreter, +use the following example: + +.. code:: shell + + $ tox -e py37 + +Integration testing +------------------- + +It is possible to run integration tests using mock-servers_ project. + +.. _mock-servers: https://gitlab.com/Orange-OpenSource/lfn/onap/mock_servers + +Make sure Docker Compose is available on your system. Install required dependencies: + +.. code:: shell + + $ pip install pytest mock + +Go to *integration_tests/* directory and execute: + +.. code:: shell + + $ docker-compose up + +Please note that *docker-compose* attempts to create subnet 172.20.0.0/24, so it can not be run if the scope is already allocated. +Also, containers are not reachable by their IP addresses on Windows host since +Docker for Windows does not support bridged network interface for Linux containers. +For reference, please see Docker docs_. + +.. _docs: https://docs.docker.com/docker-for-windows/networking/#known-limitations-use-cases-and-workarounds + +Once containers are running, execute the following in the project's directory: + +.. code:: shell + + $ PYTHONPATH=$PYTHONPATH:integration_tests/:src/ ONAP_PYTHON_SDK_SETTINGS="local_urls" pytest -c /dev/null --verbose --junitxml=pytest-integration.xml integration_tests + +Please make sure all the test are passing before creating merge request. \ No newline at end of file diff --git a/docs/examples/e2e_artifact_upload.rst b/docs/examples/e2e_artifact_upload.rst new file mode 100644 index 0000000..aea4d21 --- /dev/null +++ b/docs/examples/e2e_artifact_upload.rst @@ -0,0 +1,58 @@ +E2E Upload of an artifact +##################################### + + +.. code:: Python + + import os + import logging + + from onapsdk.sdc.vsp import Vsp + from onapsdk.sdc.vf import Vf + from onapsdk.sdc.service import Service + + logger = logging.getLogger("") + logger.setLevel(logging.INFO) + fh = logging.StreamHandler() + fh_formatter = logging.Formatter('%(asctime)s %(levelname)s %(lineno)d:%(filename)s(%(process)d) - %(message)s') + fh.setFormatter(fh_formatter) + logger.addHandler(fh) + + + + # Create required A&AI resources + VF_NAME = "my_VF" + SERVICENAME = "artifact_SERVICE" + + ARTIFACT_NAME = "clampnode" + ARTIFACT_TYPE = "DCAE_INVENTORY_BLUEPRINT" + ARTIFACT_FILE_PATH = "{os.path.dirname(os.path.abspath(__file__))}/my_ArtifactFile.yaml" + + + logger.info("*******************************") + logger.info("******** SERVICE DESIGN *******") + logger.info("*******************************") + + logger.info("******** Get VF *******") + vf = Vf(VF_NAME) + vf.onboard() + + logger.info("******** Create Service *******") + svc = Service(name=SERVICENAME) + svc.create() + svc.add_resource(vf) + + logger.info("******** Extract Artifact Data *******") + data = open(ARTIFACT_FILE_PATH,'rb').read() + + logger.info("******** Upload Artifact *******") + svc.add_artifact_to_vf(vnf_name=VF_NAME, + artifact_type=ARTIFACT_TYPE, + artifact_name=ARTIFACT_NAME, + artifact=data) + + logger.info("******** Distribute Service *******") + svc.checkin() + svc.certify() + svc.distribute() + diff --git a/docs/examples/e2e_basicvm_nomulticloud_instantiation.rst b/docs/examples/e2e_basicvm_nomulticloud_instantiation.rst new file mode 100644 index 0000000..37f3c31 --- /dev/null +++ b/docs/examples/e2e_basicvm_nomulticloud_instantiation.rst @@ -0,0 +1,414 @@ +E2E Instantiation of a simple VM without muticloud +################################################## + + +.. code:: Python + + import logging + import time + from uuid import uuid4 + from onapsdk.aai.aai_element import AaiElement + from onapsdk.aai.cloud_infrastructure import ( + CloudRegion, + Complex, + Tenant + ) + from onapsdk.aai.service_design_and_creation import ( + Service as AaiService + ) + from onapsdk.aai.business import ( + ServiceInstance, + VnfInstance, + VfModuleInstance, + ServiceSubscription, + Customer, + OwningEntity as AaiOwningEntity + ) + from onapsdk.so.instantiation import ( + ServiceInstantiation, + VnfInstantiation, + VnfParameter + ) + from onapsdk.sdc import SDC + from onapsdk.sdc.vendor import Vendor + from onapsdk.sdc.vsp import Vsp + from onapsdk.sdc.vf import Vf + from onapsdk.sdc.service import Service + import onapsdk.constants as const + import os + from onapsdk.vid import LineOfBusiness, OwningEntity, Platform, Project + + logger = logging.getLogger("") + logger.setLevel(logging.DEBUG) + fh = logging.StreamHandler() + fh_formatter = logging.Formatter('%(asctime)s %(levelname)s %(lineno)d:%(filename)s(%(process)d) - %(message)s') + fh.setFormatter(fh_formatter) + logger.addHandler(fh) + + # Required A&AI resources + VSPNAME = "ubuntu16_VSP" + VFNAME = "ubuntu16_VF" + SERVICENAME = "ubuntu16_SERVICE" + + # FULLY CUSTOMIZABLE VALUES + # ************************* + VENDOR = "" # FILL ME + GLOBAL_CUSTOMER_ID = "" # FILL ME + SERVICE_DELETION = True # True|False + + COMPLEX_PHYSICAL_LOCATION_ID = "" # FILL ME + COMPLEX_DATA_CENTER_CODE = "" # FILL ME + + CLOUD_OWNER = "" # FILL ME + + OWNING_ENTITY = "" # FILL ME + PROJECT = "" # FILL ME + PLATFORM = "" # FILL ME + LINE_OF_BUSINESS = "" # FILL ME + + SERVICE_INSTANCE_NAME = "" # FILL ME + + AVAILABILITY_ZONE_NAME = "" # FILL ME + AVAILABILITY_ZONE_HYPERVISOR_TYPE = "" # FILL ME + + + # FILL ME with your INFRA values + # ****************************** + # ubuntu16.zip file path including the heat and env files + VSPFILE_PATH = "" # FILL ME + + VIM_USERNAME = "" # FILL ME + VIM_PASSWORD = "" # FILL ME + VIM_SERVICE_URL = "" # FILL ME + + TENANT_NAME = "" # FILL ME + TENANT_ID = "" # FILL ME + + CLOUD_REGION = "" # Shall be defined in Openstack + + + # ************************************************************************************************* + logger.info("*******************************") + logger.info("******** SERVICE DESIGN *******") + logger.info("*******************************") + + logger.info("******** Onboard Vendor *******") + vendor = Vendor(name=VENDOR) + vendor.onboard() + + logger.info("******** Onboard VSP *******") + vsp = Vsp(name=VSPNAME, vendor=vendor, package=open(VSPFILE_PATH, 'rb')) + vsp.onboard() + + logger.info("******** Onboard VF *******") + vf = Vf(name=VFNAME) + vf.vsp = vsp + vf.onboard() + + logger.info("******** Onboard Service *******") + svc = Service(name=SERVICENAME, resources=[vf]) + svc.onboard() + + logger.info("******** Check Service Distribution *******") + distribution_completed = False + nb_try = 0 + nb_try_max = 10 + while distribution_completed is False and nb_try < nb_try_max: + distribution_completed = svc.distributed + if distribution_completed is True: + logger.info("Service Distribution for %s is sucessfully finished",svc.name) + break + logger.info("Service Distribution for %s ongoing, Wait for 60 s",svc.name) + time.sleep(60) + nb_try += 1 + + if distribution_completed is False: + logger.error("Service Distribution for %s failed !!",svc.name) + exit(1) + + logger.info("*******************************") + logger.info("***** RUNTIME PREPARATION *****") + logger.info("*******************************") + + logger.info("******** Create Complex *******") + cmplx = Complex.create( + physical_location_id=COMPLEX_PHYSICAL_LOCATION_ID, + data_center_code=COMPLEX_DATA_CENTER_CODE, + name=COMPLEX_PHYSICAL_LOCATION_ID + ) + + logger.info("******** Create CloudRegion *******") + # Note for non multicloud instanciation, cloud_region_version shall be set to openstack + # versus + cloud_region = CloudRegion.create( + cloud_owner=CLOUD_OWNER, + cloud_region_id=CLOUD_REGION, + orchestration_disabled=False, + in_maint=False, + cloud_type="openstack", + cloud_region_version="openstack", + cloud_zone="z1", + complex_name=COMPLEX_PHYSICAL_LOCATION_ID + ) + + logger.info("******** Link Complex to CloudRegion *******") + cloud_region.link_to_complex(cmplx) + + logger.info("******** Add ESR Info to CloudRegion *******") + cloud_region.add_esr_system_info( + esr_system_info_id=str(uuid4()), + user_name=VIM_USERNAME, + password=VIM_PASSWORD, + system_type="VIM", + service_url=VIM_SERVICE_URL, + cloud_domain="Default", + ssl_insecure=False, + system_status="active", + default_tenant=TENANT_NAME + ) + + logger.info("*******************************") + logger.info("**** SERVICE INSTANTIATION ****") + logger.info("*******************************") + + logger.info("******** Create Customer *******") + customer = None + for found_customer in list(Customer.get_all()): + logger.debug("Customer %s found", found_customer.subscriber_name) + if found_customer.subscriber_name == GLOBAL_CUSTOMER_ID: + logger.info("Customer %s found", found_customer.subscriber_name) + customer = found_customer + break + if not customer: + customer = Customer.create(GLOBAL_CUSTOMER_ID,GLOBAL_CUSTOMER_ID, "INFRA") + + logger.info("******** Find Service in SDC *******") + service = None + services = Service.get_all() + for found_service in services: + logger.debug("Service %s is found, distribution %s",found_service.name, found_service.distribution_status) + if found_service.name == SERVICENAME: + logger.info("Found Service %s in SDC",found_service.name) + service = found_service + break + + if not service: + logger.error("Service %s not found in SDC",SERVICENAME) + exit(1) + + logger.info("******** Check Service Subscription *******") + service_subscription = None + for service_sub in customer.service_subscriptions: + logger.debug("Service subscription %s is found",service_sub.service_type) + if service_sub.service_type == SERVICENAME: + logger.info("Service %s subscribed",SERVICENAME) + service_subscription = service_sub + break + + if not service_subscription: + logger.info("******** Subscribe Service *******") + customer.subscribe_service(SERVICENAME) + + logger.info("******** Get Tenant *******") + cloud_region = CloudRegion(cloud_owner=CLOUD_OWNER, cloud_region_id=CLOUD_REGION, + orchestration_disabled=True, in_maint=False) + try: + tenant: Tenant = cloud_region.get_tenant(settings.TENANT_ID) + except ValueError: + logger.warning("Impossible to retrieve the Specificed Tenant") + logger.debug("If no multicloud selected, add the tenant") + cloud_region.add_tenant( + tenant_id=settings.TENANT_ID, + tenant_name=settings.TENANT_NAME) + + # be sure that an availability zone has been created + # if not, create it + try: + cloud_region.get_availability_zone_by_name( + settings.AVAILABILITY_ZONE_NAME) + except ValueError: + cloud_region.add_availability_zone( + settings.AVAILABILITY_ZONE_NAME, + settings.AVAILABILITY_ZONE_HYPERVISOR_TYPE) + + logger.info("******** Connect Service to Tenant *******") + service_subscription = None + for service_sub in customer.service_subscriptions: + logger.debug("Service subscription %s is found",service_sub.service_type) + if service_sub.service_type == SERVICENAME: + logger.info("Service %s subscribed",SERVICENAME) + service_subscription = service_sub + break + + if not service_subscription: + logger.error("Service subscription %s is not found",SERVICENAME) + exit(1) + + service_subscription.link_to_cloud_region_and_tenant(cloud_region, tenant) + + logger.info("******** Add Business Objects (OE, P, Pl, LoB) in VID *******") + vid_owning_entity = OwningEntity.create(OWNING_ENTITY) + vid_project = Project.create(PROJECT) + vid_platform = Platform.create(PLATFORM) + vid_line_of_business = LineOfBusiness.create(LINE_OF_BUSINESS) + + logger.info("******** Add Owning Entity in AAI *******") + owning_entity = None + for oe in AaiOwningEntity.get_all(): + if oe.name == vid_owning_entity.name: + owning_entity = oe + break + if not owning_entity: + logger.info("******** Owning Entity not existing: create *******") + owning_entity = AaiOwningEntity.create(vid_owning_entity.name, str(uuid4())) + + logger.info("******** Instantiate Service *******") + service_instance = None + service_instantiation = None + for se in service_subscription.service_instances: + if se.instance_name == SERVICE_INSTANCE_NAME: + service_instance = se + break + if not service_instance: + logger.info("******** Service Instance not existing: Instantiate *******") + # Instantiate service + service_instantiation = ServiceInstantiation.instantiate_so_ala_carte( + service, + cloud_region, + tenant, + customer, + owning_entity, + vid_project, + service_instance_name=SERVICE_INSTANCE_NAME + ) + time.sleep(60) + else: + logger.info("******** Service Instance already existing *******") + + service_instance = None + for se in service_subscription.service_instances: + if se.instance_name == SERVICE_INSTANCE_NAME: + service_instance = se + break + if not service_instance: + logger.error("******** Service %s instantiation failed",SERVICE_INSTANCE_NAME) + exit(1) + + nb_try = 0 + nb_try_max = 10 + service_active = False + while service_active is False and nb_try < nb_try_max: + if service_instance.orchestration_status == "Active": + logger.info("******** Service Instance %s is active *******",service_instance.name) + service_active = True + break + logger.info("Service %s instantiation not complete,Status:%s, wait 10s",service_instance.name,service_instance.orchestration_status) + time.sleep(10) + nb_try += 1 + + if service_active is False: + logger.error("Service %s instantiation failed",service_instance.name) + exit(1) + + + logger.info("******** Get VNFs in Service Model *******") + vnfs = service_instance.service_subscription.sdc_service.vnfs + + logger.info("******** Create VNFs *******") + for vnf in vnfs: + logger.debug("Check if VNF instance of class %s exist", vnf.name) + vnf_found = False + for vnf_instance in service_instance.vnf_instances: + logger.debug("VNF instance %s found in Service Instance ",vnf_instance.name) + vnf_found = True + if vnf_found is False: + vnf_instantiation = service_instance.add_vnf(vnf, vid_line_of_business, vid_platform) + while not vnf_instantiation.finished: + print("Wait for VNF %s instantiation",vnf.name) + time.sleep(10) + + + for vnf_instance in service_instance.vnf_instances: + logger.debug("VNF instance %s found in Service Instance ",vnf_instance.name) + logger.info("******** Get VfModules in VNF Model *******") + logger.info("******** Check VF Modules *******") + vf_module = vnf_instance.vnf.vf_module + + logger.info("******** Create VF Module %s *******",vf_module.name) + + for vf_module in vnf_instance.vnf.vf_modules: + vf_module_instantiation = vnf_instance.add_vf_module( + vf_module, + cloud_region,tenant, + SERVICE_INSTANCE_NAME, + vnf_parameters=[]) + nb_try = 0 + nb_try_max = 30 + while not vf_module_instantiation.finished and nb_try < nb_try_max: + logger.info("Wait for vf module instantiation") + nb_try += 1 + time.sleep(10) + if vf_module_instantiation.finished: + logger.info("VfModule %s instantiated",vf_module.name) + else: + logger.error("VfModule instantiation %s failed",vf_module.name) + + if SERVICE_DELETION is False: + logger.info("*****************************************") + logger.info("**** No Deletion requested, finished ****") + logger.info("*****************************************") + exit(0) + + logger.info("*******************************") + logger.info("**** SERVICE DELETION *********") + logger.info("*******************************") + time.sleep(30) + + for vnf_instance in service_instance.vnf_instances: + logger.debug("VNF instance %s found in Service Instance ",vnf_instance.name) + logger.info("******** Get VF Modules *******") + for vf_module in vnf_instance.vf_modules: + logger.info("******** Delete VF Module %s *******",vf_module.name) + vf_module_deletion = vf_module.delete() + + nb_try = 0 + nb_try_max = 30 + while not vf_module_deletion.finished and nb_try < nb_try_max: + logger.info("Wait for vf module deletion") + nb_try += 1 + time.sleep(10) + if vf_module_deletion.finished: + logger.info("VfModule %s deleted",vf_module.name) + else: + logger.error("VfModule deletion %s failed",vf_module.name) + exit(1) + + logger.info("******** Delete VNF %s *******",vnf_instance.name) + vnf_deletion = vnf_instance.delete() + + nb_try = 0 + nb_try_max = 30 + while not vnf_deletion.finished and nb_try < nb_try_max: + logger.info("Wait for vnf deletion") + nb_try += 1 + time.sleep(10) + if vnf_deletion.finished: + logger.info("VNF %s deleted",vnf_instance.name) + else: + logger.error("VNF deletion %s failed",vnf_instance.name) + exit(1) + + logger.info("******** Delete Service %s *******",service_instance.name) + service_deletion = service_instance.delete() + + nb_try = 0 + nb_try_max = 30 + while not service_deletion.finished and nb_try < nb_try_max: + logger.info("Wait for Service deletion") + nb_try += 1 + time.sleep(10) + if service_deletion.finished: + logger.info("Service %s deleted",service_instance.name) + else: + logger.error("Service deletion %s failed",service_instance.name) + exit(1) diff --git a/docs/examples/e2e_closed_loop_instantiation.rst b/docs/examples/e2e_closed_loop_instantiation.rst new file mode 100644 index 0000000..a263a66 --- /dev/null +++ b/docs/examples/e2e_closed_loop_instantiation.rst @@ -0,0 +1,108 @@ +E2E Instantiation of a Closed Loop +########################################## + + +.. code:: Python + + #Service already created in this case + + logger = logging.getLogger("") + logger.setLevel(logging.INFO) + fh = logging.StreamHandler() + fh_formatter = logging.Formatter('%(asctime)s %(levelname)s %(lineno)d:%(filename)s(%(process)d) - %(message)s') + fh.setFormatter(fh_formatter) + logger.addHandler(fh) + + + #Constants + SERVICE_NAME = "Test_SDK" + POLICY_NAME = ["MinMax", "FrequencyLimiter"] + LOOP_INSTANCE_NAME = "instance01" + CERT = (PEM, KEY) # you must add clamp cert for AUTHENTIFICATION + + Clamp.set_proxy({ 'http': 'socks5h://127.0.0.1:8080', 'https': 'socks5h://127.0.0.1:8080'}) + Service.set_proxy({ 'http': 'socks5h://127.0.0.1:8080', 'https': 'socks5h://127.0.0.1:8080'}) + + logger.info("*******************************") + logger.info("******** SERVICE FETCH *******") + logger.info("*******************************") + + svc = Service(name=SERVICE_NAME) + + logger.info("***************************************") + logger.info("******** CLAMP AUTHENTIFICATION *******") + logger.info("***************************************") + + Clamp(cert=CERT) + + logger.info("*************************************") + logger.info("******** LOOP TEMPLATES CHECK *******") + logger.info("*************************************") + + loop_template = Clamp.check_loop_template(service=svc) + if not loop_template: + logger.error("Loop template for the service %s not found", svc.name) + exit(1) + + logger.info("*******************************") + logger.info("******** POLICIES CHECK *******") + logger.info("*******************************") + + minmax_exists = Clamp.check_policies(policy_name=POLICY_NAME[0], + req_policies=30) + frequency_exists = Clamp.check_policies(policy_name=POLICY_NAME[1], + req_policies=30) + policy_exists = (minmax_exists and frequency_exists) + if not policy_exists: + logger.error("Couldn't load the policy %s", POLICY_NAME) + exit(1) + + logger.info("***********************************") + logger.info("******** LOOP INSTANTIATION *******") + logger.info("***********************************") + + loop = LoopInstance(template=loop_template, name=LOOP_INSTANCE_NAME, details={}, cert=CERT) + loop.create() + if loop.details: + logger.info("Loop instance %s successfully created !!", LOOP_INSTANCE_NAME) + else: + logger.error("An error occured while creating the loop instance") + + logger.info("******** UPDATE MICROSERVICE POLICY *******") + loop._update_loop_details() + loop.update_microservice_policy() + + logger.info("******** ADD OPERATIONAL POLICY MINMAX *******") + added = loop.add_operational_policy(policy_type="onap.policies.controlloop.guard.common.MinMax", + policy_version="1.0.0") + + logger.info("******** CONFIGURE OPERATIONAL POLICY MINMAX *******") + loop.add_op_policy_config(loop.add_minmax_config) + + logger.info("******** ADD FREQUENCY POLICY *******") + added = loop.add_operational_policy(policy_type="onap.policies.controlloop.guard.common.FrequencyLimiter", + policy_version="1.0.0") + + logger.info("******** CONFIGURE FREQUENCY POLICY *******") + loop.add_op_policy_config(loop.add_frequency_limiter) + + logger.info("******** SUBMIT POLICIES TO PE *******") + submit = loop.act_on_loop_policy(loop.submit) + + logger.info("******** CHECK POLICIES SUBMITION *******") + if submit : + logger.info("Policies successfully submited to PE") + else: + logger.error("An error occured while submitting the loop instance") + exit(1) + + logger.info("******** DEPLOY LOOP INSTANCE *******") + deploy = loop.deploy_microservice_to_dcae() + if deploy: + logger.info("Loop instance %s successfully deployed on DCAE !!", LOOP_INSTANCE_NAME) + else: + logger.error("An error occured while deploying the loop instance") + exit(2) + + logger.info("******** DELETE LOOP INSTANCE *******") + loop.delete() diff --git a/docs/examples/e2e_net_instantiation.rst b/docs/examples/e2e_net_instantiation.rst new file mode 100644 index 0000000..bfa4346 --- /dev/null +++ b/docs/examples/e2e_net_instantiation.rst @@ -0,0 +1,332 @@ +E2E Instantiation of a simple Network +##################################### + + +.. code:: Python + + import logging + import time + from uuid import uuid4 + from onapsdk.aai.aai_element import AaiElement + from onapsdk.aai.cloud_infrastructure import ( + CloudRegion, + Complex, + Tenant + ) + from onapsdk.aai.service_design_and_creation import ( + Service as AaiService + ) + from onapsdk.aai.business import ( + ServiceInstance, + ServiceSubscription, + Customer, + OwningEntity as AaiOwningEntity + ) + from onapsdk.so.instantiation import ( + ServiceInstantiation, + Subnet + ) + from onapsdk.sdc.service import Service + from onapsdk.sdc.vl import Vl + import onapsdk.constants as const + import os + from onapsdk.vid import LineOfBusiness, OwningEntity, Platform, Project + + logger = logging.getLogger("") + logger.setLevel(logging.INFO) + fh = logging.StreamHandler() + fh_formatter = logging.Formatter('%(asctime)s %(levelname)s %(lineno)d:%(filename)s(%(process)d) - %(message)s') + fh.setFormatter(fh_formatter) + logger.addHandler(fh) + + + + # Create required A&AI resources + VL_NAME = "Generic NeutronNet" + SERVICENAME = "net_SERVICE" + + GLOBAL_CUSTOMER_ID = "" # FILL ME + COMPLEX_PHYSICAL_LOCATION_ID = "" # FILL ME + COMPLEX_DATA_CENTER_CODE = "" # FILL ME + + CLOUD_OWNER = "" # FILL ME + CLOUD_REGION = "" # FILL ME + + VIM_USERNAME = "" # FILL ME + VIM_PASSWORD = "" # FILL ME + VIM_SERVICE_URL = "" # FILL ME + + TENANT_NAME = "" # FILL ME + OWNING_ENTITY = "" # FILL ME + PROJECT = "" # FILL ME + PLATFORM = "" # FILL ME + LINE_OF_BUSINESS = "" # FILL ME + + SERVICE_INSTANCE_NAME = "net-Instance" + SERVICE_DELETION = True + + logger.info("*******************************") + logger.info("******** SERVICE DESIGN *******") + logger.info("*******************************") + + logger.info("******** Get VL *******") + vl = Vl(VL_NAME) + + logger.info("******** Onboard Service *******") + svc = Service(name=SERVICENAME, resources=[vl]) + svc.onboard() + + logger.info("******** Check Service Distribution *******") + distribution_completed = False + nb_try = 0 + nb_try_max = 10 + while distribution_completed is False and nb_try < nb_try_max: + distribution_completed = svc.distributed + if distribution_completed is True: + logger.info("Service Distribution for %s is sucessfully finished",svc.name) + break + logger.info("Service Distribution for %s ongoing, Wait for 60 s",svc.name) + time.sleep(60) + nb_try += 1 + + if distribution_completed is False: + logger.error("Service Distribution for %s failed !!",svc.name) + exit(1) + + logger.info("*******************************") + logger.info("***** RUNTIME PREPARATION *****") + logger.info("*******************************") + + logger.info("******** Create Complex *******") + cmplx = Complex.create( + physical_location_id=COMPLEX_PHYSICAL_LOCATION_ID, + data_center_code=COMPLEX_DATA_CENTER_CODE, + name=COMPLEX_PHYSICAL_LOCATION_ID + ) + + logger.info("******** Create CloudRegion *******") + cloud_region = CloudRegion.create( + cloud_owner=CLOUD_OWNER, + cloud_region_id=CLOUD_REGION, + orchestration_disabled=False, + in_maint=False, + cloud_type="openstack", + cloud_region_version="titanium_cloud", + cloud_zone="z1", + complex_name=COMPLEX_PHYSICAL_LOCATION_ID + ) + + logger.info("******** Link Complex to CloudRegion *******") + cloud_region.link_to_complex(cmplx) + + logger.info("******** Add ESR Info to CloudRegion *******") + cloud_region.add_esr_system_info( + esr_system_info_id=str(uuid4()), + user_name=VIM_USERNAME, + password=VIM_PASSWORD, + system_type="VIM", + service_url=VIM_SERVICE_URL, + cloud_domain="Default", + ssl_insecure=False, + system_status="active", + default_tenant=TENANT_NAME + ) + + logger.info("******** Register CloudRegion to MultiCloud *******") + cloud_region.register_to_multicloud() + + logger.info("******** Check MultiCloud Registration *******") + time.sleep(60) + registration_completed = False + nb_try = 0 + nb_try_max = 10 + while registration_completed is False and nb_try < nb_try_max: + for tenant in cloud_region.tenants: + logger.debug("Tenant %s found in %s_%s",tenant.name,cloud_region.cloud_owner,cloud_region.cloud_region_id) + registration_completed = True + if registration_completed is False: + time.sleep(60) + nb_try += 1 + + if registration_completed is False: + logger.error("Registration of Cloud %s_%s failed !!",cloud_region.cloud_owner,cloud_region.cloud_region_id) + exit(1) + else: + logger.info("Registration of Cloud %s_%s successful !!",cloud_region.cloud_owner,cloud_region.cloud_region_id) + + logger.info("*******************************") + logger.info("**** SERVICE INSTANTIATION ****") + logger.info("*******************************") + + logger.info("******** Create Customer *******") + customer = None + for found_customer in list(Customer.get_all()): + logger.debug("Customer %s found", found_customer.subscriber_name) + if found_customer.subscriber_name == GLOBAL_CUSTOMER_ID: + logger.info("Customer %s found", found_customer.subscriber_name) + customer = found_customer + break + if not customer: + customer = Customer.create(GLOBAL_CUSTOMER_ID,GLOBAL_CUSTOMER_ID, "INFRA") + + logger.info("******** Find Service in SDC *******") + service = None + services = Service.get_all() + for found_service in services: + logger.debug("Service %s is found, distribution %s",found_service.name, found_service.distribution_status) + if found_service.name == SERVICENAME: + logger.info("Found Service %s in SDC",found_service.name) + service = found_service + break + + if not service: + logger.error("Service %s not found in SDC",SERVICENAME) + exit(1) + + logger.info("******** Check Service Subscription *******") + service_subscription = None + for service_sub in customer.service_subscriptions: + logger.debug("Service subscription %s is found",service_sub.service_type) + if service_sub.service_type == SERVICENAME: + logger.info("Service %s subscribed",SERVICENAME) + service_subscription = service_sub + break + + if not service_subscription: + logger.info("******** Subscribe Service *******") + customer.subscribe_service(SERVICENAME) + + logger.info("******** Get Tenant *******") + cloud_region = CloudRegion(cloud_owner=CLOUD_OWNER, cloud_region_id=CLOUD_REGION, + orchestration_disabled=True, in_maint=False) + tenant = None + for found_tenant in cloud_region.tenants: + logger.debug("Tenant %s found in %s_%s",found_tenant.name,cloud_region.cloud_owner,cloud_region.cloud_region_id) + if found_tenant.name == TENANT_NAME: + logger.info("Found my Tenant %s",found_tenant.name) + tenant = found_tenant + break + + if not tenant: + logger.error("tenant %s not found",TENANT_NAME) + exit(1) + + logger.info("******** Connect Service to Tenant *******") + service_subscription = None + for service_sub in customer.service_subscriptions: + logger.debug("Service subscription %s is found",service_sub.service_type) + if service_sub.service_type == SERVICENAME: + logger.info("Service %s subscribed",SERVICENAME) + service_subscription = service_sub + break + + if not service_subscription: + logger.error("Service subscription %s is not found",SERVICENAME) + exit(1) + + service_subscription.link_to_cloud_region_and_tenant(cloud_region, tenant) + + logger.info("******** Add Business Objects (OE, P, Pl, LoB) in VID *******") + vid_owning_entity = OwningEntity.create(OWNING_ENTITY) + vid_project = Project.create(PROJECT) + vid_platform = Platform.create(PLATFORM) + vid_line_of_business = LineOfBusiness.create(LINE_OF_BUSINESS) + + logger.info("******** Add Owning Entity in AAI *******") + owning_entity = None + for oe in AaiOwningEntity.get_all(): + if oe.name == vid_owning_entity.name: + owning_entity = oe + break + if not owning_entity: + logger.info("******** Owning Entity not existing: create *******") + owning_entity = AaiOwningEntity.create(vid_owning_entity.name, str(uuid4())) + + logger.info("******** Instantiate Service *******") + service_instance = None + service_instantiation = None + for se in service_subscription.service_instances: + if se.instance_name == SERVICE_INSTANCE_NAME: + service_instance = se + break + if not service_instance: + logger.info("******** Service Instance not existing: Instantiate *******") + # Instantiate service + service_instantiation = ServiceInstantiation.instantiate_so_ala_carte( + service, + cloud_region, + tenant, + customer, + owning_entity, + vid_project, + service_instance_name=SERVICE_INSTANCE_NAME + ) + time.sleep(60) + else: + logger.info("******** Service Instance already existing *******") + + service_instance = None + for se in service_subscription.service_instances: + if se.instance_name == SERVICE_INSTANCE_NAME: + service_instance = se + break + if not service_instance: + logger.error("******** Service %s instantiation failed",SERVICE_INSTANCE_NAME) + exit(1) + + nb_try = 0 + nb_try_max = 10 + service_active = False + while service_active is False and nb_try < nb_try_max: + if service_instance.orchestration_status == "Active": + logger.info("******** Service Instance %s is active *******",service_instance.name) + service_active = True + break + logger.info("Service %s instantiation not complete,Status:%s, wait 10s",service_instance.name,service_instance.orchestration_status) + time.sleep(10) + nb_try += 1 + + if service_active is False: + logger.error("Service %s instantiation failed",service_instance.name) + exit(1) + + + logger.info("******** Get Networks in Service Model *******") + networks = service_instance.service_subscription.sdc_service.networks + + logger.info("******** Create Network *******") + sn=Subnet(name="test", start_address="127.0.0.0", gateway_address="127.0.0.1") + for network in networks: + logger.debug("Check if Network instance of class %s exist", network.name) + network_found = False + for network_instance in service_instance.network_instances: + logger.debug("Network instance %s found in Service Instance ",network_intance.name) + network_found = True + if network_found is False: + network_instantiation = service_instance.add_network(network, vid_line_of_business, vid_platform, subnets=[sn]) + network_instantiation.wait_for_finish() + + + if SERVICE_DELETION is False: + logger.info("*****************************************") + logger.info("**** No Deletion requested, finished ****") + logger.info("*****************************************") + exit(0) + + logger.info("*******************************") + logger.info("**** SERVICE DELETION *********") + logger.info("*******************************") + time.sleep(30) + + for network_instance in service_instance.network_instances: + logger.debug("Network instance %s found in Service Instance ",network_instance.name) + + logger.info("******** Delete Network %s *******",network_instance.name) + network_deletion = network_instance.delete() + network_deletion.wait_for_finish() + + logger.info("******** Delete Service %s *******",service_instance.name) + service_deletion = service_instance.delete() + service_deletion.wait_for_finish() + + diff --git a/docs/examples/e2e_vfw_instantiation.rst b/docs/examples/e2e_vfw_instantiation.rst new file mode 100644 index 0000000..5455d7d --- /dev/null +++ b/docs/examples/e2e_vfw_instantiation.rst @@ -0,0 +1,429 @@ +E2E Instantiation of vFW (a'la carte) +##################################### + + +.. code:: Python + + import logging + import time + from uuid import uuid4 + from onapsdk.aai.aai_element import AaiElement + from onapsdk.aai.cloud_infrastructure import ( + CloudRegion, + Complex, + Tenant + ) + from onapsdk.aai.service_design_and_creation import ( + Service as AaiService + ) + from onapsdk.aai.business import ( + ServiceInstance, + VnfInstance, + VfModuleInstance, + ServiceSubscription, + Customer, + OwningEntity as AaiOwningEntity + ) + from onapsdk.so.instantiation import ( + ServiceInstantiation, + VnfInstantiation, + VnfParameter + ) + from onapsdk.sdc import SDC + from onapsdk.sdc.vendor import Vendor + from onapsdk.sdc.vsp import Vsp + from onapsdk.sdc.vf import Vf + from onapsdk.sdc.service import Service + import onapsdk.constants as const + import os + from onapsdk.vid import LineOfBusiness, OwningEntity, Platform, Project + + logger = logging.getLogger("") + logger.setLevel(logging.INFO) + fh = logging.StreamHandler() + fh_formatter = logging.Formatter('%(asctime)s %(levelname)s %(lineno)d:%(filename)s(%(process)d) - %(message)s') + fh.setFormatter(fh_formatter) + logger.addHandler(fh) + + + + # Create required A&AI resources + VENDOR = "VNFVendor" + VSPFILE = "vsp/vfw.zip" + VSPNAME = "vfw_VSP" + VFNAME = "vfw_VF" + SERVICENAME = "vfw_SERVICE" + + GLOBAL_CUSTOMER_ID = "" # FILL ME + COMPLEX_PHYSICAL_LOCATION_ID = "" # FILL ME + COMPLEX_DATA_CENTER_CODE = "" # FILL ME + + CLOUD_OWNER = "" # FILL ME + CLOUD_REGION = "" # FILL ME + + VIM_USERNAME = "" # FILL ME + VIM_PASSWORD = "" # FILL ME + VIM_SERVICE_URL = "" # FILL ME + + TENANT_NAME = "" # FILL ME + OWNING_ENTITY = "" # FILL ME + PROJECT = "" # FILL ME + PLATFORM = "" # FILL ME + LINE_OF_BUSINESS = "" # FILL ME + + SERVICE_INSTANCE_NAME = "vFW-Instance" + SERVICE_DELETION = True + + logger.info("*******************************") + logger.info("******** SERVICE DESIGN *******") + logger.info("*******************************") + + logger.info("******** Onboard Vendor *******") + vendor = Vendor(name=VENDOR) + vendor.onboard() + + logger.info("******** Onboard VSP *******") + mypath = os.path.dirname(os.path.realpath(__file__)) + myvspfile = os.path.join(mypath, VSPFILE) + vsp = Vsp(name=VSPNAME, vendor=vendor, package=open(myvspfile, 'rb')) + vsp.onboard() + + logger.info("******** Onboard VF *******") + vf = Vf(name=VFNAME) + vf.vsp = vsp + vf.onboard() + + logger.info("******** Onboard Service *******") + svc = Service(name=SERVICENAME, resources=[vf]) + svc.onboard() + + logger.info("******** Check Service Distribution *******") + distribution_completed = False + nb_try = 0 + nb_try_max = 10 + while distribution_completed is False and nb_try < nb_try_max: + distribution_completed = svc.distributed + if distribution_completed is True: + logger.info("Service Distribution for %s is sucessfully finished",svc.name) + break + logger.info("Service Distribution for %s ongoing, Wait for 60 s",svc.name) + time.sleep(60) + nb_try += 1 + + if distribution_completed is False: + logger.error("Service Distribution for %s failed !!",svc.name) + exit(1) + + logger.info("*******************************") + logger.info("***** RUNTIME PREPARATION *****") + logger.info("*******************************") + + logger.info("******** Create Complex *******") + cmplx = Complex.create( + physical_location_id=COMPLEX_PHYSICAL_LOCATION_ID, + data_center_code=COMPLEX_DATA_CENTER_CODE, + name=COMPLEX_PHYSICAL_LOCATION_ID + ) + + logger.info("******** Create CloudRegion *******") + cloud_region = CloudRegion.create( + cloud_owner=CLOUD_OWNER, + cloud_region_id=CLOUD_REGION, + orchestration_disabled=False, + in_maint=False, + cloud_type="openstack", + cloud_region_version="titanium_cloud", + cloud_zone="z1", + complex_name=COMPLEX_PHYSICAL_LOCATION_ID + ) + + logger.info("******** Link Complex to CloudRegion *******") + cloud_region.link_to_complex(cmplx) + + logger.info("******** Add ESR Info to CloudRegion *******") + cloud_region.add_esr_system_info( + esr_system_info_id=str(uuid4()), + user_name=VIM_USERNAME, + password=VIM_PASSWORD, + system_type="VIM", + service_url=VIM_SERVICE_URL, + cloud_domain="Default", + ssl_insecure=False, + system_status="active", + default_tenant=TENANT_NAME + ) + + logger.info("******** Register CloudRegion to MultiCloud *******") + cloud_region.register_to_multicloud() + + logger.info("******** Check MultiCloud Registration *******") + time.sleep(60) + registration_completed = False + nb_try = 0 + nb_try_max = 10 + while registration_completed is False and nb_try < nb_try_max: + for tenant in cloud_region.tenants: + logger.debug("Tenant %s found in %s_%s",tenant.name,cloud_region.cloud_owner,cloud_region.cloud_region_id) + registration_completed = True + if registration_completed is False: + time.sleep(60) + nb_try += 1 + + if registration_completed is False: + logger.error("Registration of Cloud %s_%s failed !!",cloud_region.cloud_owner,cloud_region.cloud_region_id) + exit(1) + else: + logger.info("Registration of Cloud %s_%s successful !!",cloud_region.cloud_owner,cloud_region.cloud_region_id) + + logger.info("*******************************") + logger.info("**** SERVICE INSTANTIATION ****") + logger.info("*******************************") + + logger.info("******** Create Customer *******") + customer = None + for found_customer in list(Customer.get_all()): + logger.debug("Customer %s found", found_customer.subscriber_name) + if found_customer.subscriber_name == GLOBAL_CUSTOMER_ID: + logger.info("Customer %s found", found_customer.subscriber_name) + customer = found_customer + break + if not customer: + customer = Customer.create(GLOBAL_CUSTOMER_ID,GLOBAL_CUSTOMER_ID, "INFRA") + + logger.info("******** Find Service in SDC *******") + service = None + services = Service.get_all() + for found_service in services: + logger.debug("Service %s is found, distribution %s",found_service.name, found_service.distribution_status) + if found_service.name == SERVICENAME: + logger.info("Found Service %s in SDC",found_service.name) + service = found_service + break + + if not service: + logger.error("Service %s not found in SDC",SERVICENAME) + exit(1) + + logger.info("******** Check Service Subscription *******") + service_subscription = None + for service_sub in customer.service_subscriptions: + logger.debug("Service subscription %s is found",service_sub.service_type) + if service_sub.service_type == SERVICENAME: + logger.info("Service %s subscribed",SERVICENAME) + service_subscription = service_sub + break + + if not service_subscription: + logger.info("******** Subscribe Service *******") + customer.subscribe_service(SERVICENAME) + + logger.info("******** Get Tenant *******") + cloud_region = CloudRegion(cloud_owner=CLOUD_OWNER, cloud_region_id=CLOUD_REGION, + orchestration_disabled=True, in_maint=False) + tenant = None + for found_tenant in cloud_region.tenants: + logger.debug("Tenant %s found in %s_%s",found_tenant.name,cloud_region.cloud_owner,cloud_region.cloud_region_id) + if found_tenant.name == TENANT_NAME: + logger.info("Found my Tenant %s",found_tenant.name) + tenant = found_tenant + break + + if not tenant: + logger.error("tenant %s not found",TENANT_NAME) + exit(1) + + logger.info("******** Connect Service to Tenant *******") + service_subscription = None + for service_sub in customer.service_subscriptions: + logger.debug("Service subscription %s is found",service_sub.service_type) + if service_sub.service_type == SERVICENAME: + logger.info("Service %s subscribed",SERVICENAME) + service_subscription = service_sub + break + + if not service_subscription: + logger.error("Service subscription %s is not found",SERVICENAME) + exit(1) + + service_subscription.link_to_cloud_region_and_tenant(cloud_region, tenant) + + logger.info("******** Add Business Objects (OE, P, Pl, LoB) in VID *******") + vid_owning_entity = OwningEntity.create(OWNING_ENTITY) + vid_project = Project.create(PROJECT) + vid_platform = Platform.create(PLATFORM) + vid_line_of_business = LineOfBusiness.create(LINE_OF_BUSINESS) + + logger.info("******** Add Owning Entity in AAI *******") + owning_entity = None + for oe in AaiOwningEntity.get_all(): + if oe.name == vid_owning_entity.name: + owning_entity = oe + break + if not owning_entity: + logger.info("******** Owning Entity not existing: create *******") + owning_entity = AaiOwningEntity.create(vid_owning_entity.name, str(uuid4())) + + logger.info("******** Instantiate Service *******") + service_instance = None + service_instantiation = None + for se in service_subscription.service_instances: + if se.instance_name == SERVICE_INSTANCE_NAME: + service_instance = se + break + if not service_instance: + logger.info("******** Service Instance not existing: Instantiate *******") + # Instantiate service + service_instantiation = ServiceInstantiation.instantiate_so_ala_carte( + service, + cloud_region, + tenant, + customer, + owning_entity, + vid_project, + service_instance_name=SERVICE_INSTANCE_NAME + ) + time.sleep(60) + else: + logger.info("******** Service Instance already existing *******") + + service_instance = None + for se in service_subscription.service_instances: + if se.instance_name == SERVICE_INSTANCE_NAME: + service_instance = se + break + if not service_instance: + logger.error("******** Service %s instantiation failed",SERVICE_INSTANCE_NAME) + exit(1) + + nb_try = 0 + nb_try_max = 10 + service_active = False + while service_active is False and nb_try < nb_try_max: + if service_instance.orchestration_status == "Active": + logger.info("******** Service Instance %s is active *******",service_instance.name) + service_active = True + break + logger.info("Service %s instantiation not complete,Status:%s, wait 10s",service_instance.name,service_instance.orchestration_status) + time.sleep(10) + nb_try += 1 + + if service_active is False: + logger.error("Service %s instantiation failed",service_instance.name) + exit(1) + + + logger.info("******** Get VNFs in Service Model *******") + vnfs = service_instance.service_subscription.sdc_service.vnfs + + logger.info("******** Create VNFs *******") + for vnf in vnfs: + logger.debug("Check if VNF instance of class %s exist", vnf.name) + vnf_found = False + for vnf_instance in service_instance.vnf_instances: + logger.debug("VNF instance %s found in Service Instance ",vnf_instance.name) + vnf_found = True + if vnf_found is False: + vnf_instantiation = service_instance.add_vnf(vnf, vid_line_of_business, vid_platform) + while not vnf_instantiation.finished: + print("Wait for VNF %s instantiation",vnf.name) + time.sleep(10) + + + for vnf_instance in service_instance.vnf_instances: + logger.debug("VNF instance %s found in Service Instance ",vnf_instance.name) + logger.info("******** Get VfModules in VNF Model *******") + logger.info("******** Check VF Modules *******") + vf_module = vnf_instance.vnf.vf_module + + logger.info("******** Create VF Module %s *******",vf_module.name) + + vf_module_instantiation = vnf_instance.add_vf_module( + vf_module, + vnf_parameters=[ + VnfParameter(name="vfw_image_name", value="Ubuntu_1404"), + VnfParameter(name="vpg_image_name", value="Ubuntu_1404"), + VnfParameter(name="vsn_image_name", value="Ubuntu_1404"), + VnfParameter(name="vfw_flavor_name", value="m1.small"), + VnfParameter(name="vpg_flavor_name", value="m1.small"), + VnfParameter(name="vsn_flavor_name", value="m1.small"), + VnfParameter(name="public_net_id", value="admin"), + VnfParameter(name="onap_private_net_id", value="admin"), + VnfParameter(name="onap_private_subnet_id", value="admin-subnet"), + VnfParameter(name="onap_private_net_cidr", value="10.41.1.0/24"), + VnfParameter(name="vfw_onap_private_ip_0", value="10.41.1.10"), + VnfParameter(name="vpg_onap_private_ip_0", value="10.41.1.11"), + VnfParameter(name="vsn_onap_private_ip_0", value="10.41.1.12"), + VnfParameter(name="sec_group", value="ci-onap-master-vnfs-onap") + ] + ) + nb_try = 0 + nb_try_max = 30 + while not vf_module_instantiation.finished and nb_try < nb_try_max: + logger.info("Wait for vf module instantiation") + nb_try += 1 + time.sleep(10) + if vf_module_instantiation.finished: + logger.info("VfModule %s instantiated",vf_module.name) + else: + logger.error("VfModule instantiation %s failed",vf_module.name) + + if SERVICE_DELETION is False: + logger.info("*****************************************") + logger.info("**** No Deletion requested, finished ****") + logger.info("*****************************************") + exit(0) + + logger.info("*******************************") + logger.info("**** SERVICE DELETION *********") + logger.info("*******************************") + time.sleep(30) + + for vnf_instance in service_instance.vnf_instances: + logger.debug("VNF instance %s found in Service Instance ",vnf_instance.name) + logger.info("******** Get VF Modules *******") + for vf_module in vnf_instance.vf_modules: + logger.info("******** Delete VF Module %s *******",vf_module.name) + vf_module_deletion = vf_module.delete() + + nb_try = 0 + nb_try_max = 30 + while not vf_module_deletion.finished and nb_try < nb_try_max: + logger.info("Wait for vf module deletion") + nb_try += 1 + time.sleep(10) + if vf_module_deletion.finished: + logger.info("VfModule %s deleted",vf_module.name) + else: + logger.error("VfModule deletion %s failed",vf_module.name) + exit(1) + + logger.info("******** Delete VNF %s *******",vnf_instance.name) + vnf_deletion = vnf_instance.delete() + + nb_try = 0 + nb_try_max = 30 + while not vnf_deletion.finished and nb_try < nb_try_max: + logger.info("Wait for vnf deletion") + nb_try += 1 + time.sleep(10) + if vnf_deletion.finished: + logger.info("VNF %s deleted",vnf_instance.name) + else: + logger.error("VNF deletion %s failed",vnf_instance.name) + exit(1) + + logger.info("******** Delete Service %s *******",service_instance.name) + service_deletion = service_instance.delete() + + nb_try = 0 + nb_try_max = 30 + while not service_deletion.finished and nb_try < nb_try_max: + logger.info("Wait for Service deletion") + nb_try += 1 + time.sleep(10) + if service_deletion.finished: + logger.info("Service %s deleted",service_instance.name) + else: + logger.error("Service deletion %s failed",service_instance.name) + exit(1) + diff --git a/docs/examples/e2e_vfw_macro_instantiation.rst b/docs/examples/e2e_vfw_macro_instantiation.rst new file mode 100644 index 0000000..72f8167 --- /dev/null +++ b/docs/examples/e2e_vfw_macro_instantiation.rst @@ -0,0 +1,493 @@ +E2E Instantiation of vFW (macro) +################################ + + +.. code:: Python + + import logging + import time + import json + from uuid import uuid4 + from onapsdk.aai.aai_element import AaiElement + from onapsdk.aai.cloud_infrastructure import ( + CloudRegion, + Complex, + Tenant + ) + from onapsdk.aai.service_design_and_creation import ( + Service as AaiService + ) + from onapsdk.aai.business import ( + ServiceInstance, + VnfInstance, + VfModuleInstance, + ServiceSubscription, + Customer, + OwningEntity as AaiOwningEntity + ) + from onapsdk.so.instantiation import ( + ServiceInstantiation, + VnfInstantiation, + InstantiationParameter, + VnfParameters, + VfmoduleParameters + ) + from onapsdk.sdc.properties import Property + from onapsdk.sdc import SDC + from onapsdk.sdc.vendor import Vendor + from onapsdk.sdc.vsp import Vsp + from onapsdk.sdc.vf import Vf + from onapsdk.sdc.service import Service, ServiceInstantiationType + import onapsdk.constants as const + import os + from onapsdk.vid import LineOfBusiness, OwningEntity, Platform, Project + + from onapsdk.cds.blueprint import Blueprint + from onapsdk.cds.data_dictionary import DataDictionary, DataDictionarySet + + logger = logging.getLogger("") + logger.setLevel(logging.INFO) + logname = "./vfwcds.debug.log" + fh = logging.FileHandler(logname) + fh_formatter = logging.Formatter('%(asctime)s %(levelname)s %(lineno)d:%(filename)s(%(process)d) - %(message)s') + fh.setFormatter(fh_formatter) + logger.addHandler(fh) + + ########################################################################### + ######## CDS Design settings ############################################## + ######## vFW CDS Example ############################################## + ########################################################################### + # DDF Settings (dd files located in following location) + DDDIR = "resources/starter-dictionary" + DDFILE = "resources/my_dd.json" + + # CBA resources (location of base CBA file) + CBAFILE = "resources/vFWCDS/CBA/CBA.zip" + ARTIFACT_LABEL = "vnfcds" + ARTIFACT_NAME = "CBA_enriched.zip" + ARTIFACT_TYPE = "CONTROLLER_BLUEPRINT_ARCHIVE" + ARTIFACT_FILE_PATH = "resources/vFWCDS/CBA/CBA_enriched.zip" + SDNC_TEMPLATE_NAME = "vFW-CDS" + SDNC_TEMPLATE_VERSION = "1.0.0" + SDNC_ARTIFACT_NAME = "vnf" + + ########################################################################### + ######## Service Design settings ########################################## + ########################################################################### + VENDOR = "VNFVendor" + + # HEAT resources (location of zipped HEAT file) + VSPFILE = "resources/vFWCDS/HEAT/vFW/vFW.zip" + VSPNAME = "vfwcds_VS" + VFNAME = "vfwcds_VF" + SERVICENAME = "vfwcds_SERVICE" + + ########################################################################### + ######## Runtime preparation settings ##################################### + ########################################################################### + # Default Cloud + CLOUD_OWNER = "CloudOwner" + CLOUD_REGION = "RegionOne" + + GLOBAL_CUSTOMER_ID = "generic" + CLOUD_TYPE = "openstack" + CLOUD_VERSION = "pike" + VIM_USERNAME = # FILL ME + VIM_PASSWORD = # FILL ME + VIM_SERVICE_URL = "http:///v3" # FILL ME + TENANT_NAME = # FILL ME + TENANT_SEC_GROUP = # FILL ME + COMPLEX_PHYSICAL_LOCATION_ID = "location" + COMPLEX_DATA_CENTER_CODE = "1234" + + + # common + OWNING_ENTITY = "Test-OE" + PROJECT = "Test-Project" + PLATFORM = "Test-Platform" + LINE_OF_BUSINESS = "Test-BusinessLine" + + SERVICE_DELETION = False + + ########################################################################### + ######## Service Instance attributes ###################################### + ########################################################################### + SERVICE_INSTANCE_NAME = "vFWCDS-Instance-1" + ONAP_PRIVATE_NET = "onap-oam" # FILL ME + ONAP_PRIVATE_SUBNET = "onap-oam-subnet" # FILL ME + PUBLIC_NET = "admin" # FILL ME + IMAGE_NAME = "Ubuntu_1604" # FILL ME + FLAVOR_NAME = "m1.small" # FILL ME + + logger.info("*******************************") + logger.info("********* CBA Creation ********") + logger.info("*******************************") + + logger.info("******** Load Data Dictionary *******") + mypath = os.path.dirname(os.path.realpath(__file__)) + myddpath = os.path.join(mypath, DDDIR) + myddfile = os.path.join(mypath, DDFILE) + + logger.info("path: %s", myddpath) + dd_set = DataDictionarySet() + for file in os.listdir(myddpath): + logger.info("file: %s", file) + if file.endswith(".json"): + with open(os.path.join(myddpath, file), "r") as dd_file: # type file + dd_json: dict = json.loads(dd_file.read()) + logger.info("DD: %s", dd_json) + dd_set.add(DataDictionary(dd_json)) + logger.info("DD Length: %d", dd_set.length) + dd_set.upload() + + logger.info("******** Open Blueprint *******") + cbafile = os.path.join(mypath, CBAFILE) + artifactfile = os.path.join(mypath, ARTIFACT_FILE_PATH) + + blueprint = Blueprint.load_from_file(cbafile) + enriched_blueprint = blueprint.enrich() # returns enriched blueprint object + enriched_blueprint.save(artifactfile) + + + logger.info("*******************************") + logger.info("******** SERVICE DESIGN *******") + logger.info("*******************************") + + logger.info("******** Onboard Vendor *******") + vendor = Vendor(name=VENDOR) + vendor.onboard() + + logger.info("******** Onboard VSP *******") + vspfile = os.path.join(mypath, VSPFILE) + vsp = Vsp(name=VSPNAME, vendor=vendor, package=open(vspfile, 'rb')) + vsp.onboard() + + + logger.info("******** Onboard VF *******") + vf = Vf(name=VFNAME) + vf.vsp = vsp + vf.create() + + if vf.status == const.DRAFT: + + logger.info("******** Extract Artifact Data *******") + data = open(artifactfile, 'rb').read() + + logger.info("******** Upload Artifact *******") + vf.add_deployment_artifact(artifact_type=ARTIFACT_TYPE, + artifact_name=ARTIFACT_NAME, + artifact_label=ARTIFACT_LABEL, + artifact=artifactfile) + + vf.onboard() + + svc = Service(name=SERVICENAME,instantiation_type=ServiceInstantiationType.MACRO) + svc.create() + + if svc.status == const.DRAFT: + svc.add_resource(vf) + + logger.info("******** Set SDNC properties for VF ********") + component = svc.get_component(vf) + prop = component.get_property("sdnc_model_version") + prop.value = SDNC_TEMPLATE_VERSION + prop = component.get_property("sdnc_artifact_name") + prop.value = SDNC_ARTIFACT_NAME + prop = component.get_property("sdnc_model_name") + prop.value = SDNC_TEMPLATE_NAME + prop = component.get_property("controller_actor") + prop.value = "CDS" + prop = component.get_property("skip_post_instantiation_configuration") + prop.value = False + + logger.info("******** Onboard Service *******") + svc.checkin() + svc.onboard() + + logger.info("******** Check Service Distribution *******") + distribution_completed = False + nb_try = 0 + nb_try_max = 10 + while distribution_completed is False and nb_try < nb_try_max: + distribution_completed = svc.distributed + if distribution_completed is True: + logger.info("Service Distribution for %s is sucessfully finished",svc.name) + break + logger.info("Service Distribution for %s ongoing, Wait for 60 s",svc.name) + time.sleep(60) + nb_try += 1 + + if distribution_completed is False: + logger.error("Service Distribution for %s failed !!",svc.name) + exit(1) + + logger.info("*******************************") + logger.info("***** RUNTIME PREPARATION *****") + logger.info("*******************************") + + logger.info("******** Create Complex *******") + cmplx = Complex.create( + physical_location_id=COMPLEX_PHYSICAL_LOCATION_ID, + data_center_code=COMPLEX_DATA_CENTER_CODE, + name=COMPLEX_PHYSICAL_LOCATION_ID + ) + + logger.info("******** Create CloudRegion *******") + cloud_region = CloudRegion.create( + cloud_owner=CLOUD_OWNER, + cloud_region_id=CLOUD_REGION, + orchestration_disabled=False, + in_maint=False, + cloud_type=CLOUD_TYPE, + cloud_zone="z1", + complex_name=COMPLEX_PHYSICAL_LOCATION_ID, + sriov_automation=False, + owner_defined_type="t1", + cloud_region_version=CLOUD_VERSION + ) + + logger.info("******** Link Complex to CloudRegion *******") + cloud_region.link_to_complex(cmplx) + + logger.info("******** Add ESR Info to CloudRegion *******") + cloud_region.add_esr_system_info( + esr_system_info_id=str(uuid4()), + user_name=VIM_USERNAME, + password=VIM_PASSWORD, + system_type="VIM", + service_url=VIM_SERVICE_URL, + cloud_domain="Default", + ssl_insecure=False, + system_status="active", + default_tenant=TENANT_NAME + ) + + logger.info("******** Register CloudRegion to MultiCloud *******") + cloud_region.register_to_multicloud() + + logger.info("******** Check MultiCloud Registration *******") + time.sleep(60) + tenant_found = False + availability_zone_found = False + registration_completed = False + nb_try = 0 + nb_try_max = 10 + while registration_completed is False and nb_try < nb_try_max: + for tenant in cloud_region.tenants: + logger.debug("Tenant %s found in %s_%s",tenant.name,cloud_region.cloud_owner,cloud_region.cloud_region_id) + tenant_found = True + for az in cloud_region.availability_zones: + logger.debug("A-Zone %s found",az.name) + availability_zone_found = True + if availability_zone_found and tenant_found: + registration_completed = True + if registration_completed is False: + time.sleep(60) + nb_try += 1 + + if registration_completed is False: + logger.error("Registration of Cloud %s_%s failed !!",cloud_region.cloud_owner,cloud_region.cloud_region_id) + exit(1) + else: + logger.info("Registration of Cloud %s_%s successful !!",cloud_region.cloud_owner,cloud_region.cloud_region_id) + + logger.info("*******************************") + logger.info("**** SERVICE INSTANTIATION ****") + logger.info("*******************************") + + logger.info("******** Create Customer *******") + customer = None + for found_customer in list(Customer.get_all()): + logger.debug("Customer %s found", found_customer.subscriber_name) + if found_customer.subscriber_name == GLOBAL_CUSTOMER_ID: + logger.info("Customer %s found", found_customer.subscriber_name) + customer = found_customer + break + if not customer: + customer = Customer.create(GLOBAL_CUSTOMER_ID,GLOBAL_CUSTOMER_ID, "INFRA") + + logger.info("******** Find Service in SDC *******") + service = None + services = Service.get_all() + for found_service in services: + logger.debug("Service %s is found, distribution %s",found_service.name, found_service.distribution_status) + if found_service.name == SERVICENAME: + logger.info("Found Service %s in SDC",found_service.name) + service = found_service + break + + if not service: + logger.error("Service %s not found in SDC",SERVICENAME) + exit(1) + + logger.info("******** Check Service Subscription *******") + service_subscription = None + for service_sub in customer.service_subscriptions: + logger.debug("Service subscription %s is found",service_sub.service_type) + if service_sub.service_type == SERVICENAME: + logger.info("Service %s subscribed",SERVICENAME) + service_subscription = service_sub + break + + if not service_subscription: + logger.info("******** Subscribe Service *******") + customer.subscribe_service(SERVICENAME) + + logger.info("******** Get Tenant *******") + cloud_region = CloudRegion(cloud_owner=CLOUD_OWNER, cloud_region_id=CLOUD_REGION, + orchestration_disabled=True, in_maint=False) + tenant = None + for found_tenant in cloud_region.tenants: + logger.debug("Tenant %s found in %s_%s",found_tenant.name,cloud_region.cloud_owner,cloud_region.cloud_region_id) + if found_tenant.name == TENANT_NAME: + logger.info("Found my Tenant %s",found_tenant.name) + tenant = found_tenant + break + + if not tenant: + logger.error("tenant %s not found",TENANT_NAME) + exit(1) + + logger.info("******** Connect Service to Tenant *******") + service_subscription = None + for service_sub in customer.service_subscriptions: + logger.debug("Service subscription %s is found",service_sub.service_type) + if service_sub.service_type == SERVICENAME: + logger.info("Service %s subscribed",SERVICENAME) + service_subscription = service_sub + break + + if not service_subscription: + logger.error("Service subscription %s is not found",SERVICENAME) + exit(1) + + service_subscription.link_to_cloud_region_and_tenant(cloud_region, tenant) + + logger.info("******** Add Business Objects (OE, P, Pl, LoB) in VID *******") + vid_owning_entity = OwningEntity.create(OWNING_ENTITY) + vid_project = Project.create(PROJECT) + vid_platform = Platform.create(PLATFORM) + vid_line_of_business = LineOfBusiness.create(LINE_OF_BUSINESS) + + logger.info("******** Add Owning Entity in AAI *******") + owning_entity = None + for oe in AaiOwningEntity.get_all(): + if oe.name == vid_owning_entity.name: + owning_entity = oe + break + if not owning_entity: + logger.info("******** Owning Entity not existing: create *******") + owning_entity = AaiOwningEntity.create(vid_owning_entity.name, str(uuid4())) + + ########################################################################### + ######## VFModule parameters ############################################## + ########################################################################### + vfm_base=[ + InstantiationParameter(name="sec_group", value=TENANT_SEC_GROUP), + InstantiationParameter(name="public_net_id", value=PUBLIC_NET) + ] + + vfm_vsn=[ + InstantiationParameter(name="sec_group", value=TENANT_SEC_GROUP), + InstantiationParameter(name="public_net_id", value=PUBLIC_NET) + ] + + vfm_vfw=[ + InstantiationParameter(name="sec_group", value=TENANT_SEC_GROUP), + InstantiationParameter(name="public_net_id", value=PUBLIC_NET) + ] + + vfm_vpkg=[ + InstantiationParameter(name="sec_group", value=TENANT_SEC_GROUP), + InstantiationParameter(name="public_net_id", value=PUBLIC_NET) + ] + + base_paras=VfmoduleParameters("base_template",vfm_base) + vpkg_paras=VfmoduleParameters("vpkg",vfm_vpkg) + vsn_paras=VfmoduleParameters("vsn",vfm_vsn) + vfw_paras=VfmoduleParameters("vfw",vfm_vfw) + + ########################################################################### + ######## VNF parameters ################################################### + ########################################################################### + + vnf_vfw=[ + InstantiationParameter(name="onap_private_net_id", value=ONAP_PRIVATE_NET), + InstantiationParameter(name="onap_private_subnet_id", value=ONAP_PRIVATE_SUBNET), + InstantiationParameter(name="pub_key", value="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFBOB1Ea2yej68aqIQw10kEsVf+rNoxT39qrV8JvvTK2yhkniQka1t2oD9h6DlXOLM3HJ6nBegWjOasJmIbminKZ6wvmxZrDVFJXp9Sn1gni0vtEnlDgH14shRUrFDYO0PYjXRHoj7QXZMYxtAdFSbzGuCsaTLcV/xchLBQmqZ4AGhMIiYMfJJF+Ygy0lbgcVmT+8DH7kUUt8SAdh2rRsYFwpKANnQJyPV1dBNuTcD0OW1hEOhXnwqH28tjfb7uHJzTyGZlTmwTs544teTNz5B9L4yT3XiCAlMcaLOBMfBTKRIse+NkiTb+tc60JNnEYR6MqZoqTea/w+YBQaIMcil"), + InstantiationParameter(name="image_name", value=IMAGE_NAME), + InstantiationParameter(name="flavor_name", value=FLAVOR_NAME), + InstantiationParameter(name="sec_group", value=TENANT_SEC_GROUP), + InstantiationParameter(name="install_script_version", value="1.4.0-SNAPSHOT"), + InstantiationParameter(name="demo_artifacts_version", value="1.4.0-SNAPSHOT"), + InstantiationParameter(name="cloud_env", value=CLOUD_TYPE), + InstantiationParameter(name="public_net_id", value=PUBLIC_NET), + InstantiationParameter(name="aic-cloud-region", value=CLOUD_REGION) + ] + + vnf_paras=VnfParameters("vfwcds_VF", vnf_vfw, + [base_paras, vpkg_paras, vsn_paras, vfw_paras]) + + # You must define for each VNF and its vFModule the parameters, + # otherwise they stay empty. + # The matching critera are: + # - VnfParameters.name must match VNF ModelInstanceName + # (see above "vfwcds_VF") + # - VfmoduleParameters.name must match substring in vfModule "instanceName" + # (e.g. "vfwcds_vf0..VfwcdsVf..vsn..module-1") + logger.info("******** Instantiate Service *******") + + service_instantiation = ServiceInstantiation.instantiate_macro( + service, + cloud_region, + tenant, + customer, + owning_entity, + vid_project, + vid_line_of_business, + vid_platform, + service_instance_name=SERVICE_INSTANCE_NAME, + vnf_parameters=[vnf_paras] + ) + + if service_instantiation.wait_for_finish(): + logger.info("Success") + else: + logger.error("Instantiation failed, check logs") + exit(1) + + service_instance = None + for se in service_subscription.service_instances: + if se.instance_name == SERVICE_INSTANCE_NAME: + service_instance = se + break + if not service_instance: + logger.error("******** Service %s instantiation failed",SERVICE_INSTANCE_NAME) + exit(1) + + if SERVICE_DELETION is False: + logger.info("*****************************************") + logger.info("**** No Deletion requested, finished ****") + logger.info("*****************************************") + exit(0) + + logger.info("*******************************") + logger.info("**** SERVICE DELETION *********") + logger.info("*******************************") + time.sleep(30) + + logger.info("******** Delete Service %s *******",service_instance.name) + service_deletion = service_instance.delete() + + nb_try = 0 + nb_try_max = 30 + while not service_deletion.finished and nb_try < nb_try_max: + logger.info("Wait for Service deletion") + nb_try += 1 + time.sleep(10) + if service_deletion.finished: + logger.info("Service %s deleted",service_instance.name) + else: + logger.error("Service deletion %s failed",service_instance.name) + exit(1) + diff --git a/docs/examples/examples.rst b/docs/examples/examples.rst new file mode 100644 index 0000000..f6a43e3 --- /dev/null +++ b/docs/examples/examples.rst @@ -0,0 +1,13 @@ +Real life script examples +######################### + +.. toctree:: + :maxdepth: 4 + + e2e_vfw_instantiation + e2e_closed_loop_instantiation + e2e_vfw_macro_instantiation + e2e_net_instantiation + e2e_artifact_upload + e2e_basicvm_nomulticloud_instantiation + k8s_plugin_usage diff --git a/docs/examples/k8s_plugin_usage.rst b/docs/examples/k8s_plugin_usage.rst new file mode 100644 index 0000000..562dd92 --- /dev/null +++ b/docs/examples/k8s_plugin_usage.rst @@ -0,0 +1,94 @@ +E2E msb k8s plugin usage +######################## + + +.. code:: Python + + import logging + import os + + from onapsdk.msb.k8s import ( + Definition, + Instance, + ConnectivityInfo) + + logger = logging.getLogger("") + logger.setLevel(logging.DEBUG) + fh = logging.StreamHandler() + fh_formatter = logging.Formatter('%(asctime)s %(levelname)s %(lineno)d:%(filename)s(%(process)d) - %(message)s') + fh.setFormatter(fh_formatter) + logger.addHandler(fh) + + RB_NAME = "test_definition" + RB_VERSION = "ver_1" + DEFINITION_ARTIFACT_PATH = "artifacts\\vault-consul-dev.tar.gz" # FILL ME + PROFILE_NAME = "test-profile" + PROFILE_NAMESPACE = "test" + PROFILE_K8S_VERSION = "1.0" + PROFILE_ARTIFACT_PATH = "artifacts\\profile.tar.gz" # FILL ME + CLOUD_REGION_ID = "k8s_region_test" # FILL ME + CLOUD_OWNER = "CloudOwner" + KUBECONFIG_PATH = "artifacts\\kubeconfig" # FILL ME + MYPATH = os.path.dirname(os.path.realpath(__file__)) + + ######## Create new Definition ############################################ + definition = Definition.create(RB_NAME, RB_VERSION) + + ######## Upload artifact for created definition ########################### + definition_artifact_file = os.path.join(MYPATH, DEFINITION_ARTIFACT_PATH) + definition.upload_artifact(open(definition_artifact_file, 'rb').read()) + + ######## Get one Definition ############################################### + check_definition = Definition.get_definition_by_name_version(RB_NAME, + RB_VERSION) + + ######## Get all Definitions ############################################## + definitions = list(Definition.get_all()) + + ######## Create profile for Definition #################################### + profile = definition.create_profile(PROFILE_NAME, + PROFILE_NAMESPACE, + PROFILE_K8S_VERSION) + + ######## Upload artifact for created profile ############################## + profile_artifact_file = os.path.join(MYPATH, PROFILE_ARTIFACT_PATH) + profile.upload_artifact(open(profile_artifact_file, 'rb').read()) + + ######## Get one Profile ################################################## + check_profile = definition.get_profile_by_name(PROFILE_NAME) + + ######## Get all Profiles ################################################# + profiles = list(definition.get_all_profiles()) + + ######## Create Connectivity Info ######################################### + kubeconfig_file = os.path.join(MYPATH, KUBECONFIG_PATH) + conninfo = ConnectivityInfo.create(CLOUD_REGION_ID, + CLOUD_OWNER, + open(kubeconfig_file, 'rb').read()) + + ######## Instantiate Profile ############################################## + instance = Instance.create(CLOUD_REGION_ID, + profile.profile_name, + definition.rb_name, + definition.rb_version) + + ######## Get Instance by ID ############################################### + check_instance = Instance.get_by_id(instance.instance_id) + + ######## Get all Instances ################################################ + instances = list(Instance.get_all()) + + ######## Delete Instance ################################################## + instance.delete() + + ######## Check instance deletion ########################################## + instances = list(Instance.get_all()) + + ######## Delete Connectivity Info ######################################### + conninfo.delete() + + ######## Delete Profile ################################################### + profile.delete() + + ######## Delete Definition ################################################ + definition.delete() \ No newline at end of file diff --git a/docs/glossary.rst b/docs/glossary.rst new file mode 100644 index 0000000..2c95539 --- /dev/null +++ b/docs/glossary.rst @@ -0,0 +1,20 @@ +.. _glossary: + +Glossary +======== + +.. glossary:: + + Vendor + a vendor is the entity that provides the network function. + + VSP (Vendor Software Product) + a VSP is the package of the network function seen from ONAP Designer + + VF (Virtual Function) + a VF is the virtual function using the VSP. The VF will be used by + services. + + Service + a service is the model that can be instantiated. it's composed of one (or + several) Functions (VF or PNF). diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..a878cb8 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,34 @@ +.. _contents: + +ONAP SDK Documentation contents +=============================== + +.. toctree:: + :maxdepth: 2 + + description + usage/intro + usage/installation + usage/usage + examples/examples + development + + architecture + modules/modules + +.. include:: description.rst + +Indices and tables +================== + +.. only:: builder_html + + * :ref:`genindex` + * :ref:`modindex` + * :ref:`search` + * :ref:`glossary` + +.. only:: not builder_html + + * :ref:`modindex` + * :ref:`glossary` diff --git a/docs/modules/modules.rst b/docs/modules/modules.rst new file mode 100644 index 0000000..e66ce77 --- /dev/null +++ b/docs/modules/modules.rst @@ -0,0 +1,7 @@ +onapsdk +======= + +.. toctree:: + :maxdepth: 4 + + onapsdk diff --git a/docs/modules/onapsdk.aai.business.rst b/docs/modules/onapsdk.aai.business.rst new file mode 100644 index 0000000..1bec9ac --- /dev/null +++ b/docs/modules/onapsdk.aai.business.rst @@ -0,0 +1,109 @@ +onapsdk.aai.business package +============================ + +Submodules +---------- + +onapsdk.aai.business.customer module +------------------------------------ + +.. automodule:: onapsdk.aai.business.customer + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.business.instance module +------------------------------------ + +.. automodule:: onapsdk.aai.business.instance + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.business.line\_of\_business module +---------------------------------------------- + +.. automodule:: onapsdk.aai.business.line_of_business + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.business.network module +----------------------------------- + +.. automodule:: onapsdk.aai.business.network + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.business.owning\_entity module +------------------------------------------ + +.. automodule:: onapsdk.aai.business.owning_entity + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.business.platform module +------------------------------------ + +.. automodule:: onapsdk.aai.business.platform + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.business.pnf module +------------------------------- + +.. automodule:: onapsdk.aai.business.pnf + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.business.project module +----------------------------------- + +.. automodule:: onapsdk.aai.business.project + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.business.service module +----------------------------------- + +.. automodule:: onapsdk.aai.business.service + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.business.sp\_partner module +--------------------------------------- + +.. automodule:: onapsdk.aai.business.sp_partner + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.business.vf\_module module +-------------------------------------- + +.. automodule:: onapsdk.aai.business.vf_module + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.business.vnf module +------------------------------- + +.. automodule:: onapsdk.aai.business.vnf + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.aai.business + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.aai.cloud_infrastructure.rst b/docs/modules/onapsdk.aai.cloud_infrastructure.rst new file mode 100644 index 0000000..307676b --- /dev/null +++ b/docs/modules/onapsdk.aai.cloud_infrastructure.rst @@ -0,0 +1,37 @@ +onapsdk.aai.cloud\_infrastructure package +========================================= + +Submodules +---------- + +onapsdk.aai.cloud\_infrastructure.cloud\_region module +------------------------------------------------------ + +.. automodule:: onapsdk.aai.cloud_infrastructure.cloud_region + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.cloud\_infrastructure.complex module +------------------------------------------------ + +.. automodule:: onapsdk.aai.cloud_infrastructure.complex + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.cloud\_infrastructure.tenant module +----------------------------------------------- + +.. automodule:: onapsdk.aai.cloud_infrastructure.tenant + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.aai.cloud_infrastructure + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.aai.rst b/docs/modules/onapsdk.aai.rst new file mode 100644 index 0000000..161e291 --- /dev/null +++ b/docs/modules/onapsdk.aai.rst @@ -0,0 +1,46 @@ +onapsdk.aai package +=================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + onapsdk.aai.business + onapsdk.aai.cloud_infrastructure + +Submodules +---------- + +onapsdk.aai.aai\_element module +------------------------------- + +.. automodule:: onapsdk.aai.aai_element + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.bulk module +----------------------- + +.. automodule:: onapsdk.aai.bulk + :members: + :undoc-members: + :show-inheritance: + +onapsdk.aai.service\_design\_and\_creation module +------------------------------------------------- + +.. automodule:: onapsdk.aai.service_design_and_creation + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.aai + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.cds.rst b/docs/modules/onapsdk.cds.rst new file mode 100644 index 0000000..c0b8d51 --- /dev/null +++ b/docs/modules/onapsdk.cds.rst @@ -0,0 +1,53 @@ +onapsdk.cds package +=================== + +Submodules +---------- + +onapsdk.cds.blueprint module +---------------------------- + +.. automodule:: onapsdk.cds.blueprint + :members: + :undoc-members: + :show-inheritance: + +onapsdk.cds.blueprint\_model module +----------------------------------- + +.. automodule:: onapsdk.cds.blueprint_model + :members: + :undoc-members: + :show-inheritance: + +onapsdk.cds.blueprint\_processor module +--------------------------------------- + +.. automodule:: onapsdk.cds.blueprint_processor + :members: + :undoc-members: + :show-inheritance: + +onapsdk.cds.cds\_element module +------------------------------- + +.. automodule:: onapsdk.cds.cds_element + :members: + :undoc-members: + :show-inheritance: + +onapsdk.cds.data\_dictionary module +----------------------------------- + +.. automodule:: onapsdk.cds.data_dictionary + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.cds + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.clamp.rst b/docs/modules/onapsdk.clamp.rst new file mode 100644 index 0000000..65d9888 --- /dev/null +++ b/docs/modules/onapsdk.clamp.rst @@ -0,0 +1,29 @@ +onapsdk.clamp package +===================== + +Submodules +---------- + +onapsdk.clamp.clamp\_element module +----------------------------------- + +.. automodule:: onapsdk.clamp.clamp_element + :members: + :undoc-members: + :show-inheritance: + +onapsdk.clamp.loop\_instance module +----------------------------------- + +.. automodule:: onapsdk.clamp.loop_instance + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.clamp + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.configuration.rst b/docs/modules/onapsdk.configuration.rst new file mode 100644 index 0000000..4833ad3 --- /dev/null +++ b/docs/modules/onapsdk.configuration.rst @@ -0,0 +1,29 @@ +onapsdk.configuration package +============================= + +Submodules +---------- + +onapsdk.configuration.global\_settings module +--------------------------------------------- + +.. automodule:: onapsdk.configuration.global_settings + :members: + :undoc-members: + :show-inheritance: + +onapsdk.configuration.loader module +----------------------------------- + +.. automodule:: onapsdk.configuration.loader + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.configuration + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.cps.rst b/docs/modules/onapsdk.cps.rst new file mode 100644 index 0000000..a65d0d4 --- /dev/null +++ b/docs/modules/onapsdk.cps.rst @@ -0,0 +1,45 @@ +onapsdk.cps package +=================== + +Submodules +---------- + +onapsdk.cps.anchor module +------------------------- + +.. automodule:: onapsdk.cps.anchor + :members: + :undoc-members: + :show-inheritance: + +onapsdk.cps.cps\_element module +------------------------------- + +.. automodule:: onapsdk.cps.cps_element + :members: + :undoc-members: + :show-inheritance: + +onapsdk.cps.dataspace module +---------------------------- + +.. automodule:: onapsdk.cps.dataspace + :members: + :undoc-members: + :show-inheritance: + +onapsdk.cps.schemaset module +---------------------------- + +.. automodule:: onapsdk.cps.schemaset + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.cps + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.dmaap.rst b/docs/modules/onapsdk.dmaap.rst new file mode 100644 index 0000000..dc17d2e --- /dev/null +++ b/docs/modules/onapsdk.dmaap.rst @@ -0,0 +1,29 @@ +onapsdk.dmaap package +===================== + +Submodules +---------- + +onapsdk.dmaap.dmaap module +-------------------------- + +.. automodule:: onapsdk.dmaap.dmaap + :members: + :undoc-members: + :show-inheritance: + +onapsdk.dmaap.dmaap\_service module +----------------------------------- + +.. automodule:: onapsdk.dmaap.dmaap_service + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.dmaap + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.msb.k8s.rst b/docs/modules/onapsdk.msb.k8s.rst new file mode 100644 index 0000000..aaba76a --- /dev/null +++ b/docs/modules/onapsdk.msb.k8s.rst @@ -0,0 +1,37 @@ +onapsdk.msb.k8s package +======================= + +Submodules +---------- + +onapsdk.msb.k8s.connectivity\_info module +----------------------------------------- + +.. automodule:: onapsdk.msb.k8s.connectivity_info + :members: + :undoc-members: + :show-inheritance: + +onapsdk.msb.k8s.definition module +--------------------------------- + +.. automodule:: onapsdk.msb.k8s.definition + :members: + :undoc-members: + :show-inheritance: + +onapsdk.msb.k8s.instance module +------------------------------- + +.. automodule:: onapsdk.msb.k8s.instance + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.msb.k8s + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.msb.rst b/docs/modules/onapsdk.msb.rst new file mode 100644 index 0000000..eeea5b7 --- /dev/null +++ b/docs/modules/onapsdk.msb.rst @@ -0,0 +1,45 @@ +onapsdk.msb package +=================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + onapsdk.msb.k8s + +Submodules +---------- + +onapsdk.msb.esr module +---------------------- + +.. automodule:: onapsdk.msb.esr + :members: + :undoc-members: + :show-inheritance: + +onapsdk.msb.msb\_service module +------------------------------- + +.. automodule:: onapsdk.msb.msb_service + :members: + :undoc-members: + :show-inheritance: + +onapsdk.msb.multicloud module +----------------------------- + +.. automodule:: onapsdk.msb.multicloud + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.msb + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.nbi.rst b/docs/modules/onapsdk.nbi.rst new file mode 100644 index 0000000..b0a68b5 --- /dev/null +++ b/docs/modules/onapsdk.nbi.rst @@ -0,0 +1,21 @@ +onapsdk.nbi package +=================== + +Submodules +---------- + +onapsdk.nbi.nbi module +---------------------- + +.. automodule:: onapsdk.nbi.nbi + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.nbi + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.rst b/docs/modules/onapsdk.rst new file mode 100644 index 0000000..77e5575 --- /dev/null +++ b/docs/modules/onapsdk.rst @@ -0,0 +1,65 @@ +onapsdk package +=============== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + onapsdk.aai + onapsdk.cds + onapsdk.clamp + onapsdk.configuration + onapsdk.dmaap + onapsdk.msb + onapsdk.nbi + onapsdk.sdc + onapsdk.sdnc + onapsdk.so + onapsdk.utils + onapsdk.ves + onapsdk.vid + +Submodules +---------- + +onapsdk.constants module +------------------------ + +.. automodule:: onapsdk.constants + :members: + :undoc-members: + :show-inheritance: + +onapsdk.exceptions module +------------------------- + +.. automodule:: onapsdk.exceptions + :members: + :undoc-members: + :show-inheritance: + +onapsdk.onap\_service module +---------------------------- + +.. automodule:: onapsdk.onap_service + :members: + :undoc-members: + :show-inheritance: + +onapsdk.version module +---------------------- + +.. automodule:: onapsdk.version + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.sdc.rst b/docs/modules/onapsdk.sdc.rst new file mode 100644 index 0000000..5ba5ec3 --- /dev/null +++ b/docs/modules/onapsdk.sdc.rst @@ -0,0 +1,101 @@ +onapsdk.sdc package +=================== + +Submodules +---------- + +onapsdk.sdc.category\_management module +--------------------------------------- + +.. automodule:: onapsdk.sdc.category_management + :members: + :undoc-members: + :show-inheritance: + +onapsdk.sdc.component module +---------------------------- + +.. automodule:: onapsdk.sdc.component + :members: + :undoc-members: + :show-inheritance: + +onapsdk.sdc.pnf module +---------------------- + +.. automodule:: onapsdk.sdc.pnf + :members: + :undoc-members: + :show-inheritance: + +onapsdk.sdc.properties module +----------------------------- + +.. automodule:: onapsdk.sdc.properties + :members: + :undoc-members: + :show-inheritance: + +onapsdk.sdc.sdc\_element module +------------------------------- + +.. automodule:: onapsdk.sdc.sdc_element + :members: + :undoc-members: + :show-inheritance: + +onapsdk.sdc.sdc\_resource module +-------------------------------- + +.. automodule:: onapsdk.sdc.sdc_resource + :members: + :undoc-members: + :show-inheritance: + +onapsdk.sdc.service module +-------------------------- + +.. automodule:: onapsdk.sdc.service + :members: + :undoc-members: + :show-inheritance: + +onapsdk.sdc.vendor module +------------------------- + +.. automodule:: onapsdk.sdc.vendor + :members: + :undoc-members: + :show-inheritance: + +onapsdk.sdc.vf module +--------------------- + +.. automodule:: onapsdk.sdc.vf + :members: + :undoc-members: + :show-inheritance: + +onapsdk.sdc.vl module +--------------------- + +.. automodule:: onapsdk.sdc.vl + :members: + :undoc-members: + :show-inheritance: + +onapsdk.sdc.vsp module +---------------------- + +.. automodule:: onapsdk.sdc.vsp + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.sdc + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.sdnc.rst b/docs/modules/onapsdk.sdnc.rst new file mode 100644 index 0000000..2a87163 --- /dev/null +++ b/docs/modules/onapsdk.sdnc.rst @@ -0,0 +1,29 @@ +onapsdk.sdnc package +==================== + +Submodules +---------- + +onapsdk.sdnc.preload module +--------------------------- + +.. automodule:: onapsdk.sdnc.preload + :members: + :undoc-members: + :show-inheritance: + +onapsdk.sdnc.sdnc\_element module +--------------------------------- + +.. automodule:: onapsdk.sdnc.sdnc_element + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.sdnc + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.so.rst b/docs/modules/onapsdk.so.rst new file mode 100644 index 0000000..eae1eda --- /dev/null +++ b/docs/modules/onapsdk.so.rst @@ -0,0 +1,45 @@ +onapsdk.so package +================== + +Submodules +---------- + +onapsdk.so.deletion module +-------------------------- + +.. automodule:: onapsdk.so.deletion + :members: + :undoc-members: + :show-inheritance: + +onapsdk.so.instantiation module +------------------------------- + +.. automodule:: onapsdk.so.instantiation + :members: + :undoc-members: + :show-inheritance: + +onapsdk.so.so\_db\_adapter module +--------------------------------- + +.. automodule:: onapsdk.so.so_db_adapter + :members: + :undoc-members: + :show-inheritance: + +onapsdk.so.so\_element module +----------------------------- + +.. automodule:: onapsdk.so.so_element + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.so + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.utils.rst b/docs/modules/onapsdk.utils.rst new file mode 100644 index 0000000..cbb4c73 --- /dev/null +++ b/docs/modules/onapsdk.utils.rst @@ -0,0 +1,53 @@ +onapsdk.utils package +===================== + +Submodules +---------- + +onapsdk.utils.configuration module +---------------------------------- + +.. automodule:: onapsdk.utils.configuration + :members: + :undoc-members: + :show-inheritance: + +onapsdk.utils.headers\_creator module +------------------------------------- + +.. automodule:: onapsdk.utils.headers_creator + :members: + :undoc-members: + :show-inheritance: + +onapsdk.utils.jinja module +-------------------------- + +.. automodule:: onapsdk.utils.jinja + :members: + :undoc-members: + :show-inheritance: + +onapsdk.utils.mixins module +--------------------------- + +.. automodule:: onapsdk.utils.mixins + :members: + :undoc-members: + :show-inheritance: + +onapsdk.utils.tosca\_file\_handler module +----------------------------------------- + +.. automodule:: onapsdk.utils.tosca_file_handler + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.ves.rst b/docs/modules/onapsdk.ves.rst new file mode 100644 index 0000000..51a4c6b --- /dev/null +++ b/docs/modules/onapsdk.ves.rst @@ -0,0 +1,29 @@ +onapsdk.ves package +=================== + +Submodules +---------- + +onapsdk.ves.ves module +---------------------- + +.. automodule:: onapsdk.ves.ves + :members: + :undoc-members: + :show-inheritance: + +onapsdk.ves.ves\_service module +------------------------------- + +.. automodule:: onapsdk.ves.ves_service + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.ves + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/modules/onapsdk.vid.rst b/docs/modules/onapsdk.vid.rst new file mode 100644 index 0000000..36a6705 --- /dev/null +++ b/docs/modules/onapsdk.vid.rst @@ -0,0 +1,21 @@ +onapsdk.vid package +=================== + +Submodules +---------- + +onapsdk.vid.vid module +---------------------- + +.. automodule:: onapsdk.vid.vid + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: onapsdk.vid + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/usage/installation.rst b/docs/usage/installation.rst new file mode 100644 index 0000000..c324c96 --- /dev/null +++ b/docs/usage/installation.rst @@ -0,0 +1,32 @@ +Installation +############ + + + +Installing with pip +-------------------- + +.. code:: shell + + $ pip install onapsdk + + +Customize the configuration +--------------------------- + +You can customize the global settings of onapsdk by creating an environment +variable ONAP_PYTHON_SDK_SETTINGS and a file my_settings.py. + +By default the global settings are retrieved from the file located in +src/onapsdk/configuration/global_settings.py. You can create your own customized +configuration file and reference it through the environement variable. +You can thus copy/paste the existing global_settings.py file, rename it as +my_settings.py, adapt it with your favorite editor and export the environnement +variable accordingly. + +It can be useful to move from a nodeport to an an ingress based configuration +or test different API versions. + + .. code:: shell + + $ export ONAP_PYTHON_SDK_SETTINGS="onapsdk.configuration.my_settings" diff --git a/docs/usage/intro.rst b/docs/usage/intro.rst new file mode 100644 index 0000000..7031c13 --- /dev/null +++ b/docs/usage/intro.rst @@ -0,0 +1,16 @@ +Introduction +############ + +It *should* be simple to use. +Once you have installed the Python module, few lines of code are needed to +onboard a Service: + +.. code:: Python + + from onapsdk.vf import Vf + from onapsdk.service import Service + + # We assume here that the VF has been already onboarded + vf = VF(name="myVF") + service = Service(name="myService", resources=[vf]) + service.onboard() diff --git a/docs/usage/usage.rst b/docs/usage/usage.rst new file mode 100644 index 0000000..4a1dc9d --- /dev/null +++ b/docs/usage/usage.rst @@ -0,0 +1,14 @@ +Usage +##### + +A minimum knowledge of ONAP is needed, especially on the onboarding part. + +.. toctree:: + :maxdepth: 2 + + usage/cloud_configuration + usage/design_time + usage/instantiation + usage/deletion + usage/cds + usage/cps diff --git a/docs/usage/usage/cds.rst b/docs/usage/usage/cds.rst new file mode 100644 index 0000000..b516c73 --- /dev/null +++ b/docs/usage/usage/cds.rst @@ -0,0 +1,173 @@ +CDS +### + +Preparation for CDS tests +------------------------- + +To enable CDS Enrichment in an ONAP Frankfurt environment the NodePort 30449 +for the CDS Blueprint Processor API service needs to be opened + +#. Check existing CDS Services: + + .. code-block:: sh + + ubuntu@control01:~$ kubectl get service -n onap|grep cds-blueprints-processor-http + cds-blueprints-processor-http ClusterIP 10.43.101.198 8080/TCP + +#. Change NodePort to CDS cds-blueprints-processor-http + + Add the "nodePort" under "ports" section + and change "type" from "ClusterIP" to "NodePort" + + .. code-block:: sh + + ubuntu@control01:~$ kubectl edit service cds-blueprints-processor-http -n onap + + apiVersion: v1 + kind: Service + metadata: + creationTimestamp: "2020-07-23T02:57:36Z" + labels: + app: cds-blueprints-processor + chart: cds-blueprints-processor-6.0.0 + heritage: Tiller + release: onap + name: cds-blueprints-processor-http + namespace: onap + resourceVersion: "10256" + selfLink: /api/v1/namespaces/onap/services/cds-blueprints-processor-http + uid: 6f065c03-4563-4d64-b6f5-a8892226c909 + spec: + clusterIP: 10.43.101.198 + ports: + - name: blueprints-processor-http + nodePort: 30449 -> add line + port: 8080 + protocol: TCP + targetPort: 8080 + selector: + app: cds-blueprints-processor + release: onap + sessionAffinity: None + type: ClusterIP -> change to NodePort + status: + loadBalancer: {} + +#. Verify NodePort to CDS cds-blueprints-processor-http + + .. code-block:: sh + + ubuntu@control01:~$ kubectl get service -n onap|grep cds-blueprints-processor-http + cds-blueprints-processor-http NodePort 10.43.101.198 8080:30449/TCP + +#. Load ModelType via Bootstrap + + .. code-block:: sh + + curl --location --request POST 'http://:30449/api/v1/blueprint-model/bootstrap' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Basic Y2NzZGthcHBzOmNjc2RrYXBwcw==' \ + --data-raw '{ + "loadModelType" : true, + "loadResourceDictionary" : false, + "loadCBA" : false + }' + + +Load blueprint from file +------------------------ + +.. code:: Python + + from onapsdk.cds import Blueprint + blueprint = Blueprint.load_from_file("<< path to CBA file >>") + +Enrich blueprint and save +------------------------- + +.. code:: Python + + enriched_blueprint = blueprint.enrich() + enriched_blueprint.save("<< path to dest file >>") + +Publish blueprint +----------------- + +.. code:: Python + + enriched_blueprint.publish() + +Generate data dictionary from blueprint +--------------------------------------- + +The method to generate data dictionaries based on the blueprint mappings. As the result it returns a data dictionaries set +with valid structure, but some additional actions may be needed. Data dictionary input has to be filled by the user +if the type is neither "source-input" nor "source-default". Things, which are needed to be filled are marked by `<< FILL >>` mark. +If the blueprint you are using has only "source-input" or "source-default" input types, the generated data dictionary set is +ready to upload to CDS. + +.. code:: Python + + generated_dd: DataDictionarySet = blueprint.get_data_dictionaries() + generated_dd.save_to_file("<< path to dest file >>") + +Load data dictionary set from file +---------------------------------- + +.. code:: Python + + from onapsdk.cds import DataDictionarySet + dd_set = DataDictionarySet.load_from_file("<< path to dd file >>") + +Upload data dictionary set +-------------------------- + +.. code:: Python + + dd_set.upload() + +Retrieve Blueprint Models from CDS +-------------------------- + +#. All + +.. code:: Python + + from onapsdk.cds import BlueprintModel + all_blueprint_models = BlueprintModel.get_all() + +#. Selected by id of Blueprint Model + +.. code:: Python + + blueprint_model = BlueprintModel.get_by_id(blueprint_model_id='11111111-1111-1111-1111-111111111111') + +#. Selected by name and version of Blueprint Model + +.. code:: Python + + blueprint_model = BlueprintModel.get_by_name_and_version(blueprint_name='test_name', blueprint_version='1.0.0') + +Delete Blueprint Model +-------------------------- + +.. code:: Python + + blueprint_model.delete() + +Download Blueprint Model +-------------------------- + +.. code:: Python + + blueprint_model.save(dst_file_path='/tmp/blueprint.zip') + + +Get Blueprint object for Blueprint Model +-------------------------- + +After that, all operation for blueprint object, like execute blueprint workflow etc. can be executed. + +.. code:: Python + + blueprint = blueprint_model.get_blueprint() diff --git a/docs/usage/usage/cloud_configuration.rst b/docs/usage/usage/cloud_configuration.rst new file mode 100644 index 0000000..b048c0a --- /dev/null +++ b/docs/usage/usage/cloud_configuration.rst @@ -0,0 +1,210 @@ +Cloud configuration +################### + +Create a complex +---------------- + +.. code:: Python + + from onapsdk.aai.cloud_infrastructure import Complex + cmplx = Complex.create( + physical_location_id=COMPLEX_PHYSICAL_LOCATION_ID, + data_center_code=COMPLEX_DATA_CENTER_CODE, + name=COMPLEX_PHYSICAL_LOCATION_ID + ) + +Create cloud region +------------------- + +.. code:: Python + + from onapsdk.aai.cloud_infrastructure import CloudRegion + cloud_region = CloudRegion.create( + cloud_owner=CLOUD_OWNER, + cloud_region_id=CLOUD_REGION, + orchestration_disabled=False, + in_maint=False, + cloud_type=CLOUD_TYPE, + cloud_region_version=CLOUD_REGION_VERSION + ) + +Link cloud region to complex +---------------------------- + +.. code:: Python + + from onapsdk.aai.cloud_infrastructure import CloudRegion, Complex + # We assume that complex has been already created + cmplx = Complex( + physical_location_id=COMPLEX_PHYSICAL_LOCATION_ID, + name=COMPLEX_PHYSICAL_LOCATION_ID + ) + cloud_region = CloudRegion.create( + cloud_owner=CLOUD_OWNER, + cloud_region_id=CLOUD_REGION, + orchestration_disabled=False, + in_maint=False, + cloud_type=CLOUD_TYPE, + cloud_region_version=CLOUD_REGION_VERSION + ) + cloud_region.link_to_complex(cmplx) + +Add ESR Info to cloud region +---------------------------- + +.. code:: Python + + from uuid import uuid4 + from onapsdk.aai.cloud_infrastructure import CloudRegion + # We assume that cloud region has been already created + cloud_region = CloudRegion.get_by_id( + cloud_owner=CLOUD_OWNER, + cloud_region_id=CLOUD_REGION + ) + cloud_region.add_esr_system_info( + esr_system_info_id=str(uuid4()), + user_name=VIM_USERNAME, + password=VIM_PASSWORD, + system_type=CLOUD_TYPE, + service_url=VIM_SERVICE_URL, + cloud_domain=CLOUD_DOMAIN + ) + +Register cloud to MultiCloud +---------------------------- + +.. code:: Python + + from uuid import uuid4 + from onapsdk.aai.cloud_infrastructure import CloudRegion + # We assume that cloud region has been already created + cloud_region = CloudRegion.get_by_id( + cloud_owner=CLOUD_OWNER, + cloud_region_id=CLOUD_REGION + ) + cloud_region.add_esr_system_info( + esr_system_info_id=str(uuid4()), + user_name=VIM_USERNAME, + password=VIM_PASSWORD, + system_type=CLOUD_TYPE, + service_url=VIM_SERVICE_URL, + cloud_domain=CLOUD_DOMAIN + ) + cloud_region.register_to_multicloud() + +Get cloud region tenant +----------------------- + +.. code:: Python + + # We assume that cloud region has been already created + # and connected to multicloud + from onapsdk.aai.cloud_infrastructure import CloudRegion + cloud_region = CloudRegion.get_by_id( + cloud_owner=CLOUD_OWNER, + cloud_region_id=CLOUD_REGION + ) + try: + tenant = next(cloud_region.tenant) + except StopIteration + # No Tenant found in cloud region + +Create customer +--------------- + +.. code:: Python + + from onapsdk.aai.business import Customer + customer = Customer.create(GLOBAL_CUSTOMER_ID, GLOBAL_CUSTOMER_ID, "INFRA") + +Create customer service subscription +------------------------------------ + +.. code:: Python + + # We assume here that the service has been already onboarded + # and customer created + from onapsdk.aai.business import Customer + + customer = Customer.get_by_global_customer_id(GLOBAL_CUSTOMER_ID) + customer.subscribe_service("service_type") + + # Service subscriptions can be also created during Customer + # creation + from onapsdk.aai.business import Customer + + customer = Customer.create(GLOBAL_CUSTOMER_ID, GLOBAL_CUSTOMER_ID, "INFRA", service_subscriptions=["service_type"]) + +Connect service subscription to cloud region and tenant +------------------------------------------------------- + +.. code:: Python + + # We assume here that the service subscription has been already created + # and cloud region has a tenant + from onapsdk.aai.business import Customer + from onapsdk.aai.cloud_infrastructure import CloudRegion, Tenant + + customer = Customer.get_by_global_customer_id(GLOBAL_CUSTOMER_ID) + service_subscription = next(customer.service_subscriptions) + cloud_region = CloudRegion.get_by_id( + cloud_owner=CLOUD_OWNER, + cloud_region_id=CLOUD_REGION + ) + tenant = next(cloud_region.tenants) + service_subscription.link_to_cloud_region_and_tenant(cloud_region, tenant) + +Add Cloud SIte entry to SO Catalog DB +------------------------------------------------------- + +.. code:: Python + + from onapsdk.so.so_db_adapter import IdentityService, SoDbAdapter + + identity_service = IdentityService(identity_id="mc_test_identity_1_KEYSTONE", + url="http://test:5000/v3", + mso_id="test_user", + mso_pass="test_password_encrypted", + roject_domain_name="Default", + user_domain_name="Default", + identity_server_type="KEYSTONE_V3") + response = SoDbAdapter.add_cloud_site(cloud_region_id="test_region_1", + complex_id="test_clli_1", + identity_service=identity_service, + orchestrator="NULL") + +Use A&AI bulk API (experimental) +-------------------------------- + +.. code:: Python + + from onapsdk.aai.bulk import AaiBulk, AaiBulkRequest + from onapsdk.aai.cloud_infrastructure.cloud_region import CloudRegion + from onapsdk.utils.jinja import jinja_env + + + for resp in AaiBulk.single_transaction( + [ + AaiBulkRequest( + action="put", + uri=f"/cloud-infrastructure/cloud-regions/cloud-region/aai_bulk_test_cloud_owner_1/aai_bulk_test_cloud_region_id_1", + body=jinja_env().get_template("cloud_region_create.json.j2").render(cloud_region=CloudRegion( + cloud_owner="aai_bulk_test_cloud_owner_1", + cloud_region_id="aai_bulk_test_cloud_region_id_1", + orchestration_disabled=False, + in_maint=False + )) + ), + AaiBulkRequest( + action="put", + uri=f"/cloud-infrastructure/cloud-regions/cloud-region/aai_bulk_test_cloud_owner_2/aai_bulk_test_cloud_region_id_2", + body=jinja_env().get_template("cloud_region_create.json.j2").render(cloud_region=CloudRegion( + cloud_owner="aai_bulk_test_cloud_owner_2", + cloud_region_id="aai_bulk_test_cloud_region_id_2", + orchestration_disabled=False, + in_maint=False + )) + ) + ] + ): + print(resp) diff --git a/docs/usage/usage/cps.rst b/docs/usage/usage/cps.rst new file mode 100644 index 0000000..0a19b29 --- /dev/null +++ b/docs/usage/usage/cps.rst @@ -0,0 +1,32 @@ +CPS +### + +Create dataspace +---------------- + +.. code:: Python + + from onapsdk.cps import Dataspace + dataspace: Dataspace = Dataspace.create(dataspace_name="test_dataspace") + + +Create schema set +---------------- + +.. code:: Python + + from onapsdk.cps import Dataspace, SchemaSet + dataspace: Dataspace = Dataspace(name="test_dataspace") + with Path("schema_set_zip_file.zip").open("rb") as zip_file: + schema_set: SchemaSet = dataspace.create_schema_set("test_schemaset", zip_file) + + +Create anchor +------------- + +.. code:: Python + + from onapsdk.cps import Anchor, Dataspace, SchemaSet + dataspace: Dataspace = Dataspace(name="test_dataspace") + schema_set: SchemaSet = dataspace.get_schema_set("test_schemaset") + anchor: Anchor = dataspace.create_anchor(schema_set, "test_anchor") diff --git a/docs/usage/usage/deletion.rst b/docs/usage/usage/deletion.rst new file mode 100644 index 0000000..c5e5025 --- /dev/null +++ b/docs/usage/usage/deletion.rst @@ -0,0 +1,28 @@ +Instantiated resources deletion +############################### + +Service, vnf and vf module deletion +----------------------------------- + +.. code:: Python + + from onapsdk.aai.business import Customer + + customer = Customer.get_by_global_customer_id(GLOBAL_CUSTOMER_ID) + service_subscription = next(customer.service_subscriptions) + service_instance = service_subscription.get_service_instance_by_name(SERVICE_INSTANCE_NAME) + for vnf_instance in service_instance.vnf_instances: + for vf_module_instance in vnf_instance.vf_modules: + vf_module_deletion_request = vf_module_instance.delete() + while not vf_module_deletion.finished: + time.sleep(10) + + vnf_instance_deletion_request = vnf_instance.delete() + while not vnf_instance_deletion_request.finished: + time.sleep(10) + + service_instance_deletion_request = service_instance.delete() + if service_instance_deletion_request.wait_for_finish(): + print("Service instance deleted") + else: + print("Service deletion failed, check logs" diff --git a/docs/usage/usage/design_time.rst b/docs/usage/usage/design_time.rst new file mode 100644 index 0000000..5990258 --- /dev/null +++ b/docs/usage/usage/design_time.rst @@ -0,0 +1,351 @@ +Design time +########### + +Onboard a Vendor +---------------- + +.. code:: Python + + from onapsdk.vendor import Vendor + vendor = Vendor(name="myVendor") + vendor.onboard() + +Onboard a VSP +------------- + +You will need the package of the VSP to onboard. + +.. code:: Python + + from onapsdk.sdc.vendor import Vendor + from onapsdk.sdc.vsp import Vsp + + # We assume here that the Vendor has been already onboarded + vendor = Vendor(name="myVendor") + vendor.onboard() + vsp = Vsp(name="myVSP", vendor=vendor, package=open(PATH_TO_PACKAGE, 'rb')) + vsp.onboard() + +Create new VSP version +---------------------- + +You will need the package of the VSP to update. + +.. code:: Python + + from onapsdk.sdc.vendor import Vendor + from onapsdk.sdc.vsp import Vsp + + # We assume here that the Vendor has been already onboarded + vsp = Vsp(name="myVSP") + vsp.create_new_version() + vsp.update_package(open(PATH_TO_PACKAGE, 'rb')) + vsp.onboard() + +Onboard a VF +------------ + +.. code:: Python + + from onapsdk.sdc.vsp import Vsp + from onapsdk.sdc.vf import Vf + + # We assume here that the VSP has been already onboarded + vsp = Vsp(name="myVSP") + vf = Vf(name="myVF", vsp=vsp) + vf.onboard() + +Onboard a VF with properties assignement +---------------------------------------- + +.. code:: Python + + from onapsdk.sdc.properties import Property + from onapsdk.sdc.vsp import Vsp + from onapsdk.sdc.vf import Vf + + # We assume here that the VSP has been already onboarded + vsp = Vsp(name="myVSP") + property_1 = Property( + name="prop1", + property_type="string", + value="test" + ) + property_2 = Property( + name="prop2", + property_type="integer" + ) + vf = Vf(name="myVF", + vsp=vsp, + properties=[ + property_1, + property_2 + ], + inputs=[property_1]) + vf.onboard() + +Onboard a VF with Deployment Artifact +------------------------------------- + +.. code:: Python + + from onapsdk.sdc.properties import Property + from onapsdk.sdc.vsp import Vsp + from onapsdk.sdc.vf import Vf + + logger.info("******** Onboard Vendor *******") + vendor = Vendor(name="my_Vendor") + vendor.onboard() + + # We assume here that the VSP has been already onboarded + vsp = Vsp(name="myVSP") + + logger.info("******** Onboard VF *******") + vf = Vf(name="myVF") + vf.vsp = vsp + vf.create() + + logger.info("******** Upload Artifact *******") + vf.add_deployment_artifact(artifact_type="CONTROLLER_BLUEPRINT_ARCHIVE", + artifact_name="CBA.zip", + artifact_label="vfwcds", + artifact="dir/CBA_enriched.zip") + + vf.onboard() + +Onboard a VF with it's component's property input +------------------------------------------------- + +.. code:: Python + + from onapsdk.sdc.properties import ComponentProperty + from onapsdk.sdc.vsp import Vsp + from onapsdk.sdc.vf import Vf + from onapsdk.sdc.vfc import Vfc + + # We assume here that the VSP has been already onboarded + vsp = Vsp(name="myVSP") + + vfc = Vfc(name="AllottedResource") + + logger.info("******** Onboard VF *******") + vf = Vf(name="myVF") + vf.vsp = vsp + vf.create() + vf.add_resource(vfc) + vfc_comp = vf.get_component(vfc) + comp_prop = vfc_comp.get_property("min_instances") + comp_prop.value = 11 + vf.declare_input(comp_prop) + + vf.onboard() + +Onboard a PNF with VSP +---------------------- +.. code:: Python + + from onapsdk.sdc.pnf import Pnf + from onapsdk.sdc.vendor import Vendor + + logger.info("******** Onboard Vendor *******") + vendor = Vendor(name="my_Vendor") + vendor.onboard() + + # We assume here that the VSP has been already onboarded + vsp = Vsp(name="myVSP") + + logger.info("******** Onboard PNF *******") + pnf = PNF(name="myPNF") + pnf.vsp = vsp + pnf.onboard() + +Onboard a PNF with Deployment Artifact (without VSP) +---------------------------------------------------- +.. code:: Python + + from onapsdk.sdc.vendor import Vendor + from onapsdk.sdc.pnf import Pnf + + logger.info("******** Onboard Vendor *******") + vendor = Vendor(name="my_Vendor") + vendor.onboard() + + logger.info("******** Onboard PNF *******") + pnf = Pnf(name=PNF, vendor=vendor) + pnf.create() + + logger.info("******** Upload Artifact *******") + pnf.add_deployment_artifact(artifact_type=ARTIFACT_TYPE, + artifact_name=ARTIFACT_NAME, + artifact_label=ARTIFACT_LABEL, + artifact=ARTIFACT_FILE_PATH) + pnf.onboard() + +Onboard a Service +----------------- + +.. code:: Python + + from onapsdk.sdc.vf import Vf + from onapsdk.sdc.service import Service + + # We assume here that the VF has been already onboarded + vf = Vf(name="myVF") + service = Service(name="myService", resources=[vf]) + service.onboard() + +Onboard a Service with properties assignement +--------------------------------------------- + +.. code:: Python + + from onapsdk.sdc.properties import Property + from onapsdk.sdc.vf import Vf + from onapsdk.sdc.service import Service + + # We assume here that the VF has been already onboarded + vf = Vf(name="myVF") + property_1 = Property( + name="prop1", + property_type="string", + value="test" + ) + property_2 = Property( + name="prop2", + property_type="integer", + declare_input=True + ) + service = Service(name="myService", + resources=[vf], + properties=[ + property_1, + property_2 + ], + inputs=[property_1]) + service.onboard() + +Onboard a Service with Nested inputs +------------------------------------ + +.. code:: Python + + from onapsdk.sdc.properties import NestedInput + from onapsdk.sdc.vf import Vf + from onapsdk.sdc.service import Service + + # We assume here that the VF has been already onboarded + vf = Vf(name="myVF") + inp = vf.get_input("input_name_we_want_to_declare_in_service") + service = Service(name="myService", + resources=[vf], + inputs=[NestedInput(vf, inp)]) + service.onboard() + +Onboard a Service with VL +------------------------- + +.. code:: Python + + from onapsdk.sdc.vl import VL + from onapsdk.sdc.service import Service + + # No VF needed, but you need to be sure that Vl with given + # name exists in SDC + vl = Vl(name="Generic NeutronNet") + service = Service(name="myServiceWithVl", resources=[vl]) + service.onboard() + +Onboard a Service with custom category +-------------------------------------- + +.. code:: Python + + from onapsdk.sdc.category_management import ServiceCategory + from onapsdk.sdc.vf import Vf + from onapsdk.sdc.service import Service + + # Let's create a custom category + CATEGORY_NAME = "Python ONAP SDK category" + ServiceCategory.create(name=CATEGORY_NAME) + + # We assume here that the VF has been already onboarded + # Create a service with category we created few lines above + vf = Vf(name="myVF") + service = Service(name="myService", resources=[vf], category=CATEGORY_NAME) + service.onboard() + +Onboard an Artifact for an embedded VF +-------------------------------------- + +All SDC artifact types are supported + +.. code:: Python + + from onapsdk.service import Service + + # We assume here that the Service has been already onboarded + # with a Vnf + service = Service(name="myService") + # We load artifact data + data = open("{}/myArtifact.yaml".format(os.path.dirname(os.path.abspath(__file__))), 'rb').read() + # We add the artifact to the service Vnf + # + svc.add_artifact_to_vf(vnf_name="myVnf", + artifact_type="DCAE_INVENTORY_BLUEPRINT", + artifact_name="myArtifact.yaml", + artifact=data) + +Onboard a Service with Deployment Artifact +------------------------------------------ + +.. code:: Python + + from onapsdk.sdc.service import Service + + svc = Service(name="myService") + + logger.info("******** Upload Artifact *******") + svc.add_deployment_artifact(artifact_type="OTHER", + artifact_name="eMBB.zip", + artifact_label="embbcn", + artifact="dir/eMBB.zip") + + svc.onboard() + +Onboard a Service with a CBA blueprint for Macro Instantiation +-------------------------------------------------------------- + +.. code:: Python + + from onapsdk.sdc.service import Service, ServiceInstantiationType + + # Set CBA variables and artifact level + # Must match to values in the CBA TOSCA.meta file + SDNC_TEMPLATE_NAME = "vFW-CDS" + SDNC_TEMPLATE_VERSION = "1.0.0" + SDNC_ARTIFACT_NAME = "vnf" + + svc = Service(name="myService", + instantiation_type=ServiceInstantiationType.MACRO) + + svc.create() + + logger.info("*** add a VF, which includes a CBA blueprint ***") + svc.add_resource(vf) + + logger.info("******** Set SDNC properties for VF ********") + component = svc.get_component(vf) + prop = component.get_property("sdnc_model_version") + prop.value = SDNC_TEMPLATE_NAME + prop = component.get_property("sdnc_artifact_name") + prop.value = SDNC_ARTIFACT_NAME + prop = component.get_property("sdnc_model_name") + prop.value = SDNC_TEMPLATE_NAME + prop = component.get_property("controller_actor") + prop.value = "CDS" + prop = component.get_property("skip_post_instantiation_configuration") + prop.value = False + + logger.info("******** Onboard Service *******") + svc.checkin() + svc.onboard() diff --git a/docs/usage/usage/dmaap.rst b/docs/usage/usage/dmaap.rst new file mode 100644 index 0000000..ee62d04 --- /dev/null +++ b/docs/usage/usage/dmaap.rst @@ -0,0 +1,47 @@ +VES +### + +Preparation for DMAAP tests +------------------------- + +#. Check existing DMaap Services: + + .. code-block:: sh + + kubectl get service -n onap| grep mess + message-router NodePort 10.43.30.205 3905:31163/TCP,3904:32404/TCP + +#. If the port of Ves Service is different than 3904 you can change it corresponding to the installation instruction. + + +Remove all events from DMaap +--------------------------- + +.. code:: Python + + from onapsdk.dmaap.dmaap import Dmaap + response = Dmaap.reset_events() + +Get all events from DMaap +------------------------- + +.. code:: Python + + from onapsdk.dmaap.dmaap import Dmaap + response = Dmaap.get_all_events() + +Get events from specific topic from DMaap +------------------------- + +.. code:: Python + + from onapsdk.dmaap.dmaap import Dmaap + response = Dmaap.get_events_for_topic("fault") + +Get all topics from DMaap +------------------------- + +.. code:: Python + + from onapsdk.dmaap.dmaap import Dmaap + response = Dmaap.get_all_topics() \ No newline at end of file diff --git a/docs/usage/usage/instantiation.rst b/docs/usage/usage/instantiation.rst new file mode 100644 index 0000000..642439e --- /dev/null +++ b/docs/usage/usage/instantiation.rst @@ -0,0 +1,485 @@ +Instantiation +############# + +Create business objects +----------------------- + +.. code:: Python + + from onapsdk.vid import LineOfBusiness, OwningEntity, Platform, Project + + vid_owning_entity = OwningEntity.create(OWNING_ENTITY) + vid_project = Project.create(PROJECT) + vid_platform = Platform.create(PLATFORM) + vid_line_of_business = LineOfBusiness.create(LINE_OF_BUSINESS) + +Instantiate a service (ALaCarte) +-------------------------------- + +.. code:: Python + + import time + from onapsdk.aai.cloud_infrastructure import CloudRegion, Tenant + from onapsdk.aai.business import Customer + from onapsdk.service import Service + from onapsdk.vid import LineOfBusiness, OwningEntity, Platform, Project + from onapsdk.so.instantiation import ServiceInstantiation + + # We assume that: + # - service is onboarded, + # - cloud region, customer, owning_entity and project have been already created, + # - cloud region has at least one tenant + # - customer has service subscription + # - service subscription is connected with cloud region and tenant + SERVICE_INSTANCE_NAME = "vFW-AlaCarte-1" + + service = Service(name="myService") + customer = Customer.get_by_global_customer_id(GLOBAL_CUSTOMER_ID) + cloud_region = CloudRegion.get_by_id( + cloud_owner=CLOUD_OWNER, + cloud_region_id=CLOUD_REGION + ) + tenant = next(cloud_region.tenants) + vid_owning_entity = OwningEntity(OWNING_ENTITY) + owning_entity = AaiOwningEntity.get_by_owning_entity_name(OWNING_ENTITY) + vid_project = Project(PROJECT) + + service_instantiation = ServiceInstantiation.instantiate_so_ala_carte( + service, + cloud_region, + tenant, + customer, + owning_entity, + vid_project, + service_instance_name=SERVICE_INSTANCE_NAME + ) + service_instantiation.wait_for_finish(): + print("Success") + else: + print("Instantiation failed, check logs") + +Instantiate a service (Macro) +----------------------------- + +.. code:: Python + + import time + from onapsdk.aai.cloud_infrastructure import CloudRegion, Tenant + from onapsdk.aai.business import Customer + from onapsdk.service import Service + from onapsdk.vid import LineOfBusiness, OwningEntity, Platform, Project + from onapsdk.so.instantiation import ( + ServiceInstantiation, + VnfInstantiation, + InstantiationParameter, + VnfParameters, + VfmoduleParameters + ) + + ... + VSPNAME = "vfwcds_VS" + VFNAME = "vfwcds_VF" + ... + vf = Vf(name=VFNAME) + ... + + # We assume that: + # - service is onboarded, + # - cloud region, customer, owning_entity and project have been already created, + # - cloud region has at least one tenant + # - customer has service subscription + # - service subscription is connected with cloud region and tenant + SERVICE_INSTANCE_NAME = "vFW-Macro-1" + + service = Service(name="myMacroService") + customer = Customer.get_by_global_customer_id(GLOBAL_CUSTOMER_ID) + cloud_region = CloudRegion.get_by_id( + cloud_owner=CLOUD_OWNER, + cloud_region_id=CLOUD_REGION + ) + tenant = next(cloud_region.tenants) + vid_owning_entity = OwningEntity(OWNING_ENTITY) + owning_entity = AaiOwningEntity.get_by_owning_entity_name(OWNING_ENTITY) + vid_project = Project(PROJECT) + + ########################################################################### + ######## VFModule parameters ############################################## + ########################################################################### + vfm_base=[ + InstantiationParameter(name="sec_group", value=TENANT_SEC_GROUP), + InstantiationParameter(name="public_net_id", value=PUBLIC_NET) + ] + + vfm_vsn=[ + InstantiationParameter(name="sec_group", value=TENANT_SEC_GROUP), + InstantiationParameter(name="public_net_id", value=PUBLIC_NET) + ] + + vfm_vfw=[ + InstantiationParameter(name="sec_group", value=TENANT_SEC_GROUP), + InstantiationParameter(name="public_net_id", value=PUBLIC_NET) + ] + + vfm_vpkg=[ + InstantiationParameter(name="sec_group", value=TENANT_SEC_GROUP), + InstantiationParameter(name="public_net_id", value=PUBLIC_NET) + ] + + base_paras=VfmoduleParameters("base_template",vfm_base) + vpkg_paras=VfmoduleParameters("vpkg",vfm_vpkg) + vsn_paras=VfmoduleParameters("vsn",vfm_vsn) + vfw_paras=VfmoduleParameters("vfw",vfm_vfw) + + ########################################################################### + ######## VNF parameters ################################################### + ########################################################################### + + vnf_vfw=[ + InstantiationParameter(name="onap_private_net_id", value=ONAP_PRIVATE_NET), + InstantiationParameter(name="onap_private_subnet_id", value=ONAP_PRIVATE_SUBNET), + InstantiationParameter(name="pub_key", value="ssh-rsa AAAAB3NzaC1yc2EAA\ + AADAQABAAABAQDFBOB1Ea2yej68aqIQw10kEsVf+rNoxT39qrV8JvvTK2yhkniQka1t2oD9h6DlXOL\ + M3HJ6nBegWjOasJmIbminKZ6wvmxZrDVFJXp9Sn1gni0vtEnlDgH14shRUrFDYO0PYjXRHoj7QXZMY\ + xtAdFSbzGuCsaTLcV/xchLBQmqZ4AGhMIiYMfJJF+Ygy0lbgcVmT+8DH7kUUt8SAdh2rRsYFwpKANn\ + QJyPV1dBNuTcD0OW1hEOhXnwqH28tjfb7uHJzTyGZlTmwTs544teTNz5B9L4yT3XiCAlMcaLOBMfBT\ + KRIse+NkiTb+tc60JNnEYR6MqZoqTea/w+YBQaIMcil"), + InstantiationParameter(name="image_name", value=IMAGE_NAME), + InstantiationParameter(name="flavor_name", value=FLAVOR_NAME), + InstantiationParameter(name="sec_group", value=TENANT_SEC_GROUP), + InstantiationParameter(name="install_script_version", value="1.4.0-SNAPSHOT"), + InstantiationParameter(name="demo_artifacts_version", value="1.4.0-SNAPSHOT"), + InstantiationParameter(name="cloud_env", value=CLOUD_TYPE), + InstantiationParameter(name="public_net_id", value=PUBLIC_NET), + InstantiationParameter(name="aic-cloud-region", value=CLOUD_REGION) + ] + + vnf_paras=VnfParameters("vfwcds_VF", vnf_vfw, + [base_paras, vpkg_paras, vsn_paras, vfw_paras]) + + # You must define for each VNF and its vFModule the parameters, + # otherwise they stay empty. + # The matching critera are: + # - VnfParameters.name must match VNF ModelInstanceName + # (see above "vfwcds_VF") + # - VfmoduleParameters.name must match substring in vfModule "instanceName" + # (e.g. "vfwcds_vf0..VfwcdsVf..vsn..module-1") + + service_instantiation = ServiceInstantiation.instantiate_macro( + service, + cloud_region, + tenant, + customer, + owning_entity, + vid_project, + vid_line_of_business, + vid_platform, + service_instance_name=SERVICE_INSTANCE_NAME, + vnf_parameters=[vnf_paras] + ) + + service_instantiation.wait_for_finish(): + print("Success") + else: + print("Instantiation failed, check logs") + +Instantiate a service using SO service template (Macro) +------------------------------------------------------- + +To provide more control on the SO macro instantiation, you can define your service as follows: + +.. code:: Yaml + + myservice: + subscription_service_type: myservice + vnfs: + - model_name: myvfmodel + instance_name: myfirstvnf + parameters: + param1: value1 + processing_priority: 1 + vf_modules: + - instance_name: mysecondvfm + model_name: base + processing_priority: 2 + parameters: + param-vfm1: value-vfm1 + - instance_name: myfirstvfm + model_name: base + processing_priority: 1 + parameters: + param-vfm1: value-vfm1 + - model_name: myvfmodel + instance_name: mysecondvnf + parameters: + param1: value1 + processing_priority: 2 + vf_modules: + - instance_name: myfirstvfm + model_name: base + processing_priority: 1 + parameters: + param-vfm1: value-vfm1 + - instance_name: mysecondvfm + model_name: base + processing_priority: 2 + parameters: + param-vfm1: value-vfm1 + +.. code:: Python + + from onapsdk.aai.business import Customer, OwningEntity, Project, LineOfBusiness, Platform + from onapsdk.aai.cloud_infrastructure import CloudRegion + from onapsdk.sdc.service import Service + from onapsdk.so.instantiation import ServiceInstantiation + from yaml import load + + so_yaml_service = "/path/to/yaml/service" + with open(so_yaml_service, "r") as yaml_template: + so_service_data = load(yaml_template) + + # We assume that: + # - service is onboarded, + # - cloud region, customer, owning_entity and project have been already created, + # - cloud region has at least one tenant + # - customer has service subscription + # - service subscription is connected with cloud region and tenant + + service = Service(next(iter(so_service_data.keys()))) + so_service = SoService.load(so_service_data[service.name]) + SERVICE_INSTANCE_NAME = "my_svc_instance_name" + + customer = Customer.get_by_global_customer_id(GLOBAL_CUSTOMER_ID) + cloud_region = CloudRegion.get_by_id( + cloud_owner=CLOUD_OWNER, + cloud_region_id=CLOUD_REGION + ) + tenant = next(cloud_region.tenants) + owning_entity = OwningEntity(OWNING_ENTITY) + project = Project(PROJECT) + line_of_business = LineOfBusiness(LINE_OF_BUSINESS) + platform = Platform(PLATFORM) + + service_instantiation = ServiceInstantiation.instantiate_macro( + sdc_service=service, + customer=customer, + owning_entity=owning_entity, + project=project, + line_of_business=line_of_business, + platform=platform, + cloud_region=cloud_region, + tenant=tenant, + service_instance_name=SERVICE_INSTANCE_NAME, + so_service=so_service + ) + + +Instantiate VNF (Macro) +--------------- + +Since ONAP Istanbul the creation or deletion of VNFs in macro mode is supported. Examples below: + +.. code:: Python + + import time + from onapsdk.aai.business import Customer + from onapsdk.vid import LineOfBusiness, Platform + + # We assume that + # - service has been already instantiated, + # - line of business and platform are created + + SERVICE_INSTANCE_NAME = "service_instance_demo" + VNF_INSTANCE_NAME = "new_vnf_instance" + + customer = Customer.get_by_global_customer_id(GLOBAL_CUSTOMER_ID) + service_subscription = next(customer.service_subscriptions) + service_instance = service_subscription.get_service_instance_by_name(SERVICE_INSTANCE_NAME) + vnf = service_subscription.sdc_service.vnfs[0] + vid_line_of_business = LineOfBusiness.create(LINE_OF_BUSINESS) + vid_platform = Platform.create(PLATFORM) + + ########################################################################### + ######## VFModule parameters ############################################## + ########################################################################### + + myfirstvfm_params = [ + InstantiationParameter(name="param-vfm1", value="value-vfm1") + ] + + vf1_params = VfmoduleParameters("myfirstvfm", myfirstvfm_params) + + ########################################################################### + ######## VNF parameters ################################################### + ########################################################################### + + vnf_param_list = [ + InstantiationParameter(name="param1", value="value1") + ] + + vnf_paras = VnfParameters("myvfmodel", vnf_param_list, [vf1_params]) + + vnf_instantiation = service_instance.add_vnf( + vnf=vnf, + line_of_business=vid_line_of_business, + platform=vid_platform, + vnf_instance_name=VNF_INSTANCE_NAME, + vnf_parameters=[vnf_paras], + a_la_carte=False + ) + + vnf_instantiation.wait_for_finish(): + print("Success") + else: + print("Instantiation failed, check logs") + + +Instantiate VNF using SO service template (Macro) +--------------- + +To provide more control on the SO macro instantiation for new vnf, you can define your vnf as follows: + +.. code:: Yaml + + model_name: myvfmodel + instance_name: mynewvnf + parameters: + param1: value1 + vf_modules: + - instance_name: mysecondvfm + model_name: base + processing_priority: 2 + parameters: + param-vfm1: value-vfm1 + - instance_name: myfirstvfm + model_name: base + processing_priority: 1 + parameters: + param-vfm1: value-vfm1 + +.. code:: Python + + import time + from onapsdk.aai.business import Customer + from onapsdk.vid import LineOfBusiness, Platform + + SERVICE_INSTANCE_NAME = "service_instance_demo" + VNF_INSTANCE_NAME = "new_vnf_instance" + + # We assume that + # - service has been already instantiated, + # - line of business and platform are created + + customer = Customer.get_by_global_customer_id(GLOBAL_CUSTOMER_ID) + service_subscription = next(customer.service_subscriptions) + service_instance = service_subscription.get_service_instance_by_name(SERVICE_INSTANCE_NAME) + vnf = service_subscription.sdc_service.vnfs[0] + vid_line_of_business = LineOfBusiness.create(LINE_OF_BUSINESS) + vid_platform = Platform.create(PLATFORM) + + so_yaml_vnf = "/path/to/yaml/vnf" + with open(so_yaml_vnf, "r") as yaml_template: + so_vnf_data = load(yaml_template) + + so_vnf = SoServiceVnf.load(so_vnf_data) + + vnf_instantiation = service_instance.add_vnf( + vnf=vnf, + line_of_business=vid_line_of_business, + platform=vid_platform, + vnf_instance_name=VNF_INSTANCE_NAME, + so_vnfs=so_vnfs, + a_la_carte=False + ) + + vnf_instantiation.wait_for_finish(): + print("Success") + else: + print("Instantiation failed, check logs") + + +Instantiate VNF (ALaCarte) +--------------- + +.. code:: Python + + import time + from onapsdk.aai.business import Customer + from onapsdk.vid import LineOfBusiness, Platform + + # We assume that + # - service has been already instantiated, + # - line of business and platform are created + + customer = Customer.get_by_global_customer_id(GLOBAL_CUSTOMER_ID) + service_subscription = next(customer.service_subscriptions) + service_instance = service_subscription.get_service_instance_by_name(SERVICE_INSTANCE_NAME) + vnf = service_subscription.sdc_service.vnfs[0] + vid_line_of_business = LineOfBusiness.create(LINE_OF_BUSINESS) + vid_platform = Platform.create(PLATFORM) + vnf_instantiation = service_instance.add_vnf(vnf, vid_line_of_business, vid_platform) + vnf_instantiation.wait_for_finish(): + print("Success") + else: + print("Instantiation failed, check logs") + +Instantiate Vf module (ALaCarte) +--------------------- + +.. code:: Python + + import time + from onapsdk.aai.business import Customer + + customer = Customer.get_by_global_customer_id(GLOBAL_CUSTOMER_ID) + service_subscription = next(customer.service_subscriptions) + service_instance = service_subscription.get_service_instance_by_name(SERVICE_INSTANCE_NAME) + vnf_instance = next(service_instance.vnf_instances) + vf_module = vnf_instance.vnf.vf_module + vf_module_instantiation = vnf_instance.add_vf_module( + vf_module, + vnf_parameters=[ + VnfParameter(name="parameter1", value="parameter1_value"), + VnfParameter(name="parameter2", value="parameter2_value + ] + ) + vf_module_instantiation.wait_for_finish(): + print("Success") + else: + print("Instantiation failed, check logs") + +Instantiate Vl module (ALaCarte) +--------------------- + +.. code:: Python + + import time + from onapsdk.aai.business import Customer + from onapsdk.vid import LineOfBusiness, Platform + + # We assume that + # - service has been already instantiated, + # - line of business and platform are created + + customer = Customer.get_by_global_customer_id(GLOBAL_CUSTOMER_ID) + service_subscription = next(customer.service_subscriptions) + service_instance = service_subscription.get_service_instance_by_name(SERVICE_INSTANCE_NAME) + + logger.info("******** Get 1st Network in Service Model *******") + network = service_subscription.sdc_service.networks[0] + + logger.info("******** Create Network *******") + sn=Subnet(name="my_subnet", + start_address="10.0.0.1", + cidr_mask="24", + gateway_address="10.0.0.1) + + vid_line_of_business = LineOfBusiness.create(LINE_OF_BUSINESS) + vid_platform = Platform.create(PLATFORM) + + network_instantiation = service_instance.add_network(network, vid_line_of_business, + vid_platform, network_instance_name="my_net", subnets=[sn]) + + if network_instantiation.wait_for_finish(): + print("Success") + else: + print("Instantiation failed, check logs") diff --git a/docs/usage/usage/ves.rst b/docs/usage/usage/ves.rst new file mode 100644 index 0000000..4712d8b --- /dev/null +++ b/docs/usage/usage/ves.rst @@ -0,0 +1,42 @@ +VES +### + +Preparation for VES tests +------------------------- + +To enable CDS Enrichment in an ONAP Frankfurt environment the NodePort 30449 +for the CDS Blueprint Processor API service needs to be opened + +#. Check existing VES Services: + + .. code-block:: sh + + kubectl get service -n onap|grep ves + xdcae-ves-collector NodePort 10.43.48.246 8443:30417/TCP + +#. If the port of Ves Service is different than 30417 you can change it corresponding to the installation instruction. + + +Send event to Ves Collector +--------------------------- + +.. code:: Python + + from onapsdk.ves.ves import Ves + response = Ves.send_event( + basic_auth={'username': 'sample1', 'password': 'sample1'}, + json_event=event, + version="v7" + ) + +Send event batch to Ves Collector +------------------------- + +.. code:: Python + + from onapsdk.ves.ves import Ves + response = Ves.send_batch_event( + basic_auth={'username': 'sample1', 'password': 'sample1'}, + json_event=event, + version="v7" + ) diff --git a/integration_tests/__init__.py b/integration_tests/__init__.py new file mode 100644 index 0000000..d985dca --- /dev/null +++ b/integration_tests/__init__.py @@ -0,0 +1,21 @@ +"""Main test module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 logging + +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)-23s - %(levelname)-8s - %(message)s') +logging.captureWarnings(True) diff --git a/integration_tests/docker-compose.yml b/integration_tests/docker-compose.yml new file mode 100644 index 0000000..6e92625 --- /dev/null +++ b/integration_tests/docker-compose.yml @@ -0,0 +1,53 @@ +version: "3.4" +services: + sdc.api.fe.simpledemo.onap.org: + image: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-sdc:develop + networks: + sdk_integration: + ipv4_address: 172.20.0.2 + aai.api.sparky.simpledemo.onap.org: + image: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-aai + networks: + sdk_integration: + ipv4_address: 172.20.0.3 + so.api.simpledemo.onap.org: + image: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-so + networks: + sdk_integration: + ipv4_address: 172.20.0.4 + sdnc.api.simpledemo.onap.org: + image: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-sdnc + networks: + sdk_integration: + ipv4_address: 172.20.0.5 + clamp.api.simpledemo.onap.org: + image: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-clamp:develop + networks: + sdk_integration: + ipv4_address: 172.20.0.6 + cds.api.simpledemo.onap.org: + image: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-cds + networks: + sdk_integration: + ipv4_address: 172.20.0.7 + msb.k8s.api.simpledemo.onap.org: + image: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-msb-k8s + networks: + sdk_integration: + ipv4_address: 172.20.0.8 + ves.api.simpledemo.onap.org: + image: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-dcae/mock-ves + networks: + sdk_integration: + ipv4_address: 172.20.0.9 + dmaap.api.simpledemo.onap.org: + image: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-dmaap + networks: + sdk_integration: + ipv4_address: 172.20.0.10 + +networks: + sdk_integration: + ipam: + config: + - subnet: 172.20.0.0/24 \ No newline at end of file diff --git a/integration_tests/local_urls.py b/integration_tests/local_urls.py new file mode 100644 index 0000000..9b73a67 --- /dev/null +++ b/integration_tests/local_urls.py @@ -0,0 +1,23 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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. +SDC_BE_URL = "http://172.20.0.2:30206" +SDC_FE_URL = "http://172.20.0.2:30206" +AAI_URL = "http://172.20.0.3:5000" +SO_URL = "http://172.20.0.4:5001" +SDNC_URL = "http://172.20.0.5:5002" +CLAMP_URL = "http://172.20.0.6:30258" +CDS_URL = "http://172.20.0.7:8080" +MSB_URL = "http://172.20.0.8:5003" +VES_URL = "http://172.20.0.9:30417" +DMAAP_URL = "http://172.20.0.10:3904" diff --git a/integration_tests/tca_clampnode.yaml b/integration_tests/tca_clampnode.yaml new file mode 100644 index 0000000..bd682b0 --- /dev/null +++ b/integration_tests/tca_clampnode.yaml @@ -0,0 +1,171 @@ +# +# ============LICENSE_START==================================================== +# ============================================================================= +# Copyright (c) 2019 AT&T Intellectual Property. 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. +# ============LICENSE_END====================================================== + +tosca_definitions_version: cloudify_dsl_1_3 + +description: > + This blueprint deploys/manages the TCA module as a Docker container + +imports: + - https://www.getcloudify.org/spec/cloudify/4.5.5/types.yaml + - "https://nexus.onap.org/service/local/repositories/raw/content/org.onap.dcaegen2.platform.plugins/R6/k8splugin/1.7.2/k8splugin_types.yaml" + - "https://nexus.onap.org/service/local/repositories/raw/content/org.onap.dcaegen2.platform.plugins/R6/clamppolicyplugin/1.1.0/clamppolicyplugin_types.yaml" +inputs: + aaiEnrichmentHost: + type: string + default: "aai.onap.svc.cluster.local" + aaiEnrichmentPort: + type: string + default: "8443" + enableAAIEnrichment: + type: string + default: "true" + dmaap_host: + type: string + default: "message-router.onap.svc.cluster.local" + dmaap_port: + type: string + default: "3904" + enableRedisCaching: + type: string + default: "false" + redisHosts: + type: string + default: "dcae-redis.onap.svc.cluster.local:6379" + tag_version: + type: string + default: "nexus3.onap.org:10001/onap/org.onap.dcaegen2.deployments.tca-cdap-container:1.2.2" + consul_host: + type: string + default: "consul-server.onap" + consul_port: + type: string + default: "8500" + cbs_host: + type: string + default: "config-binding-service" + cbs_port: + type: string + default: "10000" + policy_id: + type: string + default: "onap.restart.tca" + external_port: + type: string + description: Kubernetes node port on which CDAPgui is exposed + default: "32012" + policy_model_id: + type: string + default: "onap.policies.monitoring.cdap.tca.hi.lo.app" +node_templates: + tca_k8s: + type: dcae.nodes.ContainerizedServiceComponent + relationships: + - target: tca.policy + type: cloudify.relationships.depends_on + properties: + service_component_type: 'dcaegen2-analytics-tca' + application_config: {} + docker_config: {} + image: + get_input: tag_version + log_info: + log_directory: "/opt/app/TCAnalytics/logs" + application_config: + app_config: + appDescription: DCAE Analytics Threshold Crossing Alert Application + appName: dcae-tca + tcaAlertsAbatementTableName: TCAAlertsAbatementTable + tcaAlertsAbatementTableTTLSeconds: '1728000' + tcaSubscriberOutputStreamName: TCASubscriberOutputStream + tcaVESAlertsTableName: TCAVESAlertsTable + tcaVESAlertsTableTTLSeconds: '1728000' + tcaVESMessageStatusTableName: TCAVESMessageStatusTable + tcaVESMessageStatusTableTTLSeconds: '86400' + thresholdCalculatorFlowletInstances: '2' + app_preferences: + aaiEnrichmentHost: + get_input: aaiEnrichmentHost + aaiEnrichmentIgnoreSSLCertificateErrors: 'true' + aaiEnrichmentPortNumber: '8443' + aaiEnrichmentProtocol: https + aaiEnrichmentUserName: dcae@dcae.onap.org + aaiEnrichmentUserPassword: demo123456! + aaiVMEnrichmentAPIPath: /aai/v11/search/nodes-query + aaiVNFEnrichmentAPIPath: /aai/v11/network/generic-vnfs/generic-vnf + enableAAIEnrichment: + get_input: enableAAIEnrichment + enableRedisCaching: + get_input: enableRedisCaching + redisHosts: + get_input: redisHosts + enableAlertCEFFormat: 'false' + publisherContentType: application/json + publisherHostName: + get_input: dmaap_host + publisherHostPort: + get_input: dmaap_port + publisherMaxBatchSize: '1' + publisherMaxRecoveryQueueSize: '100000' + publisherPollingInterval: '20000' + publisherProtocol: http + publisherTopicName: unauthenticated.DCAE_CL_OUTPUT + subscriberConsumerGroup: OpenDCAE-clamp + subscriberConsumerId: c12 + subscriberContentType: application/json + subscriberHostName: + get_input: dmaap_host + subscriberHostPort: + get_input: dmaap_port + subscriberMessageLimit: '-1' + subscriberPollingInterval: '30000' + subscriberProtocol: http + subscriberTimeoutMS: '-1' + subscriberTopicName: unauthenticated.VES_MEASUREMENT_OUTPUT + #tca.policy: '{"domain":"measurementsForVfScaling","metricsPerEventName":[{"eventName":"vFirewallBroadcastPackets","controlLoopSchemaType":"VM","policyScope":"DCAE","policyName":"DCAE.Config_tca-hi-lo","policyVersion":"v0.0.1","thresholds":[{"closedLoopControlName":"ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a","version":"1.0.2","fieldPath":"$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedTotalPacketsDelta","thresholdValue":300,"direction":"LESS_OR_EQUAL","severity":"MAJOR","closedLoopEventStatus":"ONSET"},{"closedLoopControlName":"ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a","version":"1.0.2","fieldPath":"$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedTotalPacketsDelta","thresholdValue":700,"direction":"GREATER_OR_EQUAL","severity":"CRITICAL","closedLoopEventStatus":"ONSET"}]},{"eventName":"vLoadBalancer","controlLoopSchemaType":"VM","policyScope":"DCAE","policyName":"DCAE.Config_tca-hi-lo","policyVersion":"v0.0.1","thresholds":[{"closedLoopControlName":"ControlLoop-vDNS-6f37f56d-a87d-4b85-b6a9-cc953cf779b3","version":"1.0.2","fieldPath":"$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedTotalPacketsDelta","thresholdValue":300,"direction":"GREATER_OR_EQUAL","severity":"CRITICAL","closedLoopEventStatus":"ONSET"}]},{"eventName":"Measurement_vGMUX","controlLoopSchemaType":"VNF","policyScope":"DCAE","policyName":"DCAE.Config_tca-hi-lo","policyVersion":"v0.0.1","thresholds":[{"closedLoopControlName":"ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e","version":"1.0.2","fieldPath":"$.event.measurementsForVfScalingFields.additionalMeasurements[*].arrayOfFields[0].value","thresholdValue":0,"direction":"EQUAL","severity":"MAJOR","closedLoopEventStatus":"ABATED"},{"closedLoopControlName":"ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e","version":"1.0.2","fieldPath":"$.event.measurementsForVfScalingFields.additionalMeasurements[*].arrayOfFields[0].value","thresholdValue":0,"direction":"GREATER","severity":"CRITICAL","closedLoopEventStatus":"ONSET"}]}]}' + tca.policy: '' + service_component_type: dcaegen2-analytics_tca + interfaces: + cloudify.interfaces.lifecycle: + start: + inputs: + envs: + DMAAPHOST: + { get_input: dmaap_host } + DMAAPPORT: "3904" + DMAAPPUBTOPIC: "unauthenticated.DCAE_CL_OUTPUT" + DMAAPSUBTOPIC: "unauthenticated.VES_MEASUREMENT_OUTPUT" + AAIHOST: + { get_input: aaiEnrichmentHost } + AAIPORT: "8443" + CONSUL_HOST: + { get_input: consul_host } + CONSUL_PORT: "8500" + CBS_HOST: + { get_input: cbs_host } + CBS_PORT: "10000" + CONFIG_BINDING_SERVICE: "config_binding_service" + ports: + - concat: ["11011:", { get_input: external_port }] + tca.policy: + type: clamp.nodes.policy + properties: + policy_id: + get_input: policy_id + policy_model_id: + get_input: policy_model_id diff --git a/integration_tests/test_01_vendor.py b/integration_tests/test_01_vendor.py new file mode 100644 index 0000000..26ede03 --- /dev/null +++ b/integration_tests/test_01_vendor.py @@ -0,0 +1,42 @@ +"""Integration test Vendor module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 pytest + +import requests + +from onapsdk.sdc import SDC +from onapsdk.sdc.vendor import Vendor +import onapsdk.constants as const + + +@pytest.mark.integration +def test_vendor_unknown(): + """Integration tests for Vendor.""" + response = requests.post("{}/reset".format(SDC.base_front_url)) + response.raise_for_status() + vendor = Vendor(name="test") + vendor.create() + assert vendor.created() + vendor.submit() + assert vendor.status == const.CERTIFIED + +@pytest.mark.integration +def test_vendor_onboard_unknown(): + """Integration tests for Vendor.""" + response = requests.post("{}/reset".format(SDC.base_front_url)) + response.raise_for_status() + vendor = Vendor(name="test") + vendor.onboard() + assert vendor.status == const.CERTIFIED diff --git a/integration_tests/test_02_vsp.py b/integration_tests/test_02_vsp.py new file mode 100644 index 0000000..972e78e --- /dev/null +++ b/integration_tests/test_02_vsp.py @@ -0,0 +1,63 @@ +"""Integration test Vendor module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 pytest + +import requests + +from onapsdk.sdc import SDC +from onapsdk.sdc.vendor import Vendor +from onapsdk.sdc.vsp import Vsp +import onapsdk.constants as const + + +@pytest.mark.integration +def test_vsp_unknown(): + """Integration tests for Vsp.""" + response = requests.post("{}/reset".format(Vendor.base_front_url)) + response.raise_for_status() + vendor = Vendor(name="test") + vendor.onboard() + vsp = Vsp(name="test") + vsp.vendor = vendor + vsp.create() + assert vsp.identifier is not None + assert vsp.status == const.DRAFT + vsp.upload_package(open("{}/ubuntu16.zip".format( + os.path.dirname(os.path.abspath(__file__))), 'rb')) + assert vsp.status == const.UPLOADED + vsp.validate() + assert vsp.status == const.VALIDATED + vsp.commit() + assert vsp.status == const.COMMITED + vsp.submit() + assert vsp.status == const.CERTIFIED + vsp.create_csar() + assert vsp.csar_uuid is not None + +@pytest.mark.integration +def test_vsp_onboard_unknown(): + """Integration tests for Vsp.""" + response = requests.post("{}/reset".format(Vendor.base_front_url)) + response.raise_for_status() + vendor = Vendor(name="test") + vendor.onboard() + vsp = Vsp(name="test", package=open("{}/ubuntu16.zip".format( + os.path.dirname(os.path.abspath(__file__))), 'rb')) + vsp.vendor = vendor + vsp.onboard() + assert vsp.status == const.CERTIFIED + assert vsp.csar_uuid is not None diff --git a/integration_tests/test_03_vf.py b/integration_tests/test_03_vf.py new file mode 100644 index 0000000..7cad69b --- /dev/null +++ b/integration_tests/test_03_vf.py @@ -0,0 +1,90 @@ +"""Integration test Vendor module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 pytest + +import requests + +from onapsdk.sdc import SDC +from onapsdk.sdc.properties import Property +from onapsdk.sdc.vendor import Vendor +from onapsdk.sdc.vsp import Vsp +from onapsdk.sdc.vf import Vf +import onapsdk.constants as const + + +@pytest.mark.integration +def test_vf_unknown(): + """Integration tests for Vf.""" + response = requests.post("{}/reset".format(Vendor.base_front_url)) + response.raise_for_status() + vendor = Vendor(name="test") + vendor.onboard() + vsp = Vsp(name="test", package=open("{}/ubuntu16.zip".format( + os.path.dirname(os.path.abspath(__file__))), 'rb')) + vsp.vendor = vendor + vsp.onboard() + vf = Vf(name='test') + vf.vsp = vsp + vf.create() + assert vf.identifier is not None + assert vf.status == const.DRAFT + assert vf.version == "0.1" + vf.submit() + assert vsp.status == const.CERTIFIED + assert vf.version == "1.0" + vf.load() + assert vsp.status == const.CERTIFIED + assert vf.version == "1.0" + +@pytest.mark.integration +def test_vf_onboard_unknown(): + """Integration tests for Vf.""" + response = requests.post("{}/reset".format(Vendor.base_front_url)) + response.raise_for_status() + vendor = Vendor(name="test") + vendor.onboard() + vsp = Vsp(name="test", package=open("{}/ubuntu16.zip".format( + os.path.dirname(os.path.abspath(__file__))), 'rb')) + vsp.vendor = vendor + vsp.onboard() + vf = Vf(name='test') + vf.vsp = vsp + vf.onboard() + assert vsp.status == const.CERTIFIED + assert vf.version == "1.0" + +@pytest.mark.integration +def test_vf_properties(): + """Integration test to check properties assignment for Vf.""" + response = requests.post("{}/reset".format(Vendor.base_front_url)) + response.raise_for_status() + vendor = Vendor(name="test") + vendor.onboard() + vsp = Vsp(name="test", package=open("{}/ubuntu16.zip".format( + os.path.dirname(os.path.abspath(__file__))), 'rb')) + vsp.vendor = vendor + vsp.onboard() + prop = Property(name="test1", property_type="string", value="123") + vf = Vf(name="test", vsp=vsp, properties=[ + prop, + Property(name="test2", property_type="integer")], + inputs=[prop]) + vf.onboard() + vf_properties = list(vf.properties) + vf_inputs = list(vf.inputs) + assert len(vf_properties) == 2 + assert len(vf_inputs) == 1 diff --git a/integration_tests/test_04_service.py b/integration_tests/test_04_service.py new file mode 100644 index 0000000..80370b0 --- /dev/null +++ b/integration_tests/test_04_service.py @@ -0,0 +1,122 @@ +"""Integration test Vendor module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 pytest + +import requests + +from onapsdk.sdc import SDC +from onapsdk.sdc.properties import Property +from onapsdk.sdc.vendor import Vendor +from onapsdk.sdc.vsp import Vsp +from onapsdk.sdc.vf import Vf +from onapsdk.sdc.service import Service +import onapsdk.constants as const + + +@pytest.mark.integration +def test_service_unknown(): + """Integration tests for Service.""" + response = requests.post("{}/reset".format(SDC.base_front_url)) + response.raise_for_status() + vendor = Vendor(name="test") + vendor.onboard() + vsp = Vsp(name="test", package=open("{}/ubuntu16.zip".format( + os.path.dirname(os.path.abspath(__file__))), 'rb')) + vsp.vendor = vendor + vsp.onboard() + vf = Vf(name='test', vsp=vsp) + vf.onboard() + svc = Service(name='test') + assert svc.identifier is None + assert svc.status is None + svc.create() + assert svc.identifier is not None + assert svc.status == const.DRAFT + svc.add_resource(vf) + svc.checkin() + assert svc.status == const.CHECKED_IN + svc.certify() + assert svc.status == const.CERTIFIED + svc.distribute() + assert svc.status == const.DISTRIBUTED + assert svc.distributed + +@pytest.mark.integration +def test_service_onboard_unknown(): + """Integration tests for Service.""" + response = requests.post("{}/reset".format(SDC.base_front_url)) + response.raise_for_status() + vendor = Vendor(name="test") + vendor.onboard() + vsp = Vsp(name="test", package=open("{}/ubuntu16.zip".format( + os.path.dirname(os.path.abspath(__file__))), 'rb')) + vsp.vendor = vendor + vsp.onboard() + vf = Vf(name='test', vsp=vsp) + vf.onboard() + svc = Service(name='test', resources=[vf]) + svc.onboard() + assert svc.distributed + +@pytest.mark.integration +def test_service_upload_tca_artifact(): + """Integration tests for Service.""" + response = requests.post("{}/reset".format(SDC.base_front_url)) + response.raise_for_status() + vendor = Vendor(name="test") + vendor.onboard() + vsp = Vsp(name="test", package=open("{}/ubuntu16.zip".format( + os.path.dirname(os.path.abspath(__file__))), 'rb')) + vsp.vendor = vendor + vsp.onboard() + vf = Vf(name='test', vsp=vsp) + vf.onboard() + svc = Service(name='test') + svc.create() + svc.add_resource(vf) + assert svc.status == const.DRAFT + payload_file = open("{}/tca_clampnode.yaml".format(os.path.dirname(os.path.abspath(__file__))), 'rb') + data = payload_file.read() + svc.add_artifact_to_vf(vnf_name="test", + artifact_type="DCAE_INVENTORY_BLUEPRINT", + artifact_name="tca_clampnode.yaml", + artifact=data) + payload_file.close() + +@pytest.mark.integration +def test_service_properties(): + """Integration test to check properties assignment for Service.""" + response = requests.post("{}/reset".format(SDC.base_front_url)) + response.raise_for_status() + vendor = Vendor(name="test") + vendor.onboard() + vsp = Vsp(name="test", package=open("{}/ubuntu16.zip".format( + os.path.dirname(os.path.abspath(__file__))), 'rb')) + vsp.vendor = vendor + vsp.onboard() + vf = Vf(name='test', vsp=vsp) + vf.onboard() + properties = [ + Property(name="test1", property_type="string", value="123"), + Property(name="test2", property_type="integer") + ] + svc = Service(name='test', resources=[vf], properties=properties, inputs=[properties[1]]) + svc.onboard() + service_properties = list(svc.properties) + service_inputs = list(svc.inputs) + assert len(service_properties) == 2 + assert len(service_inputs) == 1 diff --git a/integration_tests/test_05_cloud_infrastructure.py b/integration_tests/test_05_cloud_infrastructure.py new file mode 100644 index 0000000..27e3762 --- /dev/null +++ b/integration_tests/test_05_cloud_infrastructure.py @@ -0,0 +1,88 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 pytest +import requests + +from onapsdk.aai.cloud_infrastructure import CloudRegion, Complex +from onapsdk.exceptions import ResourceNotFound + + +@pytest.mark.integration +def test_cloud_region_get_all(): + requests.get(f"{CloudRegion.base_url}/reset") + cloud_regions = list(CloudRegion.get_all()) + assert len(cloud_regions) == 0 + + with pytest.raises(ResourceNotFound): + CloudRegion.get_by_id("test_owner", "test_cloud_region") + + cloud_region: CloudRegion = CloudRegion.create( + "test_owner", "test_cloud_region", orchestration_disabled=True, in_maint=False + ) + cloud_regions = list(CloudRegion.get_all()) + assert len(cloud_regions) == 1 + cloud_region = cloud_regions[0] + assert cloud_region.cloud_owner == "test_owner" + assert cloud_region.cloud_region_id == "test_cloud_region" + + +@pytest.mark.integration +def test_complex_get_all(): + requests.get(f"{Complex.base_url}/reset") + complexes = list(Complex.get_all()) + assert len(complexes) == 0 + + cmplx: Complex = Complex.create( + name="test_complex", + physical_location_id="test_physical_location_id" + ) + assert cmplx.name == "test_complex" + assert cmplx.physical_location_id == "test_physical_location_id" + + complexes = list(Complex.get_all()) + assert len(complexes) == 1 + + cmplx = complexes[0] + assert cmplx.name == "test_complex" + assert cmplx.physical_location_id == "test_physical_location_id" + + +@pytest.mark.integration +def test_link_cloud_region_to_complex(): + + requests.get(f"{Complex.base_url}/reset") + + cmplx: Complex = Complex.create( + name="test_complex", + physical_location_id="test_physical_location_id" + ) + cloud_region: CloudRegion = CloudRegion.create( + "test_owner", "test_cloud_region", orchestration_disabled=True, in_maint=False + ) + + assert len(list(cloud_region.relationships)) == 0 + cloud_region.link_to_complex(cmplx) + assert len(list(cloud_region.relationships)) == 1 + + +@pytest.mark.integration +def test_cloud_region_tenants(): + + cloud_region: CloudRegion = CloudRegion.create( + "test_owner", "test_cloud_region", orchestration_disabled=True, in_maint=False + ) + assert len(list(cloud_region.tenants)) == 0 + cloud_region.add_tenant(tenant_id="test_tenant_id", tenant_name="test_tenant_name", tenant_context="test_tenant_context") + assert len(list(cloud_region.tenants)) == 1 + tenant = cloud_region.get_tenant(tenant_id="test_tenant_id") diff --git a/integration_tests/test_06_customer.py b/integration_tests/test_06_customer.py new file mode 100644 index 0000000..62ac3f0 --- /dev/null +++ b/integration_tests/test_06_customer.py @@ -0,0 +1,85 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 uuid import uuid4 + +import pytest + +import requests +from onapsdk.aai.cloud_infrastructure import CloudRegion, Tenant +from onapsdk.aai.business import Customer, ServiceSubscription +from onapsdk.exceptions import ParameterError +from onapsdk.sdc.service import Service + + +@pytest.mark.integration +def test_create_customer(): + + requests.get(f"{Customer.base_url}/reset") + + customers = list(Customer.get_all()) + assert len(customers) == 0 + + customer = Customer.create(global_customer_id="test_global_customer_id", + subscriber_name="test_subscriber_name", + subscriber_type="test_subscriber_type") + assert customer.global_customer_id == "test_global_customer_id" + assert customer.subscriber_name == "test_subscriber_name" + assert customer.subscriber_type == "test_subscriber_type" + + customers = list(Customer.get_all()) + assert len(customers) == 1 + + +@pytest.mark.integration +def test_subscribe_service(): + + requests.get(f"{Customer.base_url}/reset") + + customer = Customer.create(global_customer_id="test_global_customer_id", + subscriber_name="test_subscriber_name", + subscriber_type="test_subscriber_type") + assert len(list(customer.service_subscriptions)) == 0 + + customer.subscribe_service("service_type") + assert len(list(customer.service_subscriptions)) == 1 + assert customer.get_service_subscription_by_service_type("service_type") + + +@pytest.mark.integration +def test_link_service_subscription_to_cloud_region_and_tenant(): + + requests.get(f"{Customer.base_url}/reset") + + customer = Customer.create(global_customer_id="test_global_customer_id", + subscriber_name="test_subscriber_name", + subscriber_type="test_subscriber_type") + customer.subscribe_service("service_type") + service_subscription = customer.get_service_subscription_by_service_type("service_type") + + assert len(list(service_subscription.relationships)) == 0 + with pytest.raises(ParameterError): + service_subscription.cloud_region + with pytest.raises(ParameterError): + service_subscription.tenant + + cloud_region = CloudRegion.create( + "test_owner", "test_cloud_region", orchestration_disabled=True, in_maint=False + ) + cloud_region.add_tenant( + tenant_id="test_tenant_name", tenant_name="test_tenant_name", tenant_context="test_tenant_context" + ) + tenant = cloud_region.get_tenant(tenant_id="test_tenant_name") + service_subscription.link_to_cloud_region_and_tenant(cloud_region=cloud_region, tenant=tenant) + assert service_subscription.cloud_region + assert service_subscription.tenant diff --git a/integration_tests/test_07_instantiation.py b/integration_tests/test_07_instantiation.py new file mode 100644 index 0000000..c6cbc85 --- /dev/null +++ b/integration_tests/test_07_instantiation.py @@ -0,0 +1,427 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 time +from unittest.mock import MagicMock, PropertyMock, patch +from uuid import uuid4 + +import pytest +import requests +from onapsdk.exceptions import StatusError +from onapsdk.aai.business import Customer, ServiceInstance +from onapsdk.aai.cloud_infrastructure import CloudRegion +from onapsdk.configuration import settings +from onapsdk.sdc.service import Service, Vnf, VfModule +from onapsdk.so.deletion import ServiceDeletionRequest, VfModuleDeletionRequest, VnfDeletionRequest +from onapsdk.so.instantiation import (ServiceInstantiation, SoService, + VfModuleInstantiation, VnfInstantiation, InstantiationParameter, + VfmoduleParameters, VnfParameters) + + +@pytest.mark.integration +def test_a_la_carte_instantiation(): + requests.get(f"{ServiceInstantiation.base_url}/reset") + requests.get(f"{Customer.base_url}/reset") + requests.post(f"{ServiceInstantiation.base_url}/set_aai_mock", json={"AAI_MOCK": settings.AAI_URL}) + + customer = Customer.create(global_customer_id="test_global_customer_id", + subscriber_name="test_subscriber_name", + subscriber_type="test_subscriber_type") + service = Service("test_service") + service.unique_uuid = str(uuid4()) + service.identifier = str(uuid4()) + service.name = str(uuid4()) + customer.subscribe_service("service_type") + service_subscription = customer.get_service_subscription_by_service_type("service_type") + cloud_region = CloudRegion.create( + "test_owner", "test_cloud_region", orchestration_disabled=True, in_maint=False + ) + cloud_region.add_tenant( + tenant_id="test_tenant_name", tenant_name="test_tenant_name", tenant_context="test_tenant_context" + ) + tenant = cloud_region.get_tenant(tenant_id="test_tenant_name") + service_subscription.link_to_cloud_region_and_tenant(cloud_region=cloud_region, tenant=tenant) + owning_entity = "test_owning_entity" + project = "test_project" + + # Service instantiation + service._distributed = True + assert len(list(service_subscription.service_instances)) == 0 + service_instantiation_request = ServiceInstantiation.instantiate_ala_carte( + service, + cloud_region, + tenant, + customer, + owning_entity, + project, + service_subscription + ) + assert service_instantiation_request.status == ServiceInstantiation.StatusEnum.IN_PROGRESS + time.sleep(2) # After 1 second mocked server changed request status to complete + assert service_instantiation_request.status == ServiceInstantiation.StatusEnum.COMPLETED + assert len(list(service_subscription.service_instances)) == 1 + + # Vnf instantiation + service_instance = next(service_subscription.service_instances) + assert len(list(service_instance.vnf_instances)) == 0 + owning_entity = "test_owning_entity" + project = "test_project" + vnf = MagicMock() + line_of_business = "test_line_of_business" + platform = "test_platform" + with pytest.raises(StatusError): + service_instance.add_vnf( + vnf, + line_of_business, + platform + ) + service_instance.orchestration_status = "Active" + service_instance._sdc_service = service + with patch.object(ServiceInstance, "sdc_service", return_value=service): + vnf_instantiation_request = service_instance.add_vnf( + vnf, + line_of_business, + platform + ) + assert vnf_instantiation_request.status == VnfInstantiation.StatusEnum.IN_PROGRESS + time.sleep(2) # After 1 second mocked server changed request status to complete + assert vnf_instantiation_request.status == VnfInstantiation.StatusEnum.COMPLETED + assert len(list(service_instance.vnf_instances)) == 1 + # VfModule instantiation + vnf_instance = next(service_instance.vnf_instances) + assert len(list(vnf_instance.vf_modules)) == 0 + vnf.model_version_id = vnf_instance.model_version_id + vf_module = MagicMock() + + with patch.object(ServiceInstance, "sdc_service", return_value=service) as service_mock: + service_mock.vnfs = [vnf] + vf_module_instantiation_request = vnf_instance.add_vf_module( + vf_module + ) + assert vf_module_instantiation_request.status == VfModuleInstantiation.StatusEnum.IN_PROGRESS + time.sleep(2) # After 1 second mocked server changed request status to complete + assert vf_module_instantiation_request.status == VfModuleInstantiation.StatusEnum.COMPLETED + assert len(list(vnf_instance.vf_modules)) == 1 + + # Cleanup + vf_module_instance = next(vnf_instance.vf_modules) + vf_module_deletion_request = vf_module_instance.delete() + assert vf_module_deletion_request.status == VfModuleDeletionRequest.StatusEnum.IN_PROGRESS + time.sleep(2) # After 1 second mocked server changed request status to complete + assert vf_module_deletion_request.status == VfModuleDeletionRequest.StatusEnum.COMPLETED + assert len(list(vnf_instance.vf_modules)) == 0 + + vnf_deletion_request = vnf_instance.delete() + assert vnf_deletion_request.status == VnfDeletionRequest.StatusEnum.IN_PROGRESS + time.sleep(2) # After 1 second mocked server changed request status to complete + assert vnf_deletion_request.status == VnfDeletionRequest.StatusEnum.COMPLETED + assert len(list(service_instance.vnf_instances)) == 0 + + with patch.object(ServiceInstance, "sdc_service", return_value=service) as service_mock: + service_deletion_request = service_instance.delete() + assert service_deletion_request.status == ServiceDeletionRequest.StatusEnum.IN_PROGRESS + time.sleep(2) # After 1 second mocked server changed request status to complete + assert service_deletion_request.status == ServiceDeletionRequest.StatusEnum.COMPLETED + assert len(list(service_subscription.service_instances)) == 0 + + +@pytest.mark.integration +def test_a_la_carte_vl_instantiation(): + requests.get(f"{ServiceInstantiation.base_url}/reset") + requests.get(f"{Customer.base_url}/reset") + requests.post(f"{ServiceInstantiation.base_url}/set_aai_mock", json={"AAI_MOCK": settings.AAI_URL}) + + customer = Customer.create(global_customer_id="test_global_customer_id", + subscriber_name="test_subscriber_name", + subscriber_type="test_subscriber_type") + service = Service("test_service") + service.unique_uuid = str(uuid4()) + service.identifier = str(uuid4()) + service.name = str(uuid4()) + customer.subscribe_service("service_type") + service_subscription = customer.get_service_subscription_by_service_type("service_type") + cloud_region = CloudRegion.create( + "test_owner", "test_cloud_region", orchestration_disabled=True, in_maint=False + ) + cloud_region.add_tenant( + tenant_id="test_tenant_name", tenant_name="test_tenant_name", tenant_context="test_tenant_context" + ) + tenant = cloud_region.get_tenant(tenant_id="test_tenant_name") + service_subscription.link_to_cloud_region_and_tenant(cloud_region=cloud_region, tenant=tenant) + owning_entity = "test_owning_entity" + project = "test_project" + + # Service instantiation + service._distributed = True + assert len(list(service_subscription.service_instances)) == 0 + service_instantiation_request = ServiceInstantiation.instantiate_ala_carte( + service, + cloud_region, + tenant, + customer, + owning_entity, + project, + service_subscription + ) + assert service_instantiation_request.status == ServiceInstantiation.StatusEnum.IN_PROGRESS + service_instantiation_request.wait_for_finish() + assert service_instantiation_request.status == ServiceInstantiation.StatusEnum.COMPLETED + assert len(list(service_subscription.service_instances)) == 1 + + # Network instantiation + service_instance = next(service_subscription.service_instances) + assert len(list(service_instance.network_instances)) == 0 + owning_entity = "test_owning_entity" + project = "test_project" + network = MagicMock() + line_of_business = "test_line_of_business" + platform = "test_platform" + with pytest.raises(AttributeError): + service_instance.network( + network, + line_of_business, + platform + ) + service_instance.orchestration_status = "Active" + with patch.object(ServiceInstance, "sdc_service", return_value=service): + network_instantiation_request = service_instance.add_network( + network, + line_of_business, + platform + ) + assert network_instantiation_request.status == VnfInstantiation.StatusEnum.IN_PROGRESS + network_instantiation_request.wait_for_finish() + assert network_instantiation_request.status == VnfInstantiation.StatusEnum.COMPLETED + assert len(list(service_instance.network_instances)) == 1 + + +@patch.object(Service, "vnfs", new_callable=PropertyMock) +@patch.object(Service, "components", new_callable=PropertyMock) +@pytest.mark.integration +def test_instantiate_macro(mock_service_components, mock_service_vnfs): + requests.get(f"{ServiceInstantiation.base_url}/reset") + requests.get(f"{Customer.base_url}/reset") + requests.post(f"{ServiceInstantiation.base_url}/set_aai_mock", json={"AAI_MOCK": settings.AAI_URL}) + + customer = Customer.create(global_customer_id="test_global_customer_id", + subscriber_name="test_subscriber_name", + subscriber_type="test_subscriber_type") + service = Service("test_service") + service._tosca_template = "n/a" + + mock_service_vnfs.return_value = [ + Vnf( + name="test_vnf", + node_template_type="vf", + model_name= "test_vnf_model", + model_version_id = str(uuid4()), + model_invariant_id=str(uuid4()), + model_version="1.0", + model_customization_id=str(uuid4()), + model_instance_name=str(uuid4()), + component=MagicMock(), + vf_modules=[ + VfModule( + name="TestVnfModel..base..module-0", + group_type="vf-module", + model_name="TestVnfModel..base..module-0", + model_version_id=str(uuid4()), + model_invariant_uuid=str(uuid4()), + model_version="1", + model_customization_id=str(uuid4()), + properties=None + ) + ] + ) + ] + service.unique_uuid = str(uuid4()) + service.identifier = str(uuid4()) + service.name = str(uuid4()) + customer.subscribe_service("service_type") + service_subscription = customer.get_service_subscription_by_service_type("service_type") + cloud_region = CloudRegion.create( + "test_owner", "test_cloud_region", orchestration_disabled=True, in_maint=False + ) + cloud_region.add_tenant( + tenant_id="test_tenant_name", tenant_name="test_tenant_name", tenant_context="test_tenant_context" + ) + tenant = cloud_region.get_tenant(tenant_id="test_tenant_name") + service_subscription.link_to_cloud_region_and_tenant(cloud_region=cloud_region, tenant=tenant) + owning_entity = "test_owning_entity" + project = "test_project" + line_of_business = "test_line_of_business" + platform = "test_platform" + + vfm_instance_params = [ + InstantiationParameter(name="vfm_param", value="vfm_param_value"), + + ] + vfm_params = VfmoduleParameters("base", vfm_instance_params) + + vnf_instance_params = [ + InstantiationParameter(name="vnf_param", value="vnf_param_value") + ] + + vnf_params = VnfParameters("test_vnf_model", vnf_instance_params, [vfm_params]) + + # Service instantiation + service._distributed = True + assert len(list(service_subscription.service_instances)) == 0 + service_instantiation_request = ServiceInstantiation.instantiate_macro( + sdc_service=service, + customer=customer, + owning_entity=owning_entity, + project=project, + line_of_business=line_of_business, + platform=platform, + cloud_region=cloud_region, + tenant=tenant, + vnf_parameters=[vnf_params], + service_subscription=service_subscription + ) + assert service_instantiation_request.status == ServiceInstantiation.StatusEnum.IN_PROGRESS + time.sleep(2) # After 1 second mocked server changed request status to complete + assert service_instantiation_request.status == ServiceInstantiation.StatusEnum.COMPLETED + assert len(list(service_subscription.service_instances)) == 1 + service_instance = next(service_subscription.service_instances) + + # Cleanup + with patch.object(ServiceInstance, "sdc_service", return_value=service) as service_mock: + service_deletion_request = service_instance.delete() + assert service_deletion_request.status == ServiceDeletionRequest.StatusEnum.IN_PROGRESS + time.sleep(2) # After 1 second mocked server changed request status to complete + assert service_deletion_request.status == ServiceDeletionRequest.StatusEnum.COMPLETED + assert len(list(service_subscription.service_instances)) == 0 + +@patch.object(Service, "vnfs", new_callable=PropertyMock) +@patch.object(Service, "components", new_callable=PropertyMock) +@pytest.mark.integration +def test_instantiate_macro_multiple_vnf(mock_service_components, mock_service_vnfs): + requests.get(f"{ServiceInstantiation.base_url}/reset") + requests.get(f"{Customer.base_url}/reset") + requests.post(f"{ServiceInstantiation.base_url}/set_aai_mock", json={"AAI_MOCK": settings.AAI_URL}) + + customer = Customer.create(global_customer_id="test_global_customer_id", + subscriber_name="test_subscriber_name", + subscriber_type="test_subscriber_type") + service = Service("test_service") + service._tosca_template = "n/a" + + mock_service_vnfs.return_value = [ + Vnf( + name="test_vnf", + node_template_type="vf", + model_name= "test_vnf_model", + model_version_id = str(uuid4()), + model_invariant_id=str(uuid4()), + model_version="1.0", + model_customization_id=str(uuid4()), + model_instance_name=str(uuid4()), + component=MagicMock(), + vf_modules=[ + VfModule( + name="TestVnfModel..base..module-0", + group_type="vf-module", + model_name="TestVnfModel..base..module-0", + model_version_id=str(uuid4()), + model_invariant_uuid=str(uuid4()), + model_version="1", + model_customization_id=str(uuid4()), + properties=None + ) + ] + ) + ] + service.unique_uuid = str(uuid4()) + service.identifier = str(uuid4()) + service.name = str(uuid4()) + customer.subscribe_service("service_type") + service_subscription = customer.get_service_subscription_by_service_type("service_type") + cloud_region = CloudRegion.create( + "test_owner", "test_cloud_region", orchestration_disabled=True, in_maint=False + ) + cloud_region.add_tenant( + tenant_id="test_tenant_name", tenant_name="test_tenant_name", tenant_context="test_tenant_context" + ) + tenant = cloud_region.get_tenant(tenant_id="test_tenant_name") + service_subscription.link_to_cloud_region_and_tenant(cloud_region=cloud_region, tenant=tenant) + owning_entity = "test_owning_entity" + project = "test_project" + line_of_business = "test_line_of_business" + platform = "test_platform" + + so_service = SoService.load({ + "subscription_service_type": "service_type", + "vnfs": [ + { + "model_name": "test_vnf_model", + "instance_name": "vnf0", + "parameters": { + "param1": "value1" + }, + "vf_modules": [ + { + "instance_name": "vnf0_vfm0", + "model_name": "base", + "parameters": { + "vfm_param1": "vfm_value1" + } + } + ] + }, + { + "model_name": "test_vnf_model", + "instance_name": "vnf1", + "parameters": { + "param2": "value2" + }, + "vf_modules": [ + { + "instance_name": "vnf1_vfm0", + "model_name": "base", + "parameters": { + "vfm_param2": "vfm_value2" + } + } + ] + } + ] + }) + + # Service instantiation + service._distributed = True + assert len(list(service_subscription.service_instances)) == 0 + service_instantiation_request = ServiceInstantiation.instantiate_macro( + sdc_service=service, + customer=customer, + owning_entity=owning_entity, + project=project, + line_of_business=line_of_business, + platform=platform, + cloud_region=cloud_region, + tenant=tenant, + so_service=so_service + ) + assert service_instantiation_request.status == ServiceInstantiation.StatusEnum.IN_PROGRESS + time.sleep(2) # After 1 second mocked server changed request status to complete + assert service_instantiation_request.status == ServiceInstantiation.StatusEnum.COMPLETED + assert len(list(service_subscription.service_instances)) == 1 + service_instance = next(service_subscription.service_instances) + + # Cleanup + with patch.object(ServiceInstance, "sdc_service", return_value=service) as service_mock: + service_deletion_request = service_instance.delete() + assert service_deletion_request.status == ServiceDeletionRequest.StatusEnum.IN_PROGRESS + time.sleep(2) # After 1 second mocked server changed request status to complete + assert service_deletion_request.status == ServiceDeletionRequest.StatusEnum.COMPLETED + assert len(list(service_subscription.service_instances)) == 0 diff --git a/integration_tests/test_08_cds.py b/integration_tests/test_08_cds.py new file mode 100644 index 0000000..3d75f37 --- /dev/null +++ b/integration_tests/test_08_cds.py @@ -0,0 +1,65 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 pytest +import requests + +from tempfile import TemporaryDirectory + +from onapsdk.configuration import settings +from onapsdk.cds.blueprint import Blueprint +from onapsdk.cds.data_dictionary import DataDictionary, DataDictionarySet + + +@pytest.mark.integration +def test_cds_connection(): + + TEST_DD_PATH = os.path.join(os.getcwd(), "integration_tests/test_files/test_dd.json") + TEST_CBA_PATH = os.path.join(os.getcwd(), "integration_tests/test_files/test_vLB_CBA_Python.zip") + + + # Endpoint availability + response = requests.post("{}/api/v1/dictionary".format(settings.CDS_URL)) + assert response.status_code == 200 + + response = requests.post("{}/api/v1/blueprint-model/enrich".format(settings.CDS_URL)) + assert response.status_code == 200 + + response = requests.post("{}/api/v1/blueprint-model/publish".format(settings.CDS_URL)) + assert response.status_code == 200 + + + # Reads from the file system + dd_set = DataDictionarySet.load_from_file(TEST_DD_PATH, True) + blueprint = Blueprint.load_from_file(TEST_CBA_PATH) + + + # Connection availability between CDS API and Blueprint/DataDictionarySet + dd_set.upload() + assert type(blueprint.cba_file_bytes) == bytes + + for dd in dd_set.dd_set: + dd_obj = DataDictionary.get_by_name(dd.name) + assert dd_obj == dd + + blueprint = blueprint.enrich() + assert type(blueprint.cba_file_bytes) == bytes + + blueprint.publish() + + + # Writes to the file system + with TemporaryDirectory() as tmpdirname: + path = os.path.join(tmpdirname, "test-CBA-enriched.zip") + blueprint.save(path) diff --git a/integration_tests/test_09_clamp.py b/integration_tests/test_09_clamp.py new file mode 100644 index 0000000..f745e2f --- /dev/null +++ b/integration_tests/test_09_clamp.py @@ -0,0 +1,78 @@ +"""Integration test Clamp module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 pytest + +import requests + +from onapsdk.sdc.service import Service +from onapsdk.clamp.clamp_element import Clamp +from onapsdk.clamp.loop_instance import LoopInstance + +@pytest.mark.integration +def test_Clamp_requirements(): + """Integration tests for Clamp.""" + requests.get("{}/reset".format(Clamp._base_url)) + # no add resource in clamp + # svc already exists in mock clamp + Clamp() + svc = Service(name="service01") + template_exists = Clamp.check_loop_template(service=svc) + assert template_exists + policy_exists = Clamp.check_policies(policy_name="MinMax", + req_policies=2) + assert policy_exists + +@pytest.mark.integration +def test_Loop_creation(): + """Integration tests for Loop Instance.""" + requests.get("{}/reset".format(Clamp._base_url)) + Clamp() + svc = Service(name="service01") + loop_template = Clamp.check_loop_template(service=svc) + response = requests.post("{}/reset".format(Clamp._base_url)) + response.raise_for_status() + loop = LoopInstance(template=loop_template, name="instance01", details={}) + loop.create() + +@pytest.mark.integration +def test_Loop_customization(): + """Integration tests for Loop Instance.""" + requests.get("{}/reset".format(Clamp._base_url)) + Clamp() + svc = Service(name="service01") + loop_template = Clamp.check_loop_template(service=svc) + response = requests.post("{}/reset".format(Clamp._base_url)) + response.raise_for_status() + loop = LoopInstance(template=loop_template, name="instance01", details={}) + loop.create() + loop.update_microservice_policy() + #add op policy FrequencyLimiter that already exists in clamp + loop.add_operational_policy(policy_type="onap.policies.controlloop.guard.common.FrequencyLimiter", + policy_version="1.0.0") + #only frequency configuration is available in mock clamp + loop.add_op_policy_config(loop.add_frequency_limiter, limit=1) + submit = loop.act_on_loop_policy(loop.submit) + assert submit + stop = loop.act_on_loop_policy(loop.stop) + assert stop + restart = loop.act_on_loop_policy(loop.restart) + assert restart + deploy = loop.deploy_microservice_to_dcae() + assert deploy + loop.undeploy_microservice_from_dcae() + new_details = loop._update_loop_details() + assert new_details["components"]["DCAE"]["componentState"]["stateName"] == "MICROSERVICE_UNINSTALLED_SUCCESSFULLY" diff --git a/integration_tests/test_10_msb_k8s.py b/integration_tests/test_10_msb_k8s.py new file mode 100644 index 0000000..5b2c0ee --- /dev/null +++ b/integration_tests/test_10_msb_k8s.py @@ -0,0 +1,125 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 logging +import os + +import pytest + +from onapsdk.msb.k8s import ( + Definition, + Instance, + ConnectivityInfo) + +logger = logging.getLogger("") +logger.setLevel(logging.DEBUG) +fh = logging.StreamHandler() +fh_formatter = logging.Formatter('%(asctime)s %(levelname)s %(lineno)d:%(filename)s(%(process)d) - %(message)s') +fh.setFormatter(fh_formatter) +logger.addHandler(fh) + +RB_NAME = "test_definition" +RB_VERSION = "ver_1" +PROFILE_NAME = "test-profile" +PROFILE_NAMESPACE = "test" +PROFILE_K8S_VERSION = "1.0" +PROFILE_ARTIFACT_PATH = "artifacts\\profile.tar.gz" # FILL ME +TEMPLATE_NAME = "test_template" +TEMPLATE_DESCRIPTION = "test description" +CLOUD_REGION_ID = "k8s_region_test" # FILL ME +CLOUD_OWNER = "CloudOwner" +KUBECONFIG_PATH = "artifacts\\kubeconfig" # FILL ME +MYPATH = os.path.dirname(os.path.realpath(__file__)) + +pytest.INSTANCE_ID = "" + + +@pytest.mark.integration +def test_definition_create_upload_artifact(): + definition = Definition.create(RB_NAME, RB_VERSION) + definition.upload_artifact(b'definition_artifact_file') + + +@pytest.mark.integration +def test_definition_get_all(): + definitions = list(Definition.get_all()) + + +@pytest.mark.integration +def test_configuration_template(): + definition = Definition.get_definition_by_name_version(RB_NAME, + RB_VERSION) + definition.create_configuration_template(TEMPLATE_NAME, TEMPLATE_DESCRIPTION) + definition.get_all_configuration_templates() + definition.get_configuration_template_by_name(TEMPLATE_NAME) + + +@pytest.mark.integration +def test_profile_create_upload_artifact(): + definition = Definition.get_definition_by_name_version(RB_NAME, + RB_VERSION) + profile = definition.create_profile(PROFILE_NAME, + PROFILE_NAMESPACE, + PROFILE_K8S_VERSION) + profile.upload_artifact(b'profile_artifact_file') + + +@pytest.mark.integration +def test_profile_get_all(): + definition = Definition.get_definition_by_name_version(RB_NAME, + RB_VERSION) + profiles = list(definition.get_all_profiles()) + + +@pytest.mark.integration +def test_connectivity_info_create(): + conninfo = ConnectivityInfo.create(CLOUD_REGION_ID, + CLOUD_OWNER, + b'kubeconfig_content_test') + + +@pytest.mark.integration +def test_instance_create(): + definition = Definition.get_definition_by_name_version(RB_NAME, + RB_VERSION) + profile = definition.get_profile_by_name(PROFILE_NAME) + instance = Instance.create(CLOUD_REGION_ID, + profile.profile_name, + definition.rb_name, + definition.rb_version) + pytest.INSTANCE_ID = instance.instance_id + + +@pytest.mark.integration +def test_instance_get_all(): + instances = list(Instance.get_all()) + + +@pytest.mark.integration +def test_instance_delete(): + instance = Instance.get_by_id(pytest.INSTANCE_ID) + instance.delete() + + +@pytest.mark.integration +def test_connectivity_info_delete(): + conninfo = ConnectivityInfo.get_connectivity_info_by_region_id(CLOUD_REGION_ID) + conninfo.delete() + + +@pytest.mark.integration +def test_definition_profile_get_delete(): + definition = Definition.get_definition_by_name_version(RB_NAME, RB_VERSION) + profile = definition.get_profile_by_name(PROFILE_NAME) + profile.delete() + definition.delete() diff --git a/integration_tests/test_11_ves.py b/integration_tests/test_11_ves.py new file mode 100644 index 0000000..5553176 --- /dev/null +++ b/integration_tests/test_11_ves.py @@ -0,0 +1,109 @@ +# Copyright 2022 Nokia +# +# 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 pytest +import logging +import os +import requests + +from onapsdk.configuration import settings +from onapsdk.utils.jinja import jinja_env +from onapsdk.ves.ves import Ves +from onapsdk.dmaap.dmaap import Dmaap + +logging.basicConfig(level=os.environ.get("LOGLEVEL", "DEBUG")) + + +def reset_dmaap_mock(): + requests.get("{}/reset".format(settings.DMAAP_URL)) + + +@pytest.mark.integration +def test_should_send_event_to_ves(): + # given + + requests.post("{}/set_dmaap_address".format(settings.VES_URL), json={"DMAAP_MOCK": settings.DMAAP_URL}) + event: str = jinja_env().get_template("ves_stnd_event.json.j2").render() + + # when + response = Ves.send_event( + basic_auth={'username': 'sample1', 'password': 'sample1'}, + json_event=event, + version="v7" + ) + + # then + assert response.status_code == 202 + + +@pytest.mark.integration +def test_should_send_batch_event_to_ves(): + # given + + requests.post("{}/set_dmaap_address".format(settings.VES_URL), json={"DMAAP_MOCK": settings.DMAAP_URL}) + event: str = jinja_env().get_template("ves7_batch_with_stndDefined_valid.json.j2").render() + + # when + response = Ves.send_batch_event( + basic_auth={'username': 'sample1', 'password': 'sample1'}, + json_event=event, + version="v7" + ) + + # then + assert response.status_code == 202 + + +@pytest.mark.integration +def test_should_send_event_to_ves_and_dmaap(): + # given + + requests.post("{}/set_dmaap_address".format(settings.VES_URL), json={"DMAAP_MOCK": settings.DMAAP_URL}) + event: str = jinja_env().get_template("ves_stnd_event.json.j2").render() + + # when + reset_dmaap_mock() + response = Ves.send_event( + basic_auth={'username': 'sample1', 'password': 'sample1'}, + json_event=event, + + version="v7" + ) + + # then + assert response.status_code == 202 + events = Dmaap.get_events_for_topic("fault", + basic_auth={'username': 'dcae@dcae.onap.org', 'password': 'demo123456!'}) + assert len(events) == 1 + + +@pytest.mark.integration +def test_should_send_batch_event_to_ves_and_dmaap(): + # given + + requests.post("{}/set_dmaap_address".format(settings.VES_URL), json={"DMAAP_MOCK": settings.DMAAP_URL}) + event: str = jinja_env().get_template("ves7_batch_with_stndDefined_valid.json.j2").render() + + # when + reset_dmaap_mock() + response = Ves.send_batch_event( + basic_auth={'username': 'sample1', 'password': 'sample1'}, + json_event=event, + version="v7" + ) + + # then + assert response.status_code == 202 + events = Dmaap.get_events_for_topic("fault", + basic_auth={'username': 'dcae@dcae.onap.org', 'password': 'demo123456!'}) + assert len(events) == 2 diff --git a/integration_tests/test_12_dmaap.py b/integration_tests/test_12_dmaap.py new file mode 100644 index 0000000..a3e9247 --- /dev/null +++ b/integration_tests/test_12_dmaap.py @@ -0,0 +1,31 @@ +# Copyright 2022 Nokia +# +# 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 pytest +import logging +import os + +from onapsdk.dmaap.dmaap import Dmaap + +logging.basicConfig(level=os.environ.get("LOGLEVEL", "DEBUG")) + + +@pytest.mark.integration +def test_should_get_all_topics_from_dmaap(): + # given + + # when + response = Dmaap.get_all_topics(basic_auth={'username': 'demo', 'password': 'demo123456!'}) + + # then + assert len(response) == 9 diff --git a/integration_tests/test_files/test_dd.json b/integration_tests/test_files/test_dd.json new file mode 100644 index 0000000..26f0b90 --- /dev/null +++ b/integration_tests/test_files/test_dd.json @@ -0,0 +1,48 @@ +[ + { + "name": "vpg_int_pktgen_private_ip_0", + "tags": "vpg_int_pktgen_private_ip_0", + "data_type": "string", + "description": "vpg_int_pktgen_private_ip_0", + "entry_schema": "string", + "updatedBy": "Singal, Kapil ", + "definition": { + "tags": "vpg_int_pktgen_private_ip_0", + "name": "vpg_int_pktgen_private_ip_0", + "property": { + "description": "vpg_int_pktgen_private_ip_0", + "type": "string" + }, + "updated-by": "Singal, Kapil ", + "sources": { + "input": { + "type": "source-input" + }, + "default": { + "type": "source-default", + "properties": {} + }, + "sdnc": { + "type": "source-rest", + "properties": { + "verb": "GET", + "type": "JSON", + "url-path": "/restconf/config/GENERIC-RESOURCE-API:services/service/$service-instance-id/service-data/vnfs/vnf/$vnf-id/vnf-data/vnf-topology/vnf-parameters-data/param/vpg_int_pktgen_private_ip_0", + "path": "/param/0/value", + "input-key-mapping": { + "service-instance-id": "service-instance-id", + "vnf-id": "vnf-id" + }, + "output-key-mapping": { + "vpg_int_pktgen_private_ip_0": "value" + }, + "key-dependencies": [ + "service-instance-id", + "vnf-id" + ] + } + } + } + } + } +] diff --git a/integration_tests/test_files/test_vLB_CBA_Python.zip b/integration_tests/test_files/test_vLB_CBA_Python.zip new file mode 100644 index 0000000..ddd41ac Binary files /dev/null and b/integration_tests/test_files/test_vLB_CBA_Python.zip differ diff --git a/integration_tests/ubuntu16.zip b/integration_tests/ubuntu16.zip new file mode 100644 index 0000000..9a98baa Binary files /dev/null and b/integration_tests/ubuntu16.zip differ diff --git a/integration_tests/urls.py b/integration_tests/urls.py new file mode 100644 index 0000000..2d6b11e --- /dev/null +++ b/integration_tests/urls.py @@ -0,0 +1,23 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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. +SDC_BE_URL = "http://sdc.api.fe.simpledemo.onap.org:30206" +SDC_FE_URL = "http://sdc.api.fe.simpledemo.onap.org:30206" +AAI_URL = "http://aai.api.sparky.simpledemo.onap.org:5000" +SO_URL = "http://so.api.simpledemo.onap.org:5001" +SDNC_URL = "http://sdnc.api.simpledemo.onap.org:5002" +CLAMP_URL = "http://clamp.api.simpledemo.onap.org:30258" +CDS_URL = "http://cds.api.simpledemo.onap.org:8080" +MSB_URL = "http://msb.k8s.api.simpledemo.onap.org:5003" +VES_URL = "http://ves.api.simpledemo.onap.org:30417" +DMAAP_URL = "http://dmaap.api.simpledemo.onap.org:3904" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a486f46 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +requests[socks]==2.27.1 +jinja2==3.0.3 +simplejson==3.17.6 +oyaml==1.0 +pyOpenSSL==22.0.0 +jsonschema==4.4.0 +dacite==1.6.0 +more-itertools>=8.12.0 diff --git a/scripts/build_all_branches_in.sh b/scripts/build_all_branches_in.sh new file mode 100755 index 0000000..3e56ddd --- /dev/null +++ b/scripts/build_all_branches_in.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +INITIAL_FOLDER=${PWD} +INITIAL_BRANCH=$(git rev-parse --abbrev-ref HEAD) + +if [ -e requirements.txt ] +then + pip install -r requirements.txt +fi +if [ -e doc-requirements.txt ] +then + pip install -r doc-requirements.txt +fi +if [ -e requirements.txt ] +then + pip install . +fi + +set -x +# Generating documentation for each other branch in a subdirectory +for BRANCH in $(git branch --remotes --format '%(refname:lstrip=3)' | grep -Ev '^(HEAD)$'); do + echo "*** Building doc for branch ${BRANCH} ***" + git checkout $BRANCH + cd ${INITIAL_FOLDER}${DOC_PATH} + make html + mkdir -p ${INITIAL_FOLDER}/public/$BRANCH + mv _build/html/ ${INITIAL_FOLDER}/public/$BRANCH + rm -rf _build/html/ + cd ${INITIAL_FOLDER} +done + +# "Develop" is the default branch so we point it as "latest" +# May/Will change to point master +ln public/develop public/latest + +git checkout $INITIAL_BRANCH diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..9fbcc6b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: Apache-2.0 +[metadata] +name = onapsdk +version = attr: src.onapsdk.version.__version__ +description = SDK to use ONAP Programatically +long_description = file: README.md, CHANGELOG.md +long_description_content_type = text/markdown +url = https://gitlab.com/Orange-OpenSource/lfn/onap/python-onapsdk +author = Orange OpenSource +license = Apache 2.0 +classifiers = + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +zip_safe = False +include_package_data = True +python_requires = >=3.7,<4 +package_dir= + =src +packages=find_namespace: +install_requires = + requests[socks]==2.24.0 + jinja2==3.0.3 + simplejson==3.17.6 + oyaml==1.0 + pyOpenSSL==22.0.0 + jsonschema==4.4.0 + dacite==1.6.0 +setup_requires = + pytest-runner==5.2 +tests_require = + pytest==7.0.1 + pytest-cov==3.0.0 + requests-mock==1.9.3 + +[options.packages.find] +where=src + +[options.package_data] +onapsdk = **/templates/* + +[aliases] +test=pytest + +[tool:pytest] +addopts = + --verbose --doctest-modules --junitxml=pytest-unit.xml + --cov-report term-missing --cov-report xml --cov-report html + --cov=src/onapsdk --maxfail=1 --cov-fail-under=98 + +testpaths = tests src diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..acfdb5d --- /dev/null +++ b/setup.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +# -*- coding: utf-8 -*- + +from setuptools import setup + +setup() diff --git a/src/onapsdk/__init__.py b/src/onapsdk/__init__.py new file mode 100644 index 0000000..ce228b1 --- /dev/null +++ b/src/onapsdk/__init__.py @@ -0,0 +1,14 @@ +"""ONAP SDK master package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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. diff --git a/src/onapsdk/aai/__init__.py b/src/onapsdk/aai/__init__.py new file mode 100644 index 0000000..e340efb --- /dev/null +++ b/src/onapsdk/aai/__init__.py @@ -0,0 +1,14 @@ +"""ONAP SDK AAI package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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. diff --git a/src/onapsdk/aai/aai_element.py b/src/onapsdk/aai/aai_element.py new file mode 100644 index 0000000..9472165 --- /dev/null +++ b/src/onapsdk/aai/aai_element.py @@ -0,0 +1,192 @@ +"""AAI Element module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 dataclasses import dataclass, field +from typing import Dict, Iterator, List, Optional + +from onapsdk.configuration import settings +from onapsdk.onap_service import OnapService +from onapsdk.utils.headers_creator import headers_aai_creator +from onapsdk.utils.jinja import jinja_env +from onapsdk.utils.gui import GuiItem, GuiList + +from onapsdk.exceptions import RelationshipNotFound, ResourceNotFound + + +@dataclass +class Relationship: + """Relationship class. + + A&AI elements could have relationship with other A&AI elements. + Relationships are represented by this class objects. + """ + + related_to: str + related_link: str + relationship_data: List[Dict[str, str]] + relationship_label: str = "" + related_to_property: List[Dict[str, str]] = field(default_factory=list) + + def get_relationship_data(self, relationship_key: str) -> Optional[str]: + """Get relationship data for given relationship key. + + From list of relationship data get the value for + given key + + Args: + relationship_key (str): Key to get relationship data value + + Returns: + Optional[str]: Relationship value or None if relationship data + with provided ket doesn't exist + + """ + for data in self.relationship_data: + if data["relationship-key"] == relationship_key: + return data["relationship-value"] + return None + + +class AaiElement(OnapService): + """Mother Class of all A&AI elements.""" + + name: str = "AAI" + server: str = "AAI" + base_url = settings.AAI_URL + api_version = "/aai/" + settings.AAI_API_VERSION + headers = headers_aai_creator(OnapService.headers) + + @classmethod + def get_guis(cls) -> GuiItem: + """Retrieve the status of the AAI GUIs. + + Only one GUI is referenced for AAI + the AAI sparky GUI + + Return the list of GUIs + """ + gui_url = settings.AAI_GUI_SERVICE + aai_gui_response = cls.send_message( + "GET", "Get AAI GUI Status", gui_url) + guilist = GuiList([]) + guilist.add(GuiItem( + gui_url, + aai_gui_response.status_code)) + return guilist + + +class AaiResource(AaiElement): + """A&AI resource class.""" + + @classmethod + def filter_none_key_values(cls, dict_to_filter: Dict[str, Optional[str]]) -> Dict[str, str]: + """Filter out None key values from dictionary. + + Iterate through given dictionary and filter None values. + + Args: + dict_to_filter (Dict): Dictionary to filter out None + + Returns:dataclasse init a field + Dict[str, str]: Filtered dictionary + + """ + return dict( + filter(lambda key_value_tuple: key_value_tuple[1] is not None, dict_to_filter.items(),) + ) + + @property + def url(self) -> str: + """Resource's url. + + Returns: + str: Resource's url + + """ + raise NotImplementedError + + @property + def relationships(self) -> Iterator[Relationship]: + """Resource relationships iterator. + + Yields: + Relationship: resource relationship + + Raises: + RelationshipNotFound: if request for relationships returned 404 + + """ + try: + generator = self.send_message_json("GET", + "Get object relationships", + f"{self.url}/relationship-list")\ + .get("relationship", []) + for relationship in generator: + yield Relationship( + related_to=relationship.get("related-to"), + relationship_label=relationship.get("relationship-label"), + related_link=relationship.get("related-link"), + relationship_data=relationship.get("relationship-data"), + ) + except ResourceNotFound as exc: + self._logger.error("Getting object relationships failed: %s", exc) + + msg = (f'{self.name} relationships not found.' + f'Server: {self.server}. Url: {self.url}') + raise RelationshipNotFound(msg) from exc + + @classmethod + def get_all_url(cls, *args, **kwargs) -> str: + """Return an url for all objects of given class. + + Returns: + str: URL to get all objects of given class + + """ + raise NotImplementedError + + @classmethod + def count(cls, *args, **kwargs) -> int: + """Get the count number of all objects of given class. + + Get the response, iterate through response (each class has different response) + -- the first key value is the count. + + Returns: + int: Count of the objects + + """ + return next(iter(cls.send_message_json( + "GET", + f"Get count of {cls.__name__} class instances", + f"{cls.get_all_url(*args, **kwargs)}?format=count" + )["results"][0].values())) + + def add_relationship(self, relationship: Relationship) -> None: + """Add relationship to aai resource. + + Add relationship to resource using A&AI API + + Args: + relationship (Relationship): Relationship to add + + """ + self.send_message( + "PUT", + f"add relationship to {self.__class__.__name__}", + f"{self.url}/relationship-list/relationship", + data=jinja_env() + .get_template("aai_add_relationship.json.j2") + .render(relationship=relationship), + ) diff --git a/src/onapsdk/aai/bulk.py b/src/onapsdk/aai/bulk.py new file mode 100644 index 0000000..435a0b4 --- /dev/null +++ b/src/onapsdk/aai/bulk.py @@ -0,0 +1,90 @@ +"""A&AI bulk module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 dataclasses import dataclass +from typing import Any, Dict, Iterable + +from more_itertools import chunked + +from onapsdk.configuration import settings +from onapsdk.utils.jinja import jinja_env + +from .aai_element import AaiElement + + +@dataclass +class AaiBulkRequest: + """Class to store information about a request to be sent in A&AI bulk request.""" + + action: str + uri: str + body: Dict[Any, Any] + + +@dataclass +class AaiBulkResponse: + """Class to store A&AI bulk response.""" + + action: str + uri: str + status_code: int + body: str + + +class AaiBulk(AaiElement): + """A&AI bulk class. + + Use it to send bulk request to A&AI. With bulk request you can send + multiple requests at once. + """ + + @property + def url(self) -> str: + """Bulk url. + + Returns: + str: A&AI bulk API url. + + """ + return f"{self.base_url}{self.api_version}/bulk" + + @classmethod + def single_transaction(cls, aai_requests: Iterable[AaiBulkRequest])\ + -> Iterable[AaiBulkResponse]: + """Singe transaction bulk request. + + Args: + aai_requests (Iterable[AaiBulkRequest]): Iterable object of requests to be sent + as a bulk request. + + Yields: + Iterator[Iterable[AaiBulkResponse]]: Bulk request responses. Each object + correspond to the sent request. + + """ + for requests_chunk in chunked(aai_requests, settings.AAI_BULK_CHUNK): + for response in cls.send_message_json(\ + "POST",\ + "Send bulk A&AI request",\ + f"{cls.base_url}{cls.api_version}/bulk/single-transaction",\ + data=jinja_env().get_template(\ + "aai_bulk.json.j2").render(operations=requests_chunk)\ + )["operation-responses"]: + yield AaiBulkResponse( + action=response["action"], + uri=response["uri"], + status_code=response["response-status-code"], + body=response["response-body"] + ) diff --git a/src/onapsdk/aai/business/__init__.py b/src/onapsdk/aai/business/__init__.py new file mode 100644 index 0000000..41f9671 --- /dev/null +++ b/src/onapsdk/aai/business/__init__.py @@ -0,0 +1,27 @@ +"""A&AI business package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 .customer import Customer, ServiceSubscription +from .instance import Instance +from .line_of_business import LineOfBusiness +from .network import NetworkInstance +from .owning_entity import OwningEntity +from .platform import Platform +from .pnf import PnfInstance +from .project import Project +from .service import ServiceInstance +from .sp_partner import SpPartner +from .vf_module import VfModuleInstance +from .vnf import VnfInstance diff --git a/src/onapsdk/aai/business/customer.py b/src/onapsdk/aai/business/customer.py new file mode 100644 index 0000000..cdefd6f --- /dev/null +++ b/src/onapsdk/aai/business/customer.py @@ -0,0 +1,603 @@ +"""AAI business module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 dataclasses import dataclass +from typing import Iterable, Iterator, Optional +from urllib.parse import urlencode + +from onapsdk.utils.jinja import jinja_env +from onapsdk.exceptions import APIError, ParameterError, ResourceNotFound + +from ..aai_element import AaiResource, Relationship +from ..cloud_infrastructure.cloud_region import CloudRegion +from .service import ServiceInstance + + +@dataclass +class ServiceSubscriptionCloudRegionTenantData: + """Dataclass to store cloud regions and tenants data for service subscription.""" + + cloud_owner: str = None + cloud_region_id: str = None + tenant_id: str = None + + +@dataclass +class ServiceSubscription(AaiResource): + """Service subscription class.""" + + service_type: str + resource_version: str + customer: "Customer" + + def __init__(self, customer: "Customer", service_type: str, resource_version: str) -> None: + """Service subscription object initialization. + + Args: + customer (Customer): Customer object + service_type (str): Service type + resource_version (str): Service subscription resource version + """ + super().__init__() + self.customer: "Customer" = customer + self.service_type: str = service_type + self.resource_version: str = resource_version + + def _get_service_instance_by_filter_parameter(self, + filter_parameter_name: str, + filter_parameter_value: str) -> ServiceInstance: + """Call a request to get service instance with given filter parameter and value. + + Args: + filter_parameter_name (str): Name of parameter to filter + filter_parameter_value (str): Value of filter parameter + + Returns: + ServiceInstance: ServiceInstance object + + """ + service_instance: dict = self.send_message_json( + "GET", + f"Get service instance with {filter_parameter_value} {filter_parameter_name}", + f"{self.url}/service-instances?{filter_parameter_name}={filter_parameter_value}" + )["service-instance"][0] + return ServiceInstance( + service_subscription=self, + instance_id=service_instance.get("service-instance-id"), + instance_name=service_instance.get("service-instance-name"), + service_type=service_instance.get("service-type"), + service_role=service_instance.get("service-role"), + environment_context=service_instance.get("environment-context"), + workload_context=service_instance.get("workload-context"), + created_at=service_instance.get("created-at"), + updated_at=service_instance.get("updated-at"), + description=service_instance.get("description"), + model_invariant_id=service_instance.get("model-invariant-id"), + model_version_id=service_instance.get("model-version-id"), + persona_model_version=service_instance.get("persona-model-version"), + widget_model_id=service_instance.get("widget-model-id"), + widget_model_version=service_instance.get("widget-model-version"), + bandwith_total=service_instance.get("bandwidth-total"), + vhn_portal_url=service_instance.get("vhn-portal-url"), + service_instance_location_id=service_instance.get("service-instance-location-id"), + resource_version=service_instance.get("resource-version"), + selflink=service_instance.get("selflink"), + orchestration_status=service_instance.get("orchestration-status"), + input_parameters=service_instance.get("input-parameters") + ) + + @classmethod + def get_all_url(cls, customer: "Customer") -> str: # pylint: disable=arguments-differ + """Return url to get all customers. + + Returns: + str: Url to get all customers + + """ + return (f"{cls.base_url}{cls.api_version}/business/customers/" + f"customer/{customer.global_customer_id}/service-subscriptions/") + + @classmethod + def create_from_api_response(cls, + api_response: dict, + customer: "Customer") -> "ServiceSubscription": + """Create service subscription using API response dict. + + Returns: + ServiceSubscription: ServiceSubscription object. + + """ + return cls( + service_type=api_response.get("service-type"), + resource_version=api_response.get("resource-version"), + customer=customer + ) + + @property + def url(self) -> str: + """Cloud region object url. + + URL used to call CloudRegion A&AI API + + Returns: + str: CloudRegion object url + + """ + return ( + f"{self.base_url}{self.api_version}/business/customers/" + f"customer/{self.customer.global_customer_id}/service-subscriptions/" + f"service-subscription/{self.service_type}" + ) + + @property + def service_instances(self) -> Iterator[ServiceInstance]: + """Service instances. + + Yields: + Iterator[ServiceInstance]: Service instance + + """ + for service_instance in \ + self.send_message_json("GET", + (f"Get all service instances for {self.service_type} service " + f"subscription"), + f"{self.url}/service-instances").get("service-instance", []): + yield ServiceInstance( + service_subscription=self, + instance_id=service_instance.get("service-instance-id"), + instance_name=service_instance.get("service-instance-name"), + service_type=service_instance.get("service-type"), + service_role=service_instance.get("service-role"), + environment_context=service_instance.get("environment-context"), + workload_context=service_instance.get("workload-context"), + created_at=service_instance.get("created-at"), + updated_at=service_instance.get("updated-at"), + description=service_instance.get("description"), + model_invariant_id=service_instance.get("model-invariant-id"), + model_version_id=service_instance.get("model-version-id"), + persona_model_version=service_instance.get("persona-model-version"), + widget_model_id=service_instance.get("widget-model-id"), + widget_model_version=service_instance.get("widget-model-version"), + bandwith_total=service_instance.get("bandwidth-total"), + vhn_portal_url=service_instance.get("vhn-portal-url"), + service_instance_location_id=service_instance.get("service-instance-location-id"), + resource_version=service_instance.get("resource-version"), + selflink=service_instance.get("selflink"), + orchestration_status=service_instance.get("orchestration-status"), + input_parameters=service_instance.get("input-parameters") + ) + + @property + def tenant_relationships(self) -> Iterator["Relationship"]: + """Tenant related relationships. + + Iterate through relationships and get related to tenant. + + Yields: + Relationship: Relationship related to tenant. + + """ + for relationship in self.relationships: + if relationship.related_to == "tenant": + yield relationship + + @property + def cloud_region(self) -> "CloudRegion": + """Cloud region associated with service subscription. + + IT'S DEPRECATED! `cloud_regions` parameter SHOULD BE USED + + Raises: + ParameterError: Service subscription has no associated cloud region. + + Returns: + CloudRegion: CloudRegion object + + """ + try: + return next(self.cloud_regions) + except StopIteration: + msg = f"No cloud region for service subscription '{self.name}'" + raise ParameterError(msg) + + @property + def tenant(self) -> "Tenant": + """Tenant associated with service subscription. + + IT'S DEPRECATED! `tenants` parameter SHOULD BE USED + + Raises: + ParameterError: Service subscription has no associated tenants + + Returns: + Tenant: Tenant object + + """ + try: + return next(self.tenants) + except StopIteration: + msg = f"No tenants for service subscription '{self.name}'" + raise ParameterError(msg) + + @property + def _cloud_regions_tenants_data(self) -> Iterator["ServiceSubscriptionCloudRegionTenantData"]: + for relationship in self.tenant_relationships: + cr_tenant_data: ServiceSubscriptionCloudRegionTenantData = \ + ServiceSubscriptionCloudRegionTenantData() + for data in relationship.relationship_data: + if data["relationship-key"] == "cloud-region.cloud-owner": + cr_tenant_data.cloud_owner = data["relationship-value"] + if data["relationship-key"] == "cloud-region.cloud-region-id": + cr_tenant_data.cloud_region_id = data["relationship-value"] + if data["relationship-key"] == "tenant.tenant-id": + cr_tenant_data.tenant_id = data["relationship-value"] + if all([cr_tenant_data.cloud_owner, + cr_tenant_data.cloud_region_id, + cr_tenant_data.tenant_id]): + yield cr_tenant_data + else: + self._logger.error("Invalid tenant relationship: %s", relationship) + + @property + def cloud_regions(self) -> Iterator["CloudRegion"]: + """Cloud regions associated with service subscription. + + Yields: + CloudRegion: CloudRegion object + + """ + cloud_region_set: set = set() + for cr_data in self._cloud_regions_tenants_data: + cloud_region_set.add((cr_data.cloud_owner, cr_data.cloud_region_id)) + for cloud_region_data in cloud_region_set: + try: + yield CloudRegion.get_by_id(cloud_owner=cloud_region_data[0], + cloud_region_id=cloud_region_data[1]) + except ResourceNotFound: + self._logger.error("Can't get cloud region %s %s", cloud_region_data[0], \ + cloud_region_data[1]) + + @property + def tenants(self) -> Iterator["Tenant"]: + """Tenants associated with service subscription. + + Yields: + Tenant: Tenant object + + """ + for cr_data in self._cloud_regions_tenants_data: + try: + cloud_region: CloudRegion = CloudRegion.get_by_id(cr_data.cloud_owner, + cr_data.cloud_region_id) + yield cloud_region.get_tenant(cr_data.tenant_id) + except ResourceNotFound: + self._logger.error("Can't get %s tenant", cr_data.tenant_id) + + def get_service_instance_by_id(self, service_instance_id) -> ServiceInstance: + """Get service instance using it's ID. + + Args: + service_instance_id (str): ID of the service instance + + Returns: + ServiceInstance: ServiceInstance object + + """ + return self._get_service_instance_by_filter_parameter( + "service-instance-id", + service_instance_id + ) + + def get_service_instance_by_name(self, service_instance_name: str) -> ServiceInstance: + """Get service instance using it's name. + + Args: + service_instance_name (str): Name of the service instance + + Returns: + ServiceInstance: ServiceInstance object + + """ + return self._get_service_instance_by_filter_parameter( + "service-instance-name", + service_instance_name + ) + + def link_to_cloud_region_and_tenant(self, + cloud_region: "CloudRegion", + tenant: "Tenant") -> None: + """Create relationship between object and cloud region with tenant. + + Args: + cloud_region (CloudRegion): Cloud region to link to + tenant (Tenant): Cloud region tenant to link to + """ + relationship: Relationship = Relationship( + related_to="tenant", + related_link=tenant.url, + relationship_data=[ + { + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": cloud_region.cloud_owner, + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": cloud_region.cloud_region_id, + }, + { + "relationship-key": "tenant.tenant-id", + "relationship-value": tenant.tenant_id, + }, + ], + related_to_property=[ + {"property-key": "tenant.tenant-name", "property-value": tenant.name} + ], + ) + self.add_relationship(relationship) + + +class Customer(AaiResource): + """Customer class.""" + + def __init__(self, + global_customer_id: str, + subscriber_name: str, + subscriber_type: str, + resource_version: str = None) -> None: + """Initialize Customer class object. + + Args: + global_customer_id (str): Global customer id used across ONAP to + uniquely identify customer. + subscriber_name (str): Subscriber name, an alternate way to retrieve a customer. + subscriber_type (str): Subscriber type, a way to provide VID with + only the INFRA customers. + resource_version (str, optional): Used for optimistic concurrency. + Must be empty on create, valid on update + and delete. Defaults to None. + + """ + super().__init__() + self.global_customer_id: str = global_customer_id + self.subscriber_name: str = subscriber_name + self.subscriber_type: str = subscriber_type + self.resource_version: str = resource_version + + def __repr__(self) -> str: # noqa + """Customer description. + + Returns: + str: Customer object description + + """ + return (f"Customer(global_customer_id={self.global_customer_id}, " + f"subscriber_name={self.subscriber_name}, " + f"subscriber_type={self.subscriber_type}, " + f"resource_version={self.resource_version})") + + def get_service_subscription_by_service_type(self, service_type: str) -> ServiceSubscription: + """Get subscribed service by service type. + + Call a request to get service subscriptions filtered by service-type parameter. + + Args: + service_type (str): Service type + + Returns: + ServiceSubscription: Service subscription + + """ + response: dict = self.send_message_json( + "GET", + f"Get service subscription with {service_type} service type", + (f"{self.base_url}{self.api_version}/business/customers/" + f"customer/{self.global_customer_id}/service-subscriptions" + f"?service-type={service_type}") + ) + return ServiceSubscription.create_from_api_response(response["service-subscription"][0], + self) + + @classmethod + def get_all_url(cls) -> str: # pylint: disable=arguments-differ + """Return an url to get all customers. + + Returns: + str: URL to get all customers + + """ + return f"{cls.base_url}{cls.api_version}/business/customers" + + @classmethod + def get_all(cls, + global_customer_id: str = None, + subscriber_name: str = None, + subscriber_type: str = None) -> Iterator["Customer"]: + """Get all customers. + + Call an API to retrieve all customers. It can be filtered + by global-customer-id, subscriber-name and/or subsriber-type. + + Args: + global_customer_id (str): global-customer-id to filer customers by. Defaults to None. + subscriber_name (str): subscriber-name to filter customers by. Defaults to None. + subscriber_type (str): subscriber-type to filter customers by. Defaults to None. + + """ + filter_parameters: dict = cls.filter_none_key_values( + { + "global-customer-id": global_customer_id, + "subscriber-name": subscriber_name, + "subscriber-type": subscriber_type, + } + ) + url: str = (f"{cls.get_all_url()}?{urlencode(filter_parameters)}") + for customer in cls.send_message_json("GET", "get customers", url).get("customer", []): + yield Customer( + global_customer_id=customer["global-customer-id"], + subscriber_name=customer["subscriber-name"], + subscriber_type=customer["subscriber-type"], + resource_version=customer["resource-version"], + ) + + @classmethod + def get_by_global_customer_id(cls, global_customer_id: str) -> "Customer": + """Get customer by it's global customer id. + + Args: + global_customer_id (str): global customer ID + + Returns: + Customer: Customer with given global_customer_id + + """ + response: dict = cls.send_message_json( + "GET", + f"Get {global_customer_id} customer", + f"{cls.base_url}{cls.api_version}/business/customers/customer/{global_customer_id}" + ) + return Customer( + global_customer_id=response["global-customer-id"], + subscriber_name=response["subscriber-name"], + subscriber_type=response["subscriber-type"], + resource_version=response["resource-version"], + ) + + @classmethod + def create(cls, + global_customer_id: str, + subscriber_name: str, + subscriber_type: str, + service_subscriptions: Optional[Iterable[str]] = None) -> "Customer": + """Create customer. + + Args: + global_customer_id (str): Global customer id used across ONAP + to uniquely identify customer. + subscriber_name (str): Subscriber name, an alternate way + to retrieve a customer. + subscriber_type (str): Subscriber type, a way to provide + VID with only the INFRA customers. + service_subscriptions (Optional[Iterable[str]], optional): Iterable + of service subscription names should be created for newly + created customer. Defaults to None. + + Returns: + Customer: Customer object. + + """ + url: str = ( + f"{cls.base_url}{cls.api_version}/business/customers/" + f"customer/{global_customer_id}" + ) + cls.send_message( + "PUT", + "declare customer", + url, + data=jinja_env() + .get_template("customer_create.json.j2") + .render( + global_customer_id=global_customer_id, + subscriber_name=subscriber_name, + subscriber_type=subscriber_type, + service_subscriptions=service_subscriptions + ), + ) + response: dict = cls.send_message_json( + "GET", "get created customer", url + ) # Call API one more time to get Customer's resource version + return Customer( + global_customer_id=response["global-customer-id"], + subscriber_name=response["subscriber-name"], + subscriber_type=response["subscriber-type"], + resource_version=response["resource-version"], + ) + + @property + def url(self) -> str: + """Return customer object url. + + Unique url address to get customer's data. + + Returns: + str: Customer object url + + """ + return ( + f"{self.base_url}{self.api_version}/business/customers/customer/" + f"{self.global_customer_id}?resource-version={self.resource_version}" + ) + + @property + def service_subscriptions(self) -> Iterator[ServiceSubscription]: + """Service subscriptions of customer resource. + + Yields: + ServiceSubscription: ServiceSubscription object + + """ + try: + response: dict = self.send_message_json( + "GET", + "get customer service subscriptions", + f"{self.base_url}{self.api_version}/business/customers/" + f"customer/{self.global_customer_id}/service-subscriptions" + ) + for service_subscription in response.get("service-subscription", []): + yield ServiceSubscription.create_from_api_response( + service_subscription, + self + ) + except ResourceNotFound as exc: + self._logger.info( + "Subscriptions are not " \ + "found for a customer: %s", exc) + except APIError as exc: + self._logger.error( + "API returned an error: %s", exc) + + def subscribe_service(self, service_type: str) -> "ServiceSubscription": + """Create SDC Service subscription. + + If service subscription with given service_type already exists it won't create + a new resource but use the existing one. + + Args: + service_type (str): Value defined by orchestration to identify this service + across ONAP. + """ + try: + return self.get_service_subscription_by_service_type(service_type) + except ResourceNotFound: + self._logger.info("Create service subscription for %s customer", + self.global_customer_id) + self.send_message( + "PUT", + "Create service subscription", + (f"{self.base_url}{self.api_version}/business/customers/" + f"customer/{self.global_customer_id}/service-subscriptions/" + f"service-subscription/{service_type}") + ) + return self.get_service_subscription_by_service_type(service_type) + + def delete(self) -> None: + """Delete customer. + + Sends request to A&AI to delete customer object. + + """ + self.send_message( + "DELETE", + "Delete customer", + self.url + ) diff --git a/src/onapsdk/aai/business/instance.py b/src/onapsdk/aai/business/instance.py new file mode 100644 index 0000000..146aee9 --- /dev/null +++ b/src/onapsdk/aai/business/instance.py @@ -0,0 +1,55 @@ +"""Base instance module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 abc import ABC, abstractmethod + +from ..aai_element import AaiResource + + +class Instance(AaiResource, ABC): + """Abstract instance class.""" + + def __init__(self, # pylint: disable=too-many-arguments + resource_version: str = None, + model_invariant_id: str = None, + model_version_id: str = None) -> None: + """Instance initialization. + + Args: + resource_version (str, optional): Used for optimistic concurrency. + Must be empty on create, valid on update and delete. Defaults to None. + model_invariant_id (str, optional): The ASDC model id for this resource or + service model. Defaults to None. + model_version_id (str, optional): The ASDC model version for this resource or + service model. Defaults to None. + """ + super().__init__() + self.resource_version: str = resource_version + self.model_invariant_id: str = model_invariant_id + self.model_version_id: str = model_version_id + + @abstractmethod + def delete(self, a_la_carte: bool = True) -> "DeletionRequest": + """Create instance deletion request. + + Send request to delete instance + + Args: + a_la_carte (boolean): deletion mode + + Returns: + DeletionRequest: Deletion request + + """ diff --git a/src/onapsdk/aai/business/line_of_business.py b/src/onapsdk/aai/business/line_of_business.py new file mode 100644 index 0000000..61fc0f8 --- /dev/null +++ b/src/onapsdk/aai/business/line_of_business.py @@ -0,0 +1,123 @@ +"""A&AI line of business module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Any, Dict, Iterator + +from onapsdk.utils.jinja import jinja_env + +from ..aai_element import AaiResource + + +class LineOfBusiness(AaiResource): + """Line of business class.""" + + def __init__(self, name: str, resource_version: str) -> None: + """Line of business object initialization. + + Args: + name (str): Line of business name + resource_version (str): resource version + """ + super().__init__() + self.name: str = name + self.resource_version: str = resource_version + + @property + def url(self) -> str: + """Line of business's url. + + Returns: + str: Resource's url + + """ + return (f"{self.base_url}{self.api_version}/business/lines-of-business/" + f"line-of-business/{self.name}") + + @classmethod + def get_all_url(cls) -> str: # pylint: disable=arguments-differ + """Return url to get all lines of business. + + Returns: + str: Url to get all lines of business + + """ + return f"{cls.base_url}{cls.api_version}/business/lines-of-business/" + + def __repr__(self) -> str: + """Line of business object representation. + + Returns: + str: Line of business object representation + + """ + return f"LineOfBusiness(name={self.name})" + + @classmethod + def get_all(cls) -> Iterator["LineOfBusiness"]: + """Get all line of business. + + Yields: + LineOfBusiness: LineOfBusiness object + + """ + url: str = f"{cls.base_url}{cls.api_version}/business/lines-of-business" + for line_of_business in cls.send_message_json("GET", + "Get A&AI lines of business", + url).get("line-of-business", []): + yield cls( + line_of_business.get("line-of-business-name"), + line_of_business.get("resource-version") + ) + + @classmethod + def create(cls, name: str) -> "LineOfBusiness": + """Create line of business A&AI resource. + + Args: + name (str): line of business name + + Returns: + LineOfBusiness: Created LineOfBusiness object + + """ + cls.send_message( + "PUT", + "Declare A&AI line of business", + (f"{cls.base_url}{cls.api_version}/business/lines-of-business/" + f"line-of-business/{name}"), + data=jinja_env().get_template("aai_line_of_business_create.json.j2").render( + line_of_business_name=name + ) + ) + return cls.get_by_name(name) + + @classmethod + def get_by_name(cls, name: str) -> "LineOfBusiness": + """Get line of business resource by it's name. + + Raises: + ResourceNotFound: Line of business requested by a name does not exist. + + Returns: + LineOfBusiness: Line of business requested by a name. + + """ + url = (f"{cls.base_url}{cls.api_version}/business/lines-of-business/" + f"line-of-business/{name}") + response: Dict[str, Any] = \ + cls.send_message_json("GET", + f"Get {name} line of business", + url) + return cls(response["line-of-business-name"], response["resource-version"]) diff --git a/src/onapsdk/aai/business/network.py b/src/onapsdk/aai/business/network.py new file mode 100644 index 0000000..e36cf62 --- /dev/null +++ b/src/onapsdk/aai/business/network.py @@ -0,0 +1,223 @@ +"""Network instance module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.so.deletion import NetworkDeletionRequest + +from .instance import Instance + + +class NetworkInstance(Instance): # pylint: disable=too-many-instance-attributes + """Network instance class.""" + + def __init__(self, # pylint: disable=too-many-arguments, too-many-locals + service_instance: "ServiceInstance", + network_id: str, + is_bound_to_vpn: bool, + is_provider_network: bool, + is_shared_network: bool, + is_external_network: bool, + network_name: str = None, + network_type: str = None, + network_role: str = None, + network_technology: str = None, + neutron_network_id: str = None, + service_id: str = None, + network_role_instance: str = None, + resource_version: str = None, + orchestration_status: str = None, + heat_stack_id: str = None, + mso_catalog_key: str = None, + model_invariant_id: str = None, + contrail_network_fqdn: str = None, + persona_model_version: str = None, + model_version_id: str = None, + model_customization_id: str = None, + widget_model_id: str = None, + physical_network_name: str = None, + widget_model_version: str = None, + selflink: str = None, + operational_status: str = None, + is_trunked: bool = None): + """Network instance object initialization. + + Args: + service_instance (ServiceInstance): Service instance object + network_id (str): Network ID, should be uuid. Unique across A&AI. + is_bound_to_vpn (bool): Set to true if bound to VPN + is_provider_network (bool): boolean indicatating whether or not network + is a provider network. + is_shared_network (bool): boolean indicatating whether + or not network is a shared network. + is_external_network (bool): boolean indicatating whether + or not network is an external network. + network_name (str, optional): Name of the network, governed by some naming convention. + Defaults to None. + network_type (str, optional): Type of the network. Defaults to None. + network_role (str, optional): Role the network. Defaults to None. + network_technology (str, optional): Network technology. Defaults to None. + neutron_network_id (str, optional): Neutron network id of this Interface. + Defaults to None. + service_id (str, optional): Unique identifier of service from ASDC. + Does not strictly map to ASDC services. Defaults to None. + network_role_instance (str, optional): network role instance. Defaults to None. + resource_version (str, optional): Used for optimistic concurrency. + Must be empty on create, valid on update and delete. Defaults to None. + orchestration_status (str, optional): Orchestration status of this VNF, + mastered by MSO. Defaults to None. + heat_stack_id (str, optional): Heat stack id corresponding to this instance, + managed by MSO. Defaults to None. + mso_catalog_key (str, optional): Corresponds to the SDN-C catalog id used to + configure this VCE. Defaults to None. + contrail_network_fqdn (str, optional): Contrail FQDN for the network. Defaults to None. + model_invariant_id (str, optional): the ASDC model id for this resource + or service model. Defaults to None. + model_version_id (str, optional): the ASDC model version for this resource + or service model. Defaults to None. + persona_model_version (str, optional): the ASDC model version for this resource + or service model. Defaults to None. + model_customization_id (str, optional): captures the id of all the configuration + used to customize the resource for the service. Defaults to None. + widget_model_id (str, optional): the ASDC data dictionary widget model. + This maps directly to the A&AI widget. Defaults to None. + widget_model_version (str, optional): the ASDC data dictionary version of + the widget model. This maps directly to the A&AI version of the widget. + Defaults to None. + physical_network_name (str, optional): Name associated with the physical network. + Defaults to None. + selflink (str, optional): Path to the controller object. Defaults to None. + operational_status (str, optional): Indicator for whether the resource is considered + operational. Defaults to None. + is_trunked (bool, optional): Trunked network indication. Defaults to None. + """ + super().__init__(resource_version=resource_version, + model_version_id=model_version_id, + model_invariant_id=model_invariant_id) + self.service_instance: "ServiceInstance" = service_instance + self.network_id: str = network_id + self.is_bound_to_vpn: bool = is_bound_to_vpn + self.is_provider_network: bool = is_provider_network + self.is_shared_network: bool = is_shared_network + self.is_external_network: bool = is_external_network + self.network_name: str = network_name + self.network_type: str = network_type + self.network_role: str = network_role + self.network_technology: str = network_technology + self.neutron_network_id: str = neutron_network_id + self.service_id: str = service_id + self.network_role_instance: str = network_role_instance + self.orchestration_status: str = orchestration_status + self.heat_stack_id: str = heat_stack_id + self.mso_catalog_key: str = mso_catalog_key + self.contrail_network_fqdn: str = contrail_network_fqdn + self.model_customization_id: str = model_customization_id + self.physical_network_name: str = physical_network_name + self.selflink: str = selflink + self.operational_status: str = operational_status + self.is_trunked: bool = is_trunked + self.persona_model_version: str = persona_model_version + self.widget_model_id: str = widget_model_id + self.widget_model_version: str = widget_model_version + + def __repr__(self) -> str: + """Network instance object representation. + + Returns: + str: Human readable network instance representation + + """ + return (f"NetworkInstance(network_id={self.network_id}, " + f"network_name={self.network_name}, " + f"is_bound_to_vpn={self.is_bound_to_vpn}, " + f"is_provider_network={self.is_provider_network}, " + f"is_shared_network={self.is_shared_network}, " + f"is_external_network={self.is_external_network}, " + f"orchestration_status={self.orchestration_status})") + + @classmethod + def get_all_url(cls) -> str: # pylint: disable=arguments-differ + """Return url to get all networks. + + Returns: + str: Url to get all networks + + """ + return f"{cls.base_url}{cls.api_version}/network/l3-networks/" + + @property + def url(self) -> str: + """Network instance url. + + Returns: + str: NetworkInstance url + + """ + return f"{self.base_url}{self.api_version}/network/l3-networks/l3-network/{self.network_id}" + + @classmethod + def create_from_api_response(cls, api_response: dict, + service_instance: "ServiceInstance") -> "NetworkInstance": + """Create network instance object using HTTP API response dictionary. + + Args: + api_response (dict): A&AI API response dictionary + service_instance (ServiceInstance): Service instance with which network is related + + Returns: + VnfInstance: VnfInstance object + + """ + return cls(service_instance=service_instance, + network_id=api_response["network-id"], + is_bound_to_vpn=api_response["is-bound-to-vpn"], + is_provider_network=api_response["is-provider-network"], + is_shared_network=api_response["is-shared-network"], + is_external_network=api_response["is-external-network"], + network_name=api_response.get("network-name"), + network_type=api_response.get("network-type"), + network_role=api_response.get("network-role"), + network_technology=api_response.get("network-technology"), + neutron_network_id=api_response.get("neutron-network-id"), + service_id=api_response.get("service-id"), + network_role_instance=api_response.get("network-role-instance"), + resource_version=api_response.get("resource-version"), + orchestration_status=api_response.get("orchestration-status"), + heat_stack_id=api_response.get("heat-stack-id"), + mso_catalog_key=api_response.get("mso-catalog-key"), + model_invariant_id=api_response.get("model-invariant-id"), + contrail_network_fqdn=api_response.get("contrail-network-fqdn"), + model_version_id=api_response.get("model-version-id"), + model_customization_id=api_response.get("model-customization-id"), + widget_model_id=api_response.get("widget-model-id"), + persona_model_version=api_response.get("persona-model-version"), + physical_network_name=api_response.get("physical-network-name"), + selflink=api_response.get("selflink"), + widget_model_version=api_response.get("widget-model-version"), + operational_status=api_response.get("operational-status"), + is_trunked=api_response.get("is-trunked")) + + def delete(self, a_la_carte: bool = True) -> "NetworkDeletionRequest": + """Create network deletion request. + + Send request to delete network instance + + Args: + a_la_carte (boolean): deletion mode + + Returns: + NetworkDeletionRequest: Deletion request + + """ + self._logger.debug("Delete %s network", self.network_id) + return NetworkDeletionRequest.send_request(self, a_la_carte) diff --git a/src/onapsdk/aai/business/owning_entity.py b/src/onapsdk/aai/business/owning_entity.py new file mode 100644 index 0000000..bf1e7c1 --- /dev/null +++ b/src/onapsdk/aai/business/owning_entity.py @@ -0,0 +1,154 @@ +"""A&AI owning entity module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 uuid import uuid4 +from typing import Iterator + +from onapsdk.utils.jinja import jinja_env +from onapsdk.exceptions import ResourceNotFound + +from ..aai_element import AaiResource + + +class OwningEntity(AaiResource): + """Owning entity class.""" + + def __init__(self, name: str, owning_entity_id: str, resource_version: str) -> None: + """Owning entity object initialization. + + Args: + name (str): Owning entity name + owning_entity_id (str): owning entity ID + resource_version (str): resource version + """ + super().__init__() + self.name: str = name + self.owning_entity_id: str = owning_entity_id + self.resource_version: str = resource_version + + def __repr__(self) -> str: + """Owning entity object representation. + + Returns: + str: Owning entity object representation + + """ + return f"OwningEntity(name={self.name}, owning_entity_id={self.owning_entity_id})" + + @property + def url(self) -> str: + """Owning entity object url. + + Returns: + str: Url + + """ + return (f"{self.base_url}{self.api_version}/business/owning-entities/owning-entity/" + f"{self.owning_entity_id}") + + @classmethod + def get_all_url(cls) -> str: # pylint: disable=arguments-differ + """Return url to get all owning entities. + + Returns: + str: Url to get all owning entities + + """ + return f"{cls.base_url}{cls.api_version}/business/owning-entities" + + @classmethod + def get_all(cls) -> Iterator["OwningEntity"]: + """Get all owning entities. + + Yields: + OwningEntity: OwningEntity object + + """ + url: str = cls.get_all_url() + for owning_entity in cls.send_message_json("GET", + "Get A&AI owning entities", + url).get("owning-entity", []): + yield cls( + owning_entity.get("owning-entity-name"), + owning_entity.get("owning-entity-id"), + owning_entity.get("resource-version") + ) + + @classmethod + def get_by_owning_entity_id(cls, owning_entity_id: str) -> "OwningEntity": + """Get owning entity by it's ID. + + Args: + owning_entity_id (str): owning entity object id + + Returns: + OwningEntity: OwningEntity object + + """ + response: dict = cls.send_message_json( + "GET", + "Get A&AI owning entity", + (f"{cls.base_url}{cls.api_version}/business/owning-entities/" + f"owning-entity/{owning_entity_id}") + ) + return cls( + response.get("owning-entity-name"), + response.get("owning-entity-id"), + response.get("resource-version") + ) + + @classmethod + def get_by_owning_entity_name(cls, owning_entity_name: str) -> "OwningEntity": + """Get owning entity resource by it's name. + + Raises: + ResourceNotFound: Owning entity requested by a name does not exist. + + Returns: + OwningEntity: Owning entity requested by a name. + + """ + for owning_entity in cls.get_all(): + if owning_entity.name == owning_entity_name: + return owning_entity + + msg = f'Owning entity "{owning_entity_name}" does not exist.' + raise ResourceNotFound(msg) + + @classmethod + def create(cls, name: str, owning_entity_id: str = None) -> "OwningEntity": + """Create owning entity A&AI resource. + + Args: + name (str): owning entity name + owning_entity_id (str): owning entity ID. Defaults to None. + + Returns: + OwningEntity: Created OwningEntity object + + """ + if not owning_entity_id: + owning_entity_id = str(uuid4()) + cls.send_message( + "PUT", + "Declare A&AI owning entity", + (f"{cls.base_url}{cls.api_version}/business/owning-entities/" + f"owning-entity/{owning_entity_id}"), + data=jinja_env().get_template("aai_owning_entity_create.json.j2").render( + owning_entity_name=name, + owning_entity_id=owning_entity_id + ) + ) + return cls.get_by_owning_entity_id(owning_entity_id) diff --git a/src/onapsdk/aai/business/platform.py b/src/onapsdk/aai/business/platform.py new file mode 100644 index 0000000..5c12ba8 --- /dev/null +++ b/src/onapsdk/aai/business/platform.py @@ -0,0 +1,123 @@ +"""A&AI platform module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Any, Dict, Iterator + +from onapsdk.utils.jinja import jinja_env + +from ..aai_element import AaiResource + + +class Platform(AaiResource): + """Platform class.""" + + def __init__(self, name: str, resource_version: str) -> None: + """Platform object initialization. + + Args: + name (str): Platform name + resource_version (str): resource version + """ + super().__init__() + self.name: str = name + self.resource_version: str = resource_version + + def __repr__(self) -> str: + """Platform object representation. + + Returns: + str: Platform object representation + + """ + return f"Platform(name={self.name})" + + @property + def url(self) -> str: + """Platform's url. + + Returns: + str: Resource's url + + """ + return (f"{self.base_url}{self.api_version}/business/platforms/" + f"platform/{self.name}") + + @classmethod + def get_all_url(cls) -> str: # pylint: disable=arguments-differ + """Return url to get all platforms. + + Returns: + str: Url to get all platforms + + """ + return f"{cls.base_url}{cls.api_version}/business/platforms" + + @classmethod + def get_all(cls) -> Iterator["Platform"]: + """Get all platform. + + Yields: + Platform: Platform object + + """ + url: str = cls.get_all_url() + for platform in cls.send_message_json("GET", + "Get A&AI platforms", + url).get("platform", []): + yield cls( + platform.get("platform-name"), + platform.get("resource-version") + ) + + @classmethod + def create(cls, name: str) -> "Platform": + """Create platform A&AI resource. + + Args: + name (str): platform name + + Returns: + Platform: Created Platform object + + """ + cls.send_message( + "PUT", + "Declare A&AI platform", + (f"{cls.base_url}{cls.api_version}/business/platforms/" + f"platform/{name}"), + data=jinja_env().get_template("aai_platform_create.json.j2").render( + platform_name=name + ) + ) + return cls.get_by_name(name) + + @classmethod + def get_by_name(cls, name: str) -> "Platform": + """Get platform resource by it's name. + + Raises: + ResourceNotFound: Platform requested by a name does not exist. + + Returns: + Platform: Platform requested by a name. + + """ + url = (f"{cls.base_url}{cls.api_version}/business/platforms/" + f"platform/{name}") + response: Dict[str, Any] = \ + cls.send_message_json("GET", + f"Get {name} platform", + url) + return cls(response["platform-name"], response["resource-version"]) diff --git a/src/onapsdk/aai/business/pnf.py b/src/onapsdk/aai/business/pnf.py new file mode 100644 index 0000000..9061ebf --- /dev/null +++ b/src/onapsdk/aai/business/pnf.py @@ -0,0 +1,267 @@ +"""Pnf instance module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Iterator, Optional, TYPE_CHECKING + +from onapsdk.exceptions import ResourceNotFound +from .instance import Instance + +if TYPE_CHECKING: + from .service import ServiceInstance # pylint: disable=cyclic-import + +class PnfInstance(Instance): # pylint: disable=too-many-instance-attributes + """Pnf instance class.""" + + def __init__(self, # pylint: disable=too-many-arguments, too-many-locals + service_instance: "ServiceInstance", + pnf_name: str, + in_maint: bool, + selflink: str = None, + pnf_id: str = None, + equip_type: str = None, + equip_vendor: str = None, + equip_model: str = None, + management_option: str = None, + orchestration_status: str = None, + ipaddress_v4_oam: str = None, + sw_version: str = None, + frame_id: str = None, + serial_number: str = None, + ipaddress_v4_loopback_0: str = None, + ipaddress_v6_loopback_0: str = None, + ipaddress_v4_aim: str = None, + ipaddress_v6_aim: str = None, + ipaddress_v6_oam: str = None, + inv_status: str = None, + resource_version: str = None, + prov_status: str = None, + nf_role: str = None, + admin_status: str = None, + operational_status: str = None, + model_customization_id: str = None, + model_invariant_id: str = None, + model_version_id: str = None, + pnf_ipv4_address: str = None, + pnf_ipv6_address: str = None) -> None: + """Pnf instance object initialization. + + Args: + service_instance (ServiceInstance): Service instance object + pnf_name (str): unique name of Physical Network Function + in_maint (bool): Used to indicate whether or not this object is in maintenance mode + (maintenance mode = True). This field (in conjunction with prov_status) + is used to suppress alarms and vSCL on VNFs/VMs. + selflink (str, optional): URL to endpoint where AAI can get more details. + Defaults to None. + pnf_id (str, optional): id of pnf. Defaults to None. + equip_type (str, optional): Equipment type. Source of truth should define valid values. + Defaults to None. + equip_vendor (str, optional): Equipment vendor. Source of truth should define + valid values. Defaults to None. + equip_model (str, optional): Equipment model. Source of truth should define + valid values. Defaults to None. + management_option (str, optional): identifier of managed customer. Defaults to None. + orchestration_status (str, optional): Orchestration status of this pnf. + Defaults to None. + ipaddress_v4_oam (str, optional): ipv4-oam-address with new naming + convention for IP addresses. Defaults to None. + sw_version (str, optional): sw-version is the version of SW for the hosted + application on the PNF. Defaults to None. + frame_id (str, optional): ID of the physical frame (relay rack) where pnf is installed. + Defaults to None. + serial_number (str, optional): Serial number of the device. Defaults to None. + ipaddress_v4_loopback_0 (str, optional): IPV4 Loopback 0 address. Defaults to None. + ipaddress_v6_loopback_0 (str, optional): IPV6 Loopback 0 address. Defaults to None. + ipaddress_v4_aim (str, optional): IPV4 AIM address. Defaults to None. + ipaddress_v6_aim (str, optional): IPV6 AIM address. Defaults to None. + ipaddress_v6_oam (str, optional): IPV6 OAM address. Defaults to None. + inv_status (str, optional): CANOPI's inventory status. Only set with values exactly + as defined by CANOPI. Defaults to None. + resource_version (str, optional): Used for optimistic concurrency. + Must be empty on create, valid on update and delete. Defaults to None. + prov_status (str, optional): Prov Status of this device (not under canopi control) + Valid values [PREPROV/NVTPROV/PROV]. Defaults to None. + nf_role (str, optional): Nf Role is the role performed by this instance in the network. + Defaults to None. + admin_status (str, optional): admin Status of this PNF. Defaults to None. + operational_status (str, optional): Store the operational-status for this object. + Defaults to None. + model_customization_id (str, optional): Store the model-customization-id + for this object. Defaults to None. + model_invariant_id (str, optional): The ASDC model id for this resource model. + Defaults to None. + model_version_id (str, optional): The ASDC model version for this resource model. + Defaults to None. + pnf_ipv4_address (str, optional): This is the IP address (IPv4) for the PNF itself. + This is the IPv4 address that the PNF iself can be accessed at. Defaults to None. + pnf_ipv6_address (str, optional): This is the IP address (IPv6) for the PNF itself. + This is the IPv6 address that the PNF iself can be accessed at. Defaults to None. + """ + super().__init__(resource_version=resource_version, + model_invariant_id=model_invariant_id, + model_version_id=model_version_id) + self.service_instance: "ServiceInstance" = service_instance + self.pnf_name: str = pnf_name + self.in_maint: bool = in_maint + self.selflink: Optional[str] = selflink + self.pnf_id: Optional[str] = pnf_id + self.equip_type: Optional[str] = equip_type + self.equip_vendor: Optional[str] = equip_vendor + self.equip_model: Optional[str] = equip_model + self.management_option: Optional[str] = management_option + self.orchestration_status: Optional[str] = orchestration_status + self.ipaddress_v4_oam: Optional[str] = ipaddress_v4_oam + self.sw_version: Optional[str] = sw_version + self.frame_id: Optional[str] = frame_id + self.serial_number: Optional[str] = serial_number + self.ipaddress_v4_loopback_0: Optional[str] = ipaddress_v4_loopback_0 + self.ipaddress_v6_loopback_0: Optional[str] = ipaddress_v6_loopback_0 + self.ipaddress_v4_aim: Optional[str] = ipaddress_v4_aim + self.ipaddress_v6_aim: Optional[str] = ipaddress_v6_aim + self.ipaddress_v6_oam: Optional[str] = ipaddress_v6_oam + self.inv_status: Optional[str] = inv_status + self.prov_status: Optional[str] = prov_status + self.nf_role: Optional[str] = nf_role + self.admin_status: Optional[str] = admin_status + self.operational_status: Optional[str] = operational_status + self.model_customization_id: Optional[str] = model_customization_id + self.pnf_ipv4_address: Optional[str] = pnf_ipv4_address + self.pnf_ipv6_address: Optional[str] = pnf_ipv6_address + + self._pnf: "Pnf" = None + + def __repr__(self) -> str: + """Pnf instance object representation. + + Returns: + str: Human readable pnf instance representation + + """ + return f"PnfInstance(pnf_name={self.pnf_name})" + + @classmethod + def get_all_url(cls) -> str: # pylint: disable=arguments-differ + """Return an url to get all pnfs. + + Returns: + str: Url to get all pnfs + + """ + return f"{cls.base_url}{cls.api_version}/network/pnfs/" + + @classmethod + def get_all(cls) -> Iterator["PnfInstance"]: + """Get all PNF instances. + + Yields: + PnfInstance: Pnf instance + + """ + for pnf_data in cls.send_message_json( \ + "GET", \ + "Get all pnf instances", \ + cls.get_all_url() \ + ).get("pnf", []): + yield cls.create_from_api_response(pnf_data, None) + + @property + def url(self) -> str: + """Network instance url. + + Returns: + str: NetworkInstance url + + """ + return f"{self.base_url}{self.api_version}/network/pnfs/pnf/{self.pnf_name}" + + @property + def pnf(self) -> "Pnf": + """Pnf associated with that pnf instance. + + Raises: + ResourceNotFound: Could not find PNF for that PNF instance + + Returns: + Pnf: Pnf object associated with Pnf instance + + """ + if not self._pnf: + for pnf in self.service_instance.sdc_service.pnfs: + if pnf.model_version_id == self.model_version_id: + self._pnf = pnf + return self._pnf + + msg = ( + f'Could not find PNF for the PNF instance' + f' with model version ID "{self.model_version_id}"' + ) + raise ResourceNotFound(msg) + return self._pnf + + @classmethod + def create_from_api_response(cls, api_response: dict, + service_instance: "ServiceInstance") -> "PnfInstance": + """Create pnf instance object using HTTP API response dictionary. + + Args: + api_response (dict): A&AI API response dictionary + service_instance (ServiceInstance): Service instance with which network is related + + Returns: + PnfInstance: PnfInstance object + + """ + return cls(service_instance=service_instance, + pnf_name=api_response["pnf-name"], + in_maint=api_response["in-maint"], + selflink=api_response.get("selflink"), + pnf_id=api_response.get("pnf-id"), + equip_type=api_response.get("equip-type"), + equip_vendor=api_response.get("equip-vendor"), + equip_model=api_response.get("equip-model"), + management_option=api_response.get("management-option"), + orchestration_status=api_response.get("orchestration-status"), + ipaddress_v4_oam=api_response.get("ipaddress-v4-oam"), + sw_version=api_response.get("sw-version"), + frame_id=api_response.get("frame-id"), + serial_number=api_response.get("serial-number"), + ipaddress_v4_loopback_0=api_response.get("ipaddress-v4-loopback-0"), + ipaddress_v6_loopback_0=api_response.get("ipaddress-v6-loopback-0"), + ipaddress_v4_aim=api_response.get("ipaddress-v4-aim"), + ipaddress_v6_aim=api_response.get("ipaddress-v6-aim"), + ipaddress_v6_oam=api_response.get("ipaddress-v6-oam"), + inv_status=api_response.get("inv-status"), + resource_version=api_response.get("resource-version"), + prov_status=api_response.get("prov-status"), + nf_role=api_response.get("nf-role"), + admin_status=api_response.get("admin-status"), + operational_status=api_response.get("operational-status"), + model_customization_id=api_response.get("model-customization-id"), + model_invariant_id=api_response.get("model-invariant-id"), + model_version_id=api_response.get("model-version-id"), + pnf_ipv4_address=api_response.get("pnf-ipv4-address"), + pnf_ipv6_address=api_response.get("pnf-ipv6-address")) + + def delete(self, a_la_carte: bool = True) -> None: + """Delete Pnf instance. + + PNF deletion it's just A&AI resource deletion. That's difference between another instances. + You don't have to wait for that task finish, because it's not async task. + + """ + self._logger.debug("Delete %s pnf", self.pnf_name) + self.send_message("DELETE", + f"Delete {self.pnf_name} PNF", + f"{self.url}?resource-version={self.resource_version}") diff --git a/src/onapsdk/aai/business/project.py b/src/onapsdk/aai/business/project.py new file mode 100644 index 0000000..989444a --- /dev/null +++ b/src/onapsdk/aai/business/project.py @@ -0,0 +1,123 @@ +"""A&AI project module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Any, Dict, Iterator + +from onapsdk.utils.jinja import jinja_env + +from ..aai_element import AaiResource + + +class Project(AaiResource): + """Project class.""" + + def __init__(self, name: str, resource_version: str) -> None: + """Project object initialization. + + Args: + name (str): Project name + resource_version (str): resource version + """ + super().__init__() + self.name: str = name + self.resource_version: str = resource_version + + @classmethod + def get_all(cls) -> Iterator["Project"]: + """Get all project. + + Yields: + Project: Project object + + """ + url: str = cls.get_all_url() + for project in cls.send_message_json("GET", + "Get A&AI projects", + url).get("project", []): + yield cls( + project.get("project-name"), + project.get("resource-version") + ) + + @classmethod + def get_all_url(cls) -> str: # pylint: disable=arguments-differ + """Return url to get all projects. + + Returns: + str: Url to get all projects + + """ + return f"{cls.base_url}{cls.api_version}/business/projects" + + def __repr__(self) -> str: + """Project object representation. + + Returns: + str: Project object representation + + """ + return f"Project(name={self.name})" + + @property + def url(self) -> str: + """Project's url. + + Returns: + str: Resource's url + + """ + return (f"{self.base_url}{self.api_version}/business/projects/" + f"project/{self.name}") + + @classmethod + def create(cls, name: str) -> "Project": + """Create project A&AI resource. + + Args: + name (str): project name + + Returns: + Project: Created Project object + + """ + cls.send_message( + "PUT", + "Declare A&AI project", + (f"{cls.base_url}{cls.api_version}/business/projects/" + f"project/{name}"), + data=jinja_env().get_template("aai_project_create.json.j2").render( + project_name=name + ) + ) + return cls.get_by_name(name) + + @classmethod + def get_by_name(cls, name: str) -> "Project": + """Get project resource by it's name. + + Raises: + ResourceNotFound: Project requested by a name does not exist. + + Returns: + Project: Project requested by a name. + + """ + url = (f"{cls.base_url}{cls.api_version}/business/projects/" + f"project/{name}") + response: Dict[str, Any] = \ + cls.send_message_json("GET", + f"Get {name} project", + url) + return cls(response["project-name"], response["resource-version"]) diff --git a/src/onapsdk/aai/business/service.py b/src/onapsdk/aai/business/service.py new file mode 100644 index 0000000..fe3b34d --- /dev/null +++ b/src/onapsdk/aai/business/service.py @@ -0,0 +1,484 @@ +"""Service instance module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Iterator, Type, Union, Iterable, Optional + +from onapsdk.exceptions import StatusError, ParameterError +from onapsdk.sdc.service import Service +from onapsdk.so.deletion import ServiceDeletionRequest +from onapsdk.so.instantiation import NetworkInstantiation, VnfInstantiation +from onapsdk.utils.jinja import jinja_env + +from .instance import Instance +from .network import NetworkInstance +from .pnf import PnfInstance +from .vnf import VnfInstance + + +class ServiceInstance(Instance): # pylint: disable=too-many-instance-attributes + """Service instanve class.""" + + def __init__(self, # pylint: disable=too-many-arguments, too-many-locals + service_subscription: "ServiceSubscription", + instance_id: str, + instance_name: str = None, + service_type: str = None, + service_role: str = None, + environment_context: str = None, + workload_context: str = None, + created_at: str = None, + updated_at: str = None, + resource_version: str = None, + description: str = None, + model_invariant_id: str = None, + model_version_id: str = None, + persona_model_version: str = None, + widget_model_id: str = None, + widget_model_version: str = None, + bandwith_total: str = None, + vhn_portal_url: str = None, + service_instance_location_id: str = None, + selflink: str = None, + orchestration_status: str = None, + input_parameters: str = None) -> None: + """Service instance object initialization. + + Args: + service_subscription (ServiceSubscription): service subscription which is belongs to + instance_id (str): Uniquely identifies this instance of a service + instance_name (str, optional): This field will store a name assigned to + the service-instance. Defaults to None. + service_type (str, optional): String capturing type of service. Defaults to None. + service_role (str, optional): String capturing the service role. Defaults to None. + environment_context (str, optional): This field will store the environment context + assigned to the service-instance. Defaults to None. + workload_context (str, optional): This field will store the workload context assigned to + the service-instance. Defaults to None. + created_at (str, optional): Create time of Network Service. Defaults to None. + updated_at (str, optional): Last update of Network Service. Defaults to None. + description (str, optional): Short description for service-instance. Defaults to None. + model_invariant_id (str, optional): The ASDC model id for this resource or + service model. Defaults to None. + model_version_id (str, optional): The ASDC model version for this resource or + service model. Defaults to None. + persona_model_version (str, optional): The ASDC model version for this resource or + service model. Defaults to None. + widget_model_id (str, optional): The ASDC data dictionary widget model. This maps + directly to the A&AI widget. Defaults to None. + widget_model_version (str, optional): The ASDC data dictionary version of the widget + model. This maps directly to the A&AI version of the widget. Defaults to None. + bandwith_total (str, optional): Indicates the total bandwidth to be used for this + service. Defaults to None. + vhn_portal_url (str, optional): URL customers will use to access the vHN Portal. + Defaults to None. + service_instance_location_id (str, optional): An identifier that customers assign to + the location where this service is being used. Defaults to None. + resource_version (str, optional): Used for optimistic concurrency. Must be empty on + create, valid on update and delete. Defaults to None. + selflink (str, optional): Path to the controller object. Defaults to None. + orchestration_status (str, optional): Orchestration status of this service. + Defaults to None. + input_parameters (str, optional): String capturing request parameters from SO to + pass to Closed Loop. Defaults to None. + """ + super().__init__(resource_version=resource_version, + model_invariant_id=model_invariant_id, + model_version_id=model_version_id) + self.service_subscription: "ServiceSubscription" = service_subscription + self.instance_id: str = instance_id + self.instance_name: str = instance_name + self.service_type: str = service_type + self.service_role: str = service_role + self.environment_context: str = environment_context + self.workload_context: str = workload_context + self.created_at: str = created_at + self.updated_at: str = updated_at + self.description: str = description + self.bandwith_total: str = bandwith_total + self.vhn_portal_url: str = vhn_portal_url + self.service_instance_location_id: str = service_instance_location_id + self.selflink: str = selflink + self.orchestration_status: str = orchestration_status + self.input_parameters: str = input_parameters + self.persona_model_version: str = persona_model_version + self.widget_model_id: str = widget_model_id + self.widget_model_version: str = widget_model_version + self._sdc_service: Optional[Service] = None + + def __repr__(self) -> str: + """Service instance object representation. + + Returns: + str: Human readable service instance representation + + """ + return (f"ServiceInstance(instance_id={self.instance_id}, " + f"instance_name={self.instance_name})") + + def _get_related_instance(self, + related_instance_class: Union[Type[NetworkInstance], + Type[VnfInstance]], + relationship_related_to_type: str) -> Iterator[\ + Union[NetworkInstance, + VnfInstance]]: + """Iterate through related service instances. + + This is method which for given `relationship_related_to_type` creates iterator + it iterate through objects which are related with service. + + Args: + related_instance_class (Union[Type[NetworkInstance], Type[VnfInstance]]): Class object + to create required object instances + relationship_related_to_type (str): Has to be "generic-vnf" or "l3-network" + + Raises: + ParameterError: relationship_related_to_type does not satisfy the requirements + + Yields: + Iterator[ Union[NetworkInstance, VnfInstance]]: [description] + + """ + if not relationship_related_to_type in ["l3-network", "generic-vnf", "pnf"]: + msg = ( + f'Invalid "relationship_related_to_type" value. ' + f'Provided "{relationship_related_to_type}". ' + f'Has to be "l3-network" or "generic-vnf".' + ) + raise ParameterError(msg) + for relationship in self.relationships: + if relationship.related_to == relationship_related_to_type: + yield related_instance_class.create_from_api_response(\ + self.send_message_json("GET", + (f"Get {self.instance_id} " + f"{related_instance_class.__class__.__name__}"), + f"{self.base_url}{relationship.related_link}"), + self) + + @classmethod + def create(cls, service_subscription: "ServiceSubscription", # pylint: disable=too-many-arguments, too-many-locals + instance_id: str, + instance_name: str = None, + service_type: str = None, + service_role: str = None, + environment_context: str = None, + workload_context: str = None, + created_at: str = None, + updated_at: str = None, + resource_version: str = None, + description: str = None, + model_invariant_id: str = None, + model_version_id: str = None, + persona_model_version: str = None, + widget_model_id: str = None, + widget_model_version: str = None, + bandwith_total: str = None, + vhn_portal_url: str = None, + service_instance_location_id: str = None, + selflink: str = None, + orchestration_status: str = None, + input_parameters: str = None): + """Service instance creation. + + Args: + service_subscription (ServiceSubscription): service subscription which is belongs to + instance_id (str): Uniquely identifies this instance of a service + instance_name (str, optional): This field will store a name assigned to + the service-instance. Defaults to None. + service_type (str, optional): String capturing type of service. Defaults to None. + service_role (str, optional): String capturing the service role. Defaults to None. + environment_context (str, optional): This field will store the environment context + assigned to the service-instance. Defaults to None. + workload_context (str, optional): This field will store the workload context assigned to + the service-instance. Defaults to None. + created_at (str, optional): Create time of Network Service. Defaults to None. + updated_at (str, optional): Last update of Network Service. Defaults to None. + description (str, optional): Short description for service-instance. Defaults to None. + model_invariant_id (str, optional): The ASDC model id for this resource or + service model. Defaults to None. + model_version_id (str, optional): The ASDC model version for this resource or + service model. Defaults to None. + persona_model_version (str, optional): The ASDC model version for this resource or + service model. Defaults to None. + widget_model_id (str, optional): The ASDC data dictionary widget model. This maps + directly to the A&AI widget. Defaults to None. + widget_model_version (str, optional): The ASDC data dictionary version of the widget + model. This maps directly to the A&AI version of the widget. Defaults to None. + bandwith_total (str, optional): Indicates the total bandwidth to be used for this + service. Defaults to None. + vhn_portal_url (str, optional): URL customers will use to access the vHN Portal. + Defaults to None. + service_instance_location_id (str, optional): An identifier that customers assign to + the location where this service is being used. Defaults to None. + resource_version (str, optional): Used for optimistic concurrency. Must be empty on + create, valid on update and delete. Defaults to None. + selflink (str, optional): Path to the controller object. Defaults to None. + orchestration_status (str, optional): Orchestration status of this service. + Defaults to None. + input_parameters (str, optional): String capturing request parameters from SO to + pass to Closed Loop. Defaults to None. + """ + service_instance: "ServiceInstance" = cls( + service_subscription, + instance_id, + instance_name, + service_type, + service_role, + environment_context, + workload_context, + created_at, + updated_at, + resource_version, + description, + model_invariant_id, + model_version_id, + persona_model_version, + widget_model_id, + widget_model_version, + bandwith_total, + vhn_portal_url, + service_instance_location_id, + selflink, + orchestration_status, + input_parameters + ) + cls.send_message("PUT", + f"Create service instance {instance_id} for "\ + f"{service_subscription.service_type} service subscription", + f"{service_subscription.url}/service-instances/service-instance/"\ + f"{instance_id}", + data=jinja_env() + .get_template("aai_service_instance_create.json.j2") + .render( + service_instance=service_instance + )) + return service_instance + + @classmethod + def get_all_url(cls, service_subscription: "ServiceSubscription") -> str: # pylint: disable=arguments-differ + """Return an url to get all service instances for service subscription. + + Args: + service_subscription (ServiceSubscription): Service subscription object + + Returns: + str: Url to get all service instances for service subscription + + """ + return f"{service_subscription.url}/service-instances/" + + @property + def url(self) -> str: + """Service instance resource URL. + + Returns: + str: Service instance url + + """ + return ( + f"{self.service_subscription.url}/service-instances/service-instance/{self.instance_id}" + ) + + @property + def vnf_instances(self) -> Iterator[VnfInstance]: + """Vnf instances associated with service instance. + + Returns iterator of VnfInstances representing VNF instantiated for that service + + Yields: + VnfInstance: VnfInstance object + + """ + return self._get_related_instance(VnfInstance, "generic-vnf") + + @property + def network_instances(self) -> Iterator[NetworkInstance]: + """Network instances associated with service instance. + + Returns iterator of NetworkInstance representing network instantiated for that service + + Yields: + NetworkInstance: NetworkInstance object + + """ + return self._get_related_instance(NetworkInstance, "l3-network") + + @property + def pnfs(self) -> Iterator[PnfInstance]: + """Pnfs associated with service instance. + + Returns iterator of PnfInstance representing pnfs instantiated for that service + + Yields: + PnfInstance: PnfInstance object + + """ + return self._get_related_instance(PnfInstance, "pnf") + + @property + def sdc_service(self) -> Service: + """Sdc service related with that instance. + + Sdc service model which was used to create that instance. + + Raises: + ResourceNotFound: Service model not found + + """ + if not self._sdc_service: + self._sdc_service = Service.get_by_unique_uuid(self.model_invariant_id) + return self._sdc_service + + @property + def active(self) -> bool: + """Information if service instance's orchestration status is active.""" + return self.orchestration_status == "Active" + + def add_vnf(self, # pylint: disable=too-many-arguments + vnf: "Vnf", + line_of_business: "LineOfBusiness", + platform: "Platform", + cloud_region: "CloudRegion" = None, + tenant: "Tenant" = None, + vnf_instance_name: str = None, + vnf_parameters: Iterable["InstantiationParameter"] = None, + so_vnf: "SoServiceVnf" = None, + a_la_carte: bool = True + ) -> "VnfInstantiation": + """Add vnf into service instance. + + Instantiate VNF. + + Args: + vnf (Vnf): Vnf from service configuration to instantiate + line_of_business (LineOfBusiness): LineOfBusiness to use in instantiation request + platform (Platform): Platform to use in instantiation request + cloud_region (CloudRegion, optional): Cloud region to use in instantiation request. + Defaults to None. + THAT PROPERTY WILL BE REQUIRED IN ONE OF THE FUTURE RELEASE. REFACTOR YOUR CODE + TO USE IT!. + tenant (Tenant, optional): Tenant to use in instnatiation request. + Defaults to None. + THAT PROPERTY WILL BE REQUIRED IN ONE OF THE FUTURE RELEASE. REFACTOR YOUR CODE + TO USE IT!. + vnf_instance_name (str, optional): VNF instantion name. + If no value is provided it's going to be + "Python_ONAP_SDK_vnf_instance_{str(uuid4())}". + Defaults to None. + vnf_parameters (Iterable[InstantiationParameter], optional): InstantiationParameter to + be passed as "userParams". Defaults to None. + so_vnf: (SoServiceVnf, optional): object with vnf instance parameters. Defaults to None. + a_la_carte (bool): instantiation type for vnf. Defaults to True. + + Raises: + StatusError: Service orchestration status is not "Active". + + Returns: + VnfInstantiation: VnfInstantiation request object + + """ + if not self.active: + raise StatusError('Service orchestration status must be "Active"') + + if a_la_carte: + return VnfInstantiation.instantiate_ala_carte( + self, + vnf, + line_of_business, + platform, + cloud_region=cloud_region, + tenant=tenant, + vnf_instance_name=vnf_instance_name, + vnf_parameters=vnf_parameters, + sdc_service=self.sdc_service + ) + + return VnfInstantiation.instantiate_macro( + self, + vnf, + line_of_business, + platform, + cloud_region=cloud_region, + tenant=tenant, + vnf_instance_name=vnf_instance_name, + vnf_parameters=vnf_parameters, + so_vnf=so_vnf, + sdc_service=self.sdc_service + ) + + def add_network(self, # pylint: disable=too-many-arguments + network: "Network", + line_of_business: "LineOfBusiness", + platform: "Platform", + cloud_region: "CloudRegion" = None, + tenant: "Tenant" = None, + network_instance_name: str = None, + subnets: Iterator["Subnet"] = None) -> "NetworkInstantiation": + """Add network into service instance. + + Instantiate vl. + + Args: + network (Network): Network from service configuration to instantiate + line_of_business (LineOfBusiness): LineOfBusiness to use in instantiation request + platform (Platform): Platform to use in instantiation request + cloud_region (CloudRegion, optional): Cloud region to use in instantiation request. + Defaults to None. + THAT PROPERTY WILL BE REQUIRED IN ONE OF THE FUTURE RELEASE. REFACTOR YOUR CODE + TO USE IT!. + tenant (Tenant, optional): Tenant to use in instnatiation request. + Defaults to None. + THAT PROPERTY WILL BE REQUIRED IN ONE OF THE FUTURE RELEASE. REFACTOR YOUR CODE + TO USE IT!. + network_instance_name (str, optional): Network instantion name. + If no value is provided it's going to be + "Python_ONAP_SDK_network_instance_{str(uuid4())}". + Defaults to None. + + Raises: + StatusError: Service orchestration status is not "Active" + + Returns: + NetworkInstantiation: NetworkInstantiation request object + + """ + if not self.active: + msg = f'Service orchestration status must be "Active"' + raise StatusError(msg) + + return NetworkInstantiation.instantiate_ala_carte( + self, + network, + line_of_business, + platform, + cloud_region=cloud_region, + tenant=tenant, + network_instance_name=network_instance_name, + subnets=subnets + ) + + def delete(self, a_la_carte: bool = True) -> "ServiceDeletionRequest": + """Create service deletion request. + + Send a request to delete service instance + + Args: + a_la_carte (boolean): deletion mode + + Returns: + ServiceDeletionRequest: Deletion request object + + """ + self._logger.debug("Delete %s service instance", self.instance_id) + return ServiceDeletionRequest.send_request(self, a_la_carte) diff --git a/src/onapsdk/aai/business/sp_partner.py b/src/onapsdk/aai/business/sp_partner.py new file mode 100644 index 0000000..05d6a05 --- /dev/null +++ b/src/onapsdk/aai/business/sp_partner.py @@ -0,0 +1,176 @@ +"""A&AI sp-partner module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Iterator, Optional + +from onapsdk.utils.jinja import jinja_env + +from ..aai_element import AaiResource + + +class SpPartner(AaiResource): # pylint: disable=too-many-instance-attributes + """Sp partner class.""" + + def __init__(self, sp_partner_id: str, resource_version: str, url: str = None, # pylint: disable=too-many-arguments, too-many-locals + callsource: str = None, operational_status: str = None, + model_customization_id: str = None, model_invariant_id: str = None, + model_version_id: str = None) -> None: + """Sp partner object initialization. + + Args: + sp_partner_id (str): Uniquely identifies this sp-partner by id + resource_version (str): resource version + url (str, optional): Store the URL of this sp-partner. Defaults to None + callsource (str, optional): Store the callsource of this sp-partner. Defaults to None + operational_status (str, optional): Store the operational-status of this sp-partner. + Defaults to None + model_customization_id (str, optional): Store the model-customization-id + of this sp-partner. Defaults to None + model_invariant_id (str, optional): The ASDC model id for this sp-partner model. + Defaults to None + model_version_id (str, optional): The ASDC model version for this sp-partner model. + Defaults to None + + """ + super().__init__() + self.sp_partner_id: str = sp_partner_id + self.resource_version: str = resource_version + self.sp_partner_url: Optional[str] = url + self.callsource: Optional[str] = callsource + self.operational_status: Optional[str] = operational_status + self.model_customization_id: Optional[str] = model_customization_id + self.model_invariant_id: Optional[str] = model_invariant_id + self.model_version_id: Optional[str] = model_version_id + + def __repr__(self) -> str: + """Sp partner object representation. + + Returns: + str: SpPartner object representation + + """ + return f"SpPartner(sp_partner_id={self.sp_partner_id})" + + @property + def url(self) -> str: + """Sp partner's url. + + Returns: + str: Resource's url + + """ + return (f"{self.base_url}{self.api_version}/business/sp-partners/" + f"sp-partner/{self.sp_partner_id}") + + @classmethod + def get_all_url(cls) -> str: # pylint: disable=arguments-differ + """Return url to get all sp partners. + + Returns: + str: Url to get all sp partners + + """ + return f"{cls.base_url}{cls.api_version}/business/sp-partners" + + @classmethod + def get_all(cls) -> Iterator["SpPartner"]: + """Get all sp partners. + + Yields: + SpPartner: SpPartner object + + """ + url: str = cls.get_all_url() + for sp_partner in cls.send_message_json("GET", + "Get A&AI sp-partners", + url).get("sp-partner", []): + yield cls( + sp_partner["sp-partner-id"], + sp_partner["resource-version"], + sp_partner.get("url"), + sp_partner.get("callsource"), + sp_partner.get("operational-status"), + sp_partner.get("model-customization-id"), + sp_partner.get("model-invariant-id"), + sp_partner.get("model-version-id"), + ) + + @classmethod + def create(cls, sp_partner_id: str, url: str = "", callsource: str = "", # pylint: disable=too-many-arguments + operational_status: str = "", model_customization_id: str = "", + model_invariant_id: str = "", model_version_id: str = "") -> "SpPartner": + """Create sp partner A&AI resource. + + Args: + sp_partner_id (str): sp partner unique ID + url (str, optional): Store the URL of this sp-partner. Defaults to None + callsource (str, optional): Store the callsource of this sp-partner. Defaults to None + operational_status (str, optional): Store the operational-status of this sp-partner. + Defaults to None + model_customization_id (str, optional): Store the model-customization-id + of this sp-partner. Defaults to None + model_invariant_id (str, optional): The ASDC model id for this sp-partner model. + Defaults to None + model_version_id (str, optional): The ASDC model version for this sp-partner model. + Defaults to None + + Returns: + SpPartner: Created SpPartner object + + """ + cls.send_message( + "PUT", + "Declare A&AI sp partner", + (f"{cls.base_url}{cls.api_version}/business/sp-partners/" + f"sp-partner/{sp_partner_id}"), + data=jinja_env().get_template("aai_sp_partner_create.json.j2").render( + sp_partner_id=sp_partner_id, + url=url, + callsource=callsource, + operational_status=operational_status, + model_customization_id=model_customization_id, + model_invariant_id=model_invariant_id, + model_version_id=model_version_id + ) + ) + return cls.get_by_sp_partner_id(sp_partner_id) + + @classmethod + def get_by_sp_partner_id(cls, sp_partner_id: str) -> "SpPartner": + """Get sp partner by it's ID. + + Args: + sp_partner_id (str): sp partner object id + + Returns: + SpPartner: SpPartner object + + """ + response: dict = cls.send_message_json( + "GET", + "Get A&AI sp partner", + (f"{cls.base_url}{cls.api_version}/business/sp-partners/" + f"sp-partner/{sp_partner_id}") + ) + return cls( + response["sp-partner-id"], + response["resource-version"], + response.get("url"), + response.get("callsource"), + response.get("operational-status"), + response.get("model-customization-id"), + response.get("model-invariant-id"), + response.get("model-version-id") + ) diff --git a/src/onapsdk/aai/business/vf_module.py b/src/onapsdk/aai/business/vf_module.py new file mode 100644 index 0000000..ac91560 --- /dev/null +++ b/src/onapsdk/aai/business/vf_module.py @@ -0,0 +1,199 @@ +"""VF module instance.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.so.deletion import VfModuleDeletionRequest +from onapsdk.exceptions import ResourceNotFound + +from .instance import Instance + + +class VfModuleInstance(Instance): # pylint: disable=too-many-instance-attributes + """Vf module instance class.""" + + def __init__(self, # pylint: disable=too-many-arguments, too-many-locals + vnf_instance: "VnfInstance", + vf_module_id: str, + is_base_vf_module: bool, + automated_assignment: bool, + vf_module_name: str = None, + heat_stack_id: str = None, + resource_version: str = None, + model_invariant_id: str = None, + orchestration_status: str = None, + persona_model_version: str = None, + model_version_id: str = None, + model_customization_id: str = None, + widget_model_id: str = None, + widget_model_version: str = None, + contrail_service_instance_fqdn: str = None, + module_index: int = None, + selflink: str = None) -> None: + """Vf module initialization. + + Args: + vnf_instance (VnfInstance): VnfInstance + vf_module_id (str): Unique ID of vf-module + is_base_vf_module (bool): used to indicate whether or not this object is base vf module + automated_assignment (bool): ndicates whether vf-module assignment was done via + automation or manually + vf_module_name (str, optional): Name of vf-module. Defaults to None. + heat_stack_id (str, optional): Heat stack id corresponding to this instance. + Defaults to None. + orchestration_status (str, optional): orchestration status of this vf-module, + mastered by MSO. Defaults to None. + resource_version (str, optional): Used for optimistic concurrency. + Must be empty on create, valid on update and delete. Defaults to None. + model_invariant_id (str, optional): the ASDC model id for this resource or + service model. Defaults to None. + model_version_id (str, optional): the ASDC model version for this resource or + service model. Defaults to None. + persona_model_version (str, optional): the ASDC model version for this resource or + service model. Defaults to None. + model_customization_id (str, optional): captures the id of all the configuration + used to customize the resource for the service. Defaults to None. + widget_model_id (str, optional): the ASDC data dictionary widget model. + This maps directly to the A&AI widget. Defaults to None. + widget_model_version (str, optional): the ASDC data dictionary version of + the widget model. This maps directly to the A&AI version of the widget. + Defaults to None. + contrail_service_instance_fqdn (str, optional): the Contrail unique ID + for a service-instance. Defaults to None. + module_index (int, optional): the index will track the number of modules + of a given type that have been deployed in a VNF, starting with 0, + and always choosing the lowest available digit. Defaults to None. + selflink (str, optional): Path to the controller object. Defaults to None. + """ + super().__init__(resource_version=resource_version, model_version_id=model_version_id, + model_invariant_id=model_invariant_id) + self.vnf_instance: "VnfInstance" = vnf_instance + self.vf_module_id: str = vf_module_id + self.is_base_vf_module: bool = is_base_vf_module + self.automated_assignment: bool = automated_assignment + self.vf_module_name: str = vf_module_name + self.heat_stack_id: str = heat_stack_id + self.orchestration_status: str = orchestration_status + self.model_customization_id: str = model_customization_id + self.contrail_service_instance_fqdn: str = contrail_service_instance_fqdn + self.module_index: int = module_index + self.selflink: str = selflink + self.persona_model_version: str = persona_model_version + self.widget_model_id: str = widget_model_id + self.widget_model_version: str = widget_model_version + + self._vf_module: "VfModule" = None + + def __repr__(self) -> str: + """Object represetation. + + Returns: + str: Human readble VfModuleInstance representation + + """ + return (f"VfModuleInstance(vf_module_id={self.vf_module_id}, " + f"is_base_vf_module={self.is_base_vf_module}, " + f"automated_assignment={self.automated_assignment})") + + @classmethod + def get_all_url(cls, vnf_instance: "VnfInstance") -> str: # pylint: disable=arguments-differ + """Return url to get all vf modules for vnf instance. + + Args: + vnf_instance (VnfInstance): VNF instance object + + Returns: + str: Url to get all vf modules for vnf instance + + """ + return f"{vnf_instance.url}/vf-modules/" + + @property + def url(self) -> str: + """Resource url. + + Returns: + str: VfModuleInstance url + + """ + return f"{self.vnf_instance.url}/vf-modules/vf-module/{self.vf_module_id}" + + @property + def vf_module(self) -> "VfModule": + """Vf module associated with that vf module instance. + + Returns: + VfModule: VfModule object associated with vf module instance + + """ + if not self._vf_module: + for vf_module in self.vnf_instance.vnf.vf_modules: + if vf_module.model_version_id == self.model_version_id: + self._vf_module = vf_module + return self._vf_module + + msg = ( + f'Could not find VF modules for the VF Module instance' + f' with model version ID "{self.model_version_id}"' + ) + raise ResourceNotFound(msg) + return self._vf_module + + @classmethod + def create_from_api_response(cls, + api_response: dict, + vnf_instance: "VnfInstance") -> "VfModuleInstance": + """Create vf module instance object using HTTP API response dictionary. + + Args: + api_response (dict): HTTP API response content + vnf_instance (VnfInstance): VnfInstance associated with VfModuleInstance + + Returns: + VfModuleInstance: VfModuleInstance object + + """ + return cls( + vnf_instance=vnf_instance, + vf_module_id=api_response.get("vf-module-id"), + is_base_vf_module=api_response.get("is-base-vf-module"), + automated_assignment=api_response.get("automated-assignment"), + vf_module_name=api_response.get("vf-module-name"), + heat_stack_id=api_response.get("heat-stack-id"), + orchestration_status=api_response.get("orchestration-status"), + resource_version=api_response.get("resource-version"), + model_invariant_id=api_response.get("model-invariant-id"), + model_version_id=api_response.get("model-version-id"), + persona_model_version=api_response.get("persona-model-version"), + model_customization_id=api_response.get("model-customization-id"), + widget_model_id=api_response.get("widget-model-id"), + widget_model_version=api_response.get("widget-model-version"), + contrail_service_instance_fqdn=api_response.get("contrail-service-instance-fqdn"), + module_index=api_response.get("module-index"), + selflink=api_response.get("selflink") + ) + + def delete(self, a_la_carte: bool = True) -> "VfModuleDeletionRequest": + """Create deletion request. + + Send request to delete VF module instance + + Args: + a_la_carte (boolean): deletion mode + + Returns: + VfModuleDeletionRequest: Deletion request object + + """ + self._logger.debug("Delete %s VF module", self.vf_module_id) + return VfModuleDeletionRequest.send_request(self, a_la_carte) diff --git a/src/onapsdk/aai/business/vnf.py b/src/onapsdk/aai/business/vnf.py new file mode 100644 index 0000000..2045291 --- /dev/null +++ b/src/onapsdk/aai/business/vnf.py @@ -0,0 +1,536 @@ +"""Vnf instance module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Iterable, Iterator + +from onapsdk.exceptions import ResourceNotFound, StatusError +from onapsdk.so.deletion import VnfDeletionRequest +from onapsdk.so.instantiation import VfModuleInstantiation, VnfInstantiation, SoService, \ + InstantiationParameter, VnfOperation +from onapsdk.configuration import settings + +from .instance import Instance +from .vf_module import VfModuleInstance + + +class VnfInstance(Instance): # pylint: disable=too-many-instance-attributes + """VNF Instance class.""" + + def __init__(self, # pylint: disable=too-many-arguments, too-many-locals + service_instance: "ServiceInstance", + vnf_id: str, + vnf_type: str, + in_maint: bool, + is_closed_loop_disabled: bool, + vnf_name: str = None, + service_id: str = None, + regional_resource_zone: str = None, + prov_status: str = None, + operational_status: str = None, + equipment_role: str = None, + orchestration_status: str = None, + vnf_package_name: str = None, + vnf_discriptor_name: str = None, + job_id: str = None, + heat_stack_id: str = None, + mso_catalog_key: str = None, + management_option: str = None, + ipv4_oam_address: str = None, + ipv4_loopback0_address: str = None, + nm_lan_v6_address: str = None, + management_v6_address: str = None, + vcpu: int = None, + vcpu_units: str = None, + vmemory: int = None, + vmemory_units: str = None, + vdisk: int = None, + vdisk_units: str = None, + nshd: int = None, + nvm: int = None, + nnet: int = None, + resource_version: str = None, + encrypted_access_flag: bool = None, + model_invariant_id: str = None, + model_version_id: str = None, + persona_model_version: str = None, + model_customization_id: str = None, + widget_model_id: str = None, + widget_model_version: str = None, + as_number: str = None, + regional_resource_subzone: str = None, + nf_type: str = None, + nf_function: str = None, + nf_role: str = None, + nf_naming_code: str = None, + selflink: str = None, + ipv4_oam_gateway_address: str = None, + ipv4_oam_gateway_address_prefix_length: int = None, + vlan_id_outer: int = None, + nm_profile_name: str = None) -> None: + """Vnf instance object initialization. + + Args: + vnf_id (str): Unique id of VNF. This is unique across the graph. + vnf_type (str): String capturing type of vnf, that was intended to identify + the ASDC resource. This field has been overloaded in service-specific ways and + clients should expect changes to occur in the future to this field as ECOMP + matures. + in_maint (bool): used to indicate whether or not this object is in maintenance mode + (maintenance mode = true). This field (in conjunction with prov-status) + is used to suppress alarms and vSCL on VNFs/VMs. + is_closed_loop_disabled (bool): used to indicate whether closed loop function is + enabled on this node + vnf_name (str, optional): Name of VNF. Defaults to None. + service_id (str, optional): Unique identifier of service, does not necessarily map to + ASDC service models. Defaults to None. + regional_resource_zone (str, optional): Regional way of organizing pservers, source of + truth should define values. Defaults to None. + prov_status (str, optional): Trigger for operational monitoring of this resource by + Service Assurance systems. Defaults to None. + operational_status (str, optional): Indicator for whether the resource is considered + operational. Valid values are in-service-path and out-of-service-path. + Defaults to None. + equipment_role (str, optional): Client should send valid enumerated value. + Defaults to None. + orchestration_status (str, optional): Orchestration status of this VNF, used by MSO. + Defaults to None. + vnf_package_name (str, optional): vnf package name. Defaults to None. + vnf_discriptor_name (str, optional): vnf discriptor name. Defaults to None. + job_id (str, optional): job id corresponding to vnf. Defaults to None. + heat_stack_id (str, optional): Heat stack id corresponding to this instance, + managed by MSO. Defaults to None. + mso_catalog_key (str, optional): Corresponds to the SDN-C catalog id used to + configure this VCE. Defaults to None. + management_option (str, optional): identifier of managed customer. Defaults to None. + ipv4_oam_address (str, optional): Address tail-f uses to configure generic-vnf, + also used for troubleshooting and is IP used for traps generated by generic-vnf. + Defaults to None. + ipv4_loopback0_address (str, optional): v4 Loopback0 address. Defaults to None. + nm_lan_v6_address (str, optional): v6 Loopback address. Defaults to None. + management_v6_address (str, optional): v6 management address. Defaults to None. + vcpu (int, optional): number of vcpus ordered for this instance of VNF, + used for VNFs with no vservers/flavors, to be used only by uCPE. Defaults to None. + vcpu_units (str, optional): units associated with vcpu, used for VNFs with no + vservers/flavors, to be used only by uCPE. Defaults to None. + vmemory (int, optional): number of GB of memory ordered for this instance of VNF, + used for VNFs with no vservers/flavors, to be used only by uCPE. Defaults to None. + vmemory_units (str, optional): units associated with vmemory, used for VNFs with + no vservers/flavors, to be used only by uCPE. Defaults to None. + vdisk (int, optional): number of vdisks ordered for this instance of VNF, + used for VNFs with no vservers/flavors, to be used only uCPE. Defaults to None. + vdisk_units (str, optional): units associated with vdisk, used for VNFs with + no vservers/flavors, to be used only by uCPE. Defaults to None. + nshd (int, optional): number of associated SHD in vnf. Defaults to None. + nvm (int, optional): number of vms in vnf. Defaults to None. + nnet (int, optional): number of network in vnf. Defaults to None. + resource_version (str, optional): Used for optimistic concurrency. + Must be empty on create, valid on update and delete. Defaults to None. + encrypted_access_flag (bool, optional): indicates whether generic-vnf access uses SSH. + Defaults to None. + model_invariant_id (str, optional): the ASDC model id for this resource or + service model. Defaults to None. + model_version_id (str, optional): the ASDC model version for this resource or + service model. Defaults to None. + persona_model_version (str, optional): the ASDC model version for this resource or + service model. Defaults to None. + model_customization_id (str, optional): captures the id of all the configuration used + to customize the resource for the service. Defaults to None. + widget_model_id (str, optional): the ASDC data dictionary widget model. This maps + directly to the A&AI widget. Defaults to None. + widget_model_version (str, optional): the ASDC data dictionary version of + the widget model.This maps directly to the A&AI version of the widget. + Defaults to None. + as_number (str, optional): as-number of the VNF. Defaults to None. + regional_resource_subzone (str, optional): represents sub zone of the rr plane. + Defaults to None. + nf_type (str, optional): Generic description of the type of NF. Defaults to None. + nf_function (str, optional): English description of Network function that + the specific VNF deployment is providing. Defaults to None. + nf_role (str, optional): role in the network that this model will be providing. + Defaults to None. + nf_naming_code (str, optional): string assigned to this model used for naming purposes. + Defaults to None. + selflink (str, optional): Path to the controller object. Defaults to None. + ipv4_oam_gateway_address (str, optional): Gateway address. Defaults to None. + ipv4_oam_gateway_address_prefix_length (int, optional): Prefix length for oam-address. + Defaults to None. + vlan_id_outer (int, optional): Temporary location for S-TAG to get to VCE. + Defaults to None. + nm_profile_name (str, optional): Network Management profile of this VNF. + Defaults to None. + + """ + super().__init__(resource_version=resource_version, + model_invariant_id=model_invariant_id, + model_version_id=model_version_id) + self.service_instance: "ServiceInstance" = service_instance + self.vnf_id: str = vnf_id + self.vnf_type: str = vnf_type + self.in_maint: bool = in_maint + self.is_closed_loop_disabled: bool = is_closed_loop_disabled + self.vnf_name: str = vnf_name + self.service_id: str = service_id + self.regional_resource_zone: str = regional_resource_zone + self.prov_status: str = prov_status + self.operational_status: str = operational_status + self.equipment_role: str = equipment_role + self.orchestration_status: str = orchestration_status + self.vnf_package_name: str = vnf_package_name + self.vnf_discriptor_name: str = vnf_discriptor_name + self.job_id: str = job_id + self.heat_stack_id: str = heat_stack_id + self.mso_catalog_key: str = mso_catalog_key + self.management_option: str = management_option + self.ipv4_oam_address: str = ipv4_oam_address + self.ipv4_loopback0_address: str = ipv4_loopback0_address + self.nm_lan_v6_address: str = nm_lan_v6_address + self.management_v6_address: str = management_v6_address + self.vcpu: int = vcpu + self.vcpu_units: str = vcpu_units + self.vmemory: int = vmemory + self.vmemory_units: str = vmemory_units + self.vdisk: int = vdisk + self.vdisk_units: str = vdisk_units + self.nshd: int = nshd + self.nvm: int = nvm + self.nnet: int = nnet + self.encrypted_access_flag: bool = encrypted_access_flag + self.model_customization_id: str = model_customization_id + self.as_number: str = as_number + self.regional_resource_subzone: str = regional_resource_subzone + self.nf_type: str = nf_type + self.nf_function: str = nf_function + self.nf_role: str = nf_role + self.nf_naming_code: str = nf_naming_code + self.selflink: str = selflink + self.ipv4_oam_gateway_address: str = ipv4_oam_gateway_address + self.ipv4_oam_gateway_address_prefix_length: int = ipv4_oam_gateway_address_prefix_length + self.vlan_id_outer: int = vlan_id_outer + self.nm_profile_name: str = nm_profile_name + self.persona_model_version: str = persona_model_version + self.widget_model_id: str = widget_model_id + self.widget_model_version: str = widget_model_version + + self._vnf: "Vnf" = None + + def __repr__(self) -> str: + """Vnf instance object representation. + + Returns: + str: Human readable vnf instance representation + + """ + return (f"VnfInstance(vnf_id={self.vnf_id}, vnf_type={self.vnf_type}, " + f"in_maint={self.in_maint}, " + f"is_closed_loop_disabled={self.is_closed_loop_disabled})") + + @classmethod + def get_all_url(cls) -> str: # pylint: disable=arguments-differ + """Return url to get all vnfs. + + Returns: + str: Url to get all vnfs + + """ + return f"{cls.base_url}{cls.api_version}/network/generic-vnfs/" + + @property + def url(self) -> str: + """Vnf instance url. + + Returns: + str: VnfInstance url + + """ + return f"{self.base_url}{self.api_version}/network/generic-vnfs/generic-vnf/{self.vnf_id}" + + @property + def vf_modules(self) -> Iterator[VfModuleInstance]: + """Vf modules associated with vnf instance. + + Yields: + VfModuleInstance: VfModuleInstance associated with VnfInstance + + """ + for vf_module in self.send_message_json("GET", + f"GET VNF {self.vnf_name} VF modules", + f"{self.url}/vf-modules").get("vf-module", []): + yield VfModuleInstance.create_from_api_response(vf_module, self) + + @property + def vnf(self) -> "Vnf": + """Vnf associated with that vnf instance. + + Raises: + ResourceNotFound: Could not find VNF for that VNF instance + + Returns: + Vnf: Vnf object associated with vnf instance + + """ + if not self._vnf: + for vnf in self.service_instance.sdc_service.vnfs: + if vnf.model_version_id == self.model_version_id: + self._vnf = vnf + return self._vnf + + msg = ( + f'Could not find VNF for the VNF instance' + f' with model version ID "{self.model_version_id}"' + ) + raise ResourceNotFound(msg) + return self._vnf + + @classmethod + def create_from_api_response(cls, api_response: dict, + service_instance: "ServiceInstance") -> "VnfInstance": + """Create vnf instance object using HTTP API response dictionary. + + Returns: + VnfInstance: VnfInstance object + + """ + return cls(service_instance=service_instance, + vnf_id=api_response.get("vnf-id"), + vnf_type=api_response.get("vnf-type"), + in_maint=api_response.get("in-maint"), + is_closed_loop_disabled=api_response.get("is-closed-loop-disabled"), + vnf_name=api_response.get("vnf-name"), + service_id=api_response.get("service-id"), + regional_resource_zone=api_response.get("regional-resource-zone"), + prov_status=api_response.get("prov-status"), + operational_status=api_response.get("operational-status"), + equipment_role=api_response.get("equipment-role"), + orchestration_status=api_response.get("orchestration-status"), + vnf_package_name=api_response.get("vnf-package-name"), + vnf_discriptor_name=api_response.get("vnf-discriptor-name"), + job_id=api_response.get("job-id"), + heat_stack_id=api_response.get("heat-stack-id"), + mso_catalog_key=api_response.get("mso-catalog-key"), + management_option=api_response.get("management-option"), + ipv4_oam_address=api_response.get("ipv4-oam-address"), + ipv4_loopback0_address=api_response.get("ipv4-loopback0-address"), + nm_lan_v6_address=api_response.get("nm-lan-v6-address"), + management_v6_address=api_response.get("management-v6-address"), + vcpu=api_response.get("vcpu"), + vcpu_units=api_response.get("vcpu-units"), + vmemory=api_response.get("vmemory"), + vmemory_units=api_response.get("vmemory-units"), + vdisk=api_response.get("vdisk"), + vdisk_units=api_response.get("vdisk-units"), + nshd=api_response.get("nshd"), + nvm=api_response.get("nvm"), + nnet=api_response.get("nnet"), + resource_version=api_response.get("resource-version"), + model_invariant_id=api_response.get("model-invariant-id"), + model_version_id=api_response.get("model-version-id"), + encrypted_access_flag=api_response.get("encrypted-access-flag"), + persona_model_version=api_response.get("persona-model-version"), + model_customization_id=api_response.get("model-customization-id"), + widget_model_id=api_response.get("widget-model-id"), + widget_model_version=api_response.get("widget-model-version"), + as_number=api_response.get("as-number"), + regional_resource_subzone=api_response.get("regional-resource-subzone"), + nf_type=api_response.get("nf-type"), + nf_function=api_response.get("nf-function"), + nf_role=api_response.get("nf-role"), + nf_naming_code=api_response.get("nf-naming-code"), + selflink=api_response.get("selflink"), + ipv4_oam_gateway_address=api_response.get("ipv4-oam-gateway-address"), + ipv4_oam_gateway_address_prefix_length=\ + api_response.get("ipv4-oam-gateway-address-prefix-length"), + vlan_id_outer=api_response.get("vlan-id-outer"), + nm_profile_name=api_response.get("nm-profile-name")) + + def add_vf_module(self, # pylint: disable=too-many-arguments + vf_module: "VfModule", + cloud_region: "CloudRegion" = None, + tenant: "Tenant" = None, + vf_module_instance_name: str = None, + vnf_parameters: Iterable["InstantiationParameter"] = None, + use_preload: bool = True + ) -> "VfModuleInstantiation": + """Instantiate vf module for that VNF instance. + + Args: + vf_module (VfModule): VfModule to instantiate + cloud_region (CloudRegion, optional): Cloud region to use in instantiation request. + Defaults to None. + THAT PROPERTY WILL BE REQUIRED IN ONE OF THE FUTURE RELEASE. REFACTOR YOUR CODE + TO USE IT!. + tenant (Tenant, optional): Tenant to use in instnatiation request. + Defaults to None. + THAT PROPERTY WILL BE REQUIRED IN ONE OF THE FUTURE RELEASE. REFACTOR YOUR CODE + TO USE IT!. + vf_module_instance_name (str, optional): VfModule instance name. Defaults to None. + vnf_parameters (Iterable[InstantiationParameter], optional): InstantiationParameter + to use for preloading or to be passed as "userParams". Defaults to None. + use_preload (bool, optional): Based on this flag InstantiationParameters are passed + in preload or as "userParam" in the request. Defaults to True + + Returns: + VfModuleInstantiation: VfModuleInstantiation request object + + """ + return VfModuleInstantiation.instantiate_ala_carte( + vf_module, + self, + cloud_region=cloud_region, + tenant=tenant, + vf_module_instance_name=vf_module_instance_name, + vnf_parameters=vnf_parameters, + use_preload=use_preload + ) + + def update(self, + vnf_parameters: Iterable["InstantiationParameter"] = None + ) -> VnfInstantiation: + """Update vnf instance. + + Args: + vnf_parameters (Iterable["InstantiationParameter"], Optional): list of instantiation + parameters for update operation. + Raises: + StatusError: Skip post instantiation configuration flag for VF to True. + It might cause problems with SO component. + + Returns: + VnfInstantiation: VnfInstantiation object. + + """ + skip_flag = next(p for p in self.vnf.properties + if p.name == 'skip_post_instantiation_configuration') + if not skip_flag.value or skip_flag.value != "false": + raise StatusError("Operation for the vnf is not supported! " + "Skip_post_instantiation_configuration flag for VF should be False") + + return self._execute_so_action(operation_type=VnfOperation.UPDATE, + vnf_parameters=vnf_parameters) + + def healthcheck(self) -> VnfInstantiation: + """Execute healthcheck operation for vnf instance. + + Returns: + VnfInstantiation: VnfInstantiation object. + + """ + return self._execute_so_action(operation_type=VnfOperation.HEALTHCHECK) + + def _execute_so_action(self, + operation_type: VnfOperation, + vnf_parameters: Iterable["InstantiationParameter"] = None + ) -> VnfInstantiation: + """Execute SO workflow for selected operation. + + Args: + operation_type (str): Name of the operation to execute. + vnf_parameters (Iterable["InstantiationParameter"], Optional): list of instantiation + parameters for update operation. + + Returns: + VnfInstantiation: VnfInstantiation object. + + """ + if not self.service_instance.active: + msg = f'Service orchestration status must be "Active"' + raise StatusError(msg) + + lob = settings.LOB + platform = settings.PLATFORM + + for relationship in self.relationships: + if relationship.related_to == "line-of-business": + lob = relationship.relationship_data.pop().get("relationship-value") + if relationship.related_to == "platform": + platform = relationship.relationship_data.pop().get("relationship-value") + + so_input = self._build_so_input(vnf_params=vnf_parameters) + + return VnfInstantiation.so_action( + vnf_instance=self, + operation_type=operation_type, + aai_service_instance=self.service_instance, + line_of_business=lob, + platform=platform, + sdc_service=self.service_instance.sdc_service, + so_service=so_input + ) + + def _build_so_input(self, vnf_params: Iterable[InstantiationParameter] = None) -> SoService: + """Prepare so_input with params retrieved from existing service instance. + + Args: + vnf_params (Iterable[InstantiationParameter], Optional): list of instantiation + parameters for update operation. + + Returns: + SoService: SoService object to store SO Service parameters used for macro instantiation. + + """ + so_vnfs = [] + so_pnfs = [] + + if not vnf_params: + vnf_params = [] + + for pnf in self.service_instance.pnfs: + _pnf = { + "model_name": pnf.pnf.model_name, + "instance_name": pnf.pnf_name + } + + so_pnfs.append(_pnf) + + for vnf in self.service_instance.vnf_instances: + _vnf = {"model_name": vnf.vnf.model_name, + "instance_name": vnf.vnf_name, + "parameters": {}} + if vnf.vnf_name == self.vnf_name: + for _param in vnf_params: + _vnf["parameters"][_param.name] = _param.value + + _vf_modules = [] + for vf_module in vnf.vf_modules: + _vf_module = { + "model_name": vf_module.vf_module.model_name.split('..')[1], + "instance_name": vf_module.vf_module_name, + "parameters": {} + } + + _vf_modules.append(_vf_module) + + _vnf["vf_modules"] = _vf_modules + so_vnfs.append(_vnf) + + return SoService.load(data={ + 'subscription_service_type': self.service_instance.service_subscription.service_type, + 'vnfs': so_vnfs, + 'pnfs': so_pnfs + }) + + def delete(self, a_la_carte: bool = True) -> "VnfDeletionRequest": + """Create VNF deletion request. + + Send request to delete VNF instance + + Args: + a_la_carte (boolean): deletion mode + + Returns: + VnfDeletionRequest: Deletion request + + """ + self._logger.debug("Delete %s VNF", self.vnf_id) + return VnfDeletionRequest.send_request(self, a_la_carte) diff --git a/src/onapsdk/aai/cloud_infrastructure/__init__.py b/src/onapsdk/aai/cloud_infrastructure/__init__.py new file mode 100644 index 0000000..a380ce3 --- /dev/null +++ b/src/onapsdk/aai/cloud_infrastructure/__init__.py @@ -0,0 +1,18 @@ +"""A&AI cloud infrastructure package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 .cloud_region import AvailabilityZone, CloudRegion, EsrSystemInfo +from .complex import Complex +from .geo_region import GeoRegion +from .tenant import Tenant diff --git a/src/onapsdk/aai/cloud_infrastructure/cloud_region.py b/src/onapsdk/aai/cloud_infrastructure/cloud_region.py new file mode 100644 index 0000000..d57a025 --- /dev/null +++ b/src/onapsdk/aai/cloud_infrastructure/cloud_region.py @@ -0,0 +1,621 @@ +"""Cloud region module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 dataclasses import dataclass +from typing import Any, Dict, Iterator, List, Optional +from urllib.parse import urlencode + +from onapsdk.msb.multicloud import Multicloud +from onapsdk.utils.jinja import jinja_env +from onapsdk.exceptions import ResourceNotFound + +from ..aai_element import AaiResource, Relationship +from .complex import Complex +from .tenant import Tenant + + +@dataclass +class AvailabilityZone: + """Availability zone. + + A collection of compute hosts/pservers + """ + + name: str + hypervisor_type: str + operational_status: str = None + resource_version: str = None + + +@dataclass +class EsrSystemInfo: # pylint: disable=too-many-instance-attributes + """Persist common address information of external systems.""" + + esr_system_info_id: str + user_name: str + password: str + system_type: str + resource_version: str + system_name: str = None + esr_type: str = None + vendor: str = None + version: str = None + service_url: str = None + protocol: str = None + ssl_cacert: str = None + ssl_insecure: Optional[bool] = None + ip_address: str = None + port: str = None + cloud_domain: str = None + default_tenant: str = None + passive: Optional[bool] = None + remote_path: str = None + system_status: str = None + openstack_region_id: str = None + + +class CloudRegion(AaiResource): # pylint: disable=too-many-instance-attributes + """Cloud region class. + + Represents A&AI cloud region object. + """ + + def __init__(self, + cloud_owner: str, + cloud_region_id: str, + orchestration_disabled: bool, + in_maint: bool, + *, # rest of parameters are keyword + cloud_type: str = "", + owner_defined_type: str = "", + cloud_region_version: str = "", + identity_url: str = "", + cloud_zone: str = "", + complex_name: str = "", + sriov_automation: str = "", + cloud_extra_info: str = "", + upgrade_cycle: str = "", + resource_version: str = "") -> None: + """Cloud region object initialization. + + Args: + cloud_owner (str): Identifies the vendor and cloud name. + cloud_region_id (str): Identifier used by the vendor for the region. + orchestration_disabled (bool): Used to indicate whether orchestration is + enabled for this cloud-region. + in_maint (bool): Used to indicate whether or not cloud-region object + is in maintenance mode. + owner_defined_type (str, optional): Cloud-owner defined type + indicator (e.g., dcp, lcp). Defaults to "". + cloud_region_version (str, optional): Software version employed at the site. + Defaults to "". + identity_url (str, optional): URL of the keystone identity service. Defaults to "". + cloud_zone (str, optional): Zone where the cloud is homed. Defaults to "". + complex_name (str, optional): Complex name for cloud-region instance. Defaults to "". + sriov_automation (str, optional): Whether the cloud region supports (true) or does + not support (false) SR-IOV automation. Defaults to "". + cloud_extra_info (str, optional): ESR inputs extra information about the VIM or Cloud + which will be decoded by MultiVIM. Defaults to "". + upgrade_cycle (str, optional): Upgrade cycle for the cloud region. + For AIC regions upgrade cycle is designated by A,B,C etc. Defaults to "". + resource_version (str, optional): Used for optimistic concurrency. + Must be empty on create, valid on update and delete. Defaults to "". + + """ + super().__init__() + self.cloud_owner = cloud_owner + self.cloud_region_id = cloud_region_id + self.orchestration_disabled = orchestration_disabled + self.in_maint = in_maint + self.cloud_type = cloud_type + self.owner_defined_type = owner_defined_type + self.cloud_region_version = cloud_region_version + self.identity_url = identity_url + self.cloud_zone = cloud_zone + self.complex_name = complex_name + self.sriov_automation = sriov_automation + self.cloud_extra_info = cloud_extra_info + self.upgrade_cycle = upgrade_cycle + self.resource_version = resource_version + + def __repr__(self) -> str: + """Cloud region object representation. + + Returns: + str: Human readable string contains most important information about cloud region. + + """ + return ( + f"CloudRegion(cloud_owner={self.cloud_owner}, cloud_region_id={self.cloud_region_id})" + ) + + @classmethod + def get_all_url(cls) -> str: # pylint: disable=arguments-differ + """Return url to get all cloud regions. + + Returns: + str: Url to get all cloud regions + + """ + return f"{cls.base_url}{cls.api_version}/cloud-infrastructure/cloud-regions" + + @classmethod + def get_all(cls, + cloud_owner: str = None, + cloud_region_id: str = None, + cloud_type: str = None, + owner_defined_type: str = None) -> Iterator["CloudRegion"]: + """Get all A&AI cloud regions. + + Cloud regions can be filtered by 4 parameters: cloud-owner, + cloud-region-id, cloud-type and owner-defined-type. + + Yields: + CloudRegion -- CloudRegion object. Can not yield anything + if cloud region with given filter parameters doesn't exist + + """ + # Filter request parameters - use only these which are not None + filter_parameters: dict = cls.filter_none_key_values( + { + "cloud-owner": cloud_owner, + "cloud-region-id": cloud_region_id, + "cloud-type": cloud_type, + "owner-defined-type": owner_defined_type, + } + ) + url: str = (f"{cls.get_all_url()}?{urlencode(filter_parameters)}") + response_json: Dict[str, List[Dict[str, Any]]] = cls.send_message_json( + "GET", "get cloud regions", url + ) + for cloud_region in response_json.get("cloud-region", []): # typing: dict + yield CloudRegion( + cloud_owner=cloud_region["cloud-owner"], # required + cloud_region_id=cloud_region["cloud-region-id"], # required + cloud_type=cloud_region.get("cloud-type"), + owner_defined_type=cloud_region.get("owner-defined-type"), + cloud_region_version=cloud_region.get("cloud-region-version"), + identity_url=cloud_region.get("identity_url"), + cloud_zone=cloud_region.get("cloud-zone"), + complex_name=cloud_region.get("complex-name"), + sriov_automation=cloud_region.get("sriov-automation"), + cloud_extra_info=cloud_region.get("cloud-extra-info"), + upgrade_cycle=cloud_region.get("upgrade-cycle"), + orchestration_disabled=cloud_region["orchestration-disabled"], # required + in_maint=cloud_region["in-maint"], # required + resource_version=cloud_region.get("resource-version"), + ) + + @classmethod + def get_by_id(cls, cloud_owner: str, cloud_region_id: str) -> "CloudRegion": + """Get CloudRegion object by cloud_owner and cloud-region-id field value. + + This method calls A&AI cloud region API filtering them by cloud_owner and + cloud-region-id field value. + + Raises: + ResourceNotFound: Cloud region with given id does not exist. + + Returns: + CloudRegion: CloudRegion object with given cloud-region-id. + + """ + try: + return next(cls.get_all(cloud_owner=cloud_owner, cloud_region_id=cloud_region_id)) + except StopIteration: + msg = ( + f'CloudRegion with {cloud_owner}, ' + f'{cloud_region_id} cloud-id not found. ' + ) + raise ResourceNotFound(msg) + + @classmethod + def create(cls, # pylint: disable=too-many-locals + cloud_owner: str, + cloud_region_id: str, + orchestration_disabled: bool, + in_maint: bool, + *, # rest of parameters are keyword + cloud_type: str = "", + owner_defined_type: str = "", + cloud_region_version: str = "", + identity_url: str = "", + cloud_zone: str = "", + complex_name: str = "", + sriov_automation: str = "", + cloud_extra_info: str = "", + upgrade_cycle: str = "") -> "CloudRegion": + """Create CloudRegion object. + + Create cloud region with given values. + + Returns: + CloudRegion: Created cloud region. + + """ + cloud_region: "CloudRegion" = CloudRegion( + cloud_owner=cloud_owner, + cloud_region_id=cloud_region_id, + orchestration_disabled=orchestration_disabled, + in_maint=in_maint, + cloud_type=cloud_type, + owner_defined_type=owner_defined_type, + cloud_region_version=cloud_region_version, + identity_url=identity_url, + cloud_zone=cloud_zone, + complex_name=complex_name, + sriov_automation=sriov_automation, + cloud_extra_info=cloud_extra_info, + upgrade_cycle=upgrade_cycle, + ) + url: str = ( + f"{cls.base_url}{cls.api_version}/cloud-infrastructure/cloud-regions/cloud-region/" + f"{cloud_region.cloud_owner}/{cloud_region.cloud_region_id}" + ) + cls.send_message( + "PUT", + "Create cloud region", + url, + data=jinja_env() + .get_template("cloud_region_create.json.j2") + .render(cloud_region=cloud_region), + ) + return cloud_region + + @property + def url(self) -> str: + """Cloud region object url. + + URL used to call CloudRegion A&AI API + + Returns: + str: CloudRegion object url + + """ + return ( + f"{self.base_url}{self.api_version}/cloud-infrastructure/cloud-regions/cloud-region/" + f"{self.cloud_owner}/{self.cloud_region_id}" + ) + + @property + def tenants(self) -> Iterator["Tenant"]: + """Tenants iterator. + + Cloud region tenants iterator. + + Returns: + Iterator[Tenant]: Iterate through cloud region tenants + + """ + response: dict = self.send_message_json("GET", "get tenants", f"{self.url}/tenants") + return ( + Tenant( + cloud_region=self, + tenant_id=tenant["tenant-id"], + tenant_name=tenant["tenant-name"], + tenant_context=tenant.get("tenant-context"), + resource_version=tenant.get("resource-version"), + ) + for tenant in response.get("tenant", []) + ) + + @property + def availability_zones(self) -> Iterator[AvailabilityZone]: + """Cloud region availability zones. + + Iterate over CloudRegion availability zones. Relationship list is given using A&AI API call. + + Returns: + Iterator[AvailabilityZone]: CloudRegion availability zone + + """ + response: dict = self.send_message_json( + "GET", "get cloud region availability zones", f"{self.url}/availability-zones" + ) + return ( + AvailabilityZone( + name=availability_zone["availability-zone-name"], + hypervisor_type=availability_zone["hypervisor-type"], + operational_status=availability_zone.get("operational-status"), + resource_version=availability_zone.get("resource-version") + ) + for availability_zone in response.get("availability-zone", []) + ) + + @property + def esr_system_infos(self) -> Iterator[EsrSystemInfo]: + """Cloud region collection of persistent block-level external system auth info. + + Returns: + Iterator[EsrSystemInfo]: Cloud region external system address information. + + """ + response: dict = self.send_message_json( + "GET", "get cloud region external systems info list", f"{self.url}/esr-system-info-list" + ) + return ( + EsrSystemInfo( + esr_system_info_id=esr_system_info.get("esr-system-info-id"), + user_name=esr_system_info.get("user-name"), + password=esr_system_info.get("password"), + system_type=esr_system_info.get("system-type"), + system_name=esr_system_info.get("system-name"), + esr_type=esr_system_info.get("type"), + vendor=esr_system_info.get("vendor"), + version=esr_system_info.get("version"), + service_url=esr_system_info.get("service-url"), + protocol=esr_system_info.get("protocol"), + ssl_cacert=esr_system_info.get("ssl-cacert"), + ssl_insecure=esr_system_info.get("ssl-insecure"), + ip_address=esr_system_info.get("ip-address"), + port=esr_system_info.get("port"), + cloud_domain=esr_system_info.get("cloud-domain"), + default_tenant=esr_system_info.get("default-tenant"), + passive=esr_system_info.get("passive"), + remote_path=esr_system_info.get("remote-path"), + system_status=esr_system_info.get("system-status"), + openstack_region_id=esr_system_info.get("openstack-region-id"), + resource_version=esr_system_info.get("resource-version"), + ) + for esr_system_info in response.get("esr-system-info", []) + ) + + @property + def complex(self) -> Optional[Complex]: + """Complex related with cloud region. + + Returns: + Optional[Complex]: Complex object related with CloudRegion or None if + CloudRegion has no relationship with any Complex + + """ + try: + for relationship in self.relationships: + if relationship.related_to == "complex": + physical_location_id: Optional[str] = relationship.get_relationship_data( + "complex.physical-location-id" + ) + if physical_location_id is not None: + try: + return Complex.get_by_physical_location_id( + physical_location_id + ) + except ResourceNotFound: + self._logger.error("Complex with %s physical location id does " + "not exist", physical_location_id) + self._logger.error("Invalid Complex relationship!") + return None + except ResourceNotFound: + self._logger.debug("Cloud region %s has no relationships", self.cloud_region_id) + self._logger.debug("Cloud region %s has no related complex", self.cloud_region_id) + return None + + def add_tenant(self, tenant_id: str, tenant_name: str, tenant_context: str = None) -> None: + """Add tenant to cloud region. + + Args: + tenant_id (str): Unique id relative to the cloud-region. + tenant_name (str): Readable name of tenant + tenant_context (str, optional): This field will store + the tenant context.. Defaults to None. + + """ + self.send_message( + "PUT", + "add tenant to cloud region", + f"{self.url}/tenants/tenant/{tenant_id}", + data=jinja_env() + .get_template("cloud_region_add_tenant.json.j2") + .render(tenant_id=tenant_id, tenant_name=tenant_name, + tenant_context=tenant_context) + ) + + def get_tenant(self, tenant_id: str) -> "Tenant": + """Get tenant with provided ID. + + Args: + tenant_id (str): Tenant ID + + Returns: + Tenant: Tenant object + + """ + response: dict = self.send_message_json( + "GET", + "get tenants", + f"{self.url}/tenants/tenant/{tenant_id}" + ) + return Tenant( + cloud_region=self, + tenant_id=response["tenant-id"], + tenant_name=response["tenant-name"], + tenant_context=response.get("tenant-context"), + resource_version=response.get("resource-version"), + ) + + def get_tenants_by_name(self, tenant_name: str) -> Iterator["Tenant"]: + """Get tenants with given name. + + Args: + tenant_name (str): Tenant name + + Returns: + Iterator[Tenant]: Iterate through cloud region tenants with given name + + """ + return (tenant for tenant in self.tenants if tenant.name == tenant_name) + + + def get_availability_zone_by_name(self, + zone_name: str) -> "AvailabilityZone": + """Get availability zone with provided Name. + + Args: + availability_zone name (str): The name of the availibilty zone + + Returns: + AvailabilityZone: AvailabilityZone object + + """ + response: dict = self.send_message_json( + "GET", + "get availability_zones", + f"{self.url}/availability-zones/availability-zone/{zone_name}" + ) + return AvailabilityZone( + name=response["availability-zone-name"], + hypervisor_type=response["hypervisor-type"], + resource_version=response["resource-version"] + ) + + def add_availability_zone(self, + availability_zone_name: str, + availability_zone_hypervisor_type: str, + availability_zone_operational_status: str = None) -> None: + """Add avaiability zone to cloud region. + + Args: + availability_zone_name (str): Name of the availability zone. + Unique across a cloud region + availability_zone_hypervisor_type (str): Type of hypervisor + availability_zone_operational_status (str, optional): State that indicates whether + the availability zone should be used. Defaults to None. + """ + self.send_message( + "PUT", + "Add availability zone to cloud region", + f"{self.url}/availability-zones/availability-zone/{availability_zone_name}", + data=jinja_env() + .get_template("cloud_region_add_availability_zone.json.j2") + .render(availability_zone_name=availability_zone_name, + availability_zone_hypervisor_type=availability_zone_hypervisor_type, + availability_zone_operational_status=availability_zone_operational_status) + ) + + def add_esr_system_info(self, # pylint: disable=too-many-arguments, too-many-locals + esr_system_info_id: str, + user_name: str, + password: str, + system_type: str, + system_name: str = None, + esr_type: str = None, + vendor: str = None, + version: str = None, + service_url: str = None, + protocol: str = None, + ssl_cacert: str = None, + ssl_insecure: Optional[bool] = None, + ip_address: str = None, + port: str = None, + cloud_domain: str = None, + default_tenant: str = None, + passive: Optional[bool] = None, + remote_path: str = None, + system_status: str = None, + openstack_region_id: str = None, + resource_version: str = None) -> None: + """Add external system info to cloud region. + + Args: + esr_system_info_id (str): Unique ID of esr system info + user_name (str): username used to access external system + password (str): password used to access external system + system_type (str): it could be vim/vnfm/thirdparty-sdnc/ + ems-resource/ems-performance/ems-alarm + system_name (str, optional): name of external system. Defaults to None. + esr_type (str, optional): type of external system. Defaults to None. + vendor (str, optional): vendor of external system. Defaults to None. + version (str, optional): version of external system. Defaults to None. + service_url (str, optional): url used to access external system. Defaults to None. + protocol (str, optional): protocol of third party SDNC, + for example netconf/snmp. Defaults to None. + ssl_cacert (str, optional): ca file content if enabled ssl on auth-url. + Defaults to None. + ssl_insecure (bool, optional): Whether to verify VIM's certificate. Defaults to True. + ip_address (str, optional): service IP of ftp server. Defaults to None. + port (str, optional): service port of ftp server. Defaults to None. + cloud_domain (str, optional): domain info for authentication. Defaults to None. + default_tenant (str, optional): default tenant of VIM. Defaults to None. + passive (bool, optional): ftp passive mode or not. Defaults to False. + remote_path (str, optional): resource or performance data file path. Defaults to None. + system_status (str, optional): he status of external system. Defaults to None. + openstack_region_id (str, optional): OpenStack region ID used by MultiCloud plugin to + interact with an OpenStack instance. Defaults to None. + """ + self.send_message( + "PUT", + "Add external system info to cloud region", + f"{self.url}/esr-system-info-list/esr-system-info/{esr_system_info_id}", + data=jinja_env() + .get_template("cloud_region_add_esr_system_info.json.j2") + .render(esr_system_info_id=esr_system_info_id, + user_name=user_name, + password=password, + system_type=system_type, + system_name=system_name, + esr_type=esr_type, + vendor=vendor, + version=version, + service_url=service_url, + protocol=protocol, + ssl_cacert=ssl_cacert, + ssl_insecure=ssl_insecure, + ip_address=ip_address, + port=port, + cloud_domain=cloud_domain, + default_tenant=default_tenant, + passive=passive, + remote_path=remote_path, + system_status=system_status, + openstack_region_id=openstack_region_id, + resource_version=resource_version) + ) + + def register_to_multicloud(self, default_tenant: str = None) -> None: + """Register cloud to multicloud using MSB API. + + Args: + default_tenant (str, optional): Default tenant. Defaults to None. + """ + Multicloud.register_vim(self.cloud_owner, self.cloud_region_id, default_tenant) + + def unregister_from_multicloud(self) -> None: + """Unregister cloud from mutlicloud.""" + Multicloud.unregister_vim(self.cloud_owner, self.cloud_region_id) + + def delete(self) -> None: + """Delete cloud region.""" + self.send_message( + "DELETE", + f"Delete cloud region {self.cloud_region_id}", + self.url, + params={"resource-version": self.resource_version} + ) + + def link_to_complex(self, complex_object: Complex) -> None: + """Link cloud region to comples. + + It creates relationhip object and add it into cloud region. + """ + relationship = Relationship( + related_to="complex", + related_link=(f"aai/v13/cloud-infrastructure/complexes/" + f"complex/{complex_object.physical_location_id}"), + relationship_data={ + "relationship-key": "complex.physical-location-id", + "relationship-value": f"{complex_object.physical_location_id}", + }, + relationship_label="org.onap.relationships.inventory.LocatedIn", + ) + self.add_relationship(relationship) diff --git a/src/onapsdk/aai/cloud_infrastructure/complex.py b/src/onapsdk/aai/cloud_infrastructure/complex.py new file mode 100644 index 0000000..a854f02 --- /dev/null +++ b/src/onapsdk/aai/cloud_infrastructure/complex.py @@ -0,0 +1,300 @@ +"""A&AI Complex module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Any, Dict, Iterator +from urllib.parse import urlencode + +from onapsdk.utils.jinja import jinja_env + +from ..aai_element import AaiResource + + +class Complex(AaiResource): # pylint: disable=too-many-instance-attributes + """Complex class. + + Collection of physical locations that can house cloud-regions. + """ + + def __init__(self, # pylint: disable=too-many-locals + physical_location_id: str, + *, + name: str = "", + data_center_code: str = "", + identity_url: str = "", + resource_version: str = "", + physical_location_type: str = "", + street1: str = "", + street2: str = "", + city: str = "", + state: str = "", + postal_code: str = "", + country: str = "", + region: str = "", + latitude: str = "", + longitude: str = "", + elevation: str = "", + lata: str = "", + timezone: str = "", + data_owner: str = "", + data_source: str = "", + data_source_version: str = "") -> None: + """Complex object initialization. + + Args: + name (str): complex name + physical_location_id (str): complex ID + data_center_code (str, optional): complex data center code. Defaults to "". + identity_url (str, optional): complex identity url. Defaults to "". + resource_version (str, optional): complex resource version. Defaults to "". + physical_location_type (str, optional): complex physical location type. Defaults to "". + street1 (str, optional): complex address street part one. Defaults to "". + street2 (str, optional): complex address street part two. Defaults to "". + city (str, optional): complex address city. Defaults to "". + state (str, optional): complex address state. Defaults to "". + postal_code (str, optional): complex address postal code. Defaults to "". + country (str, optional): complex address country. Defaults to "". + region (str, optional): complex address region. Defaults to "". + latitude (str, optional): complex geographical location latitude. Defaults to "". + longitude (str, optional): complex geographical location longitude. Defaults to "". + elevation (str, optional): complex elevation. Defaults to "". + lata (str, optional): complex lata. Defaults to "". + timezone (str, optional): the time zone where the complex is located. Defaults to "". + data_owner (str, optional): Identifies the entity that is responsible managing this + inventory object. Defaults to "". + data_source (str, optional): Identifies the upstream source of the data. Defaults to "". + data_source_version (str, optional): Identifies the version of the upstream source. + Defaults to "". + + """ + super().__init__() + self.name: str = name + self.physical_location_id: str = physical_location_id + self.data_center_code: str = data_center_code + self.identity_url: str = identity_url + self.resource_version: str = resource_version + self.physical_location_type: str = physical_location_type + self.street1: str = street1 + self.street2: str = street2 + self.city: str = city + self.state: str = state + self.postal_code: str = postal_code + self.country: str = country + self.region: str = region + self.latitude: str = latitude + self.longitude: str = longitude + self.elevation: str = elevation + self.lata: str = lata + self.timezone: str = timezone + self.data_owner: str = data_owner + self.data_source: str = data_source + self.data_source_version: str = data_source_version + + def __repr__(self) -> str: + """Complex object description. + + Returns: + str: Complex object description + + """ + return (f"Complex(name={self.name}, " + f"physical_location_id={self.physical_location_id}, " + f"resource_version={self.resource_version})") + + @property + def url(self) -> str: + """Complex url. + + Returns: + str: Complex url + + """ + return (f"{self.base_url}{self.api_version}/cloud-infrastructure/complexes/complex/" + f"{self.physical_location_id}") + + @classmethod + def create(cls, # pylint: disable=too-many-locals + physical_location_id: str, + *, + name: str = "", + data_center_code: str = "", + identity_url: str = "", + resource_version: str = "", + physical_location_type: str = "", + street1: str = "", + street2: str = "", + city: str = "", + state: str = "", + postal_code: str = "", + country: str = "", + region: str = "", + latitude: str = "", + longitude: str = "", + elevation: str = "", + lata: str = "", + timezone: str = "", + data_owner: str = "", + data_source: str = "", + data_source_version: str = "") -> "Complex": + """Create complex. + + Create complex object by calling A&AI API. + If API request doesn't fail it returns Complex object. + + Returns: + Complex: Created complex object + + """ + complex_object: Complex = Complex( + name=name, + physical_location_id=physical_location_id, + data_center_code=data_center_code, + identity_url=identity_url, + resource_version=resource_version, + physical_location_type=physical_location_type, + street1=street1, + street2=street2, + city=city, + state=state, + postal_code=postal_code, + country=country, + region=region, + latitude=latitude, + longitude=longitude, + elevation=elevation, + lata=lata, + timezone=timezone, + data_owner=data_owner, + data_source=data_source, + data_source_version=data_source_version + ) + payload: str = jinja_env().get_template("complex_create.json.j2").render( + complex=complex_object) + url: str = ( + f"{cls.base_url}{cls.api_version}/cloud-infrastructure/complexes/complex/" + f"{complex_object.physical_location_id}" + ) + cls.send_message("PUT", "create complex", url, data=payload) + return complex_object + + @classmethod + def get_all_url(cls) -> str: # pylint: disable=arguments-differ + """Return an url to get all complexes. + + Returns: + str: URL to get all complexes + + """ + return f"{cls.base_url}{cls.api_version}/cloud-infrastructure/complexes" + + @classmethod + def get_all(cls, + physical_location_id: str = None, + data_center_code: str = None, + complex_name: str = None, + identity_url: str = None) -> Iterator["Complex"]: + """Get all complexes from A&AI. + + Call A&AI API to get all complex objects. + + Args: + physical_location_id (str, optional): Unique identifier for physical location, + e.g., CLLI. Defaults to None. + data_center_code (str, optional): Data center code which can be an alternate way + to identify a complex. Defaults to None. + complex_name (str, optional): Gamma complex name for LCP instance. Defaults to None. + identity_url (str, optional): URL of the keystone identity service. Defaults to None. + + Yields: + Complex -- Complex object. Can not yield anything if any complex with given filter + parameters doesn't exist + + """ + filter_parameters: dict = cls.filter_none_key_values( + { + "physical-location-id": physical_location_id, + "data-center-code": data_center_code, + "complex-name": complex_name, + "identity-url": identity_url, + } + ) + url: str = (f"{cls.get_all_url()}?{urlencode(filter_parameters)}") + for complex_json in cls.send_message_json("GET", + "get cloud regions", + url).get("complex", []): + yield cls.create_from_api_response(complex_json) + + @classmethod + def get_by_physical_location_id(cls, physical_location_id: str) -> "Complex": + """Get complex by physical location id. + + Args: + physical_location_id (str): Physical location id of Complex + + Returns: + Complex: Complex object + + Raises: + ResourceNotFound: Complex with given physical location id not found + + """ + response = cls.send_message_json("GET", + "Get complex with physical location id: " + f"{physical_location_id}", + f"{cls.base_url}{cls.api_version}/cloud-infrastructure/" + f"complexes/complex/{physical_location_id}") + return cls.create_from_api_response(response) + + @classmethod + def create_from_api_response(cls, + api_response: Dict[str, Any]) -> "Complex": + """Create complex object using given A&AI API response JSON. + + Args: + api_response (Dict[str, Any]): Complex A&AI API response + + Returns: + Complex: Complex object created from given response + + """ + return cls( + name=api_response.get("complex-name"), + physical_location_id=api_response["physical-location-id"], + data_center_code=api_response.get("data-center-code"), + identity_url=api_response.get("identity-url"), + resource_version=api_response.get("resource-version"), + physical_location_type=api_response.get("physical-location-type"), + street1=api_response.get("street1"), + street2=api_response.get("street2"), + city=api_response.get("city"), + state=api_response.get("state"), + postal_code=api_response.get("postal-code"), + country=api_response.get("country"), + region=api_response.get("region"), + latitude=api_response.get("latitude"), + longitude=api_response.get("longitude"), + elevation=api_response.get("elevation"), + lata=api_response.get("lata"), + timezone=api_response.get("time-zone"), + data_owner=api_response.get("data-owner"), + data_source=api_response.get("data-source"), + data_source_version=api_response.get("data-source-version") + ) + + def delete(self) -> None: + """Delete complex.""" + self.send_message( + "DELETE", + f"Delete {self.physical_location_id} complex", + f"{self.url}?resource-version={self.resource_version}" + ) diff --git a/src/onapsdk/aai/cloud_infrastructure/geo_region.py b/src/onapsdk/aai/cloud_infrastructure/geo_region.py new file mode 100644 index 0000000..32ff820 --- /dev/null +++ b/src/onapsdk/aai/cloud_infrastructure/geo_region.py @@ -0,0 +1,191 @@ +"""Geo region module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Iterator, Optional + +from onapsdk.utils.jinja import jinja_env + +from ..aai_element import AaiResource + + +class GeoRegion(AaiResource): # pylint: disable=too-many-instance-attributes + """Geo region class.""" + + def __init__(self, + geo_region_id: str, + *, + geo_region_name: str = "", + geo_region_type: str = "", + geo_region_role: str = "", + geo_region_function: str = "", + data_owner: str = "", + data_source: str = "", + data_source_version: str = "", + resource_version: str = "", + ) -> None: + """Geo region init. + + Args: + geo_region_id (str): UUID, key for geo-region object. + geo_region_name (str, optional): Name of geo-region. Defaults to "". + geo_region_type (str, optional): Type of geo-region. Defaults to "". + geo_region_role (str, optional): Role of geo-region. Defaults to "". + geo_region_function (str, optional): Function of geo-region. Defaults to "". + data_owner (str, optional): Identifies the entity that is responsible managing + this inventory object. Defaults to "". + data_source (str, optional): Identifies the upstream source of the data. + Defaults to "". + data_source_version (str, optional): Identifies the version of + the upstream source. Defaults to "". + resource_version (str, optional): Resource version. Defaults to "". + + """ + super().__init__() + self.geo_region_id: str = geo_region_id + self.geo_region_name: str = geo_region_name + self.geo_region_type: str = geo_region_type + self.geo_region_role: str = geo_region_role + self.geo_region_function: str = geo_region_function + self.data_owner: str = data_owner + self.data_source: str = data_source + self.data_source_version: str = data_source_version + self.resource_version: str = resource_version + + def __repr__(self) -> str: + """Geo region object representation. + + Returns: + str: Human readable string contains most important information about geo region. + + """ + return ( + f"GeoRegion(geo_region_id={self.geo_region_id})" + ) + + @property + def url(self) -> str: + """Geo region's url. + + Returns: + str: Geo Region's url + + """ + return (f"{self.base_url}{self.api_version}/cloud-infrastructure/" + f"geo-regions/geo-region/{self.geo_region_id}") + + @classmethod + def get_all_url(cls, *args, **kwargs) -> str: # pylint: disable=arguments-differ + """Return url to get all geo regions. + + Returns: + str: Url to get all geo regions + + Raises: + ResourceNotFound: No geo regions found + + """ + return f"{cls.base_url}{cls.api_version}/cloud-infrastructure/geo-regions" + + @classmethod + def get_all(cls) -> Iterator["GeoRegion"]: + """Get all geo regions. + + Yields: + GeoRegion: Geo region + + """ + for geo_region_data in cls.send_message_json("GET", + "Get all geo regions", + cls.get_all_url()).get("geo-region", []): + yield cls(geo_region_id=geo_region_data["geo-region-id"], + geo_region_name=geo_region_data.get("geo-region-name", ""), + geo_region_type=geo_region_data.get("geo-region-type", ""), + geo_region_role=geo_region_data.get("geo-region-role", ""), + geo_region_function=geo_region_data.get("geo-region-function", ""), + data_owner=geo_region_data.get("data-owner", ""), + data_source=geo_region_data.get("data-source", ""), + data_source_version=geo_region_data.get("data-source-version", ""), + resource_version=geo_region_data.get("resource-version", "")) + + @classmethod + def get_by_geo_region_id(cls, geo_region_id: str) -> "GeoRegion": + """Get geo region by it's id. + + Args: + geo_region_id (str): Geo region id + + Returns: + GeoRegion: Geo region + + """ + resp = cls.send_message_json("GET", + f"Get geo region with {geo_region_id} id", + f"{cls.get_all_url()}/geo-region/{geo_region_id}") + return GeoRegion(resp["geo-region-id"], + geo_region_name=resp.get("geo-region-name", ""), + geo_region_type=resp.get("geo-region-type", ""), + geo_region_role=resp.get("geo-region-role", ""), + geo_region_function=resp.get("geo-region-function", ""), + data_owner=resp.get("data-owner", ""), + data_source=resp.get("data-source", ""), + data_source_version=resp.get("data-source-version", ""), + resource_version=resp["resource-version"]) + + @classmethod + def create(cls, # pylint: disable=too-many-arguments + geo_region_id: str, + geo_region_name: Optional[str] = None, + geo_region_type: Optional[str] = None, + geo_region_role: Optional[str] = None, + geo_region_function: Optional[str] = None, + data_owner: Optional[str] = None, + data_source: Optional[str] = None, + data_source_version: Optional[str] = None) -> "GeoRegion": + """Create geo region. + + Args: + geo_region_id (str): UUID, key for geo-region object. + geo_region_name (Optional[str], optional): Name of geo-region. Defaults to None. + geo_region_type (Optional[str], optional): Type of geo-region. Defaults to None. + geo_region_role (Optional[str], optional): Role of geo-region. Defaults to None. + geo_region_function (Optional[str], optional): Function of geo-region. + Defaults to None. + data_owner (Optional[str], optional): Identifies the entity that is + responsible managing this inventory object.. Defaults to None. + data_source (Optional[str], optional): Identifies the upstream source of the data. + Defaults to None. + data_source_version (Optional[str], optional): Identifies the version of + the upstream source. Defaults to None. + + Returns: + GeoRegion: Geo region object + + """ + cls.send_message( + "PUT", + "Create geo region", + f"{cls.get_all_url()}/geo-region/{geo_region_id}", + data=jinja_env() + .get_template("geo_region_create.json.j2") + .render(geo_region_id=geo_region_id, + geo_region_name=geo_region_name, + geo_region_type=geo_region_type, + geo_region_role=geo_region_role, + geo_region_function=geo_region_function, + data_owner=data_owner, + data_source=data_source, + data_source_version=data_source_version), + ) + return cls.get_by_geo_region_id(geo_region_id) diff --git a/src/onapsdk/aai/cloud_infrastructure/tenant.py b/src/onapsdk/aai/cloud_infrastructure/tenant.py new file mode 100644 index 0000000..13d9aec --- /dev/null +++ b/src/onapsdk/aai/cloud_infrastructure/tenant.py @@ -0,0 +1,101 @@ +"""A&AI Tenant module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.aai.cloud_infrastructure.cloud_region import CloudRegion +from ..aai_element import AaiResource + + +class Tenant(AaiResource): + """Tenant class.""" + + def __init__(self, # pylint: disable=too-many-arguments + cloud_region: "CloudRegion", + tenant_id: str, + tenant_name: str, + tenant_context: str = None, + resource_version: str = None): + """Tenant object initialization. + + Tenant object represents A&AI Tenant resource. + + Args: + cloud_region (str): Cloud region object + tenant_id (str): Unique Tenant ID + tenant_name (str): Tenant name + tenant_context (str, optional): Tenant context. Defaults to None. + resource_version (str, optional): Tenant resource version. Defaults to None. + + """ + super().__init__() + self.cloud_region: "CloudRegion" = cloud_region + self.tenant_id: str = tenant_id + self.name: str = tenant_name + self.context: str = tenant_context + self.resource_version: str = resource_version + + def __repr__(self) -> str: + """Tenant repr. + + Returns: + str: Human readable Tenant object description + + """ + return ( + f"Tenant(tenant_id={self.tenant_id}, tenant_name={self.name}, " + f"tenant_context={self.context}, " + f"resource_version={self.resource_version}, " + f"cloud_region={self.cloud_region.cloud_region_id})" + ) + + @classmethod + def get_all_url(cls, cloud_region: "CloudRegion") -> str: # pylint: disable=arguments-differ + """Return an url to get all tenants for given cloud region. + + Args: + cloud_region (CloudRegion): Cloud region object + + Returns: + str: Url to get all tenants + + """ + return (f"{cls.base_url}{cls.api_version}/cloud-infrastructure/cloud-regions/cloud-region/" + f"{cloud_region.cloud_owner}/{cloud_region.cloud_region_id}" + f"/tenants/") + + @property + def url(self) -> str: + """Tenant url. + + Returns: + str: Url which can be used to update or delete tenant. + + """ + return ( + f"{self.base_url}{self.api_version}/cloud-infrastructure/cloud-regions/cloud-region/" + f"{self.cloud_region.cloud_owner}/{self.cloud_region.cloud_region_id}" + f"/tenants/tenant/{self.tenant_id}?" + f"resource-version={self.resource_version}" + ) + + def delete(self) -> None: + """Delete tenant. + + Remove tenant from cloud region. + + """ + return self.send_message( + "DELETE", + f"Remove tenant {self.name} from {self.cloud_region.cloud_region_id} cloud region", + url=self.url, + ) diff --git a/src/onapsdk/aai/network/__init__.py b/src/onapsdk/aai/network/__init__.py new file mode 100644 index 0000000..c3795c1 --- /dev/null +++ b/src/onapsdk/aai/network/__init__.py @@ -0,0 +1,16 @@ +"""A&AI network package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 .site_resource import SiteResource diff --git a/src/onapsdk/aai/network/site_resource.py b/src/onapsdk/aai/network/site_resource.py new file mode 100644 index 0000000..3ac3c20 --- /dev/null +++ b/src/onapsdk/aai/network/site_resource.py @@ -0,0 +1,244 @@ +"""A&AI site resource module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Iterable, Optional + +from onapsdk.utils.jinja import jinja_env +from ..aai_element import AaiResource + + +class SiteResource(AaiResource): # pylint: disable=too-many-instance-attributes + """Site resource class.""" + + def __init__(self, # pylint: disable=too-many-locals + site_resource_id: str, + *, + site_resource_name: str = "", + description: str = "", + site_resource_type: str = "", + role: str = "", + generated_site_id: str = "", + selflink: str = "", + operational_status: str = "", + model_customization_id: str = "", + model_invariant_id: str = "", + model_version_id: str = "", + data_owner: str = "", + data_source: str = "", + data_source_version: str = "", + resource_version: str = "") -> None: + """Site resource object init. + + Args: + site_resource_id (str): Uniquely identifies this site-resource by id. + site_resource_name (str, optional): Store the name of this site-resource. + Defaults to "". + description (str, optional): Store the description of this site-resource. + Defaults to "". + site_resource_type (str, optional): Store the type of this site-resource. + Defaults to "". + role (str, optional): Store the role of this site-resource. Defaults to "". + generated_site_id (str, optional): Store the generated-site-id of this site-resource. + Defaults to "". + selflink (str, optional): Store the link to get more information for this object. + Defaults to "". + operational_status (str, optional): Store the operational-status for this object. + Defaults to "". + model_customization_id (str, optional): Store the model-customization-id + for this object. Defaults to "". + model_invariant_id (str, optional): The ASDC model id for this resource or + service model. Defaults to "". + model_version_id (str, optional): The ASDC model version for this resource or service + model. Defaults to "". + data_owner (str, optional): Identifies the entity that is responsible managing + this inventory object. Defaults to "". + data_source (str, optional): Identifies the upstream source of the data. + Defaults to "". + data_source_version (str, optional): Identifies the version of the upstream source. + Defaults to "". + resource_version (str, optional): Used for optimistic concurrency. Must be empty on + create, valid on update and delete. Defaults to "". + + """ + super().__init__() + self.site_resource_id: str = site_resource_id + self.site_resource_name: str = site_resource_name + self.description: str = description + self.site_resource_type: str = site_resource_type + self.role: str = role + self.generated_site_id: str = generated_site_id + self.selflink: str = selflink + self.operational_status: str = operational_status + self.model_customization_id: str = model_customization_id + self.model_invariant_id: str = model_invariant_id + self.model_version_id: str = model_version_id + self.data_owner: str = data_owner + self.data_source: str = data_source + self.data_source_version: str = data_source_version + self.resource_version: str = resource_version + + @property + def url(self) -> str: + """Site resource's url. + + Returns: + str: Site resources's url + + """ + return (f"{self.base_url}{self.api_version}/network/site-resources" + f"/site-resource/{self.site_resource_id}") + + @classmethod + def get_all_url(cls, *args, **kwargs) -> str: + """Get all site resources request url. + + Returns: + str: Url used on get all site resources request + + """ + return f"{cls.base_url}{cls.api_version}/network/site-resources" + + @classmethod + def get_all(cls) -> Iterable["SiteResource"]: + """Get all site resources. + + Yields: + SiteResource: Site resource object + + """ + for site_resource_data in cls.send_message_json("GET", + "Get all site resources", + cls.get_all_url()).get("site-resource", []): + yield SiteResource(site_resource_id=site_resource_data["site-resource-id"], + site_resource_name=site_resource_data.get("site-resource-name", ""), + description=site_resource_data.get("description", ""), + site_resource_type=site_resource_data.get("type", ""), + role=site_resource_data.get("role", ""), + generated_site_id=site_resource_data.get("generated-site-id", ""), + selflink=site_resource_data.get("selflink", ""), + operational_status=site_resource_data.get("operational-status", ""), + model_customization_id=site_resource_data.\ + get("model-customization-id", ""), + model_invariant_id=site_resource_data.get("model-invariant-id", ""), + model_version_id=site_resource_data.get("model-version-id", ""), + data_owner=site_resource_data.get("data-owner", ""), + data_source=site_resource_data.get("data-source", ""), + data_source_version=site_resource_data.get("data-source-version", + ""), + resource_version=site_resource_data.get("resource-version", "")) + + @classmethod + def get_by_site_resource_id(cls, site_resource_id: str) -> "SiteResource": + """Get site resource by it's id. + + Args: + site_resource_id (str): Site resource id. + + Returns: + SiteResource: Site resource object. + + """ + site_resource_data = cls.send_message_json("GET", + f"Get site resource with {site_resource_id} id", + f"{cls.get_all_url()}" + f"/site-resource/{site_resource_id}") + return SiteResource(site_resource_id=site_resource_data["site-resource-id"], + site_resource_name=site_resource_data.get("site-resource-name", ""), + description=site_resource_data.get("description", ""), + site_resource_type=site_resource_data.get("type", ""), + role=site_resource_data.get("role", ""), + generated_site_id=site_resource_data.get("generated-site-id", ""), + selflink=site_resource_data.get("selflink", ""), + operational_status=site_resource_data.get("operational-status", ""), + model_customization_id=site_resource_data.get("model-customization-id", + ""), + model_invariant_id=site_resource_data.get("model-invariant-id", ""), + model_version_id=site_resource_data.get("model-version-id", ""), + data_owner=site_resource_data.get("data-owner", ""), + data_source=site_resource_data.get("data-source", ""), + data_source_version=site_resource_data.get("data-source-version", ""), + resource_version=site_resource_data.get("resource-version", "")) + + @classmethod + def create(cls, # pylint: disable=too-many-arguments + site_resource_id: str, + site_resource_name: Optional[str] = None, + description: Optional[str] = None, + site_resource_type: Optional[str] = None, + role: Optional[str] = None, + generated_site_id: Optional[str] = None, + selflink: Optional[str] = None, + operational_status: Optional[str] = None, + model_customization_id: Optional[str] = None, + model_invariant_id: Optional[str] = None, + model_version_id: Optional[str] = None, + data_owner: Optional[str] = None, + data_source: Optional[str] = None, + data_source_version: Optional[str] = None) -> "SiteResource": + """Create site resource. + + Args: + site_resource_id (str): Uniquely identifies this site-resource by id + site_resource_name (Optional[str], optional): Store the name of this site-resource. + Defaults to None. + description (Optional[str], optional): Store the description of this site-resource. + Defaults to None. + site_resource_type (Optional[str], optional): Store the type of this site-resource. + Defaults to None. + role (Optional[str], optional): Store the role of this site-resource. + Defaults to None. + generated_site_id (Optional[str], optional): Store the generated-site-id of + this site-resource. Defaults to None. + selflink (Optional[str], optional): Store the link to get more information + for this object. Defaults to None. + operational_status (Optional[str], optional): Store the operational-status + for this object. Defaults to None. + model_customization_id (Optional[str], optional): Store the model-customization-id + for this object. Defaults to None. + model_invariant_id (Optional[str], optional): The ASDC model id for + this resource or service model. Defaults to None. + model_version_id (Optional[str], optional): The ASDC model version for this + resource or service model. Defaults to None. + data_owner (Optional[str], optional): Identifies the entity that is responsible + managing this inventory object. Defaults to None. + data_source (Optional[str], optional): Identifies the upstream source of the data. + Defaults to None. + data_source_version (Optional[str], optional): Identifies the version of the upstream + source. Defaults to None. + + Returns: + SiteResource: Site resource object + + """ + cls.send_message("PUT", + f"Create site resource {site_resource_id}", + f"{cls.get_all_url()}/site-resource/{site_resource_id}", + data=jinja_env() + .get_template("site_resource_create.json.j2") + .render(site_resource_id=site_resource_id, + site_resource_name=site_resource_name, + description=description, + site_resource_type=site_resource_type, + role=role, + generated_site_id=generated_site_id, + selflink=selflink, + operational_status=operational_status, + model_customization_id=model_customization_id, + model_invariant_id=model_invariant_id, + model_version_id=model_version_id, + data_owner=data_owner, + data_source=data_source, + data_source_version=data_source_version)) + return cls.get_by_site_resource_id(site_resource_id) diff --git a/src/onapsdk/aai/service_design_and_creation.py b/src/onapsdk/aai/service_design_and_creation.py new file mode 100644 index 0000000..5bf2c6f --- /dev/null +++ b/src/onapsdk/aai/service_design_and_creation.py @@ -0,0 +1,186 @@ +"""AAI service-design-and-creation module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Iterator +from urllib.parse import urlencode + +from onapsdk.utils.jinja import jinja_env + +from .aai_element import AaiResource + + +class Service(AaiResource): + """SDC service class.""" + + def __init__(self, service_id: str, service_description: str, resource_version: str) -> None: + """Service model initialization. + + Args: + service_id (str): This gets defined by others to provide a unique ID for the service. + service_description (str): Description of the service. + resource_version (str): Used for optimistic concurrency. + + """ + super().__init__() + self.service_id = service_id + self.service_description = service_description + self.resource_version = resource_version + + def __repr__(self) -> str: + """Service object description. + + Returns: + str: Service object description + + """ + return ( + f"Service(service_id={self.service_id}, " + f"service_description={self.service_description}, " + f"resource_version={self.resource_version})" + ) + + @property + def url(self) -> str: + """Service object url. + + Returns: + str: Service object url address + + """ + return (f"{self.base_url}{self.api_version}/service-design-and-creation/services/service/" + f"{self.service_id}?resource-version={self.resource_version}") + + @classmethod + def get_all_url(cls) -> str: # pylint: disable=arguments-differ + """Return url to get all services. + + Returns: + str: Url to get all services + + """ + return f"{cls.base_url}{cls.api_version}/service-design-and-creation/services" + + @classmethod + def get_all(cls, + service_id: str = None, + service_description: str = None) -> Iterator["Service"]: + """Services iterator. + + Stand-in for service model definitions. + + Returns: + Iterator[Service]: Service + + """ + filter_parameters: dict = cls.filter_none_key_values( + {"service-id": service_id, "service-description": service_description} + ) + url: str = (f"{cls.get_all_url()}?{urlencode(filter_parameters)}") + for service in cls.send_message_json("GET", "get subscriptions", url).get("service", []): + yield Service( + service_id=service["service-id"], + service_description=service["service-description"], + resource_version=service["resource-version"], + ) + + @classmethod + def create(cls, + service_id: str, + service_description: str) -> None: + """Create service. + + Args: + service_id (str): service ID + service_description (str): service description + + """ + cls.send_message( + "PUT", + "Create A&AI service", + f"{cls.base_url}{cls.api_version}/service-design-and-creation/" + f"services/service/{service_id}", + data=jinja_env() + .get_template("aai_service_create.json.j2") + .render( + service_id=service_id, + service_description=service_description + ) + ) + + +class Model(AaiResource): + """Model resource class.""" + + def __init__(self, invariant_id: str, model_type: str, resource_version: str) -> None: + """Model object initialization. + + Args: + invariant_id (str): invariant id + model_type (str): model type + resource_version (str): resource version + + """ + super().__init__() + self.invariant_id: str = invariant_id + self.model_type: str = model_type + self.resource_version: str = resource_version + + def __repr__(self) -> str: + """Model object representation. + + Returns: + str: model object representation + + """ + return (f"Model(invatiant_id={self.invariant_id}, " + f"model_type={self.model_type}, " + f"resource_version={self.resource_version}") + + @property + def url(self) -> str: + """Model instance url. + + Returns: + str: Model's url + + """ + return (f"{self.base_url}{self.api_version}/service-design-and-creation/models/" + f"model/{self.invariant_id}?resource-version={self.resource_version}") + + @classmethod + def get_all_url(cls) -> str: # pylint: disable=arguments-differ + """Return url to get all models. + + Returns: + str: Url to get all models + + """ + return f"{cls.base_url}{cls.api_version}/service-design-and-creation/models" + + @classmethod + def get_all(cls) -> Iterator["Model"]: + """Get all models. + + Yields: + Model: Model object + + """ + for model in cls.send_message_json("GET", + "Get A&AI sdc models", + cls.get_all_url()).get("model", []): + yield Model( + invariant_id=model.get("model-invariant-id"), + model_type=model.get("model-type"), + resource_version=model.get("resource-version") + ) diff --git a/src/onapsdk/aai/templates/aai_add_relationship.json.j2 b/src/onapsdk/aai/templates/aai_add_relationship.json.j2 new file mode 100644 index 0000000..5d7acb8 --- /dev/null +++ b/src/onapsdk/aai/templates/aai_add_relationship.json.j2 @@ -0,0 +1,11 @@ +{ + "related-to": "{{ relationship.related_to }}", + "related-link": "{{ relationship.related_link }}", + {% if relationship.relationship_label %} + "relationship-label": "{{ relationship.relationship_label }}", + {% endif %} + {% if relationship.related_to_property %} + "related-to-property": {{ relationship.related_to_property | tojson }}, + {% endif %} + "relationship-data": {{ relationship.relationship_data | tojson }} +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/aai_bulk.json.j2 b/src/onapsdk/aai/templates/aai_bulk.json.j2 new file mode 100644 index 0000000..40a97bd --- /dev/null +++ b/src/onapsdk/aai/templates/aai_bulk.json.j2 @@ -0,0 +1,11 @@ +{ + "operations": [ + {% for operation in operations %} + { + "action": "{{ operation.action }}", + "uri": "{{ operation.uri }}", + "body": {{ operation.body }} + }{% if not loop.last %},{% endif %} + {% endfor %} + ] +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/aai_line_of_business_create.json.j2 b/src/onapsdk/aai/templates/aai_line_of_business_create.json.j2 new file mode 100644 index 0000000..adab1fa --- /dev/null +++ b/src/onapsdk/aai/templates/aai_line_of_business_create.json.j2 @@ -0,0 +1,3 @@ +{ + "line-of-business-name": "{{ line_of_business_name }}" +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/aai_owning_entity_create.json.j2 b/src/onapsdk/aai/templates/aai_owning_entity_create.json.j2 new file mode 100644 index 0000000..2877a3d --- /dev/null +++ b/src/onapsdk/aai/templates/aai_owning_entity_create.json.j2 @@ -0,0 +1,4 @@ +{ + "owning-entity-name": "{{ owning_entity_name }}", + "owning-entity-id": "{{ owning_entity_id }}" +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/aai_platform_create.json.j2 b/src/onapsdk/aai/templates/aai_platform_create.json.j2 new file mode 100644 index 0000000..afe339a --- /dev/null +++ b/src/onapsdk/aai/templates/aai_platform_create.json.j2 @@ -0,0 +1,3 @@ +{ + "platform-name": "{{ platform_name }}" +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/aai_project_create.json.j2 b/src/onapsdk/aai/templates/aai_project_create.json.j2 new file mode 100644 index 0000000..3c7a426 --- /dev/null +++ b/src/onapsdk/aai/templates/aai_project_create.json.j2 @@ -0,0 +1,3 @@ +{ + "project-name": "{{ project_name }}" +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/aai_service_create.json.j2 b/src/onapsdk/aai/templates/aai_service_create.json.j2 new file mode 100644 index 0000000..ee360cc --- /dev/null +++ b/src/onapsdk/aai/templates/aai_service_create.json.j2 @@ -0,0 +1,4 @@ +{ + "service-id": "{{ service_id }}", + "service-description": "{{ service_description }}" +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/aai_service_instance_create.json.j2 b/src/onapsdk/aai/templates/aai_service_instance_create.json.j2 new file mode 100644 index 0000000..91f0046 --- /dev/null +++ b/src/onapsdk/aai/templates/aai_service_instance_create.json.j2 @@ -0,0 +1,22 @@ +{ + "service-instance-id": "{{ service_instance.instance_id }}" + {% if service_instance.instance_name %}, "service-instance-name": "{{ service_instance.instance_name }}"{% endif %} + {% if service_instance.service_type %}, "service-type": "{{ service_instance.service_type }}"{% endif %} + {% if service_instance.service_role %}, "service-role": "{{ service_instance.service_role }}"{% endif %} + {% if service_instance.environment_context %}, "environment-context": "{{ service_instance.environment_context }}"{% endif %} + {% if service_instance.workload_context %}, "workload-context": "{{ service_instance.workload_context }}"{% endif %} + {% if service_instance.created_at %}, "created-at": "{{ service_instance.created_at }}"{% endif %} + {% if service_instance.updated_at %}, "updated-at": "{{ service_instance.updated_at }}"{% endif %} + {% if service_instance.description %}, "description": "{{ service_instance.description }}"{% endif %} + {% if service_instance.model_invariant_id %}, "model-invariant-id": "{{ service_instance.model_invariant_id }}"{% endif %} + {% if service_instance.model_version_id %}, "model-version-id": "{{ service_instance.model_version_id }}"{% endif %} + {% if service_instance.persona_model_version %}, "persona-model-version": "{{ service_instance.persona_model_version }}"{% endif %} + {% if service_instance.widget_model_id %}, "widget-model-id": "{{ service_instance.widget_model_id }}"{% endif %} + {% if service_instance.widget_model_version %}, "widget-model-version": "{{ service_instance.widget_model_version }}"{% endif %} + {% if service_instance.bandwith_total %}, "bandwidth-total": "{{ service_instance.bandwith_total }}"{% endif %} + {% if service_instance.vhn_portal_url %}, "vhn-portal-url": "{{ service_instance.vhn_portal_url }}"{% endif %} + {% if service_instance.service_instance_location_id %}, "service-instance-location-id": "{{ service_instance.service_instance_location_id }}"{% endif %} + {% if service_instance.selflink %}, "selflink": "{{ service_instance.selflink }}"{% endif %} + {% if service_instance.orchestration_status %}, "orchestration-status": "{{ service_instance.orchestration_status }}"{% endif %} + {% if service_instance.input_parameters %}, "input-parameters": "{{ service_instance.input_parameters }}"{% endif %} +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/aai_sp_partner_create.json.j2 b/src/onapsdk/aai/templates/aai_sp_partner_create.json.j2 new file mode 100644 index 0000000..40ba1d7 --- /dev/null +++ b/src/onapsdk/aai/templates/aai_sp_partner_create.json.j2 @@ -0,0 +1,21 @@ +{ + "sp-partner-id": "{{ sp_partner_id }}" + {% if url %} + , "url": "{{ url }}" + {% endif %} + {% if callsource %} + , "callsource": "{{ callsource }}" + {% endif %} + {% if operational_status %} + , "operational-status": "{{ operational_status }}" + {% endif %} + {% if model_customization_id %} + , "model-customization-id": "{{ model_customization_id }}" + {% endif %} + {% if model_invariant_id %} + , "model-invariant-id": "{{ model_invariant_id }}" + {% endif %} + {% if model_version_id %} + , "model-version-id": "{{ model_version_id }}" + {% endif %} +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/cloud_region_add_availability_zone.json.j2 b/src/onapsdk/aai/templates/cloud_region_add_availability_zone.json.j2 new file mode 100644 index 0000000..be6ebc5 --- /dev/null +++ b/src/onapsdk/aai/templates/cloud_region_add_availability_zone.json.j2 @@ -0,0 +1,7 @@ +{ + "availability-zone-name": "{{ availability_zone_name }}", + "hypervisor-type": "{{ availability_zone_hypervisor_type }}" + {% if availability_zone_operational_status %} + , "operational-status": "{{ availability_zone_operational_status }}" + {% endif %} +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/cloud_region_add_esr_system_info.json.j2 b/src/onapsdk/aai/templates/cloud_region_add_esr_system_info.json.j2 new file mode 100644 index 0000000..ab03de3 --- /dev/null +++ b/src/onapsdk/aai/templates/cloud_region_add_esr_system_info.json.j2 @@ -0,0 +1,54 @@ +{ + "esr-system-info-id": "{{ esr_system_info_id }}", + "user-name": "{{ user_name }}", + "password": "{{ password }}", + "system-type": "{{ system_type }}" + {% if system_name %} + , "system-name": "{{ system_name }}" + {% endif %} + {% if esr_type %} + , "type": "{{ esr_type }}" + {% endif %} + {% if vendor %} + , "vendor": "{{ vendor }}" + {% endif %} + {% if version %} + , "version": "{{ version }}" + {% endif %} + {% if service_url %} + , "service-url": "{{ service_url }}" + {% endif %} + {% if protocol %} + , "protocol": "{{ protocol }}" + {% endif %} + {% if ssl_cacert %} + , "ssl-cacert": "{{ ssl_cacert }}" + {% endif %} + {% if ssl_insecure is not none %} + , "ssl-insecure": {{ ssl_insecure | tojson }} + {% endif %} + {% if ip_address %} + , "ip-address": "{{ ip_address }}" + {% endif %} + {% if port %} + , "port": "{{ port }}" + {% endif %} + {% if cloud_domain %} + , "cloud-domain": "{{ cloud_domain }}" + {% endif %} + {% if default_tenant %} + , "default-tenant": "{{ default_tenant }}" + {% endif %} + {% if passive is not none %} + , "passive": {{ passive | tojson }} + {% endif %} + {% if remote_path %} + , "remote-path": "{{ remote_path }}" + {% endif %} + {% if system_status %} + , "system-status": "{{ system_status }}" + {% endif %} + {% if openstack_region_id %} + , "openstack-region-id": "{{ openstack_region_id }}" + {% endif %} +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/cloud_region_add_tenant.json.j2 b/src/onapsdk/aai/templates/cloud_region_add_tenant.json.j2 new file mode 100644 index 0000000..fd7bcb7 --- /dev/null +++ b/src/onapsdk/aai/templates/cloud_region_add_tenant.json.j2 @@ -0,0 +1,5 @@ +{ + "tenant-id": "{{ tenant_id }}", + "tenant-name": "{{ tenant_name }}", + "tenant-context": "{{ tenant_context }}" +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/cloud_region_create.json.j2 b/src/onapsdk/aai/templates/cloud_region_create.json.j2 new file mode 100644 index 0000000..65a7057 --- /dev/null +++ b/src/onapsdk/aai/templates/cloud_region_create.json.j2 @@ -0,0 +1,16 @@ +{ + "cloud-owner": "{{ cloud_region.cloud_owner }}", + "cloud-region-id": "{{ cloud_region.cloud_region_id }}", + "orchestration-disabled": "{{ cloud_region.orchestration_disabled }}", + "in-maint": "{{ cloud_region.in_maint }}", + "cloud-type": "{{ cloud_region.cloud_type }}", + "owner-defined-type": "{{ cloud_region.owner_defined_type }}", + "cloud-region-version": "{{ cloud_region.cloud_region_version }}", + "identity-url": "{{ cloud_region.identity_url }}", + "cloud-zone": "{{ cloud_region.cloud_zone }}", + "complex-name": "{{ cloud_region.complex_name }}", + "sriov-automation": "{{ cloud_region.sriov_automation }}", + "cloud-extra-info": "{{ cloud_region.cloud_extra_info }}", + "upgrade-cycle": "{{ cloud_region.upgrade_cycle }}", + "resource-version": "{{ cloud_region.resource_version }}" +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/complex_create.json.j2 b/src/onapsdk/aai/templates/complex_create.json.j2 new file mode 100644 index 0000000..681fdad --- /dev/null +++ b/src/onapsdk/aai/templates/complex_create.json.j2 @@ -0,0 +1,23 @@ +{ + "physical-location-id": "{{ complex.physical_location_id }}", + "data-center-code": "{{ complex.data_center_code }}", + "complex-name": "{{ complex.name }}", + "identity-url": "{{ complex.identity_url }}", + "resource-version": "{{ complex.resource_version }}", + "physical-location-type": "{{ complex.physical_location_type }}", + "street1": "{{ complex.street1 }}", + "street2": "{{ complex.street2 }}", + "city": "{{ complex.city }}", + "state": "{{ complex.state }}", + "postal-code": "{{ complex.postal_code }}", + "country": "{{ complex.country }}", + "region": "{{ complex.region }}", + "latitude": "{{ complex.latitude }}", + "longitude": "{{ complex.longitude }}", + "elevation": "{{ complex.elevation }}", + "lata": "{{ complex.lata }}", + "time-zone": "{{ complex.timezone }}", + "data-owner": "{{ complex.data_owner }}", + "data-source": "{{ complex.data_source }}", + "data-source-version": "{{ complex.data_source_version }}" +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/customer_create.json.j2 b/src/onapsdk/aai/templates/customer_create.json.j2 new file mode 100644 index 0000000..0eea2ed --- /dev/null +++ b/src/onapsdk/aai/templates/customer_create.json.j2 @@ -0,0 +1,15 @@ +{ + "global-customer-id": "{{ global_customer_id }}", + "subscriber-name": "{{ subscriber_name }}", + "subscriber-type": "{{ subscriber_type }}"{% if service_subscriptions %}, + "service-subscriptions": { + "service-subscription": [ + {% for service_subscription in service_subscriptions %} + { + "service-type": "{{ service_subscription }}" + }{% if not loop.last %},{% endif %} + {% endfor %} + ] + } + {% endif %} +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/customer_service_subscription_create.json.j2 b/src/onapsdk/aai/templates/customer_service_subscription_create.json.j2 new file mode 100644 index 0000000..c1ee61e --- /dev/null +++ b/src/onapsdk/aai/templates/customer_service_subscription_create.json.j2 @@ -0,0 +1,3 @@ +{ + "service-id": "{{ service_id }}" +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/geo_region_create.json.j2 b/src/onapsdk/aai/templates/geo_region_create.json.j2 new file mode 100644 index 0000000..d84b427 --- /dev/null +++ b/src/onapsdk/aai/templates/geo_region_create.json.j2 @@ -0,0 +1,10 @@ +{ + {% if geo_region_name is not none %}"geo-region-name": "{{ geo_region_name }}",{% endif %} + {% if geo_region_type is not none %}"geo-region-type": "{{ geo_region_type }}",{% endif %} + {% if geo_region_role is not none %}"geo-region-role": "{{ geo_region_role }}",{% endif %} + {% if geo_region_function is not none %}"geo-region-function": "{{ geo_region_function }}",{% endif %} + {% if data_owner is not none %}"data-owner": "{{ data_owner }}",{% endif %} + {% if data_source is not none %}"data-source": "{{ data_source }}",{% endif %} + {% if data_source_version is not none %}"data-source-version": "{{ data_source_version }}",{% endif %} + "geo-region-id": "{{ geo_region_id }}" +} \ No newline at end of file diff --git a/src/onapsdk/aai/templates/site_resource_create.json.j2 b/src/onapsdk/aai/templates/site_resource_create.json.j2 new file mode 100644 index 0000000..caaf291 --- /dev/null +++ b/src/onapsdk/aai/templates/site_resource_create.json.j2 @@ -0,0 +1,16 @@ +{ + {% if site_resource_name is not none %}"site-resource-name": "{{ site_resource_name }}",{% endif %} + {% if description is not none %}"description": "{{ description }}",{% endif %} + {% if site_resource_type is not none %}"type": "{{ site_resource_type }}",{% endif %} + {% if role is not none %}"role": "{{ role }}",{% endif %} + {% if generated_site_id is not none %}"generated-site-id": "{{ generated_site_id }}",{% endif %} + {% if selflink is not none %}"selflink": "{{ selflink }}",{% endif %} + {% if operational_status is not none %}"operational-status": "{{ operational_status }}",{% endif %} + {% if model_customization_id is not none %}"model-customization-id": "{{ model_customization_id }}",{% endif %} + {% if model_invariant_id is not none %}"model-invariant-id": "{{ model_invariant_id }}",{% endif %} + {% if model_version_id is not none %}"model-version-id": "{{ model_version_id }}",{% endif %} + {% if data_owner is not none %}"data-owner": "{{ data_owner }}",{% endif %} + {% if data_source is not none %}"data-source": "{{ data_source }}",{% endif %} + {% if data_source_version is not none %}"data-source-version": "{{ data_source_version }}",{% endif %} + "site-resource-id": "{{ site_resource_id }}" +} \ No newline at end of file diff --git a/src/onapsdk/cds/README.md b/src/onapsdk/cds/README.md new file mode 100644 index 0000000..5875e43 --- /dev/null +++ b/src/onapsdk/cds/README.md @@ -0,0 +1,71 @@ +# CDS module # + +## Load blueprint ## + +``` +>>> from onapsdk.cds import Blueprint +>>> blueprint = Blueprint.load_from_file("<< path to CBA file >>") # load a blueprint from ZIP file +``` + +## Enrich, publish blueprint + +``` +>>> enriched_blueprint = blueprint.enrich() # returns enriched blueprint object +>>> enriched_blueprint.publish() +``` + +## Execute blueprint workflow + +``` +>>> blueprint.workflows +[Workflow(name='resource-assignment', blueprint_name='vDNS-CDS-test1)', Workflow(name='config-assign', blueprint_name='vDNS-CDS-test1)', Workflow(name='config-deploy', blueprint_name='vDNS-CDS-test1)'] +>>> workflow = blueprint.workflows[0] # get the first workflow named 'resource-assignment` +>>> workflow.inputs # display what workflow needs as an input +[Workflow.WorkflowInput(name='template-prefix', required=True, type='list', description=None), Workflow.WorkflowInput(name='resource-assignment-properties', required=True, type='dt-resource-assignment-properties', description='Dynamic PropertyDefinition for workflow(resource-assignment).')] +>>> response = workflow.execute({"template-prefix": ["vpkg"], "resource-assignment-properties": {}}) # execute workflow with required inputs +``` + +## Generate data dictionary for blueprint + +Generated data dictionaries have to be manually filled for "source-rest" and "source-db" input types. + +``` +>>> blueprint.get_data_dictionaries().save_to_file("/tmp/dd.json") # generate data dictionaries for blueprint and save it to "/tmp/dd.json" file +``` + +## Manage Blueprint Models in CDS + +### Retrieve Blueprint Models from CDS + - All +``` +>>> from onapsdk.cds import BlueprintModel +>>> all_blueprint_models = BlueprintModel.get_all() +``` + - Selected by **id** of Blueprint Model +``` +>>> blueprint_model = BlueprintModel.get_by_id(blueprint_model_id='11111111-1111-1111-1111-111111111111') +>>> blueprint_model +BlueprintModel(artifact_name='test_name', blueprint_model_id='11111111-1111-1111-1111-111111111111') +``` +- Selected by **name and version** of Blueprint Model +``` +>>> blueprint_model = BlueprintModel.get_by_name_and_version(blueprint_name='test_name', blueprint_version='1.0.0') +>>> blueprint_model +BlueprintModel(artifact_name='test_name', blueprint_model_id='11111111-1111-1111-1111-111111111111') +``` + +### Delete Blueprint Model +``` +>>> blueprint_model.delete() +``` + +### Download Blueprint Model +``` +>>> blueprint_model.save(dst_file_path='/tmp/blueprint.zip') +``` + +### Get Blueprint object for Blueprint Model +``` +>>> blueprint = blueprint_model.get_blueprint() +``` +After that, all operation for blueprint object, like execute blueprint workflow etc. can be executed. diff --git a/src/onapsdk/cds/__init__.py b/src/onapsdk/cds/__init__.py new file mode 100644 index 0000000..f58e7e1 --- /dev/null +++ b/src/onapsdk/cds/__init__.py @@ -0,0 +1,18 @@ +"""ONAP SDK CDS package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 .blueprint import Blueprint +from .blueprint_model import BlueprintModel +from .data_dictionary import DataDictionarySet diff --git a/src/onapsdk/cds/blueprint.py b/src/onapsdk/cds/blueprint.py new file mode 100644 index 0000000..1286375 --- /dev/null +++ b/src/onapsdk/cds/blueprint.py @@ -0,0 +1,830 @@ +"""CDS Blueprint module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 re +from dataclasses import dataclass, field +from datetime import datetime +from io import BytesIO +from typing import Any, Dict, Generator, Iterator, List, Optional +from urllib.parse import urlencode +from uuid import uuid4 +from zipfile import ZipFile + +import oyaml as yaml + +from onapsdk.utils.jinja import jinja_env +from onapsdk.exceptions import FileError, ParameterError, ValidationError + +from .cds_element import CdsElement +from .data_dictionary import DataDictionary, DataDictionarySet + + +@dataclass +class CbaMetadata: + """Class to hold CBA metadata values.""" + + tosca_meta_file_version: str + csar_version: str + created_by: str + entry_definitions: str + template_name: str + template_version: str + template_tags: str + + +@dataclass +class Mapping: + """Blueprint's template mapping. + + Stores mapping data: + - name, + - type, + - name of dictionary from which value should be get, + - dictionary source of value. + """ + + name: str + mapping_type: str + dictionary_name: str + dictionary_sources: List[str] = field(default_factory=list) + + def __hash__(self) -> int: # noqa: D401 + """Mapping object hash. + + Based on mapping name. + + Returns: + int: Mapping hash + + """ + return hash(self.name) + + def __eq__(self, mapping: "Mapping") -> bool: + """Compare two mapping objects. + + Mappings are equal if have the same name. + + Args: + mapping (Mapping): Mapping object to compare with. + + Returns: + bool: True if objects have the same name, False otherwise. + + """ + return self.name == mapping.name + + def merge(self, mapping: "Mapping") -> None: + """Merge mapping objects. + + Merge objects dictionary sources. + + Args: + mapping (Mapping): Mapping object to merge. + + """ + self.dictionary_sources = list( + set(self.dictionary_sources) | set(mapping.dictionary_sources) + ) + + def generate_data_dictionary(self) -> dict: + """Generate data dictionary for mapping. + + Data dictionary with required data sources, type and name for mapping will be created from + Jinja2 template. + + Returns: + dict: Data dictionary + + """ + return json.loads( + jinja_env().get_template("data_dictionary_base.json.j2").render(mapping=self) + ) + + +class MappingSet: + """Set of mapping objects. + + Mapping objects will be stored in dictionary where mapping name is a key. + No two mappings with the same name can be stored in this collection. + """ + + def __init__(self) -> None: + """Initialize mappings collection. + + Create dictionary to store mappings. + """ + self.mappings = {} + + def __len__(self) -> int: # noqa: D401 + """Mapping set length. + + Returns: + int: Number of stored mapping objects. + + """ + return len(self.mappings) + + def __iter__(self) -> Iterator[Mapping]: + """Iterate through mapping stored in set. + + Returns: + Iterator[Mapping]: Stored mappings iterator. + + """ + return iter(list(self.mappings.values())) + + def __getitem__(self, index: int) -> Mapping: + """Get item stored on given index. + + Args: + index (int): Index number. + + Returns: + Mapping: Mapping stored on given index. + + """ + return list(self.mappings.values())[index] + + def add(self, mapping: Mapping) -> None: + """Add mapping to set. + + If there is already mapping object with the same name in collection + they will be merged. + + Args: + mapping (Mapping): Mapping to add to collection. + + """ + if mapping.name not in self.mappings: + self.mappings.update({mapping.name: mapping}) + else: + self.mappings[mapping.name].merge(mapping) + + def extend(self, iterable: Iterator[Mapping]) -> None: + """Extend set with an iterator of mappings. + + Args: + iterable (Iterator[Mapping]): Mappings iterator. + + """ + for mapping in iterable: + self.add(mapping) + + +class Workflow(CdsElement): + """Blueprint's workflow. + + Stores workflow steps, inputs, outputs. + Executes workflow using CDS HTTP API. + """ + + @dataclass + class WorkflowStep: + """Workflow step class. + + Stores step name, description, target and optional activities. + """ + + name: str + description: str + target: str + activities: List[Dict[str, str]] = field(default_factory=list) + + @dataclass + class WorkflowInput: + """Workflow input class. + + Stores input name, information if it's required, type, and optional description. + """ + + name: str + required: bool + type: str + description: str = "" + + @dataclass + class WorkflowOutput: + """Workflow output class. + + Stores output name, type na value. + """ + + name: str + type: str + value: Dict[str, Any] + + def __init__(self, + cba_workflow_name: str, + cba_workflow_data: dict, + blueprint: "Blueprint") -> None: + """Workflow initialization. + + Args: + cba_workflow_name (str): Workflow name. + cba_workflow_data (dict): Workflow data. + blueprint (Blueprint): Blueprint object which contains workflow. + + """ + super().__init__() + self.name: str = cba_workflow_name + self.workflow_data: dict = cba_workflow_data + self.blueprint: "Blueprint" = blueprint + self._steps: List[self.WorkflowStep] = None + self._inputs: List[self.WorkflowInput] = None + self._outputs: List[self.WorkflowOutput] = None + + def __repr__(self) -> str: + """Representation of object. + + Returns: + str: Object's string representation + + """ + return (f"Workflow(name='{self.name}', " + f"blueprint_name='{self.blueprint.metadata.template_name})'") + + @property + def steps(self) -> List["Workflow.WorkflowStep"]: + """Workflow's steps property. + + Returns: + List[Workflow.WorkflowStep]: List of workflow's steps. + + """ + if self._steps is None: + self._steps = [] + for step_name, step_data in self.workflow_data.get("steps", {}).items(): + self._steps.append( + self.WorkflowStep( + name=step_name, + description=step_data.get("description"), + target=step_data.get("target"), + activities=step_data.get("activities", []), + ) + ) + return self._steps + + @property + def inputs(self) -> List["Workflow.WorkflowInput"]: + """Workflow's inputs property. + + Returns: + List[Workflow.WorkflowInput]: List of workflows's inputs. + + """ + if self._inputs is None: + self._inputs = [] + for input_name, input_data in self.workflow_data.get("inputs", {}).items(): + self._inputs.append( + self.WorkflowInput( + name=input_name, + required=input_data.get("required"), + type=input_data.get("type"), + description=input_data.get("description"), + ) + ) + return self._inputs + + @property + def outputs(self) -> List["Workflow.WorkflowOutput"]: + """Workflow's outputs property. + + Returns: + List[Workflow.WorkflowOutput]: List of workflows's outputs. + + """ + if self._outputs is None: + self._outputs = [] + for output_name, output_data in self.workflow_data.get("outputs", {}).items(): + self._outputs.append( + self.WorkflowOutput( + name=output_name, + type=output_data.get("type"), + value=output_data.get("value"), + ) + ) + return self._outputs + + @property + def url(self) -> str: + """Workflow execution url. + + Returns: + str: Url to call warkflow execution. + + """ + return f"{self._url}/api/v1/execution-service/process" + + def execute(self, inputs: dict) -> dict: + """Execute workflow. + + Call CDS HTTP API to execute workflow. + + Args: + inputs (dict): Inputs dictionary. + + Returns: + dict: Response's payload. + + """ + # There should be some flague to check if CDS UI API is used or blueprintprocessor. + # For CDS UI API there is no endporint to execute workflow, so it has to be turned off. + execution_service_input: dict = { + "commonHeader": { + "originatorId": "onapsdk", + "requestId": str(uuid4()), + "subRequestId": str(uuid4()), + "timestamp": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + }, + "actionIdentifiers": { + "blueprintName": self.blueprint.metadata.template_name, + "blueprintVersion": self.blueprint.metadata.template_version, + "actionName": self.name, + "mode": "SYNC", # Has to be SYNC for REST call + }, + "payload": {f"{self.name}-request": inputs}, + } + response: "requests.Response" = self.send_message_json( + "POST", + f"Execute {self.blueprint.metadata.template_name} blueprint {self.name} workflow", + self.url, + auth=self.auth, + data=json.dumps(execution_service_input) + ) + return response["payload"] + + +class ResolvedTemplate(CdsElement): + """Resolved template class. + + Store and retrieve rendered template results. + """ + + def __init__(self, blueprint: "Blueprint", # pylint: disable=too-many-arguments + artifact_name: Optional[str] = None, + resolution_key: Optional[str] = None, + resource_id: Optional[str] = None, + resource_type: Optional[str] = None, + occurrence: Optional[str] = None, + response_format: str = "application/json") -> None: + """Init resolved template class instance. + + Args: + blueprint (Blueprint): Blueprint object. + artifact_name (Optional[str], optional): Artifact name for which to retrieve + a resolved resource. Defaults to None. + resolution_key (Optional[str], optional): Resolution Key associated with + the resolution. Defaults to None. + resource_id (Optional[str], optional): Resource Id associated with + the resolution. Defaults to None. + resource_type (Optional[str], optional): Resource Type associated + with the resolution. Defaults to None. + occurrence (Optional[str], optional): Occurrence of the template resolution (1-n). + Defaults to None. + response_format (str): Expected format of the template being retrieved. + + """ + super().__init__() + self.blueprint: "Blueprint" = blueprint + self.artifact_name: Optional[str] = artifact_name + self.resolution_key: Optional[str] = resolution_key + self.resource_id: Optional[str] = resource_id + self.resource_type: Optional[str] = resource_type + self.occurrence: Optional[str] = occurrence + self.response_format: str = response_format + + @property + def url(self) -> str: + """Url property. + + Returns: + str: Url + + """ + return f"{self._url}/api/v1/template" + + @property + def resolved_template_url(self) -> str: + """Url to retrieve resolved template. + + Filter None parameters. + + Returns: + str: Retrieve resolved template url + + """ + params_dict: Dict[str, str] = urlencode(dict(filter(lambda item: item[1] is not None, { + "bpName": self.blueprint.metadata.template_name, + "bpVersion": self.blueprint.metadata.template_version, + "artifactName": self.artifact_name, + "resolutionKey": self.resolution_key, + "resourceType": self.resource_type, + "resourceId": self.resource_id, + "occurrence": self.occurrence, + "format": self.response_format + }.items()))) + return f"{self.url}?{params_dict}" + + def get_resolved_template(self) -> Dict[str, str]: + """Get resolved template. + + Returns: + Dict[str, str]: Resolved template + + """ + return self.send_message_json( + "GET", + f"Get resolved template {self.artifact_name} for " + f"{self.blueprint.metadata.template_name} version " + f"{self.blueprint.metadata.template_version}", + self.resolved_template_url, + auth=self.auth + ) + + def store_resolved_template(self, resolved_template: str) -> None: + """Store resolved template. + + Args: + resolved_template (str): Template to store + + Raises: + ParameterError: To store template it's needed to pass artifact name and: + - resolution key, or + - resource type and resource id. + If not all needed parameters are given that exception will be raised. + + """ + if self.artifact_name and self.resolution_key: + return self.store_resolved_template_with_resolution_key(resolved_template) + if self.artifact_name and self.resource_type and self.resource_id: + return self.store_resolved_template_with_resource_type_and_id(resolved_template) + raise ParameterError("To store template artifact name with resolution key or both " + "resource type and id is needed") + + def store_resolved_template_with_resolution_key(self, resolved_template: str) -> None: + """Store template using resolution key. + + Args: + resolved_template (str): Template to store + + """ + return self.send_message( + "POST", + f"Store resolved template {self.artifact_name} for " + f"{self.blueprint.metadata.template_name} version " + f"{self.blueprint.metadata.template_version}", + f"{self.url}/{self.blueprint.metadata.template_name}/" + f"{self.blueprint.metadata.template_version}/{self.artifact_name}/" + f"{self.resolution_key}", + auth=self.auth, + data=resolved_template + ) + + def store_resolved_template_with_resource_type_and_id(self, resolved_template: str) -> None: + """Store template using resource type and resource ID. + + Args: + resolved_template (str): Template to store + + """ + return self.send_message( + "POST", + f"Store resolved template {self.artifact_name} for " + f"{self.blueprint.metadata.template_name} version " + f"{self.blueprint.metadata.template_version}", + f"{self.url}/{self.blueprint.metadata.template_name}/" + f"{self.blueprint.metadata.template_version}/{self.artifact_name}/" + f"{self.resource_type}/{self.resource_id}", + auth=self.auth, + data=resolved_template + ) + +class Blueprint(CdsElement): + """CDS blueprint representation.""" + + TEMPLATES_RE = r"Templates\/.*json$" + TOSCA_META = "TOSCA-Metadata/TOSCA.meta" + + def __init__(self, cba_file_bytes: bytes) -> None: + """Blueprint initialization. + + Save blueprint zip file bytes. + You can create that object using opened file or bytes: + blueprint = Blueprint(open("path/to/CBA.zip", "rb")) + or + with open("path/to/CBA.zip", "rb") as cba: + blueprint = Blueprint(cba.read()) + It is even better to use second example due to CBA file will be correctly closed for sure. + + Args: + cba_file_bytes (bytes): CBA ZIP file bytes + + """ + super().__init__() + self.cba_file_bytes: bytes = cba_file_bytes + self._cba_metadata: CbaMetadata = None + self._cba_mappings: MappingSet = None + self._cba_workflows: List[Workflow] = None + + @property + def url(self) -> str: + """URL address to use for CDS API call. + + Returns: + str: URL to CDS blueprintprocessor. + + """ + return f"{self._url}/api/v1/blueprint-model" + + @property + def metadata(self) -> CbaMetadata: + """Blueprint metadata. + + Data from TOSCA.meta file. + + Returns: + CbaMetadata: Blueprint metadata object. + + """ + if not self._cba_metadata: + with ZipFile(BytesIO(self.cba_file_bytes)) as cba_zip_file: + self._cba_metadata = self.get_cba_metadata(cba_zip_file.read(self.TOSCA_META)) + return self._cba_metadata + + @property + def mappings(self) -> MappingSet: + """Blueprint mappings collection. + + Returns: + MappingSet: Mappings collection. + + """ + if not self._cba_mappings: + with ZipFile(BytesIO(self.cba_file_bytes)) as cba_zip_file: + self._cba_mappings = self.get_mappings(cba_zip_file) + return self._cba_mappings + + @property + def workflows(self) -> List["Workflow"]: + """Blueprint's workflows property. + + Returns: + List[Workflow]: Blueprint's workflow list. + + """ + if not self._cba_workflows: + with ZipFile(BytesIO(self.cba_file_bytes)) as cba_zip_file: + self._cba_workflows = list( + self.get_workflows(cba_zip_file.read(self.metadata.entry_definitions)) + ) + return self._cba_workflows + + @classmethod + def load_from_file(cls, cba_file_path: str) -> "Blueprint": + """Load blueprint from file. + + Raises: + FileError: File to load blueprint from doesn't exist. + + Returns: + Blueprint: Blueprint object + + """ + try: + with open(cba_file_path, "rb") as cba_file: + return Blueprint(cba_file.read()) + except FileNotFoundError as exc: + msg = "The requested file with a blueprint doesn't exist." + raise FileError(msg) from exc + + def enrich(self) -> "Blueprint": + """Call CDS API to get enriched blueprint file. + + Returns: + Blueprint: Enriched blueprint object + + """ + response: "requests.Response" = self.send_message( + "POST", + "Enrich CDS blueprint", + f"{self.url}/enrich", + files={"file": self.cba_file_bytes}, + headers={}, # Leave headers empty to fill it correctly by `requests` library + auth=self.auth + ) + return Blueprint(response.content) + + def publish(self) -> None: + """Publish blueprint.""" + self.send_message( + "POST", + "Publish CDS blueprint", + f"{self.url}/publish", + files={"file": self.cba_file_bytes}, + headers={}, # Leave headers empty to fill it correctly by `requests` library + auth=self.auth + ) + + def deploy(self) -> None: + """Deploy blueprint.""" + self.send_message( + "POST", + "Deploy CDS blueprint", + f"{self.url}", + files={"file": self.cba_file_bytes}, + headers={}, # Leave headers empty to fill it correctly by `requests` library + auth=self.auth + ) + + def save(self, dest_file_path: str) -> None: + """Save blueprint to file. + + Args: + dest_file_path (str): Path of file where blueprint is going to be saved + + """ + with open(dest_file_path, "wb") as cba_file: + cba_file.write(self.cba_file_bytes) + + def get_data_dictionaries(self) -> DataDictionarySet: + """Get the generated data dictionaries required by blueprint. + + If mapping reqires other source than input it should be updated before upload to CDS. + + Returns: + Generator[DataDictionary, None, None]: DataDictionary objects. + + """ + dd_set: DataDictionarySet = DataDictionarySet() + for mapping in self.mappings: + dd_set.add(DataDictionary(mapping.generate_data_dictionary())) + return dd_set + + @staticmethod + def get_cba_metadata(cba_tosca_meta_bytes: bytes) -> CbaMetadata: + """Parse CBA TOSCA.meta file and get values from it. + + Args: + cba_tosca_meta_bytes (bytes): TOSCA.meta file bytes. + + Raises: + ValidationError: TOSCA Meta file has invalid format. + + Returns: + CbaMetadata: Dataclass with CBA metadata + + """ + meta_dict: dict = yaml.safe_load(cba_tosca_meta_bytes) + if not isinstance(meta_dict, dict): + raise ValidationError("Invalid TOSCA Meta file") + return CbaMetadata( + tosca_meta_file_version=meta_dict.get("TOSCA-Meta-File-Version"), + csar_version=meta_dict.get("CSAR-Version"), + created_by=meta_dict.get("Created-By"), + entry_definitions=meta_dict.get("Entry-Definitions"), + template_name=meta_dict.get("Template-Name"), + template_version=meta_dict.get("Template-Version"), + template_tags=meta_dict.get("Template-Tags"), + ) + + @staticmethod + def get_mappings_from_mapping_file(cba_mapping_file_bytes: bytes + ) -> Generator[Mapping, None, None]: + """Read mapping file and create Mappping object for it. + + Args: + cba_mapping_file_bytes (bytes): CBA mapping file bytes. + + Yields: + Generator[Mapping, None, None]: Mapping object. + + """ + mapping_file_json = json.loads(cba_mapping_file_bytes) + for mapping in mapping_file_json: + yield Mapping( + name=mapping["name"], + mapping_type=mapping["property"]["type"], + dictionary_name=mapping["dictionary-name"], + dictionary_sources=[mapping["dictionary-source"]], + ) + + def get_mappings(self, cba_zip_file: ZipFile) -> MappingSet: + """Read mappings from CBA file. + + Search mappings in CBA file and create Mapping object for each of them. + + Args: + cba_zip_file (ZipFile): CBA file to get mappings from. + + Returns: + MappingSet: Mappings set object. + + """ + mapping_set = MappingSet() + for info in cba_zip_file.infolist(): + if re.match(self.TEMPLATES_RE, info.filename): + mapping_set.extend( + self.get_mappings_from_mapping_file(cba_zip_file.read(info.filename)) + ) + return mapping_set + + def get_workflows(self, + cba_entry_definitions_file_bytes: bytes) -> Generator[Workflow, None, None]: + """Get worfklows from entry_definitions file. + + Parse entry_definitions file and create Workflow objects for workflows stored in. + + Args: + cba_entry_definitions_file_bytes (bytes): entry_definition file. + + Yields: + Generator[Workflow, None, None]: Workflow object. + + """ + entry_definitions_json: dict = json.loads(cba_entry_definitions_file_bytes) + workflows: dict = entry_definitions_json.get("topology_template", {}).get("workflows", {}) + for workflow_name, workflow_data in workflows.items(): + yield Workflow(workflow_name, workflow_data, self) + + def get_workflow_by_name(self, workflow_name: str) -> Workflow: + """Get workflow by name. + + If there is no workflow with given name `ParameterError` is going to be raised. + + Args: + workflow_name (str): Name of the workflow + + Returns: + Workflow: Workflow with given name + + """ + try: + return next(filter(lambda workflow: workflow.name == workflow_name, self.workflows)) + except StopIteration: + raise ParameterError("Workflow with given name does not exist") + + def get_resolved_template(self, # pylint: disable=too-many-arguments + artifact_name: str, + resolution_key: Optional[str] = None, + resource_type: Optional[str] = None, + resource_id: Optional[str] = None, + occurrence: Optional[str] = None) -> Dict[str, str]: + """Get resolved template for Blueprint. + + Args: + artifact_name (str): Resolved template's artifact name + resolution_key (Optional[str], optional): Resolved template's resolution key. + Defaults to None. + resource_type (Optional[str], optional): Resolved template's resource type. + Defaults to None. + resource_id (Optional[str], optional): Resolved template's resource ID. + Defaults to None. + occurrence: (Optional[str], optional): Resolved template's occurrence value. + Defaults to None. + + Returns: + Dict[str, str]: Resolved template + + """ + return ResolvedTemplate(blueprint=self, + artifact_name=artifact_name, + resolution_key=resolution_key, + resource_type=resource_type, + resource_id=resource_id, + occurrence=occurrence).get_resolved_template() + + def store_resolved_template(self, # pylint: disable=too-many-arguments + artifact_name: str, + data: str, + resolution_key: Optional[str] = None, + resource_type: Optional[str] = None, + resource_id: Optional[str] = None) -> None: + """Store resolved template for Blueprint. + + Args: + artifact_name (str): Resolved template's artifact name + data (str): Resolved template + resolution_key (Optional[str], optional): Resolved template's resolution key. + Defaults to None. + resource_type (Optional[str], optional): Resolved template's resource type. + Defaults to None. + resource_id (Optional[str], optional): Resolved template's resource ID. + Defaults to None. + """ + ResolvedTemplate(blueprint=self, + artifact_name=artifact_name, + resolution_key=resolution_key, + resource_type=resource_type, + resource_id=resource_id).store_resolved_template(data) diff --git a/src/onapsdk/cds/blueprint_model.py b/src/onapsdk/cds/blueprint_model.py new file mode 100644 index 0000000..7976001 --- /dev/null +++ b/src/onapsdk/cds/blueprint_model.py @@ -0,0 +1,222 @@ +"""CDS Blueprint Models module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Iterator +from onapsdk.exceptions import ResourceNotFound # for custom exceptions + +from .blueprint import Blueprint +from .cds_element import CdsElement + + +class BlueprintModel(CdsElement): # pylint: disable=too-many-instance-attributes + """Blueprint Model class. + + Represents blueprint models in CDS + """ + + def __init__(self, # pylint: disable=too-many-arguments + blueprint_model_id: str, + artifact_uuid: str = None, + artifact_type: str = None, + artifact_version: str = None, + artifact_description: str = None, + internal_version: str = None, + created_date: str = None, + artifact_name: str = None, + published: str = 'N', + updated_by: str = None, + tags: str = None): + """Blueprint Model initialization. + + Args: + blueprint_model_id (str): Blueprint model identifier + artifact_uuid (str): Blueprint model uuid + artifact_type (str): Blueprint artifact type + artifact_version (str): Blueprint model version + artifact_description (str): Blueprint model description + internal_version (str): Blueprint model internal version + created_date (str): Blueprint model created date + artifact_name (str): Blueprint model name + published (str): Blueprint model publish status - 'N' or 'Y' + updated_by (str): Blueprint model author + tags (str): Blueprint model tags + + """ + super().__init__() + self.blueprint_model_id = blueprint_model_id + self.artifact_uuid = artifact_uuid + self.artifact_type = artifact_type + self.artifact_version = artifact_version + self.artifact_description = artifact_description + self.internal_version = internal_version + self.created_date = created_date + self.artifact_name = artifact_name + self.published = published + self.updated_by = updated_by + self.tags = tags + + def __repr__(self) -> str: + """Representation of object. + + Returns: + str: Object's string representation + + """ + return (f"BlueprintModel(artifact_name='{self.artifact_name}', " + f"blueprint_model_id='{self.blueprint_model_id}')") + + @classmethod + def get_by_id(cls, blueprint_model_id: str) -> "BlueprintModel": + """Retrieve blueprint model with provided ID. + + Args: blueprint_model_id (str): + + Returns: + BlueprintModel: Blueprint model object + + Raises: + ResourceNotFound: Blueprint model with provided ID doesn't exist + + """ + try: + blueprint_model = cls.send_message_json( + "GET", + "Retrieve blueprint", + f"{cls._url}/api/v1/blueprint-model/{blueprint_model_id}", + auth=cls.auth) + + return cls( + blueprint_model_id=blueprint_model["blueprintModel"]['id'], + artifact_uuid=blueprint_model["blueprintModel"]['artifactUUId'], + artifact_type=blueprint_model["blueprintModel"]['artifactType'], + artifact_version=blueprint_model["blueprintModel"]['artifactVersion'], + internal_version=blueprint_model["blueprintModel"]['internalVersion'], + created_date=blueprint_model["blueprintModel"]['createdDate'], + artifact_name=blueprint_model["blueprintModel"]['artifactName'], + published=blueprint_model["blueprintModel"]['published'], + updated_by=blueprint_model["blueprintModel"]['updatedBy'], + tags=blueprint_model["blueprintModel"]['tags'] + ) + + except ResourceNotFound: + raise ResourceNotFound(f"BlueprintModel blueprint_model_id='{blueprint_model_id}" + f" not found") + + @classmethod + def get_by_name_and_version(cls, blueprint_name: str, + blueprint_version: str) -> "BlueprintModel": + """Retrieve blueprint model with provided name and version. + + Args: + blueprint_name (str): Blueprint model name + blueprint_version (str): Blueprint model version + + Returns: + BlueprintModel: Blueprint model object + + Raises: + ResourceNotFound: Blueprint model with provided name and version doesn't exist + + """ + try: + blueprint_model = cls.send_message_json( + "GET", + "Retrieve blueprint", + f"{cls._url}/api/v1/blueprint-model/by-name/{blueprint_name}" + f"/version/{blueprint_version}", + auth=cls.auth) + + return cls( + blueprint_model_id=blueprint_model["blueprintModel"]['id'], + artifact_uuid=blueprint_model["blueprintModel"]['artifactUUId'], + artifact_type=blueprint_model["blueprintModel"]['artifactType'], + artifact_version=blueprint_model["blueprintModel"]['artifactVersion'], + internal_version=blueprint_model["blueprintModel"]['internalVersion'], + created_date=blueprint_model["blueprintModel"]['createdDate'], + artifact_name=blueprint_model["blueprintModel"]['artifactName'], + published=blueprint_model["blueprintModel"]['published'], + updated_by=blueprint_model["blueprintModel"]['updatedBy'], + tags=blueprint_model["blueprintModel"]['tags'] + ) + + except ResourceNotFound: + raise ResourceNotFound(f"BlueprintModel blueprint_name='{blueprint_name}" + f" and blueprint_version='{blueprint_version}' not found") + + @classmethod + def get_all(cls) -> Iterator["BlueprintModel"]: + """Get all blueprint models. + + Yields: + BlueprintModel: BlueprintModel object. + + """ + for blueprint_model in cls.send_message_json( + "GET", + "Retrieve all blueprints", + f"{cls._url}/api/v1/blueprint-model", + auth=cls.auth): + + yield cls( + blueprint_model_id=blueprint_model["blueprintModel"]['id'], + artifact_uuid=blueprint_model["blueprintModel"]['artifactUUId'], + artifact_type=blueprint_model["blueprintModel"]['artifactType'], + artifact_version=blueprint_model["blueprintModel"]['artifactVersion'], + internal_version=blueprint_model["blueprintModel"]['internalVersion'], + created_date=blueprint_model["blueprintModel"]['createdDate'], + artifact_name=blueprint_model["blueprintModel"]['artifactName'], + published=blueprint_model["blueprintModel"]['published'], + updated_by=blueprint_model["blueprintModel"]['updatedBy'], + tags=blueprint_model["blueprintModel"]['tags'] + ) + + def get_blueprint(self) -> Blueprint: + """Get Blueprint object for selected blueprint model. + + Returns: + Blueprint: Blueprint object + + """ + cba_package = self.send_message( + "GET", + "Retrieve selected blueprint object", + f"{self._url}/api/v1/blueprint-model/download/{self.blueprint_model_id}", + auth=self.auth) + + return Blueprint(cba_file_bytes=cba_package.content) + + def save(self, dst_file_path: str): + """Save blueprint model to file. + + Args: + dst_file_path (str): Path of file where blueprint is going to be saved + """ + cba_package = self.send_message( + "GET", + "Retrieve and save selected blueprint", + f"{self._url}/api/v1/blueprint-model/download/{self.blueprint_model_id}", + auth=self.auth) + + with open(dst_file_path, "wb") as content: + for chunk in cba_package.iter_content(chunk_size=128): + content.write(chunk) + + def delete(self): + """Delete blueprint model.""" + self.send_message( + "DELETE", + "Delete blueprint", + f"{self._url}/api/v1/blueprint-model/{self.blueprint_model_id}", + auth=self.auth) diff --git a/src/onapsdk/cds/blueprint_processor.py b/src/onapsdk/cds/blueprint_processor.py new file mode 100644 index 0000000..3763e1b --- /dev/null +++ b/src/onapsdk/cds/blueprint_processor.py @@ -0,0 +1,53 @@ +"""CDS Blueprintprocessor module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.utils.jinja import jinja_env + +from .cds_element import CdsElement + + +class Blueprintprocessor(CdsElement): + """Blueprintprocessor class.""" + + @classmethod + def bootstrap(cls, + load_model_type: bool = True, + load_resource_dictionary: bool = True, + load_cba: bool = True) -> None: + """Bootstrap CDS blueprintprocessor. + + That action in needed to work with CDS. Can be done only once. + + Args: + load_model_type (bool, optional): Datermines if model types should be loaded + on bootstrap. Defaults to True. + load_resource_dictionary (bool, optional): Determines if resource dictionaries + should be loaded on bootstrap. Defaults to True. + load_cba (bool, optional): Determines if cba files should be loaded on + bootstrap. Defaults to True. + + """ + cls.send_message( + "POST", + "Bootstrap CDS blueprintprocessor", + f"{cls._url}/api/v1/blueprint-model/bootstrap", + data=jinja_env().get_template("cds_blueprintprocessor_bootstrap.json.j2").render( + load_model_type=load_model_type, + load_resource_dictionary=load_resource_dictionary, + load_cba=load_cba + ), + auth=cls.auth, + headers=cls.headers + ) diff --git a/src/onapsdk/cds/cds_element.py b/src/onapsdk/cds/cds_element.py new file mode 100644 index 0000000..7a4b9c0 --- /dev/null +++ b/src/onapsdk/cds/cds_element.py @@ -0,0 +1,47 @@ +"""Base CDS module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 abc import ABC + +from onapsdk.configuration import settings +from onapsdk.onap_service import OnapService +from onapsdk.utils.gui import GuiItem, GuiList + +class CdsElement(OnapService, ABC): + """Base CDS class. + + Stores url to CDS API (edit if you want to use other) and authentication tuple + (username, password). + """ + + # These should be stored in configuration. There is even a task in Orange repo. + _url: str = settings.CDS_URL + auth: tuple = settings.CDS_AUTH + + @classmethod + def get_guis(cls) -> GuiItem: + """Retrieve the status of the CDS GUIs. + + Only one GUI is referenced for CDS: CDS UI + + Return the list of GUIs + """ + gui_url = settings.CDS_GUI_SERVICE + cds_gui_response = cls.send_message( + "GET", "Get CDS GUI Status", gui_url) + guilist = GuiList([]) + guilist.add(GuiItem( + gui_url, + cds_gui_response.status_code)) + return guilist diff --git a/src/onapsdk/cds/data_dictionary.py b/src/onapsdk/cds/data_dictionary.py new file mode 100644 index 0000000..b4d8d0e --- /dev/null +++ b/src/onapsdk/cds/data_dictionary.py @@ -0,0 +1,266 @@ +"""CDS data dictionary module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 +from logging import getLogger, Logger + +from onapsdk.exceptions import FileError, ValidationError + +from .cds_element import CdsElement + + +class DataDictionary(CdsElement): + """Data dictionary class.""" + + logger: Logger = getLogger(__name__) + + def __init__(self, data_dictionary_json: dict, fix_schema: bool = True) -> None: + """Initialize data dictionary. + + Args: + data_dictionary_json (dict): data dictionary json + fix_schema (bool, optional): determines if data dictionary should be fixed if + the invalid schema is detected. Fixing can raise ValidationError if + dictionary is invalid. Defaults to True. + + """ + super().__init__() + self.data_dictionary_json: dict = data_dictionary_json + if not self.has_valid_schema() and fix_schema: + self.fix_schema() + + def __hash__(self) -> int: # noqa: D401 + """Data dictionary object hash. + + Based on data dictionary name + + Returns: + int: Data dictionary hash + + """ + return hash(self.name) + + def __eq__(self, dd: "DataDictionary") -> bool: + """Compare two data dictionaries. + + Data dictionaries are equal if have the same name. + + Args: + dd (DataDictionary): Object to compare with. + + Returns: + bool: True if objects have the same name, False otherwise. + + """ + return self.name == dd.name + + def __repr__(self) -> str: + """Representation of object. + + Returns: + str: Object's string representation + + """ + return f'DataDictionary[name: "{self.name}"]' + + @property + def name(self) -> str: # noqa: D401 + """Data dictionary name. + + Returns: + str: Data dictionary name + + """ + return self.data_dictionary_json["name"] + + @property + def url(self) -> str: + """URL to call. + + Returns: + str: CDS dictionary API url + + """ + return f"{self._url}/api/v1/dictionary" + + @classmethod + def get_by_name(cls, name: str) -> "DataDictionary": + """Get data dictionary by the provided name. + + Returns: + DataDictionary: Data dicionary object with the given name + + """ + cls.logger.debug("Get CDS data dictionary with %s name", name) + return DataDictionary( + data_dictionary_json=cls.send_message_json( + "GET", + f"Get {name} CDS data dictionary", + f"{cls._url}/api/v1/dictionary/{name}", + auth=cls.auth), + fix_schema=False + ) + + def upload(self) -> None: + """Upload data dictionary using CDS API.""" + self.logger.debug("Upload %s data dictionary", self.name) + self.send_message( + "POST", + "Publish CDS data dictionary", + f"{self.url}", + auth=self.auth, + data=json.dumps(self.data_dictionary_json) + ) + + def has_valid_schema(self) -> bool: + """Check data dictionary json schema. + + Check data dictionary JSON and return bool if schema is valid or not. + Valid schema means that data dictionary has given keys: + - "name" + - "tags" + - "data_type" + - "description" + - "entry_schema" + - "updatedBy" + - "definition" + "definition" key value should contains the "raw" data dictionary. + + Returns: + bool: True if data dictionary has valid schema, False otherwise + + """ + return all(key_to_check in self.data_dictionary_json for + key_to_check in ["name", "tags", "data_type", "description", "entry_schema", + "updatedBy", "definition"]) + + def fix_schema(self) -> None: + """Fix data dictionary schema. + + "Raw" data dictionary can be passed during initialization, but + this kind of data dictionary can't be uploaded to blueprintprocessor. + That method tries to fix it. It can be done only if "raw" data dictionary + has a given schema: + { + "name": "string", + "tags": "string", + "updated-by": "string", + "property": { + "description": "string", + "type": "string" + } + } + + Raises: + ValidationError: Data dictionary doesn't have all required keys + + """ + try: + self.data_dictionary_json = { + "name": self.data_dictionary_json["name"], + "tags": self.data_dictionary_json["tags"], + "data_type": self.data_dictionary_json["property"]["type"], + "description": self.data_dictionary_json["property"]["description"], + "entry_schema": self.data_dictionary_json["property"]["type"], + "updatedBy": self.data_dictionary_json["updated-by"], + "definition": self.data_dictionary_json + } + except KeyError: + raise ValidationError("Raw data dictionary JSON has invalid schema") + + +class DataDictionarySet: + """Data dictionary set. + + Stores data dictionary and upload to server. + """ + + logger: Logger = getLogger(__name__) + + def __init__(self) -> None: + """Initialize data dictionary set.""" + self.dd_set = set() + + @property + def length(self) -> int: + """Get the length of data dicitonary set. + + Returns: + int: Number of data dictionaries in set + + """ + return len(self.dd_set) + + def add(self, data_dictionary: DataDictionary) -> None: + """Add data dictionary object to set. + + Based on name it won't add duplications. + + Args: + data_dictionary (DataDictionary): object to add to set. + + """ + self.dd_set.add(data_dictionary) + + def upload(self) -> None: + """Upload all data dictionaries using CDS API. + + Raises: + RuntimeError: Raises if any data dictionary won't be uploaded to server. + Data dictionaries uploaded before the one which raises excepion won't be + deleted from server. + + """ + self.logger.debug("Upload data dictionary") + for data_dictionary in self.dd_set: # type DataDictionary + data_dictionary.upload() # raise a relevant exception + + def save_to_file(self, dd_file_path: str) -> None: + """Save data dictionaries to file. + + Args: + dd_file_path (str): Data dictinary file path. + """ + with open(dd_file_path, "w") as dd_file: + dd_file.write(json.dumps([dd.data_dictionary_json for dd in self.dd_set], indent=4)) + + @classmethod + def load_from_file(cls, dd_file_path: str, fix_schema: bool = True) -> "DataDictionarySet": + """Create data dictionary set from file. + + File has to have valid JSON with data dictionaries list. + + Args: + dd_file_path (str): Data dictionaries file path. + fix_schema (bool): Determines if schema should be fixed or not. + + Raises: + FileError: File to load data dictionaries from doesn't exist. + + Returns: + DataDictionarySet: Data dictionary set with data dictionaries from given file. + + """ + dd_set: DataDictionarySet = DataDictionarySet() + + try: + with open(dd_file_path, "r") as dd_file: # type file + dd_json: dict = json.loads(dd_file.read()) + for data_dictionary in dd_json: # type DataDictionary + dd_set.add(DataDictionary(data_dictionary, fix_schema=fix_schema)) + return dd_set + except FileNotFoundError as exc: + msg = "File with a set of data dictionaries does not exist." + raise FileError(msg) from exc diff --git a/src/onapsdk/cds/templates/cds_blueprintprocessor_bootstrap.json.j2 b/src/onapsdk/cds/templates/cds_blueprintprocessor_bootstrap.json.j2 new file mode 100644 index 0000000..41f43cd --- /dev/null +++ b/src/onapsdk/cds/templates/cds_blueprintprocessor_bootstrap.json.j2 @@ -0,0 +1,5 @@ +{ + "loadModelType" : {{ load_model_type | tojson }}, + "loadResourceDictionary" : {{ load_resource_dictionary | tojson }}, + "loadCBA" : {{ load_cba | tojson }} +} \ No newline at end of file diff --git a/src/onapsdk/cds/templates/data_dictionary_base.json.j2 b/src/onapsdk/cds/templates/data_dictionary_base.json.j2 new file mode 100644 index 0000000..0ea6752 --- /dev/null +++ b/src/onapsdk/cds/templates/data_dictionary_base.json.j2 @@ -0,0 +1,52 @@ +{ + "name": "{{ mapping.dictionary_name }}", + "tags": "{{ mapping.dictionary_name }}", + "data_type": "{{ mapping.mapping_type }}", + "description": "{{ mapping.dictionary_name }}", + "entry_schema": "{{ mapping.mapping_type }}", + "updatedBy": "Python ONAP SDK", + "definition": { + "tags": "{{ mapping.dictionary_name }}", + "name": "{{ mapping.dictionary_name }}", + "property": { + "description": "{{ mapping.dictionary_name }}", + "type": "{{ mapping.mapping_type }}" + }, + "updated-by": "Python ONAP SDK", + "sources": { + {% for source in mapping.dictionary_sources %} + {% if source == "input" %} + "input": { + "type": "source-input" + }, + {% elif source == "sdnc" %} + "sdnc": {% include "data_dictionary_source_rest.json.j2" %}, + {% elif source == "processor-db" %} + "processor-db": { + "type": "source-db", + "properties": { + "type": "<< FILL >>", + "query": "<< FILL >>", + "input-key-mapping": {}, + "output-key-mapping": {}, + "key-dependencies": [] + } + }, + {% elif source == "aai-data" %} + "aai-data": {% include "data_dictionary_source_rest.json.j2" %}, + {% elif source == "default" %} + {# Do not do anything, default will be always added #} + {% else %} + "{{ source }}": { + "type": "unknown", + "properties": {} + }, + {% endif %} + {% endfor %} + "default": { + "type": "source-default", + "properties": {} + } + } + } +} \ No newline at end of file diff --git a/src/onapsdk/cds/templates/data_dictionary_source_rest.json.j2 b/src/onapsdk/cds/templates/data_dictionary_source_rest.json.j2 new file mode 100644 index 0000000..088a044 --- /dev/null +++ b/src/onapsdk/cds/templates/data_dictionary_source_rest.json.j2 @@ -0,0 +1,13 @@ +{ + "type": "source-rest", + "properties": { + "verb": "<< FILL >>", + "type": "<< FILL >>", + "url-path": "<< FILL >>", + "path": "<< FILL >>", + "payload": "<< FILL >>", + "input-key-mapping": {}, + "output-key-mapping": {}, + "key-dependencies": [] + } +} \ No newline at end of file diff --git a/src/onapsdk/clamp/__init__.py b/src/onapsdk/clamp/__init__.py new file mode 100644 index 0000000..621adc9 --- /dev/null +++ b/src/onapsdk/clamp/__init__.py @@ -0,0 +1,14 @@ +"""ONAP SDK CLAMP package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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. diff --git a/src/onapsdk/clamp/clamp_element.py b/src/onapsdk/clamp/clamp_element.py new file mode 100644 index 0000000..843db42 --- /dev/null +++ b/src/onapsdk/clamp/clamp_element.py @@ -0,0 +1,79 @@ +"""Clamp module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.configuration import settings +from onapsdk.onap_service import OnapService as Onap +from onapsdk.sdc.service import Service +from onapsdk.exceptions import ResourceNotFound +from onapsdk.utils.headers_creator import headers_clamp_creator + + +class Clamp(Onap): + """Mother Class of all CLAMP elements.""" + + #class variable + _base_url = settings.CLAMP_URL + name: str = "CLAMP" + headers = headers_clamp_creator(Onap.headers) + + @classmethod + def base_url(cls) -> str: + """Give back the base url of Clamp.""" + return f"{cls._base_url}/restservices/clds/v2" + + @classmethod + def check_loop_template(cls, service: Service) -> str: + """ + Return loop template name if exists. + + Args: + service (Service): the distributed sdc service with tca blueprint artifact + + Raises: + ResourceNotFound: Template not found. + + Returns: + if required template exists in CLAMP or not + + """ + url = f"{cls.base_url()}/templates/" + for template in cls.send_message_json('GET', + 'Get Loop Templates', + url): + if template["modelService"]["serviceDetails"]["name"] == service.name: + return template["name"] + raise ResourceNotFound("Template not found.") + + @classmethod + def check_policies(cls, policy_name: str, req_policies: int = 30) -> bool: + """ + Ensure that a policy is stored in CLAMP. + + Args: + policy_name (str): policy acronym + req_policies (int): number of required policies in CLAMP + + Returns: + if required policy exists in CLAMP or not + + """ + url = f"{cls.base_url()}/policyToscaModels/" + policies = cls.send_message_json('GET', + 'Get stocked policies', + url) + exist_policy = False + for policy in policies: + if policy["policyAcronym"] == policy_name: + exist_policy = True + return (len(policies) >= req_policies) and exist_policy diff --git a/src/onapsdk/clamp/loop_instance.py b/src/onapsdk/clamp/loop_instance.py new file mode 100644 index 0000000..a72f9d1 --- /dev/null +++ b/src/onapsdk/clamp/loop_instance.py @@ -0,0 +1,349 @@ +"""Control Loop module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 +from pathlib import Path +from jsonschema import validate, ValidationError + +from onapsdk.clamp.clamp_element import Clamp +from onapsdk.utils.jinja import jinja_env +from onapsdk.exceptions import ParameterError + +CLAMP_UPDDATE_REFRESH_TIMER = 60 + +class LoopInstance(Clamp): + """Control Loop instantiation class.""" + + # class variable + _loop_schema = None + operational_policies = "" + + def __init__(self, template: str, name: str, details: dict) -> None: + """ + Initialize loop instance object. + + Args: + template (str): template from which we build the loop + name (str) : loop creation name + details (dict) : dictionnary containing all loop details + + """ + super().__init__() + self.template = template + self.name = "LOOP_" + name + self._details = details + + @property + def details(self) -> dict: + """Return and lazy load the details.""" + if not self._details: + self._update_loop_details() + return self._details + + @details.setter + def details(self, details: dict) -> None: + """Set value for details.""" + self._details = details + + def _update_loop_details(self) -> dict: + """ + Update loop details. + + Returns: + the dictionnary of loop details + + """ + url = f"{self.base_url()}/loop/{self.name}" + loop_details = self.send_message_json('GET', + 'Get loop details', + url) + return loop_details + + def refresh_status(self) -> None: + """Reshresh loop status.""" + url = f"{self.base_url()}/loop/getstatus/{self.name}" + loop_details = self.send_message_json('GET', + 'Get loop status', + url) + + self.details = loop_details + + @property + def loop_schema(self) -> dict: + """ + Return and lazy load the details schema. + + Returns: + schema to be respected to accede to loop details + + """ + if not self._loop_schema: + schema_file = Path.cwd() / 'src' / 'onapsdk' / 'clamp' / 'schema_details.json' + with open(schema_file, "rb") as plan: + self._loop_schema = json.load(plan) + return self._loop_schema + + def validate_details(self) -> bool: + """ + Validate Loop Instance details. + + Returns: + schema validation status (True, False) + + """ + try: + validate(self.details, self.loop_schema) + except ValidationError as error: + self._logger.error(error) + self._logger.error("---------") + self._logger.error(error.absolute_path) + self._logger.error("---------") + self._logger.error(error.absolute_schema_path) + return False + return True + + def create(self) -> None: + """Create instance and load loop details.""" + url = f"{self.base_url()}/loop/create/{self.name}?templateName={self.template}" + instance_details = self.send_message_json('POST', + 'Create Loop Instance', + url) + self.details = instance_details + + def add_operational_policy(self, policy_type: str, policy_version: str) -> None: + """ + Add operational policy to the loop instance. + + Args: + policy_type (str): the full policy model type + policy_version (str): policy version + + Raises: + ParameterError : Corrupt response or a key in a dictionary not found. + It will also be raised when the response contains more operational + policies than there are currently. + + """ + url = (f"{self.base_url()}/loop/addOperationaPolicy/{self.name}/" + f"policyModel/{policy_type}/{policy_version}") + add_response = self.send_message_json('PUT', + 'Create Operational Policy', + url) + + key = "operationalPolicies" + + try: + if self.details[key] is None: + self.details[key] = [] + + response_policies = add_response[key] + current_policies = self.details[key] + except KeyError as exc: + msg = 'Corrupt response, current loop details. Key not found.' + raise ParameterError(msg) from exc + + if len(response_policies) > len(current_policies): + self.details = add_response + else: + raise ParameterError("Couldn't add the operational policy.") + + def remove_operational_policy(self, policy_type: str, policy_version: str) -> None: + """ + Remove operational policy from the loop instance. + + Args: + policy_type (str): the full policy model type + policy_version (str): policy version + + """ + url = (f"{self.base_url()}/loop/removeOperationaPolicy/" + f"{self.name}/policyModel/{policy_type}/{policy_version}") + self.details = self.send_message_json('PUT', + 'Remove Operational Policy', + url) + + def update_microservice_policy(self) -> None: + """ + Update microservice policy configuration. + + Update microservice policy configuration using payload data. + + """ + url = f"{self.base_url()}/loop/updateMicroservicePolicy/{self.name}" + template = jinja_env().get_template("clamp_add_tca_config.json.j2") + microservice_name = self.details["globalPropertiesJson"]["dcaeDeployParameters"]\ + ["uniqueBlueprintParameters"]["policy_id"] + data = template.render(name=microservice_name, + LOOP_name=self.name) + + self.send_message('POST', + 'ADD TCA config', + url, + data=data) + + def extract_operational_policy_name(self, policy_type: str) -> str: + """ + Return operational policy name for a closed loop and a given policy. + + Args: + policy_type (str): the policy acronym. + + Raises: + ParameterError : Couldn't load the operational policy name. + + Returns: + Operational policy name in the loop details after adding a policy. + + """ + for policy in filter(lambda x: x["policyModel"]["policyAcronym"] == policy_type, + self.details["operationalPolicies"]): + return policy["name"] + + raise ParameterError("Couldn't load the operational policy name.") + + def add_drools_conf(self) -> dict: + """Add drools configuration.""" + self.validate_details() + vfmodule_dicts = self.details["modelService"]["resourceDetails"]["VFModule"] + entity_ids = {} + #Get the vf module details + for vfmodule in vfmodule_dicts.values(): + entity_ids["resourceID"] = vfmodule["vfModuleModelName"] + entity_ids["modelInvariantId"] = vfmodule["vfModuleModelInvariantUUID"] + entity_ids["modelVersionId"] = vfmodule["vfModuleModelUUID"] + entity_ids["modelName"] = vfmodule["vfModuleModelName"] + entity_ids["modelVersion"] = vfmodule["vfModuleModelVersion"] + entity_ids["modelCustomizationId"] = vfmodule["vfModuleModelCustomizationUUID"] + template = jinja_env().get_template("clamp_add_drools_policy.json.j2") + data = template.render(name=self.extract_operational_policy_name("Drools"), + resourceID=entity_ids["resourceID"], + modelInvariantId=entity_ids["modelInvariantId"], + modelVersionId=entity_ids["modelVersionId"], + modelName=entity_ids["modelName"], + modelVersion=entity_ids["modelVersion"], + modelCustomizationId=entity_ids["modelCustomizationId"], + LOOP_name=self.name) + return data + + def add_minmax_config(self) -> None: + """Add MinMax operational policy config.""" + #must configure start/end time and min/max instances in json file + template = jinja_env().get_template("clamp_MinMax_config.json.j2") + return template.render(name=self.extract_operational_policy_name("MinMax")) + + def add_frequency_limiter(self, limit: int = 1) -> None: + """Add frequency limiter config.""" + template = jinja_env().get_template("clamp_add_frequency.json.j2") + return template.render(name=self.extract_operational_policy_name("FrequencyLimiter"), + LOOP_name=self.name, + limit=limit) + + def add_op_policy_config(self, func, **kwargs) -> None: + """ + Add operational policy configuration. + + Add operation policy configuration using payload data. + + Args: + func (function): policy configuration function in (add_drools_conf, + add_minmax_config, + add_frequency_limiter) + + """ + data = func(**kwargs) + if not data: + raise ParameterError("Payload data from configuration is None.") + if self.operational_policies: + self.operational_policies = self.operational_policies[:-1] + "," + data = data[1:] + self.operational_policies += data + url = f"{self.base_url()}/loop/updateOperationalPolicies/{self.name}" + self.send_message('POST', + 'ADD operational policy config', + url, + data=self.operational_policies) + + self._logger.info(("Files for op policy config %s have been uploaded to loop's" + "Op policy"), self.name) + + def submit(self): + """Submit policies to policy engine.""" + state = self.details["components"]["POLICY"]["componentState"]["stateName"] + return state == "SENT_AND_DEPLOYED" + + def stop(self): + """Undeploy Policies from policy engine.""" + state = self.details["components"]["POLICY"]["componentState"]["stateName"] + return state == "SENT" + + def restart(self): + """Redeploy policies to policy engine.""" + state = self.details["components"]["POLICY"]["componentState"]["stateName"] + return state == "SENT_AND_DEPLOYED" + + def act_on_loop_policy(self, func) -> bool: + """ + Act on loop's policy. + + Args: + func (function): function of action to be done (submit, stop, restart) + + Returns: + action state : failed or done + + """ + url = f"{self.base_url()}/loop/{func.__name__}/{self.name}" + self.send_message('PUT', + f'{func.__name__} policy', + url) + self.refresh_status() + self.validate_details() + return func() + + def deploy_microservice_to_dcae(self) -> bool: + """ + Execute the deploy operation on the loop instance. + + Returns: + loop deploy on DCAE status (True, False) + + """ + url = f"{self.base_url()}/loop/deploy/{self.name}" + self.send_message('PUT', + 'Deploy microservice to DCAE', + url) + self.validate_details() + state = self.details["components"]["DCAE"]["componentState"]["stateName"] + failure = "MICROSERVICE_INSTALLATION_FAILED" + success = "MICROSERVICE_INSTALLED_SUCCESSFULLY" + while state not in (success, failure): + self.refresh_status() + self.validate_details() + state = self.details["components"]["DCAE"]["componentState"]["stateName"] + return state == success + + def undeploy_microservice_from_dcae(self) -> None: + """Stop the deploy operation.""" + url = f"{self.base_url()}/loop/undeploy/{self.name}" + self.send_message('PUT', + 'Undeploy microservice from DCAE', + url) + + def delete(self) -> None: + """Delete the loop instance.""" + self._logger.debug("Delete %s loop instance", self.name) + url = "{}/loop/delete/{}".format(self.base_url(), self.name) + self.send_message('PUT', + 'Delete loop instance', + url) diff --git a/src/onapsdk/clamp/schema_details.json b/src/onapsdk/clamp/schema_details.json new file mode 100644 index 0000000..3caea6c --- /dev/null +++ b/src/onapsdk/clamp/schema_details.json @@ -0,0 +1,138 @@ +{ + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "components": { + "type": "object", + "properties": { + "POLICY": { + "type": "object", + "properties": { + "componentState": { + "type": "object", + "properties": { + "stateName": { + "type": "string" + } + }, + "required": [ + "stateName" + ] + } + }, + "required": [ + "componentState" + ] + }, + "DCAE": { + "type": "object", + "properties": { + "componentState": { + "type": "object", + "properties": { + "stateName": { + "type": "string" + } + }, + "required": [ + "stateName" + ] + } + }, + "required": [ + "componentState" + ] + } + }, + "required": [ + "POLICY", + "DCAE" + ] + }, + "modelService": { + "type": "object", + "properties": { + "resourceDetails": { + "type": "object", + "properties": { + "VFModule": { + "type": "object", + "properties": { + "resourceID": { + "type": "object", + "properties": { + "vfModuleModelName": { + "type": "string" + }, + "vfModuleModelInvariantUUID": { + "type": "string" + }, + "vfModuleModelUUID": { + "type": "string" + }, + "vfModuleModelVersion": { + "type": "string" + }, + "vfModuleModelCustomizationUUID": { + "type": "string" + } + }, + "required": [ + "vfModuleModelName", + "vfModuleModelInvariantUUID", + "vfModuleModelUUID", + "vfModuleModelVersion", + "vfModuleModelCustomizationUUID" + ] + } + } + } + }, + "required": [ + "VFModule" + ] + } + }, + "required": [ + "resourceDetails" + ] + }, + "operationalPolicies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "microServicePolicies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + } + } + }, + "required": [ + "name", + "components", + "modelService", + "operationalPolicies", + "microServicePolicies" + ] + } \ No newline at end of file diff --git a/src/onapsdk/clamp/templates/clamp_MinMax_config.json.j2 b/src/onapsdk/clamp/templates/clamp_MinMax_config.json.j2 new file mode 100644 index 0000000..2402ace --- /dev/null +++ b/src/onapsdk/clamp/templates/clamp_MinMax_config.json.j2 @@ -0,0 +1,94 @@ +[ + { + "name": "{{ name }}", + "jsonRepresentation": { + "title": "onap.policies.controlloop.guard.common.MinMax", + "type": "object", + "description": "Supports Min/Max number of entity for scaling operations. Although min and max fields are marked as not\nrequired, you need to have at least one or the other.\n", + "required": [ + "actor", + "operation", + "target" + ], + "properties": { + "id": { + "type": "string", + "description": "The Control Loop id this applies to." + }, + "actor": { + "type": "string", + "description": "Specifies the Actor the guard applies to." + }, + "operation": { + "type": "string", + "description": "Specified the operation that the actor is performing the guard applies to." + }, + "timeRange": { + "title": "tosca.datatypes.TimeInterval", + "type": "object", + "required": [ + "start_time", + "end_time" + ], + "properties": { + "start_time": { + "type": "string", + "format": "date-time" + }, + "end_time": { + "type": "string", + "format": "date-time" + } + } + }, + "min": { + "type": "integer", + "description": "The minimum instances of this entity" + }, + "max": { + "type": "integer", + "description": "The maximum instances of this entity" + }, + "target": { + "type": "string", + "description": "The target entity that has scaling restricted" + } + } + }, + "configurationsJson": { + "actor": "test", + "operation": "test", + "target": "test", + "timeRange": { + "start_time": "00:00:00", + "end_time": "01:00:00" + }, + "min": 1, + "max": 10 + }, + "policyModel": { + "policyModelType": "onap.policies.controlloop.guard.common.MinMax", + "version": "1.0.0", + "policyAcronym": "MinMax", + "policyPdpGroup": { + "supportedPdpGroups": [ + { + "defaultGroup": [ + "xacml" + ] + } + ] + }, + "createdDate": "2020-07-22T01:37:35.861060Z", + "updatedDate": "2020-07-22T01:37:51.719018Z", + "updatedBy": "Not found", + "createdBy": "Not found" + }, + "createdDate": "2020-07-22T09:01:14.168344Z", + "updatedDate": "2020-07-22T09:01:14.168344Z", + "updatedBy": "clamp@clamp.onap.org", + "createdBy": "clamp@clamp.onap.org", + "pdpGroup": "defaultGroup", + "pdpSubgroup": "xacml" + } +] \ No newline at end of file diff --git a/src/onapsdk/clamp/templates/clamp_add_drools_policy.json.j2 b/src/onapsdk/clamp/templates/clamp_add_drools_policy.json.j2 new file mode 100644 index 0000000..40ca7cd --- /dev/null +++ b/src/onapsdk/clamp/templates/clamp_add_drools_policy.json.j2 @@ -0,0 +1,325 @@ +[ + { + "name": "{{ name }}", + "jsonRepresentation": { + "title": "onap.policies.controlloop.operational.common.Drools", + "type": "object", + "description": "Operational policies for Drools PDP", + "required": [ + "abatement", + "operations", + "trigger", + "timeout", + "id" + ], + "properties": { + "abatement": { + "type": "boolean", + "description": "Whether an abatement event message will be expected for the control loop from DCAE.", + "default": "false" + }, + "operations": { + "type": "array", + "description": "List of operations to be performed when Control Loop is triggered.", + "items": { + "title": "onap.datatype.controlloop.Operation", + "type": "object", + "description": "An operation supported by an actor", + "required": [ + "id", + "operation", + "retries", + "timeout" + ], + "properties": { + "failure_retries": { + "type": "string", + "description": "Points to the operation to invoke when the current operation has exceeded its max retries.", + "default": "final_failure_retries" + }, + "id": { + "type": "string", + "description": "Unique identifier for the operation" + }, + "failure_timeout": { + "type": "string", + "description": "Points to the operation to invoke when the time out for the operation occurs.", + "default": "final_failure_timeout" + }, + "failure": { + "type": "string", + "description": "Points to the operation to invoke on Actor operation failure.", + "default": "final_failure" + }, + "operation": { + "title": "onap.datatype.controlloop.Actor", + "type": "object", + "description": "An actor/operation/target definition", + "required": [ + "target", + "actor", + "operation" + ], + "properties": { + "payload": { + "type": "object", + "description": "Name/value pairs of payload information passed by Policy to the actor", + "anyOf": [ + { + "title": "User defined", + "properties": { + + } + } + ] + }, + "target": { + "title": "onap.datatype.controlloop.Target", + "type": "object", + "description": "Definition for a entity in A&AI to perform a control loop operation on", + "required": [ + "targetType" + ], + "properties": { + "entityIds": { + "type": "object", + "description": "Map of values that identify the resource. If none are provided, it is assumed that the\nentity that generated the ONSET event will be the target.\n", + "anyOf": [ + { + "title": "User defined", + "properties": { + + } + }, + { + "title": "VNF-ubuntu18agent_VF 0", + "properties": { + "resourceID": { + "title": "Resource ID", + "type": "string", + "default": "6daf6e05-fc26-4aa3-9f0b-d47cf3f37ece", + "readOnly": "True" + } + } + }, + { + "title": "VFMODULE-Ubuntu18agentVf..base_ubuntu18..module-0", + "properties": { + "resourceID": { + "title": "Resource ID", + "type": "string", + "default": "Ubuntu18agentVf..base_ubuntu18..module-0", + "readOnly": "True" + }, + "modelInvariantId": { + "title": "Model Invariant Id (ModelInvariantUUID)", + "type": "string", + "default": "2556faee-75dd-448f-8d2f-d4201a957e7c", + "readOnly": "True" + }, + "modelVersionId": { + "title": "Model Version Id (ModelUUID)", + "type": "string", + "default": "98df9741-530a-486c-b156-b2cb62e6fc6c", + "readOnly": "True" + }, + "modelName": { + "title": "Model Name", + "type": "string", + "default": "Ubuntu18agentVf..base_ubuntu18..module-0", + "readOnly": "True" + }, + "modelVersion": { + "title": "Model Version", + "type": "string", + "default": "1", + "readOnly": "True" + }, + "modelCustomizationId": { + "title": "Customization ID", + "type": "string", + "default": "ba567b66-e46b-4521-8fdd-54185cb21a7f", + "readOnly": "True" + } + } + } + ] + }, + "targetType": { + "type": "string", + "description": "Category for the target type", + "enum": [ + "VNF", + "VM", + "VFMODULE", + "PNF" + ] + } + } + }, + "actor": { + "type": "string", + "description": "The actor performing the operation.", + "enum": [ + "SDNR", + "SDNC", + "VFC", + "SO", + "APPC", + "CDS" + ], + "options": { + "enum_titles": [ + "SDNR", + "SDNC", + "VFC", + "SO", + "APPC" + ] + } + }, + "operation": { + "type": "string", + "description": "The operation the actor is performing.", + "enum": [ + "BandwidthOnDemand", + "VF Module Delete", + "Reroute", + "VF Module Create", + "ModifyConfig", + "Rebuild", + "Restart", + "Migrate", + "Health-Check" + ], + "options": { + "enum_titles": [ + "BandwidthOnDemand (SDNC operation)", + "VF Module Delete (SO operation)", + "Reroute (SDNC operation)", + "VF Module Create (SO operation)", + "ModifyConfig (APPC/VFC operation)", + "Rebuild (APPC operation)", + "Restart (APPC operation)", + "Migrate (APPC operation)", + "Health-Check (APPC operation)" + ] + } + } + } + }, + "failure_guard": { + "type": "string", + "description": "Points to the operation to invoke when the current operation is blocked due to guard policy enforcement.", + "default": "final_failure_guard" + }, + "retries": { + "type": "integer", + "description": "The number of retries the actor should attempt to perform the operation.", + "default": "0" + }, + "timeout": { + "type": "integer", + "description": "The amount of time for the actor to perform the operation." + }, + "failure_exception": { + "type": "string", + "description": "Points to the operation to invoke when the current operation causes an exception.", + "default": "final_failure_exception" + }, + "description": { + "type": "string", + "description": "A user-friendly description of the intent for the operation" + }, + "success": { + "type": "string", + "description": "Points to the operation to invoke on success. A value of \"final_success\" indicates and end to the operation.", + "default": "final_success" + } + } + }, + "format": "tabs-top" + }, + "trigger": { + "type": "string", + "description": "Initial operation to execute upon receiving an Onset event message for the Control Loop." + }, + "timeout": { + "type": "integer", + "description": "Overall timeout for executing all the operations. This timeout should equal or exceed the total\ntimeout for each operation listed.\n" + }, + "id": { + "type": "string", + "description": "The unique control loop id." + }, + "controllerName": { + "type": "string", + "description": "Drools controller properties" + } + } + }, + "configurationsJson": { + "abatement": false, + "operations": [ + { + "failure_retries": "final_failure_retries", + "id": "policy-1-vfmodule-create", + "failure_timeout": "final_failure_timeout", + "failure": "final_failure", + "operation": { + "payload": { + "requestParameters": "{\"usePreload\":false,\"userParams\":[]}", + "configurationParameters": "[{\"ip-addr\":\"$.vf-module-topology.vf-module-parameters.param[16].value\",\"oam-ip-addr\":\"$.vf-module-topology.vf-module-parameters.param[30].value\"}]" + }, + "target": { + "entityIds": { + "resourceID": "{{ resourceID }}", + "modelInvariantId": "{{ modelInvariantId }}", + "modelVersionId": "{{ modelVersionId }}", + "modelName": "{{ modelName }}", + "modelVersion": "{{ modelVersion }}", + "modelCustomizationId": "{{ modelCustomizationId }}" + }, + "targetType": "VFMODULE" + }, + "actor": "SO", + "operation": "VF Module Create" + }, + "failure_guard": "final_failure_guard", + "retries": 1, + "timeout": 300, + "failure_exception": "final_failure_exception", + "description": "test", + "success": "final_success" + } + ], + "trigger": "policy-1-vfmodule-create", + "timeout": 650, + "id": "{{ LOOP_name }}" + }, + "policyModel": { + "policyModelType": "onap.policies.controlloop.operational.common.Drools", + "version": "1.0.0", + "policyAcronym": "Drools", + "policyPdpGroup": { + "supportedPdpGroups": [ + { + "defaultGroup": [ + "drools" + ] + } + ] + }, + "createdDate": "2020-07-22T01:37:38.528901Z", + "updatedDate": "2020-07-22T01:37:51.752302Z", + "updatedBy": "Not found", + "createdBy": "Not found" + }, + "createdDate": "2020-07-22T07:50:00.076714Z", + "updatedDate": "2020-07-22T07:50:00.076714Z", + "updatedBy": "clamp@clamp.onap.org", + "createdBy": "clamp@clamp.onap.org", + "pdpGroup": "defaultGroup", + "pdpSubgroup": "drools" + } +] \ No newline at end of file diff --git a/src/onapsdk/clamp/templates/clamp_add_frequency.json.j2 b/src/onapsdk/clamp/templates/clamp_add_frequency.json.j2 new file mode 100644 index 0000000..fabf9e6 --- /dev/null +++ b/src/onapsdk/clamp/templates/clamp_add_frequency.json.j2 @@ -0,0 +1,102 @@ +[ + { + "name": "{{ name }}", + "jsonRepresentation": { + "title": "onap.policies.controlloop.guard.common.FrequencyLimiter", + "type": "object", + "description": "Supports limiting the frequency of actions being taken by a Actor.", + "required": [ + "actor", + "operation", + "limit", + "timeWindow", + "timeUnits" + ], + "properties": { + "id": { + "type": "string", + "description": "The Control Loop id this applies to." + }, + "actor": { + "type": "string", + "description": "Specifies the Actor the guard applies to." + }, + "operation": { + "type": "string", + "description": "Specified the operation that the actor is performing the guard applies to." + }, + "timeRange": { + "title": "tosca.datatypes.TimeInterval", + "type": "object", + "required": [ + "start_time", + "end_time" + ], + "properties": { + "start_time": { + "type": "string", + "format": "date-time" + }, + "end_time": { + "type": "string", + "format": "date-time" + } + } + }, + "limit": { + "type": "integer", + "description": "The limit", + "exclusiveMinimum": "0" + }, + "timeWindow": { + "type": "integer", + "description": "The time window to count the actions against." + }, + "timeUnits": { + "type": "string", + "description": "The units of time the window is counting.", + "enum": [ + "second", + "minute", + "hour", + "day", + "week", + "month", + "year" + ] + } + } + }, + "configurationsJson": { + "actor": "SO", + "operation": "VF Module Create", + "limit": {{ limit }}, + "timeWindow": 10, + "timeUnits": "minute" + }, + "policyModel": { + "policyModelType": "onap.policies.controlloop.guard.common.FrequencyLimiter", + "version": "1.0.0", + "policyAcronym": "FrequencyLimiter", + "policyPdpGroup": { + "supportedPdpGroups": [ + { + "defaultGroup": [ + "xacml" + ] + } + ] + }, + "createdDate": "2020-07-22T01:37:35.106757Z", + "updatedDate": "2020-07-22T01:37:51.709386Z", + "updatedBy": "Not found", + "createdBy": "Not found" + }, + "createdDate": "2020-07-22T08:27:34.576868Z", + "updatedDate": "2020-07-22T08:27:34.576868Z", + "updatedBy": "clamp@clamp.onap.org", + "createdBy": "clamp@clamp.onap.org", + "pdpGroup": "defaultGroup", + "pdpSubgroup": "xacml" + } +] \ No newline at end of file diff --git a/src/onapsdk/clamp/templates/clamp_add_tca_config.json.j2 b/src/onapsdk/clamp/templates/clamp_add_tca_config.json.j2 new file mode 100644 index 0000000..0919a6b --- /dev/null +++ b/src/onapsdk/clamp/templates/clamp_add_tca_config.json.j2 @@ -0,0 +1,30 @@ +{ + "name": "{{ name }}", + "configurationsJson": { + "tca.policy": { + "domain": "measurementsForVfScaling", + "metricsPerEventName": [ + { + "policyScope": "DCAE", + "thresholds": [ + { + "version": "1.0.2", + "severity": "MAJOR", + "thresholdValue": 200, + "closedLoopEventStatus": "ONSET", + "closedLoopControlName": "{{ LOOP_name }}", + "direction": "LESS_OR_EQUAL", + "fieldPath": "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedTotalPacketsDelta" + } + ], + "eventName": "vLoadBalancer", + "policyVersion": "v0.0.1", + "controlLoopSchemaType": "VM", + "policyName": "DCAE.Config_tca-hi-lo" + } + ] + } + }, + "pdpGroup": "defaultGroup", + "pdpSubgroup": "xacml" +} diff --git a/src/onapsdk/configuration/__init__.py b/src/onapsdk/configuration/__init__.py new file mode 100644 index 0000000..027e7ef --- /dev/null +++ b/src/onapsdk/configuration/__init__.py @@ -0,0 +1,18 @@ +"""Configuration module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 .loader import SettingsLoader, SETTINGS_ENV + + +settings = SettingsLoader() # pylint: disable=invalid-name diff --git a/src/onapsdk/configuration/global_settings.py b/src/onapsdk/configuration/global_settings.py new file mode 100644 index 0000000..6f7e4d7 --- /dev/null +++ b/src/onapsdk/configuration/global_settings.py @@ -0,0 +1,71 @@ +"""Global settings module.""" # pylint: disable=bad-whitespace +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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. + + +###################### +# # +# ONAP SERVICES URLS # +# # +###################### + +## API +AAI_URL = "https://aai.api.sparky.simpledemo.onap.org:30233" +AAI_API_VERSION = "v23" +AAI_AUTH = "Basic QUFJOkFBSQ==" +AAI_BULK_CHUNK = 30 +CDS_URL = "http://portal.api.simpledemo.onap.org:30449" +CDS_AUTH = ("ccsdkapps", "ccsdkapps") +CPS_URL = "http://portal.api.simpledemo.onap.org:8080" +CPS_AUTH = ("cpsuser", "cpsr0cks!") +MSB_URL = "https://msb.api.simpledemo.onap.org:30283" +SDC_BE_URL = "https://sdc.api.be.simpledemo.onap.org:30204" +SDC_FE_URL = "https://sdc.api.fe.simpledemo.onap.org:30207" +SDC_AUTH = "Basic YWFpOktwOGJKNFNYc3pNMFdYbGhhazNlSGxjc2UyZ0F3ODR2YW9HR21KdlV5MlU=" +SDNC_URL = "https://sdnc.api.simpledemo.onap.org:30267" +SDNC_AUTH = "Basic YWRtaW46S3A4Yko0U1hzek0wV1hsaGFrM2VIbGNzZTJnQXc4NHZhb0dHbUp2VXkyVQ==" +SO_URL = "http://so.api.simpledemo.onap.org:30277" +SO_API_VERSION = "v7" +SO_AUTH = "Basic SW5mcmFQb3J0YWxDbGllbnQ6cGFzc3dvcmQxJA==" +SO_CAT_DB_AUTH = "Basic YnBlbDpwYXNzd29yZDEk" +VID_URL = "https://vid.api.simpledemo.onap.org:30200" +VID_API_VERSION = "/vid" +CLAMP_URL = "https://clamp.api.simpledemo.onap.org:30258" +CLAMP_AUTH = "Basic ZGVtb0BwZW9wbGUub3NhYWYub3JnOmRlbW8xMjM0NTYh" +VES_URL = "http://ves.api.simpledemo.onap.org:30417" +DMAAP_URL = "http://dmaap.api.simpledemo.onap.org:3904" +NBI_URL = "https://nbi.api.simpledemo.onap.org:30274" +NBI_API_VERSION = "/nbi/api/v4" +DCAEMOD_URL = "" +HOLMES_URL = "https://aai.api.sparky.simpledemo.onap.org:30293" +POLICY_URL = "" + +## GUI +AAI_GUI_URL = "https://aai.api.sparky.simpledemo.onap.org:30220" +AAI_GUI_SERVICE = f"{AAI_GUI_URL}/services/aai/webapp/index.html#/browse" +CDS_GUI_SERVICE = f"{CDS_URL}/" +SO_MONITOR_GUI_SERVICE = f"{SO_URL}/" +SDC_GUI_SERVICE = f"{SDC_FE_URL}/sdc1/portal" +SDNC_DG_GUI_SERVICE = f"{SDNC_URL}/nifi/" +SDNC_ODL_GUI_SERVICE = f"{SDNC_URL}/odlux/index.html" + +DCAEMOD_GUI_SERVICE = f"{DCAEMOD_URL}/" +HOLMES_GUI_SERVICE = f"{HOLMES_URL}/iui/holmes/default.html" +POLICY_GUI_SERVICE = f"{POLICY_URL}/onap/login.html" +POLICY_CLAMP_GUI_SERVICE = f"{CLAMP_URL}/" + +# VID OBJECTS DEFAULT VALUES +PROJECT = "Onapsdk_project" +LOB = "Onapsdk_lob" +PLATFORM = "Onapsdk_platform" diff --git a/src/onapsdk/configuration/loader.py b/src/onapsdk/configuration/loader.py new file mode 100644 index 0000000..a9aae6f --- /dev/null +++ b/src/onapsdk/configuration/loader.py @@ -0,0 +1,115 @@ +"""Settings loader module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 importlib +import os +from typing import Any + +from onapsdk.exceptions import ModuleError, SettingsError + +from . import global_settings + + +SETTINGS_ENV = "ONAP_PYTHON_SDK_SETTINGS" + + +class SettingsLoader: + """Settings loader class. + + Load global settings and optionally load + custom settings by importing the module + stored in ONAP_PYTHON_SDK_SETTINGS environment + variable. + The module has to be uder PYTHONPATH. + """ + + def __init__(self) -> None: + """Load settings. + + Load global settings and optionally load custom one. + + Raises: + ModuleError: If ONAP_PYTHON_SDK_SETTINGS environment variable + is set and module can't be imported. + + """ + self._settings = {} + + # Load values from global_settings (only uppercase) + self.filter_and_set(global_settings) + + settings_env_value: str = os.environ.get(SETTINGS_ENV) + if settings_env_value: + # Load values from custom settings + try: + module = importlib.import_module(settings_env_value) + except ModuleNotFoundError: + msg = "Can't import custom settings. Is it under PYTHONPATH?" + raise ModuleError(msg) + self.filter_and_set(module) + + def __getattribute__(self, name: str) -> Any: + """Return stored attributes. + + If attribute name is uppercase return it from + _settings dictionary. + Look for attribute in __dict__ otherwise. + + Args: + name (str): Attribute's name + + Raises: + SettingsError: a setting is not found by the key. + + Returns: + Any: Attribute's value + + """ + if name.isupper(): + try: + return self._settings[name] + except KeyError as exc: + msg = f"Requested setting {exc.args[0]} does not exist." + raise SettingsError(msg) from exc + return super().__getattribute__(name) + + def __setattr__(self, name: str, value: Any) -> None: + """Save attribute. + + If attribute name is uppercase save the value + in _settings dictionary. + Use Object class __setattr__ implementation + otherwise. + + Args: + name (str): Attribute's name + value (Any): Attribute's value + + """ + if name.isupper(): + self._settings[name] = value + super().__setattr__(name, value) + + def filter_and_set(self, module: "module") -> None: + """Filter module attributes and save the uppercased. + + Iterate through module's attribures and save the value + of them which name is uppercase. + + Args: + module (module): Module to get attributes from + + """ + for key in filter(lambda x: x.isupper(), dir(module)): + self._settings[key] = getattr(module, key) diff --git a/src/onapsdk/constants.py b/src/onapsdk/constants.py new file mode 100644 index 0000000..191e7aa --- /dev/null +++ b/src/onapsdk/constants.py @@ -0,0 +1,61 @@ +"""Constant package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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. + +## +# State Machines +# Vendor: DRAFT --> CERTIFIED +# VSP: DRAFT --> UPLOADED --> VALIDATED --> COMMITED --> CERTIFIED +## + +## +# States +## +DRAFT = "Draft" +CERTIFIED = "Certified" +COMMITED = "Commited" +UPLOADED = "Uploaded" +VALIDATED = "Validated" +APPROVED = "Approved" +UNDER_CERTIFICATION = "Certification in progress" +CHECKED_IN = "Checked In" +SUBMITTED = "Submitted" +DISTRIBUTED = "Distributed" +## +# Actions +## +CERTIFY = "Certify" +COMMIT = "Commit" +CREATE_PACKAGE = "Create_Package" +SUBMIT = "Submit" +SUBMIT_FOR_TESTING = "certificationRequest" +CHECKOUT = "checkout" +UNDOCHECKOUT = "UNDOCHECKOUT" +CHECKIN = "checkin" +APPROVE = "approve" +DISTRIBUTE = "PROD/activate" +TOSCA = "toscaModel" +DISTRIBUTION = "distribution" +START_CERTIFICATION = "startCertification" +NOT_CERTIFIED_CHECKOUT = "NOT_CERTIFIED_CHECKOUT" +NOT_CERTIFIED_CHECKIN = "NOT_CERTIFIED_CHECKIN" +READY_FOR_CERTIFICATION = "READY_FOR_CERTIFICATION" +CERTIFICATION_IN_PROGRESS = "CERTIFICATION_IN_PROGRESS" +DISTRIBUTION_APPROVED = "DISTRIBUTION_APPROVED" +DISTRIBUTION_NOT_APPROVED = "DISTRIBUTION_NOT_APPROVED" +SDC_DISTRIBUTED = "DISTRIBUTED" +## +# Distribution States +## +DOWNLOAD_OK = "DOWNLOAD_OK" diff --git a/src/onapsdk/cps/__init__.py b/src/onapsdk/cps/__init__.py new file mode 100644 index 0000000..63d5dd5 --- /dev/null +++ b/src/onapsdk/cps/__init__.py @@ -0,0 +1,18 @@ +"""ONAP SDK CPS package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 .anchor import Anchor # pylint: disable=unused-import +from .dataspace import Dataspace # pylint: disable=unused-import +from .schemaset import SchemaSet, SchemaSetModuleReference # pylint: disable=unused-import diff --git a/src/onapsdk/cps/anchor.py b/src/onapsdk/cps/anchor.py new file mode 100644 index 0000000..f02687d --- /dev/null +++ b/src/onapsdk/cps/anchor.py @@ -0,0 +1,193 @@ +"""ONAP SDK CPS anchor module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Any, Dict, TYPE_CHECKING + +from .cps_element import CpsElement + +if TYPE_CHECKING: + from .schemaset import SchemaSet # pylint: disable=cyclic-import + + +class Anchor(CpsElement): + """CPS anchor class.""" + + def __init__(self, name: str, schema_set: "SchemaSet") -> None: + """Initialise CPS anchor object. + + Args: + name (str): Anchor name + schema_set (SchemaSet): Schema set + + """ + super().__init__() + self.name: str = name + self.schema_set: "SchemaSet" = schema_set + + def __repr__(self) -> str: + """Human readable representation of the object. + + Returns: + str: Human readable string + + """ + return f"Anchor(name={self.name}, "\ + f"schema set={self.schema_set.name}, "\ + f"dataspace={self.schema_set.dataspace.name})" + + @property + def url(self) -> str: + """Anchor url. + + Returns: + str: Anchor url + + """ + return f"{self._url}/cps/api/v1/dataspaces/"\ + f"{self.schema_set.dataspace.name}/anchors/{self.name}" + + def delete(self) -> None: + """Delete anchor.""" + self.send_message( + "DELETE", + f"Delete {self.name} anchor", + self.url, + auth=self.auth + ) + + def create_node(self, node_data: str) -> None: + """Create anchor node. + + Fill CPS anchor with a data. + + Args: + node_data (str): Node data. Should be JSON formatted. + + """ + self.send_message( + "POST", + f"Create {self.name} anchor node", + f"{self.url}/nodes", + data=node_data, + auth=self.auth + ) + + def get_node(self, xpath: str, include_descendants: bool = False) -> Dict[Any, Any]: + """Get anchor node data. + + Using XPATH get anchor's node data. + + Args: + xpath (str): Anchor node xpath. + include_descendants (bool, optional): Determies if descendants should be included in + response. Defaults to False. + + Returns: + Dict[Any, Any]: Anchor node data. + + """ + return self.send_message_json( + "GET", + f"Get {self.name} anchor node with {xpath} xpath", + f"{self.url}/node?xpath={xpath}&include-descendants={include_descendants}", + auth=self.auth + ) + + def update_node(self, xpath: str, node_data: str) -> None: + """Update anchor node data. + + Using XPATH update anchor's node data. + + Args: + xpath (str): Anchor node xpath. + node_data (str): Node data. + + """ + self.send_message( + "PATCH", + f"Update {self.name} anchor node with {xpath} xpath", + f"{self.url}/nodes?xpath={xpath}", + data=node_data, + auth=self.auth + ) + + def replace_node(self, xpath: str, node_data: str) -> None: + """Replace anchor node data. + + Using XPATH replace anchor's node data. + + Args: + xpath (str): Anchor node xpath. + node_data (str): Node data. + + """ + self.send_message( + "PUT", + f"Replace {self.name} anchor node with {xpath} xpath", + f"{self.url}/nodes?xpath={xpath}", + data=node_data, + auth=self.auth + ) + + def add_list_node(self, xpath: str, node_data: str) -> None: + """Add an element to the list node of an anchor. + + Args: + xpath (str): Xpath to the list node. + node_data (str): Data to be added. + + """ + self.send_message( + "POST", + f"Add element to {self.name} anchor node with {xpath} xpath", + f"{self.url}/list-nodes?xpath={xpath}", + data=node_data, + auth=self.auth + ) + + def query_node(self, query: str, include_descendants: bool = False) -> Dict[Any, Any]: + """Query CPS anchor data. + + Args: + query (str): Query + include_descendants (bool, optional): Determies if descendants should be included in + response. Defaults to False. + + Returns: + Dict[Any, Any]: Query return values. + + """ + return self.send_message_json( + "GET", + f"Get {self.name} anchor node with {query} query", + f"{self.url}/nodes/query?cps-path={query}&include-descendants={include_descendants}", + auth=self.auth + ) + + def delete_nodes(self, xpath: str) -> None: + """Delete nodes. + + Use XPATH to delete Anchor nodes. + + Args: + xpath (str): Nodes to delete + + """ + self.send_message( + "DELETE", + f"Delete {self.name} anchor nodes with {xpath} xpath", + f"{self.url}/nodes?xpath={xpath}", + auth=self.auth + ) diff --git a/src/onapsdk/cps/cps_element.py b/src/onapsdk/cps/cps_element.py new file mode 100644 index 0000000..27f1faa --- /dev/null +++ b/src/onapsdk/cps/cps_element.py @@ -0,0 +1,24 @@ +"""ONAP SDK CPS element module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.configuration import settings +from onapsdk.onap_service import OnapService + + +class CpsElement(OnapService): + """Mother Class of all CPS elements.""" + + _url: str = settings.CPS_URL + auth: tuple = settings.CPS_AUTH diff --git a/src/onapsdk/cps/dataspace.py b/src/onapsdk/cps/dataspace.py new file mode 100644 index 0000000..e6340d7 --- /dev/null +++ b/src/onapsdk/cps/dataspace.py @@ -0,0 +1,193 @@ +"""ONAP SDK CPS dataspace module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Any, Dict, Iterable + +from .anchor import Anchor +from .cps_element import CpsElement +from .schemaset import SchemaSet, SchemaSetModuleReference + + +class Dataspace(CpsElement): + """CPS dataspace class.""" + + def __init__(self, name: str) -> None: + """Initialize dataspace object. + + Args: + name (str): Dataspace name + + """ + super().__init__() + self.name: str = name + + def __repr__(self) -> str: + """Human readable representation of the object. + + Returns: + str: Human readable string + + """ + return f"Dataspace(name={self.name})" + + @property + def url(self) -> str: + """Dataspace url. + + Returns: + str: Dataspace url + + """ + return f"{self._url}/cps/api/v1/dataspaces/{self.name}" + + @classmethod + def create(cls, dataspace_name: str) -> "Dataspace": + """Create dataspace with given name. + + Args: + dataspace_name (str): Dataspace name + + Returns: + Dataspace: Newly created dataspace + + """ + cls.send_message( + "POST", + f"Create {dataspace_name} dataspace", + f"{cls._url}/cps/api/v1/dataspaces?dataspace-name={dataspace_name}", + auth=cls.auth + ) + return Dataspace(dataspace_name) + + def create_anchor(self, schema_set: SchemaSet, anchor_name: str) -> Anchor: + """Create anchor. + + Args: + schema_set (SchemaSet): Schema set object which is going to be used to create anchor. + anchor_name (str): Anchor name + + Returns: + Anchor: Created anchor + + """ + self.send_message( + "POST", + "Get all CPS dataspace schemasets", + f"{self.url}/anchors/?schema-set-name={schema_set.name}&anchor-name={anchor_name}", + auth=self.auth + ) + return Anchor(name=anchor_name, schema_set=schema_set) + + def get_anchors(self) -> Iterable[Anchor]: + """Get all dataspace's anchors. + + Iterable of related with dataspace anchors. + + Yields: + Iterator[Anchor]: Anchor object + + """ + for anchor_data in self.send_message_json(\ + "GET",\ + "Get all CPS dataspace anchors",\ + f"{self.url}/anchors",\ + auth=self.auth\ + ): + yield Anchor(name=anchor_data["name"], + schema_set=SchemaSet(name=anchor_data["schemaSetName"], + dataspace=self)) + + def get_anchor(self, anchor_name: str) -> Anchor: + """Get dataspace anchor by name. + + To get anchor there is no need to use `SchemaSet` object, but to create anchor it it. + + Args: + anchor_name (str): Anchor name. + + Returns: + Anchor: Anchor object + + """ + anchor_data: Dict[str, Any] = self.send_message_json( + "GET", + f"Get {anchor_name} anchor", + f"{self.url}/anchors/{anchor_name}", + auth=self.auth + ) + return Anchor(name=anchor_data["name"], + schema_set=SchemaSet(name=anchor_data["schemaSetName"], + dataspace=self)) + + def get_schema_set(self, schema_set_name: str) -> SchemaSet: + """Get schema set by name. + + Args: + schema_set_name (str): Schema set name + + Returns: + SchemaSet: Schema set object + + """ + schema_set_data: Dict[str, Any] = self.send_message_json( + "GET", + "Get all CPS dataspace schemasets", + f"{self._url}/cps/api/v1/dataspaces/{self.name}/schema-sets/{schema_set_name}", + auth=self.auth + ) + return SchemaSet( + name=schema_set_data["name"], + dataspace=self, + module_references=[ + SchemaSetModuleReference( + name=module_reference_data["name"], + namespace=module_reference_data["namespace"], + revision=module_reference_data["revision"] + ) for module_reference_data in schema_set_data["moduleReferences"] + ] + ) + + def create_schema_set(self, schema_set_name: str, schema_set: bytes) -> SchemaSet: + """Create schema set. + + Create CPS schema set in dataspace + + Args: + schema_set_name (str): Schema set name + schema_set (bytes): Schema set YANG + + Returns: + SchemaSet: Created schema set object + + """ + self.send_message( + "POST", + "Create schema set", + f"{self._url}/cps/api/v1/dataspaces/{self.name}/schema-sets/", + files={"file": schema_set}, + data={"schema-set-name": schema_set_name}, + headers={}, # Leave headers empty to fill it correctly by `requests` library + auth=self.auth + ) + return self.get_schema_set(schema_set_name) + + def delete(self) -> None: + """Delete dataspace.""" + self.send_message( + "DELETE", + f"Delete {self.name} dataspace", + f"{self._url}/cps/api/v1/dataspaces/{self.name}", + auth=self.auth + ) diff --git a/src/onapsdk/cps/schemaset.py b/src/onapsdk/cps/schemaset.py new file mode 100644 index 0000000..650d12d --- /dev/null +++ b/src/onapsdk/cps/schemaset.py @@ -0,0 +1,73 @@ +"""ONAP SDK CPS schemaset module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 dataclasses import dataclass +from typing import List, Optional, TYPE_CHECKING + +from .cps_element import CpsElement + +if TYPE_CHECKING: + from .dataspace import Dataspace # pylint: disable=cyclic-import + + +@dataclass +class SchemaSetModuleReference: + """Schema set module reference dataclass. + + Stores all information about module reference. + """ + + name: str + namespace: str + revision: str + + +class SchemaSet(CpsElement): + """Schema set class.""" + + def __init__(self, + name: str, + dataspace: "Dataspace", + module_references: Optional[List[SchemaSetModuleReference]] = None) -> None: + """Initialize schema set class object. + + Args: + name (str): Schema set name + dataspace (Dataspace): Dataspace on which schema set was created. + module_references (Optional[List[SchemaSetModuleReference]], optional): + List of module references. Defaults to None. + """ + super().__init__() + self.name: str = name + self.dataspace: "Dataspace" = dataspace + self.module_refences: List[SchemaSetModuleReference] = module_references \ + if module_references else [] + + def __repr__(self) -> str: + """Human readable representation of the object. + + Returns: + str: Human readable string + + """ + return f"SchemaSet(name={self.name}, dataspace={self.dataspace.name})" + + def delete(self) -> None: + """Delete schema set.""" + self.send_message( + "DELETE", + f"Delete {self.name} schema set", + f"{self._url}/cps/api/v1/dataspaces/{self.dataspace.name}/schema-sets/{self.name}" + ) diff --git a/src/onapsdk/dmaap/__init__.py b/src/onapsdk/dmaap/__init__.py new file mode 100644 index 0000000..e0e448d --- /dev/null +++ b/src/onapsdk/dmaap/__init__.py @@ -0,0 +1,14 @@ +"""ONAP SDK Dmaap package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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. diff --git a/src/onapsdk/dmaap/dmaap.py b/src/onapsdk/dmaap/dmaap.py new file mode 100644 index 0000000..27c72c5 --- /dev/null +++ b/src/onapsdk/dmaap/dmaap.py @@ -0,0 +1,87 @@ +"""Base Dmaap event store.""" +# Copyright 2022 Orange, Deutsche Telekom AG, Nokia +# +# 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 typing import Dict + +from onapsdk.dmaap.dmaap_service import DmaapService + +ACTION = "Get events from Dmaap" +GET_HTTP_METHOD = "GET" + + +class Dmaap(DmaapService): + """Dmaap library provides functions for getting events from Dmaap.""" + + dmaap_url = DmaapService._url + get_all_events_url = f"{dmaap_url}/events" + get_all_topics_url = f"{dmaap_url}/topics" + get_events_from_topic_url = "{}/events/{}/CG1/C1" + + @classmethod + def get_all_events(cls, + basic_auth: Dict[str, str]) -> dict: + """ + Get all events stored in Dmaap. + + Args: + basic_auth: (Dict[str, str]) for example:{ 'username': 'bob', 'password': 'secret' } + Returns: + (dict) Events from Dmaap + + """ + return Dmaap.__get_events(cls.get_all_events_url, basic_auth) + + @classmethod + def get_events_for_topic(cls, + topic: str, + basic_auth: Dict[str, str]) -> dict: + """ + Get all events stored specific topic in Dmaap. + + Args: + topic: (str) topic of events stored in Dmaap + basic_auth: (Dict[str, str]) for example:{ 'username': 'bob', 'password': 'secret' } + + Returns: + (dict) Events from Dmaap + + """ + url = cls.get_events_from_topic_url.format(cls.dmaap_url, topic) + return Dmaap.__get_events(url, basic_auth) + + @classmethod + def get_all_topics(cls, + basic_auth: Dict[str, str]) -> dict: + """ + Get all topics stored in Dmaap. + + Args: + basic_auth: (Dict[str, str]) for example:{ 'username': 'bob', 'password': 'secret' } + + Returns: + (dict) Topics from Dmaap + + """ + return Dmaap.__get_events(cls.get_all_topics_url, basic_auth)['topics'] + + @classmethod + def __get_events(cls, + url: str, + basic_auth: Dict[str, str]) -> dict: + return cls.send_message_json( + GET_HTTP_METHOD, + ACTION, + url, + basic_auth=basic_auth + ) diff --git a/src/onapsdk/dmaap/dmaap_service.py b/src/onapsdk/dmaap/dmaap_service.py new file mode 100644 index 0000000..5fe393b --- /dev/null +++ b/src/onapsdk/dmaap/dmaap_service.py @@ -0,0 +1,26 @@ +"""Base VES module.""" +# Copyright 2022 Orange, Deutsche Telekom AG, Nokia +# +# 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 onapsdk.configuration import settings +from onapsdk.onap_service import OnapService + + +class DmaapService(OnapService): + """Base DMAAP class. + + Stores url to DMAAP API (edit if you want to use other). + """ + + _url: str = settings.DMAAP_URL diff --git a/src/onapsdk/exceptions.py b/src/onapsdk/exceptions.py new file mode 100644 index 0000000..76af60e --- /dev/null +++ b/src/onapsdk/exceptions.py @@ -0,0 +1,109 @@ +"""ONAP Exception module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Optional + + +class SDKException(Exception): + """Generic exception for ONAP SDK.""" + + +class RequestError(SDKException): + """Request error occured.""" + + +class ConnectionFailed(RequestError): + """Unable to connect.""" + + +class APIError(RequestError): + """API error occured.""" + + def __init__(self, + message: Optional[str] = None, + response_status_code: Optional[int] = None) -> None: + """Init api error exception. + + Save message and optional response status code. + + Args: + message (Optional[str]): Response error message. Defaults to None. + response_status_code (Optional[int], optional): Response status code. Defaults to None. + + """ + if message: + super().__init__(message) + else: + super().__init__() + self._response_status_code: int = response_status_code if response_status_code else 0 + + @property + def response_status_code(self) -> int: + """Response status code property. + + Returns: + int: Response status code. If not set, returns 0 + + """ + return self._response_status_code + + @response_status_code.setter + def response_status_code(self, status_code: int) -> None: + """Response status code property setter. + + Args: + status_code (int): Response status code + + """ + self._response_status_code = status_code + + +class InvalidResponse(RequestError): + """Unable to decode response.""" + + +class ResourceNotFound(APIError): + """Requested resource does not exist.""" + + +class RelationshipNotFound(ResourceNotFound): + """Required relationship is missing.""" + + +class StatusError(SDKException): + """Invalid status.""" + + +class ParameterError(SDKException): + """Parameter does not satisfy requirements.""" + +class ModuleError(SDKException): + """Unable to import module.""" + + +class ValidationError(SDKException): + """Data validation failed.""" + + +class FileError(ValidationError): + """Reading in a file failed.""" + + +class SettingsError(SDKException): + """Some settings are wrong.""" + + +class NoGuiError(SDKException): + """No GUI available for this component.""" diff --git a/src/onapsdk/msb/__init__.py b/src/onapsdk/msb/__init__.py new file mode 100644 index 0000000..6b23278 --- /dev/null +++ b/src/onapsdk/msb/__init__.py @@ -0,0 +1,17 @@ +"""Microsevice bus package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 .msb_service import MSB +from .esr import ESR +from .multicloud import Multicloud diff --git a/src/onapsdk/msb/esr.py b/src/onapsdk/msb/esr.py new file mode 100644 index 0000000..b29cfa4 --- /dev/null +++ b/src/onapsdk/msb/esr.py @@ -0,0 +1,85 @@ +"""ESR module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.utils.jinja import jinja_env +from .msb_service import MSB + + +class ESR(MSB): + """External system EST module.""" + + base_url = f"{MSB.base_url}/api/aai-esr-server/v1/vims" + + @classmethod + def register_vim(cls, # pylint: disable=too-many-arguments + cloud_owner: str, + cloud_region_id: str, + cloud_type: str, + cloud_region_version: str, + auth_info_cloud_domain: str, + auth_info_username: str, + auth_info_password: str, + auth_info_url: str, + owner_defined_type: str = None, + cloud_zone: str = None, + physical_location_id: str = None, + cloud_extra_info: str = None, + auth_info_ssl_cacert: str = None, + auth_info_ssl_insecure: bool = None) -> None: + """Register VIM. + + Args: + cloud_owner (str): cloud owner name, can be customized, e.g. att-aic + cloud_region_id (str): cloud region info based on deployment, e.g. RegionOne + cloud_type (str): type of the cloud, decides which multicloud plugin to use, + openstack or vio + cloud_region_version (str): cloud version, ocata, mitaka or other + auth_info_cloud_domain (str): domain info for keystone v3 + auth_info_username (str): user name + auth_info_password (str): password + auth_info_url (str): authentication url of the cloud, e.g. keystone url + owner_defined_type (str, optional): cloud-owner defined type indicator (e.g., dcp, lcp). + Defaults to None. + cloud_zone (str, optional): zone where the cloud is homed.. Defaults to None. + physical_location_id (str, optional): complex physical location id for + cloud-region instance. Defaults to None. + cloud_extra_info (str, optional): extra info for Cloud. Defaults to None. + auth_info_ssl_cacert (str, optional): ca file content if enabled ssl on auth-url. + Defaults to None. + auth_info_ssl_insecure (bool, optional): whether to verify VIM's certificate. + Defaults to None. + """ + cls.send_message( + "POST", + "Register VIM instance to ONAP", + cls.base_url, + data=jinja_env() + .get_template("msb_esr_vim_registration.json.j2") + .render( + cloud_owner=cloud_owner, + cloud_region_id=cloud_region_id, + cloud_type=cloud_type, + cloud_region_version=cloud_region_version, + auth_info_cloud_domain=auth_info_cloud_domain, + auth_info_username=auth_info_username, + auth_info_password=auth_info_password, + auth_info_url=auth_info_url, + owner_defined_type=owner_defined_type, + cloud_zone=cloud_zone, + physical_location_id=physical_location_id, + cloud_extra_info=cloud_extra_info, + auth_info_ssl_cacert=auth_info_ssl_cacert, + auth_info_ssl_insecure=auth_info_ssl_insecure, + ), + ) diff --git a/src/onapsdk/msb/k8s/__init__.py b/src/onapsdk/msb/k8s/__init__.py new file mode 100644 index 0000000..655502d --- /dev/null +++ b/src/onapsdk/msb/k8s/__init__.py @@ -0,0 +1,17 @@ +"""K8s package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 .definition import Definition, Profile, ConfigurationTemplate +from .connectivity_info import ConnectivityInfo +from .instance import InstantiationParameter, InstantiationRequest, Instance diff --git a/src/onapsdk/msb/k8s/connectivity_info.py b/src/onapsdk/msb/k8s/connectivity_info.py new file mode 100644 index 0000000..71a43c1 --- /dev/null +++ b/src/onapsdk/msb/k8s/connectivity_info.py @@ -0,0 +1,105 @@ +"""Connectivity-Info module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.utils.jinja import jinja_env +from ..msb_service import MSB + + +class ConnectivityInfo(MSB): + """Connectivity-Info class.""" + + api_version = "/api/multicloud-k8s/v1/v1" + url = f"{MSB.base_url}{api_version}/connectivity-info" + + def __init__(self, cloud_region_id: str, + cloud_owner: str, + other_connectivity_list: dict, + kubeconfig: str) -> None: + """Connectivity-info object initialization. + + Args: + cloud_region_id (str): Cloud region ID + cloud_owner (str): Cloud owner name + other_connectivity_list (dict): Optional other connectivity list + kubeconfig (str): kubernetes cluster kubeconfig + """ + super().__init__() + self.cloud_region_id: str = cloud_region_id + self.cloud_owner: str = cloud_owner + self.other_connectivity_list: dict = other_connectivity_list + self.kubeconfig: str = kubeconfig + + @classmethod + def get_connectivity_info_by_region_id(cls, cloud_region_id: str) -> "ConnectivityInfo": + """Get connectivity-info by its name (cloud region id). + + Args: + cloud_region_id (str): Cloud region ID + + Returns: + ConnectivityInfo: Connectivity-Info object + + """ + url: str = f"{cls.url}/{cloud_region_id}" + connectivity_info: dict = cls.send_message_json( + "GET", + "Get Connectivity Info", + url + ) + return cls( + connectivity_info["cloud-region"], + connectivity_info["cloud-owner"], + connectivity_info.get("other-connectivity-list"), + connectivity_info["kubeconfig"] + ) + + def delete(self) -> None: + """Delete connectivity info.""" + url: str = f"{self.url}/{self.cloud_region_id}" + self.send_message( + "DELETE", + "Delete Connectivity Info", + url + ) + + @classmethod + def create(cls, + cloud_region_id: str, + cloud_owner: str, + kubeconfig: bytes = None) -> "ConnectivityInfo": + """Create Connectivity Info. + + Args: + cloud_region_id (str): Cloud region ID + cloud_owner (str): Cloud owner name + kubeconfig (bytes): kubernetes cluster kubeconfig file + + Returns: + ConnectivityInfo: Created object + + """ + json_file = jinja_env().get_template("multicloud_k8s_add_connectivity_info.json.j2").render( + cloud_region_id=cloud_region_id, + cloud_owner=cloud_owner + ) + url: str = f"{cls.url}" + cls.send_message( + "POST", + "Create Connectivity Info", + url, + files={"file": kubeconfig, + "metadata": (None, json_file)}, + headers={} + ) + return cls.get_connectivity_info_by_region_id(cloud_region_id) diff --git a/src/onapsdk/msb/k8s/definition.py b/src/onapsdk/msb/k8s/definition.py new file mode 100644 index 0000000..6c0def2 --- /dev/null +++ b/src/onapsdk/msb/k8s/definition.py @@ -0,0 +1,424 @@ +"""Definition module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Iterator +from dataclasses import dataclass + +from onapsdk.utils.jinja import jinja_env +from ..msb_service import MSB + + +# pylint: disable=too-many-arguments, too-few-public-methods +class DefinitionBase(MSB): + """DefinitionBase class.""" + + base_url = f"{MSB.base_url}/api/multicloud-k8s/v1/v1/rb/definition" + + def __init__(self, rb_name: str, + rb_version: str) -> None: + """Definition-Base object initialization. + + Args: + rb_name (str): Definition name + rb_version (str): Definition version + """ + super().__init__() + self.rb_name: str = rb_name + self.rb_version: str = rb_version + + @property + def url(self) -> str: + """URL address for Definition Based calls. + + Returns: + str: URL to RB Definition + + """ + return f"{self.base_url}/{self.rb_name}/{self.rb_version}" + + def delete(self) -> None: + """Delete Definition Based object.""" + self.send_message( + "DELETE", + f"Delete {self.__class__.__name__}", + self.url + ) + + def upload_artifact(self, package: bytes = None): + """Upload artifact. + + Args: + package (bytes): Artifact to be uploaded to multicloud-k8s plugin + + """ + url: str = f"{self.url}/content" + self.send_message( + "POST", + "Upload Artifact content", + url, + data=package, + headers={} + ) + + +class Definition(DefinitionBase): + """Definition class.""" + + def __init__(self, rb_name: str, + rb_version: str, + chart_name: str, + description: str, + labels: dict) -> None: + """Definition object initialization. + + Args: + rb_name (str): Definition name + rb_version (str): Definition version + chart_name (str): Chart name, optional field, will be detected if it is not provided + description (str): Definition description + labels (str): Labels + """ + super().__init__(rb_name, rb_version) + self.rb_name: str = rb_name + self.rb_version: str = rb_version + self.chart_name: str = chart_name + self.description: str = description + self.labels: dict = labels + + @classmethod + def get_all(cls): + """Get all definitions. + + Yields: + Definition: Definition object + + """ + for definition in cls.send_message_json("GET", + "Get definitions", + cls.base_url): + yield cls( + definition["rb-name"], + definition["rb-version"], + definition.get("chart-name"), + definition.get("description"), + definition.get("labels") + ) + + @classmethod + def get_definition_by_name_version(cls, rb_name: str, rb_version: str) -> "Definition": + """Get definition by it's name and version. + + Args: + rb_name (str): definition name + rb_version (str): definition version + + Returns: + Definition: Definition object + + """ + url: str = f"{cls.base_url}/{rb_name}/{rb_version}" + definition: dict = cls.send_message_json( + "GET", + "Get definition", + url + ) + return cls( + definition["rb-name"], + definition["rb-version"], + definition.get("chart-name"), + definition.get("description"), + definition.get("labels") + ) + + @classmethod + def create(cls, rb_name: str, + rb_version: str, + chart_name: str = "", + description: str = "", + labels=None) -> "Definition": + """Create Definition. + + Args: + rb_name (str): Definition name + rb_version (str): Definition version + chart_name (str): Chart name, optional field, will be detected if it is not provided + description (str): Definition description + labels (str): Labels + + Returns: + Definition: Created object + + """ + if labels is None: + labels = {} + url: str = f"{cls.base_url}" + cls.send_message( + "POST", + "Create definition", + url, + data=jinja_env().get_template("multicloud_k8s_add_definition.json.j2").render( + rb_name=rb_name, + rb_version=rb_version, + chart_name=chart_name, + description=description, + labels=labels + ) + ) + return cls.get_definition_by_name_version(rb_name, rb_version) + + def create_profile(self, profile_name: str, + namespace: str, + kubernetes_version: str, + release_name=None) -> "Profile": + """Create Profile for Definition. + + Args: + profile_name (str): Name of profile + namespace (str): Namespace that service is created in + kubernetes_version (str): Required Kubernetes version + release_name (str): Release name + + Returns: + Profile: Created object + + """ + url: str = f"{self.url}/profile" + if release_name is None: + release_name = profile_name + self.send_message( + "POST", + "Create profile for definition", + url, + data=jinja_env().get_template("multicloud_k8s_create_profile_" + "for_definition.json.j2").render( + rb_name=self.rb_name, + rb_version=self.rb_version, + profile_name=profile_name, + release_name=release_name, + namespace=namespace, + kubernetes_version=kubernetes_version + ) + ) + return self.get_profile_by_name(profile_name) + + def get_all_profiles(self) -> Iterator["Profile"]: + """Get all profiles. + + Yields: + Profile: Profile object + + """ + url: str = f"{self.url}/profile" + + for profile in self.send_message_json("GET", + "Get profiles", + url): + yield Profile( + profile["rb-name"], + profile["rb-version"], + profile["profile-name"], + profile["namespace"], + profile.get("kubernetes-version"), + profile.get("labels"), + profile.get("release-name") + ) + + def get_profile_by_name(self, profile_name: str) -> "Profile": + """Get profile by it's name. + + Args: + profile_name (str): profile name + + Returns: + Profile: Profile object + + """ + url: str = f"{self.url}/profile/{profile_name}" + + profile: dict = self.send_message_json( + "GET", + "Get profile", + url + ) + return Profile( + profile["rb-name"], + profile["rb-version"], + profile["profile-name"], + profile["namespace"], + profile.get("kubernetes-version"), + profile.get("labels"), + profile.get("release-name") + ) + + def get_all_configuration_templates(self): + """Get all configuration templates. + + Yields: + ConfigurationTemplate: ConfigurationTemplate object + + """ + url: str = f"{self.url}/config-template" + + for template in self.send_message_json("GET", + "Get configuration templates", + url): + yield ConfigurationTemplate( + self.rb_name, + self.rb_version, + template["template-name"], + template.get("description") + ) + + def create_configuration_template(self, template_name: str, + description="") -> "ConfigurationTemplate": + """Create configuration template. + + Args: + template_name (str): Name of the template + description (str): Description + + Returns: + ConfigurationTemplate: Created object + + """ + url: str = f"{self.url}/config-template" + + self.send_message( + "POST", + "Create configuration template", + url, + data=jinja_env().get_template("multicloud_k8s_create_configuration_" + "template.json.j2").render( + template_name=template_name, + description=description + ) + ) + + return self.get_configuration_template_by_name(template_name) + + def get_configuration_template_by_name(self, template_name: str) -> "ConfigurationTemplate": + """Get configuration template. + + Args: + template_name (str): Name of the template + + Returns: + ConfigurationTemplate: object + + """ + url: str = f"{self.url}/config-template/{template_name}" + + template: dict = self.send_message_json( + "GET", + "Get Configuration template", + url + ) + return ConfigurationTemplate( + self.rb_name, + self.rb_version, + template["template-name"], + template.get("description") + ) + + +class ProfileBase(DefinitionBase): + """ProfileBase class.""" + + def __init__(self, rb_name: str, + rb_version: str, + profile_name: str) -> None: + """Profile-Base object initialization. + + Args: + rb_name (str): Definition name + rb_version (str): Definition version + profile_name (str): Name of profile + """ + super().__init__(rb_name, rb_version) + self.rb_name: str = rb_name + self.rb_version: str = rb_version + self.profile_name: str = profile_name + + @property + def url(self) -> str: + """URL address for Profile calls. + + Returns: + str: URL to RB Profile + + """ + return f"{super().url}/profile/{self.profile_name}" + + +@dataclass +class Profile(ProfileBase): + """Profile class.""" + + def __init__(self, rb_name: str, + rb_version: str, + profile_name: str, + namespace: str, + kubernetes_version: str, + labels=None, + release_name=None) -> None: + """Profile object initialization. + + Args: + rb_name (str): Definition name + rb_version (str): Definition version + profile_name (str): Name of profile + release_name (str): Release name, if release_name is not provided, + namespace (str): Namespace that service is created in + kubernetes_version (str): Required Kubernetes version + labels (dict): Labels + """ + super().__init__(rb_name, rb_version, profile_name) + if release_name is None: + release_name = profile_name + self.release_name: str = release_name + self.namespace: str = namespace + self.kubernetes_version: str = kubernetes_version + self.labels: dict = labels + if self.labels is None: + self.labels = dict() + + +class ConfigurationTemplate(DefinitionBase): + """ConfigurationTemplate class.""" + + @property + def url(self) -> str: + """URL address for ConfigurationTemplate calls. + + Returns: + str: URL to Configuration template in Multicloud-k8s API. + + """ + return f"{super().url}/config-template/{self.template_name}" + + def __init__(self, rb_name: str, + rb_version: str, + template_name: str, + description="") -> None: + """Configuration-Template object initialization. + + Args: + rb_name (str): Definition name + rb_version (str): Definition version + template_name (str): Configuration template name + description (str): Namespace that service is created in + """ + super().__init__(rb_name, rb_version) + self.template_name: str = template_name + self.description: str = description diff --git a/src/onapsdk/msb/k8s/instance.py b/src/onapsdk/msb/k8s/instance.py new file mode 100644 index 0000000..196b9d2 --- /dev/null +++ b/src/onapsdk/msb/k8s/instance.py @@ -0,0 +1,190 @@ +"""Instantiation module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Iterator +from dataclasses import dataclass + +from onapsdk.msb import MSB +from onapsdk.utils.jinja import jinja_env + + +# pylint: disable=too-many-arguments +@dataclass +class InstantiationRequest: + """Instantiation Request class.""" + + def __init__(self, request: dict) -> None: + """Request object initialization. + + Args: + cloud_region_id (str): Cloud region ID + profile_name (str): Name of profile + rb_name (str): Definition name + rb_version (str): Definition version + override_values (dict): Optional parameters + labels (dict): Optional labels + """ + super().__init__() + self.cloud_region_id: str = request["cloud-region"] + self.profile_name: str = request["profile-name"] + self.rb_name: str = request["rb-name"] + self.rb_version: str = request["rb-version"] + self.override_values: dict = request["override-values"] + self.labels: dict = request["labels"] + + +@dataclass +class InstantiationParameter: + """Class to store instantiation parameters used to pass override_values and labels. + + Contains two values: name of parameter and it's value + """ + + name: str + value: str + + +class Instance(MSB): + """Instance class.""" + + base_url = f"{MSB.base_url}/api/multicloud-k8s/v1/v1/instance" + + def __init__(self, instance_id: str, + namespace: str, + request: InstantiationRequest, + resources: dict = None, + override_values: dict = None) -> None: + """Instance object initialization. + + Args: + instance_id (str): instance ID + namespace (str): namespace that instance is created in + request (InstantiationRequest): datails of the instantiation request + resources (dict): Created resources + override_values (dict): Optional values + """ + super().__init__() + self.instance_id: str = instance_id + self.namespace: str = namespace + self.request: InstantiationRequest = request + self.resources: dict = resources + self.override_values: dict = override_values + + @property + def url(self) -> str: + """URL address. + + Returns: + str: URL to Instance + + """ + return f"{self.base_url}/{self.instance_id}" + + @classmethod + def get_all(cls) -> Iterator["Instance"]: + """Get all instantiated Kubernetes resources. + + Yields: + Instantiation: Instantiation object + + """ + for resource in cls.send_message_json("GET", + "Get Kubernetes resources", + cls.base_url): + yield cls( + instance_id=resource["id"], + namespace=resource["namespace"], + request=InstantiationRequest(resource["request"]) + ) + + @classmethod + def get_by_id(cls, instance_id: str) -> "Instance": + """Get Kubernetes resource by id. + + Args: + instance_id (str): instance ID + + Returns: + Instantiation: Instantiation object + + """ + url: str = f"{cls.base_url}/{instance_id}" + resource: dict = cls.send_message_json( + "GET", + "Get Kubernetes resource by id", + url + ) + return cls( + instance_id=resource["id"], + namespace=resource["namespace"], + request=InstantiationRequest(resource["request"]), + resources=resource["resources"], + override_values=resource.get("override-values") + ) + + @classmethod + def create(cls, + cloud_region_id: str, + profile_name: str, + rb_name: str, + rb_version: str, + override_values: dict = None, + labels: dict = None) -> "Instance": + """Create Instance. + + Args: + cloud_region_id (str): Cloud region ID + profile_name (str): Name of profile to be instantiated + rb_name: (bytes): Definition name + rb_version (str): Definition version + override_values (dict): List of optional override values + labels (dict): List of optional labels + + Returns: + Instance: Created object + + """ + if labels is None: + labels = {} + if override_values is None: + override_values = {} + url: str = f"{cls.base_url}" + response: dict = cls.send_message_json( + "POST", + "Create Instance", + url, + data=jinja_env().get_template("multicloud_k8s_instantiate.json.j2").render( + cloud_region_id=cloud_region_id, + profile_name=profile_name, + rb_name=rb_name, + rb_version=rb_version, + override_values=override_values, + labels=labels), + headers={} + ) + return cls( + instance_id=response["id"], + namespace=response["namespace"], + request=InstantiationRequest(response["request"]), + resources=response["resources"], + override_values=response.get("override-values") + ) + + def delete(self) -> None: + """Delete Instance object.""" + self.send_message( + "DELETE", + f"Delete {self.instance_id} instance", + self.url + ) diff --git a/src/onapsdk/msb/msb_service.py b/src/onapsdk/msb/msb_service.py new file mode 100644 index 0000000..017fed7 --- /dev/null +++ b/src/onapsdk/msb/msb_service.py @@ -0,0 +1,24 @@ +"""Microsevice bus module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.configuration import settings +from onapsdk.onap_service import OnapService +from onapsdk.utils.headers_creator import headers_msb_creator + + +class MSB(OnapService): + """Microservice Bus base class.""" + + base_url = settings.MSB_URL + headers = headers_msb_creator(OnapService.headers) diff --git a/src/onapsdk/msb/multicloud.py b/src/onapsdk/msb/multicloud.py new file mode 100644 index 0000000..bc8b468 --- /dev/null +++ b/src/onapsdk/msb/multicloud.py @@ -0,0 +1,55 @@ +"""Multicloud module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 .msb_service import MSB + + +class Multicloud(MSB): + """MSB subclass to register/unregister instance to ONAP.""" + + base_url = f"{MSB.base_url}/api/multicloud/v1" + + @classmethod + def register_vim(cls, + cloud_owner: str, + cloud_region_id: str, + default_tenant: str = None) -> None: + """Register a VIM instance to ONAP. + + Args: + cloud_owner (str): Cloud owner name + cloud_region_id (str): Cloud region ID + default_tenant (str, optional): Default tenant name. Defaults to None. + """ + cls.send_message( + "POST", + "Register VIM instance to ONAP", + f"{cls.base_url}/{cloud_owner}/{cloud_region_id}/registry", + data={"defaultTenant": default_tenant} if default_tenant else None + ) + + @classmethod + def unregister_vim(cls, cloud_owner: str, cloud_region_id: str) -> None: + """Unregister a VIM instance from ONAP. + + Args: + cloud_owner (str): Cloud owner name + cloud_region_id (str): Cloud region ID + """ + cls.send_message( + "DELETE", + "Unregister VIM instance from ONAP", + f"{cls.base_url}/{cloud_owner}/{cloud_region_id}" + ) diff --git a/src/onapsdk/msb/templates/msb_esr_vim_registration.json.j2 b/src/onapsdk/msb/templates/msb_esr_vim_registration.json.j2 new file mode 100644 index 0000000..ba19258 --- /dev/null +++ b/src/onapsdk/msb/templates/msb_esr_vim_registration.json.j2 @@ -0,0 +1,31 @@ +{ + "cloudOwner": "{{ cloud_owner }}", + "cloudRegionId": "{{ cloud_region_id }}", + "cloudType": "{{ cloud_type }}", + "cloudRegionVersion": "{{ cloud_region_version }}" + {% if owner_defined_type %} + , "ownerDefinedType": "{{ owner_defined_type }}" + {% endif %} + {% if cloud_zone %} + , "cloudZone": "{{ cloud_zone }}" + {% endif %} + {% if complex_name %} + , "physicalLocationId": "{{ physical_location_id }}" + {% endif %} + {% if cloud_extra_info %} + , "cloudExtraInfo": "{{ cloud_extra_info }}" + {% endif %} + , "vimAuthInfos": + [{ + "userName": "{{ auth_info_username }}", + "password": "{{ auth_info_password }}", + "authUrl": "{{ auth_info_url }}", + "cloudDomain": "{{ auth_info_cloud_domain }}" + {% if auth_info_ssl_cacert %} + , "sslCacert": "{{ auth_info_ssl_cacert }}" + {% endif %} + {% if auth_info_ssl_insecure is not none %} + , "sslInsecure": {{ auth_info_ssl_insecure | tojson }} + {% endif %} + }] +} diff --git a/src/onapsdk/msb/templates/multicloud_k8s_add_connectivity_info.json.j2 b/src/onapsdk/msb/templates/multicloud_k8s_add_connectivity_info.json.j2 new file mode 100644 index 0000000..4a3dc2d --- /dev/null +++ b/src/onapsdk/msb/templates/multicloud_k8s_add_connectivity_info.json.j2 @@ -0,0 +1,8 @@ +{ + "cloud-region" : "{{ cloud_region_id }}", + "cloud-owner" : "{{ cloud_owner }}", + "other-connectivity-list" : { + "connectivity-records" : [ + ] + } +} \ No newline at end of file diff --git a/src/onapsdk/msb/templates/multicloud_k8s_add_definition.json.j2 b/src/onapsdk/msb/templates/multicloud_k8s_add_definition.json.j2 new file mode 100644 index 0000000..866d577 --- /dev/null +++ b/src/onapsdk/msb/templates/multicloud_k8s_add_definition.json.j2 @@ -0,0 +1,7 @@ +{ + "rb-name": "{{ rb_name }}", + "rb-version": "{{ rb_version }}", + "chart-name": "{{ chart_name }}", + "description": "{{ description }}", + "labels": {{ labels }} +} diff --git a/src/onapsdk/msb/templates/multicloud_k8s_create_configuration_template.json.j2 b/src/onapsdk/msb/templates/multicloud_k8s_create_configuration_template.json.j2 new file mode 100644 index 0000000..61e6d2b --- /dev/null +++ b/src/onapsdk/msb/templates/multicloud_k8s_create_configuration_template.json.j2 @@ -0,0 +1,4 @@ +{ + "template-name": "{{ template_name }}", + "description": "{{ description }}" +} \ No newline at end of file diff --git a/src/onapsdk/msb/templates/multicloud_k8s_create_profile_for_definition.json.j2 b/src/onapsdk/msb/templates/multicloud_k8s_create_profile_for_definition.json.j2 new file mode 100644 index 0000000..5ea2de1 --- /dev/null +++ b/src/onapsdk/msb/templates/multicloud_k8s_create_profile_for_definition.json.j2 @@ -0,0 +1,8 @@ +{ + "rb-name": "{{ rb_name }}", + "rb-version": "{{ rb_version }}", + "profile-name": "{{ profile_name }}", + "release-name": "{{ release_name }}", + "namespace": "{{ namespace }}", + "kubernetes-version": "{{ kubernetes_version }}" +} \ No newline at end of file diff --git a/src/onapsdk/msb/templates/multicloud_k8s_instantiate.json.j2 b/src/onapsdk/msb/templates/multicloud_k8s_instantiate.json.j2 new file mode 100644 index 0000000..fa5ef66 --- /dev/null +++ b/src/onapsdk/msb/templates/multicloud_k8s_instantiate.json.j2 @@ -0,0 +1,18 @@ +{ + "cloud-region": "{{ cloud_region_id }}", + "profile-name": "{{ profile_name }}", + "rb-name": "{{ rb_name }}", + "rb-version": "{{ rb_version }}", + "override-values": + { + {% for override_value in override_values %} + "{{ override_value.name }}": "{{ override_value.value }}"{% if not loop.last %},{% endif %} + {% endfor %} + }, + "labels": + { + {% for label in labels %} + "{{ label.name }}": "{{ label.value }}"{% if not loop.last %},{% endif %} + {% endfor %} + } +} \ No newline at end of file diff --git a/src/onapsdk/nbi/__init__.py b/src/onapsdk/nbi/__init__.py new file mode 100644 index 0000000..d3ceaf6 --- /dev/null +++ b/src/onapsdk/nbi/__init__.py @@ -0,0 +1,16 @@ +"""NBI package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 .nbi import Nbi, Service, ServiceOrder, ServiceSpecification diff --git a/src/onapsdk/nbi/nbi.py b/src/onapsdk/nbi/nbi.py new file mode 100644 index 0000000..17356cb --- /dev/null +++ b/src/onapsdk/nbi/nbi.py @@ -0,0 +1,490 @@ +"""NBI module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 abc import ABC +from enum import Enum +from typing import Iterator +from uuid import uuid4 + +from onapsdk.aai.business.customer import Customer +from onapsdk.exceptions import RequestError +from onapsdk.onap_service import OnapService +from onapsdk.utils import get_zulu_time_isoformat +from onapsdk.utils.jinja import jinja_env +from onapsdk.utils.mixins import WaitForFinishMixin +from onapsdk.configuration import settings + + +class Nbi(OnapService, ABC): + """NBI base class.""" + + base_url = settings.NBI_URL + api_version = settings.NBI_API_VERSION + + @classmethod + def is_status_ok(cls) -> bool: + """Check NBI service status. + + Returns: + bool: True if NBI works fine, False otherwise + + """ + try: + cls.send_message( + "GET", + "Check NBI status", + f"{cls.base_url}{cls.api_version}/status" + ) + except RequestError as exc: + msg = f"An error occured during NBI status check: {exc}" + cls._logger.error(msg) + return False + return True + + +class ServiceSpecification(Nbi): + """NBI service specification class.""" + + def __init__(self, # pylint: disable=too-many-arguments + unique_id: str, + name: str, + invariant_uuid: str, + category: str, + distribution_status: str, + version: str, + lifecycle_status: str) -> None: + """Service specification object initialization. + + Args: + unique_id (str): Unique ID + name (str): Service specification name + invariant_uuid (str): Invariant UUID + category (str): Category + distribution_status (str): Service distribution status + version (str): Service version + lifecycle_status (str): Service lifecycle status + """ + super().__init__() + self.unique_id: str = unique_id + self.name: str = name + self.invariant_uuid: str = invariant_uuid + self.category: str = category + self.distribution_status: str = distribution_status + self.version: str = version + self.lifecycle_status: str = lifecycle_status + + def __repr__(self) -> str: + """Service specification representation. + + Returns: + str: Service specification object human readable representation + + """ + return (f"ServiceSpecification(unique_id={self.unique_id}, name={self.name}, " + f"invariant_uuid={self.invariant_uuid}, category={self.category}, " + f"distribution_status={self.distribution_status}, version={self.version}, " + f"lifecycle_status={self.lifecycle_status})") + + @classmethod + def get_all(cls) -> Iterator["ServiceSpecification"]: + """Get all service specifications. + + Yields: + ServiceSpecification: Service specification object + + """ + for service_specification in cls.send_message_json("GET", + "Get service specifications from NBI", + (f"{cls.base_url}{cls.api_version}/" + "serviceSpecification")): + yield ServiceSpecification( + service_specification.get("id"), + service_specification.get("name"), + service_specification.get("invariantUUID"), + service_specification.get("category"), + service_specification.get("distributionStatus"), + service_specification.get("version"), + service_specification.get("lifecycleStatus"), + ) + + @classmethod + def get_by_id(cls, service_specification_id: str) -> "ServiceSpecification": + """Get service specification by ID. + + Args: + service_specification_id (str): Service specification ID + + Returns: + ServiceSpecification: Service specification object + + """ + service_specification: dict = cls.send_message_json( + "GET", + f"Get service specification with {service_specification_id} ID from NBI", + f"{cls.base_url}{cls.api_version}/serviceSpecification/{service_specification_id}" + ) + return ServiceSpecification( + service_specification.get("id"), + service_specification.get("name"), + service_specification.get("invariantUUID"), + service_specification.get("category"), + service_specification.get("distributionStatus"), + service_specification.get("version"), + service_specification.get("lifecycleStatus"), + ) + + +class Service(Nbi): + """NBI service.""" + + def __init__(self, # pylint: disable=too-many-arguments + name: str, + service_id: str, + service_specification_name: str, + service_specification_id: str, + customer_id: str, + customer_role: str, + href: str) -> None: + """Service object initialization. + + Args: + name (str): Service name + service_id (str): Service ID + service_specification_name (str): Service specification name + service_specification_id (str): Service specification ID + customer_id (str): Global customer ID + customer_role (str): Customer role + href (str): Service object href + """ + super().__init__() + self.name: str = name + self.service_id: str = service_id + self._service_specification_name: str = service_specification_name + self._service_specification_id: str = service_specification_id + self._customer_id: str = customer_id + self.customer_role: str = customer_role + self.href: str = href + + def __repr__(self) -> str: + """Service object representation. + + Returns: + str: Human readable service object representation + + """ + return (f"Service(name={self.name}, service_id={self.service_id}, " + f"service_specification={self.service_specification}, customer={self.customer}, " + f"customer_role={self.customer_role})") + + @classmethod + def get_all(cls, customer_id: str = 'generic') -> Iterator["Service"]: + """Get all services for selected customer. + + Args: + customer_id (str): Global customer ID + + Yields: + Service: Service object + + """ + for service in cls.send_message_json("GET", + "Get service instances from NBI", + f"{cls.base_url}{cls.api_version}/service?" + f"relatedParty.id={customer_id}"): + yield cls(service.get("name"), + service.get("id"), + service.get("serviceSpecification", {}).get("name"), + service.get("serviceSpecification", {}).get("id"), + service.get("relatedParty", {}).get("id"), + service.get("relatedParty", {}).get("role"), + service.get("href")) + + @property + def customer(self) -> Customer: + """Service order Customer object. + + Returns: + Customer: Customer object + + """ + if not self._customer_id: + return None + return Customer.get_by_global_customer_id(self._customer_id) + + @property + def service_specification(self) -> ServiceSpecification: + """Service specification. + + Returns: + ServiceSpecification: Service specification object + + """ + if not self._service_specification_id: + return None + return ServiceSpecification.get_by_id(self._service_specification_id) + + +class ServiceOrder(Nbi, WaitForFinishMixin): # pylint: disable=too-many-instance-attributes + """Service order class.""" + + WAIT_FOR_SLEEP_TIME = 10 + + def __init__(self, # pylint: disable=too-many-arguments + unique_id: str, + href: str, + priority: str, + description: str, + category: str, + external_id: str, + service_instance_name: str, + state: str = None, + customer: Customer = None, + customer_id: str = None, + service_specification: ServiceSpecification = None, + service_specification_id: str = None) -> None: + """Service order object initialization. + + Args: + unique_id (str): unique ID + href (str): object's href + priority (str): order priority + description (str): order description + category (str): category description + external_id (str): external ID + service_instance_name (str): name of service instance + state (str, optional): instantiation state. Defaults to None. + customer (Customer, optional): Customer object. Defaults to None. + customer_id (str, optional): global customer ID. Defaults to None. + service_specification (ServiceSpecification, optional): service specification object. + Defaults to None. + service_specification_id (str, optional): service specification ID. Defaults to None. + """ + super().__init__() + self.unique_id: str = unique_id + self.href: str = href + self.priority: str = priority + self.category: str = category + self.description: str = description + self.external_id: str = external_id + self._customer: Customer = customer + self._customer_id: str = customer_id + self._service_specification: ServiceSpecification = service_specification + self._service_specification_id: str = service_specification_id + self.service_instance_name: str = service_instance_name + self.state: str = state + + class StatusEnum(Enum): + """Status enum. + + Store possible statuses for service order: + - completed, + - failed, + - inProgress. + If instantiation has status which is not covered by these values + `unknown` value is used. + + """ + + ACKNOWLEDGED = "acknowledged" + IN_PROGRESS = "inProgress" + FAILED = "failed" + COMPLETED = "completed" + REJECTED = "rejected" + UNKNOWN = "unknown" + + def __repr__(self) -> str: + """Service order object representation. + + Returns: + str: Service order object representation. + + """ + return (f"ServiceOrder(unique_id={self.unique_id}, href={self.href}, " + f"priority={self.priority}, category={self.category}, " + f"description={self.description}, external_id={self.external_id}, " + f"customer={self.customer}, service_specification={self.service_specification}" + f"service_instance_name={self.service_instance_name}, state={self.state})") + + @property + def customer(self) -> Customer: + """Get customer object used in service order. + + Returns: + Customer: Customer object + + """ + if not self._customer: + if not self._customer_id: + self._logger.error("No customer ID") + return None + self._customer = Customer.get_by_global_customer_id(self._customer_id) + return self._customer + + @property + def service_specification(self) -> ServiceSpecification: + """Service order service specification used in order item. + + Returns: + ServiceSpecification: Service specification + + """ + if not self._service_specification: + if not self._service_specification_id: + self._logger.error("No service specification") + return None + self._service_specification = ServiceSpecification.\ + get_by_id(self._service_specification_id) + return self._service_specification + + @classmethod + def get_all(cls) -> Iterator["ServiceOrder"]: + """Get all service orders. + + Returns: + Iterator[ServiceOrder]: ServiceOrder object + + """ + for service_order in cls.send_message_json("GET", + "Get all service orders", + f"{cls.base_url}{cls.api_version}/serviceOrder"): + service_order_related_party = None + if service_order.get("relatedParty") is not None: + service_order_related_party = service_order.get( + "relatedParty", [{}])[0].get("id") + + yield ServiceOrder( + unique_id=service_order.get("id"), + href=service_order.get("href"), + priority=service_order.get("priority"), + category=service_order.get("category"), + description=service_order.get("description"), + external_id=service_order.get("externalId"), + customer_id=service_order_related_party, + service_specification_id=service_order.get("orderItem", [{}])[0].get("service")\ + .get("serviceSpecification").get("id"), + service_instance_name=service_order.get("orderItem", [{}])[0].\ + get("service", {}).get("name"), + state=service_order.get("state") + ) + + @classmethod + def create(cls, + customer: Customer, + service_specification: ServiceSpecification, + name: str = None, + external_id: str = None) -> "ServiceOrder": + """Create service order. + + Returns: + ServiceOrder: ServiceOrder object + + """ + if external_id is None: + external_id = str(uuid4()) + if name is None: + name = f"Python_ONAP_SDK_service_instance_{str(uuid4())}" + response: dict = cls.send_message_json( + "POST", + "Add service instance via ServiceOrder API", + f"{cls.base_url}{cls.api_version}/serviceOrder", + data=jinja_env() + .get_template("nbi_service_order_create.json.j2") + .render( + customer=customer, + service_specification=service_specification, + service_instance_name=name, + external_id=external_id, + request_time=get_zulu_time_isoformat() + ) + ) + return cls( + unique_id=response.get("id"), + href=response.get("href"), + priority=response.get("priority"), + description=response.get("description"), + category=response.get("category"), + external_id=response.get("externalId"), + customer=customer, + service_specification=service_specification, + service_instance_name=name + ) + + @property + def status(self) -> "StatusEnum": + """Service order instantiation status. + + It's populated by call Service order endpoint. + + Returns: + StatusEnum: Service order status. + + """ + response: dict = self.send_message_json("GET", + "Get service order status", + (f"{self.base_url}{self.api_version}/" + f"serviceOrder/{self.unique_id}")) + try: + return self.StatusEnum(response.get("state")) + except (KeyError, ValueError): + self._logger.exception("Invalid status") + return self.StatusEnum.UNKNOWN + + @property + def completed(self) -> bool: + """Store an information if service order is completed or not. + + Service orded is completed if it's status is COMPLETED. + + Returns: + bool: True if service orded is completed, False otherwise. + + """ + return self.status == self.StatusEnum.COMPLETED + + @property + def rejected(self) -> bool: + """Store an information if service order is rejected or not. + + Service orded is completed if it's status is REJECTED. + + Returns: + bool: True if service orded is rejected, False otherwise. + + """ + return self.status == self.StatusEnum.REJECTED + + @property + def failed(self) -> bool: + """Store an information if service order is failed or not. + + Service orded is completed if it's status is FAILED. + + Returns: + bool: True if service orded is failed, False otherwise. + + """ + return self.status == self.StatusEnum.FAILED + + @property + def finished(self) -> bool: + """Store an information if service order is finished or not. + + Service orded is finished if it's status is not ACKNOWLEDGED or IN_PROGRESS. + + Returns: + bool: True if service orded is finished, False otherwise. + + """ + return self.status not in [self.StatusEnum.ACKNOWLEDGED, + self.StatusEnum.IN_PROGRESS] diff --git a/src/onapsdk/nbi/templates/nbi_service_order_create.json.j2 b/src/onapsdk/nbi/templates/nbi_service_order_create.json.j2 new file mode 100644 index 0000000..aee124d --- /dev/null +++ b/src/onapsdk/nbi/templates/nbi_service_order_create.json.j2 @@ -0,0 +1,28 @@ +{ + "externalId": "{{ external_id }}", + "priority": "1", + "description": "{{ service_specification.name }} order for {{ customer.global_customer_id }} customer via Python ONAP SDK", + "category": "Consumer", + "requestedStartDate": "{{ request_time }}", + "requestedCompletionDate": "{{ request_time }}", + "relatedParty": [ + { + "id": "{{ customer.global_customer_id }}", + "role": "ONAPcustomer", + "name": "{{ customer.global_customer_id }}" + } + ], + "orderItem": [ + { + "id": "1", + "action": "add", + "service": { + "name": "{{ service_instance_name }}", + "serviceState": "active", + "serviceSpecification": { + "id": "{{ service_specification.unique_id }}" + } + } + } + ] +} \ No newline at end of file diff --git a/src/onapsdk/onap_service.py b/src/onapsdk/onap_service.py new file mode 100644 index 0000000..9298715 --- /dev/null +++ b/src/onapsdk/onap_service.py @@ -0,0 +1,327 @@ +"""ONAP Service module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 abc import ABC +from dataclasses import dataclass, field +from typing import Any, Callable, Dict, Iterator, List, Optional, Union + +import logging +import requests +import urllib3 +from urllib3.util.retry import Retry +import simplejson.errors + +from requests.adapters import HTTPAdapter +from requests import ( # pylint: disable=redefined-builtin + HTTPError, RequestException, ConnectionError +) + +from onapsdk.exceptions import ( + RequestError, APIError, ResourceNotFound, InvalidResponse, + ConnectionFailed, NoGuiError +) + +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + +class OnapService(ABC): + """ + Mother Class of all ONAP services. + + An important attribute when inheriting from this class is `_jinja_env`. + it allows to fetch simply jinja templates where they are. + by default jinja engine will look for templates in `templates` directory of + the package. + See in Examples to see how to use. + + Attributes: + server (str): nickname of the server we send the request. Used in logs + strings. For example, 'SDC' is the nickame for SDC server. + headers (Dict[str, str]): the headers dictionnary to use. + proxy (Dict[str, str]): the proxy configuration if needed. + permanent_headers (Optional[Dict[str, str]]): optional dictionary of + headers which could be set by the user and which are **always** + added into sended request. Unlike the `headers`, which could be + overrided on `send_message` call these headers are constant. + + """ + + @dataclass + class PermanentHeadersCollection: + """Collection to store permanent headers.""" + + ph_dict: Dict[str, Any] = field(default_factory=dict) + ph_call: List[Callable] = field(default_factory=list) + + def __iter__(self) -> Iterator[Dict[str, any]]: + """Iterate through the headers. + + For dictionary based headers just return the dict and + for the callables iterate through the list of them, + call them and yield the result. + """ + yield self.ph_dict + for ph_call in self.ph_call: + yield ph_call() + + _logger: logging.Logger = logging.getLogger(__qualname__) + server: str = None + headers: Dict[str, str] = { + "Content-Type": "application/json", + "Accept": "application/json", + } + proxy: Dict[str, str] = None + permanent_headers: PermanentHeadersCollection = PermanentHeadersCollection() + + def __init_subclass__(cls): + """Subclass initialization. + + Add _logger property for any OnapService with it's class name as a logger name + """ + super().__init_subclass__() + cls._logger: logging.Logger = logging.getLogger(cls.__qualname__) + + def __init__(self) -> None: + """Initialize the service.""" + + @classmethod + def send_message(cls, method: str, action: str, url: str, # pylint: disable=too-many-locals + **kwargs) -> Union[requests.Response, None]: + """ + Send a message to an ONAP service. + + Args: + method (str): which method to use (GET, POST, PUT, PATCH, ...) + action (str): what action are we doing, used in logs strings. + url (str): the url to use + exception (Exception, optional): if an error occurs, raise the + exception given instead of RequestError + **kwargs: Arbitrary keyword arguments. any arguments used by + requests can be used here. + + Raises: + RequestError: if other exceptions weren't caught or didn't raise, + or if there was an ambiguous exception by a request + ResourceNotFound: 404 returned + APIError: returned an error code within 400 and 599, except 404 + ConnectionFailed: connection can't be established + + Returns: + the request response if OK + + """ + cert = kwargs.pop('cert', None) + basic_auth: Dict[str, str] = kwargs.pop('basic_auth', None) + exception = kwargs.pop('exception', None) + headers = kwargs.pop('headers', cls.headers).copy() + if OnapService.permanent_headers: + for header in OnapService.permanent_headers: + headers.update(header) + data = kwargs.get('data', None) + try: + # build the request with the requested method + session = cls.__requests_retry_session() + if cert: + session.cert = cert + OnapService._set_basic_auth_if_needed(basic_auth, session) + + cls._logger.debug("[%s][%s] sent header: %s", cls.server, action, + headers) + cls._logger.debug("[%s][%s] url used: %s", cls.server, action, url) + cls._logger.debug("[%s][%s] data sent: %s", cls.server, action, + data) + + response = session.request(method, + url, + headers=headers, + verify=False, + proxies=cls.proxy, + **kwargs) + + cls._logger.info( + "[%s][%s] response code: %s", + cls.server, action, + response.status_code if response is not None else "n/a") + cls._logger.debug( + "[%s][%s] response: %s", + cls.server, action, + response.text if (response is not None and + response.headers.get("Content-Type", "") in \ + ["application/json", "text/plain"]) else "n/a") + + response.raise_for_status() + return response + + except HTTPError as cause: + cls._logger.error("[%s][%s] API returned and error: %s", + cls.server, action, headers) + + msg = f'Code: {cause.response.status_code}. Info: {cause.response.text}.' + + if cause.response.status_code == 404: + exc = ResourceNotFound(msg) + else: + exc = APIError(msg) + + exc.response_status_code = cause.response.status_code + + raise exc from cause + + except ConnectionError as cause: + cls._logger.error("[%s][%s] Failed to connect: %s", cls.server, + action, cause) + + msg = f"Can't connect to {url}." + raise ConnectionFailed(msg) from cause + + except RequestException as cause: + cls._logger.error("[%s][%s] Request failed: %s", + cls.server, action, cause) + + if not exception: + msg = f"Ambiguous error while requesting {url}." + raise RequestError(msg) + + raise exception + + @classmethod + def _set_basic_auth_if_needed(cls, basic_auth, session): + if basic_auth: + session.auth = (basic_auth.get('username'), + basic_auth.get('password')) + + @classmethod + def send_message_json(cls, method: str, action: str, url: str, + **kwargs) -> Dict[Any, Any]: + """ + Send a message to an ONAP service and parse the response as JSON. + + Args: + method (str): which method to use (GET, POST, PUT, PATCH, ...) + action (str): what action are we doing, used in logs strings. + url (str): the url to use + exception (Exception, optional): if an error occurs, raise the + exception given + **kwargs: Arbitrary keyword arguments. any arguments used by + requests can be used here. + + Raises: + InvalidResponse: if JSON coudn't be decoded + RequestError: if other exceptions weren't caught or didn't raise + APIError/ResourceNotFound: send_message() got an HTTP error code + ConnectionFailed: connection can't be established + RequestError: send_message() raised an ambiguous exception + + + Returns: + the response body in dict format if OK + + """ + exception = kwargs.get('exception', None) + try: + + response = cls.send_message(method, action, url, **kwargs) + + if response: + return response.json() + + except simplejson.errors.JSONDecodeError as cause: + cls._logger.error("[%s][%s]Failed to decode JSON: %s", cls.server, + action, cause) + raise InvalidResponse from cause + + except RequestError as exc: + cls._logger.error("[%s][%s] request failed: %s", + cls.server, action, exc) + if not exception: + exception = exc + + raise exception + + @staticmethod + def __requests_retry_session(retries: int = 10, + backoff_factor: float = 0.3, + session: requests.Session = None + ) -> requests.Session: + """ + Create a request Session with retries. + + Args: + retries (int, optional): number of retries. Defaults to 10. + backoff_factor (float, optional): backoff_factor. Defaults to 0.3. + session (requests.Session, optional): an existing session to + enhance. Defaults to None. + + Returns: + requests.Session: the session with retries set + + """ + session = session or requests.Session() + retry = Retry( + total=retries, + read=retries, + connect=retries, + backoff_factor=backoff_factor, + ) + adapter = HTTPAdapter(max_retries=retry) + session.mount('http://', adapter) + session.mount('https://', adapter) + return session + + @staticmethod + def set_proxy(proxy: Dict[str, str]) -> None: + """ + Set the proxy for Onap Services rest calls. + + Args: + proxy (Dict[str, str]): the proxy configuration + + Examples: + >>> OnapService.set_proxy({ + ... 'http': 'socks5h://127.0.0.1:8082', + ... 'https': 'socks5h://127.0.0.1:8082'}) + + """ + OnapService.proxy = proxy + + @staticmethod + def set_header(header: Optional[Union[Dict[str, Any], Callable]] = None) -> None: + """Set the header which will be always send on request. + + The header can be: + * dictionary - will be used same dictionary for each request + * callable - a method which is going to be called every time on request + creation. Could be useful if you need to connect with ONAP through some API + gateway and you need to take care about authentication. The callable shouldn't + require any parameters + * None - reset headers + + Args: + header (Optional[Union[Dict[str, Any], Callable]]): header to set. Defaults to None + + """ + if not header: + OnapService._logger.debug("Reset headers") + OnapService.permanent_headers = OnapService.PermanentHeadersCollection() + return + if callable(header): + OnapService.permanent_headers.ph_call.append(header) + else: + OnapService.permanent_headers.ph_dict.update(header) + OnapService._logger.debug("Set permanent header %s", header) + + @classmethod + def get_guis(cls): + """Return the list of GUI and its status.""" + raise NoGuiError diff --git a/src/onapsdk/sdc/__init__.py b/src/onapsdk/sdc/__init__.py new file mode 100644 index 0000000..15280d9 --- /dev/null +++ b/src/onapsdk/sdc/__init__.py @@ -0,0 +1,486 @@ +"""SDC Element module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Any, Dict, List, Optional, Union +from operator import attrgetter +from abc import ABC, abstractmethod + +from requests import Response + +from onapsdk.configuration import settings +from onapsdk.exceptions import APIError, RequestError +from onapsdk.onap_service import OnapService +import onapsdk.constants as const +from onapsdk.utils.jinja import jinja_env +from onapsdk.utils.gui import GuiItem, GuiList + +class SDC(OnapService, ABC): + """Mother Class of all SDC elements.""" + + server: str = "SDC" + base_front_url = settings.SDC_FE_URL + base_back_url = settings.SDC_BE_URL + + def __init__(self, name: str = None) -> None: + """Initialize SDC.""" + super().__init__() + self.name: str = name + + def __eq__(self, other: Any) -> bool: + """ + Check equality for SDC and children. + + Args: + other: another object + + Returns: + bool: True if same object, False if not + + """ + if isinstance(other, type(self)): + return self.name == other.name + return False + + @classmethod + @abstractmethod + def _get_all_url(cls) -> str: + """ + Get URL for all elements in SDC. + + Raises: + NotImplementedError: this is an abstract method. + + """ + + @classmethod + @abstractmethod + def _get_objects_list(cls, + result: List[Dict[str, Any]]) -> List['SdcResource']: + """ + Import objects created in SDC. + + Args: + result (Dict[str, Any]): the result returned by SDC in a Dict + + Raises: + NotImplementedError: this is an abstract method. + + """ + + @classmethod + @abstractmethod + def _base_url(cls) -> str: + """ + Give back the base url of Sdc. + + Raises: + NotImplementedError: this is an abstract method. + + """ + + @classmethod + @abstractmethod + def _base_create_url(cls) -> str: + """ + Give back the base url of Sdc. + + Raises: + NotImplementedError: this is an abstract method. + + """ + + @abstractmethod + def _copy_object(self, obj: 'SDC') -> None: + """ + Copy relevant properties from object. + + Args: + obj (Sdc): the object to "copy" + + Raises: + NotImplementedError: this is an abstract method. + + """ + + @classmethod + @abstractmethod + def import_from_sdc(cls, values: Dict[str, Any]) -> 'SDC': + """ + Import Sdc object from SDC. + + Args: + values (Dict[str, Any]): dict to parse returned from SDC. + + Raises: + NotImplementedError: this is an abstract method. + + """ + + @staticmethod + def _get_mapped_version(item: "SDC") -> Optional[Union[float, str]]: + """Map Sdc objects version to float. + + Mostly we need to get the newest version of the requested objects. To do + so we use the version property of them. In most cases it's string + formatted float value, but in some cases (like VSP objects) it isn't. + That method checks if given object has "version" attribute and if it's not + a None it tries to map it's value to float. If it's not possible it + returns the alrady existing value. + + Args: + item (SDC): SDC item to map version to float + + Returns: + Optional[Union[float, str]]: Float format version if possible, + string otherwise. If object doesn't have "version" + attribut returns None. + + """ + if hasattr(item, "version") and item.version is not None: + try: + return float(item.version) + except ValueError: + return item.version + else: + return None + + @classmethod + def get_all(cls, **kwargs) -> List['SDC']: + """ + Get the objects list created in SDC. + + Returns: + the list of the objects + + """ + cls._logger.info("retrieving all objects of type %s from SDC", + cls.__name__) + url = cls._get_all_url() + objects = [] + + try: + result = \ + cls.send_message_json('GET', "get {}s".format(cls.__name__), + url, **kwargs) + + for obj_info in cls._get_objects_list(result): + objects.append(cls.import_from_sdc(obj_info)) + + except APIError as exc: + cls._logger.debug("Couldn't get %s: %s", cls.__name__, exc) + except KeyError as exc: + cls._logger.debug("Invalid result dictionary: %s", exc) + + cls._logger.debug("number of %s returned: %s", cls.__name__, + len(objects)) + return objects + + def exists(self) -> bool: + """ + Check if object already exists in SDC and update infos. + + Returns: + True if exists, False either + + """ + self._logger.debug("check if %s %s exists in SDC", + type(self).__name__, self.name) + objects = self.get_all() + + self._logger.debug("filtering objects of all versions to be %s", + self.name) + relevant_objects = list(filter(lambda obj: obj == self, objects)) + + if not relevant_objects: + + self._logger.info("%s %s doesn't exist in SDC", + type(self).__name__, self.name) + return False + + if hasattr(self, 'version_filter') and self.version_filter is not None: # pylint: disable=no-member + + self._logger.debug("filtering %s objects by version %s", + self.name, self.version_filter) # pylint: disable=no-member + + all_versioned = filter( + lambda obj: obj.version == self.version_filter, relevant_objects) # pylint: disable=no-member + + try: + versioned_object = next(all_versioned) + except StopIteration: + self._logger.info("Version %s of %s %s, doesn't exist in SDC", + self.version_filter, type(self).__name__, # pylint: disable=no-member + self.name) + return False + + else: + versioned_object = max(relevant_objects, key=self._get_mapped_version) + + self._logger.info("%s found, updating information", type(self).__name__) + self._copy_object(versioned_object) + return True + + @classmethod + def get_guis(cls) -> GuiItem: + """Retrieve the status of the SDC GUIs. + + Only one GUI is referenced for SDC + the SDC Front End + + Return the list of GUIs + """ + gui_url = settings.SDC_GUI_SERVICE + sdc_gui_response = cls.send_message( + "GET", "Get SDC GUI Status", gui_url) + guilist = GuiList([]) + guilist.add(GuiItem( + gui_url, + sdc_gui_response.status_code)) + return guilist + +class SdcOnboardable(SDC, ABC): + """Base class for onboardable SDC resources (Vendors, Services, ...).""" + + ACTION_TEMPLATE: str + ACTION_METHOD: str + + def __init__(self, name: str = None) -> None: + """Initialize the object.""" + super().__init__(name) + self._identifier: str = None + self._status: str = None + self._version: str = None + + @property + def identifier(self) -> str: + """Return and lazy load the identifier.""" + if not self._identifier: + self.load() + return self._identifier + + @property + def status(self) -> str: + """Return and lazy load the status.""" + if self.created() and not self._status: + self.load() + return self._status + + @property + def version(self) -> str: + """Return and lazy load the version.""" + if self.created() and not self._version: + self.load() + return self._version + + @identifier.setter + def identifier(self, value: str) -> None: + """Set value for identifier.""" + self._identifier = value + + @status.setter + def status(self, status: str) -> None: + """Return and lazy load the status.""" + self._status = status + + @version.setter + def version(self, version: str) -> None: + """Return and lazy load the status.""" + self._version = version + + def created(self) -> bool: + """Determine if SDC is created.""" + if self.name and not self._identifier: + return self.exists() + return bool(self._identifier) + + def submit(self) -> None: + """Submit the SDC object in order to enable it.""" + self._logger.info("attempting to certify/sumbit %s %s in SDC", + type(self).__name__, self.name) + if self.status != const.CERTIFIED and self.created(): + self._really_submit() + elif self.status == const.CERTIFIED: + self._logger.warning("%s %s in SDC is already submitted/certified", + type(self).__name__, self.name) + elif not self.created(): + self._logger.warning("%s %s in SDC is not created", + type(self).__name__, self.name) + + def _create(self, template_name: str, **kwargs) -> None: + """Create the object in SDC if not already existing.""" + self._logger.info("attempting to create %s %s in SDC", + type(self).__name__, self.name) + if not self.exists(): + url = "{}/{}".format(self._base_create_url(), self._sdc_path()) + template = jinja_env().get_template(template_name) + data = template.render(**kwargs) + try: + create_result = self.send_message_json('POST', + "create {}".format( + type(self).__name__), + url, + data=data) + except RequestError as exc: + self._logger.error( + "an error occured during creation of %s %s in SDC", + type(self).__name__, self.name) + raise exc + else: + self._logger.info("%s %s is created in SDC", + type(self).__name__, self.name) + self._status = const.DRAFT + self.identifier = self._get_identifier_from_sdc(create_result) + self._version = self._get_version_from_sdc(create_result) + self.update_informations_from_sdc_creation(create_result) + + else: + self._logger.warning("%s %s is already created in SDC", + type(self).__name__, self.name) + + def _action_to_sdc(self, action: str, action_type: str = None, + **kwargs) -> Response: + """ + Really do an action in the SDC. + + Args: + action (str): the action to perform + action_type (str, optional): the type of action + headers (Dict[str, str], optional): headers to use if any + + Returns: + Response: the response + + """ + subpath = self._generate_action_subpath(action) + url = self._action_url(self._base_create_url(), + subpath, + self._version_path(), + action_type=action_type) + template = jinja_env().get_template(self.ACTION_TEMPLATE) + data = template.render(action=action, const=const) + + return self.send_message(self.ACTION_METHOD, + "{} {}".format(action, + type(self).__name__), + url, + data=data, + **kwargs) + + @abstractmethod + def update_informations_from_sdc(self, details: Dict[str, Any]) -> None: + """ + + Update instance with details from SDC. + + Args: + details ([type]): [description] + + """ + @abstractmethod + def update_informations_from_sdc_creation(self, + details: Dict[str, Any]) -> None: + """ + + Update instance with details from SDC after creation. + + Args: + details ([type]): the details from SDC + + """ + + @abstractmethod + def load(self) -> None: + """ + Load Object information from SDC. + + Raises: + NotImplementedError: this is an abstract method. + + """ + + @abstractmethod + def _get_version_from_sdc(self, sdc_infos: Dict[str, Any]) -> str: + """ + Get version from SDC results. + + Args: + sdc_infos (Dict[str, Any]): the result dict from SDC + + Raises: + NotImplementedError: this is an abstract method. + + """ + @abstractmethod + def _get_identifier_from_sdc(self, sdc_infos: Dict[str, Any]) -> str: + """ + Get identifier from SDC results. + + Args: + sdc_infos (Dict[str, Any]): the result dict from SDC + + Raises: + NotImplementedError: this is an abstract method. + + """ + @abstractmethod + def _generate_action_subpath(self, action: str) -> str: + """ + + Generate subpath part of SDC action url. + + Args: + action (str): the action that will be done + + Raises: + NotImplementedError: this is an abstract method. + + """ + @abstractmethod + def _version_path(self) -> str: + """ + Give the end of the path for a version. + + Raises: + NotImplementedError: this is an abstract method. + + """ + @abstractmethod + def _really_submit(self) -> None: + """Really submit the SDC Vf in order to enable it.""" + @staticmethod + @abstractmethod + def _action_url(base: str, + subpath: str, + version_path: str, + action_type: str = None) -> str: + """ + Generate action URL for SDC. + + Raises: + NotImplementedError: this is an abstract method. + + """ + @classmethod + @abstractmethod + def _sdc_path(cls) -> None: + """Give back the end of SDC path.""" + + @abstractmethod + def onboard(self) -> None: + """Onboard resource. + + Onboarding is a full stack of actions which needs to be done to + make SDC resource ready to use. It depends on the type of object + but most of them needs to be created and submitted. + """ diff --git a/src/onapsdk/sdc/category_management.py b/src/onapsdk/sdc/category_management.py new file mode 100644 index 0000000..c2d63b4 --- /dev/null +++ b/src/onapsdk/sdc/category_management.py @@ -0,0 +1,285 @@ +"""SDC category management module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 +from abc import ABC, abstractmethod +from typing import Any, Dict, List + +from onapsdk.configuration import settings +from onapsdk.exceptions import ResourceNotFound +from onapsdk.sdc import SDC +from onapsdk.utils.headers_creator import headers_sdc_generic + + +class BaseCategory(SDC, ABC): # pylint: disable=too-many-instance-attributes + """Base SDC category class. + + It's SDC admin resource, has no common properties with + SDC resourcer or elements, so SDC class can't be it's + base class. + + """ + + SDC_ADMIN_USER = "demo" + + def __init__(self, name: str) -> None: + """Service category initialization. + + Args: + name (str): Service category name. + + """ + super().__init__(name) + self.normalized_name: str = None + self.unique_id: str = None + self.icons: List[str] = None + self.subcategories: List[Dict[str, str]] = None + self.version: str = None + self.owner_id: str = None + self.empty: bool = None + self.type: str = None + + @classmethod + def _get_all_url(cls) -> str: + """Get URL for all categories in SDC.""" + return f"{cls.base_front_url}/sdc1/feProxy/rest/v1/setup/ui" + + @classmethod + def _base_url(cls) -> str: + """Give back the base url of Sdc.""" + return f"{settings.SDC_FE_URL}/sdc1/feProxy/rest/v1/category" + + @classmethod + def headers(cls) -> Dict[str, str]: + """Headers used for category management. + + It uses SDC admin user. + + Returns: + Dict[str, str]: Headers + + """ + return headers_sdc_generic(super().headers, user=cls.SDC_ADMIN_USER) + + @classmethod + def get_all(cls, **kwargs) -> List['SDC']: + """ + Get the categories list created in SDC. + + Returns: + the list of the categories + + """ + return super().get_all(headers=cls.headers()) + + @classmethod + def import_from_sdc(cls, values: Dict[str, Any]) -> 'BaseCategory': + """ + Import category object from SDC. + + Args: + values (Dict[str, Any]): dict to parse returned from SDC. + + """ + category_obj = cls(name=values["name"]) + category_obj.normalized_name = values["normalizedName"] + category_obj.unique_id = values["uniqueId"] + category_obj.icons = values["icons"] + category_obj.subcategories = values["subcategories"] + category_obj.version = values["version"] + category_obj.owner_id = values["ownerId"] + category_obj.empty = values["empty"] + return category_obj + + @classmethod + @abstractmethod + def category_name(cls) -> str: + """Class category name. + + Used for logs. + + Returns: + str: Category name + + """ + + @classmethod + def get(cls, name: str) -> "BaseCategory": + """Get category with given name. + + Raises: + ResourceNotFound: Category with given name does not exist + + Returns: + BaseCategory: BaseCategory instance + + """ + category_obj: "BaseCategory" = cls(name) + if category_obj.exists(): + return category_obj + msg = f"{cls.category_name()} with \"{name}\" name does not exist." + raise ResourceNotFound(msg) + + @classmethod + def create(cls, name: str) -> "BaseCategory": + """Create category instance. + + Checks if category with given name exists and if it already + exists just returns category with given name. + + Returns: + BaseCategory: Created category instance + + """ + category_obj: "BaseCategory" = cls(name) + if category_obj.exists(): + return category_obj + cls.send_message_json("POST", + f"Create {name} {cls.category_name()}", + cls._base_create_url(), + data=json.dumps({"name": name}), + headers=cls.headers()) + category_obj.exists() + return category_obj + + def _copy_object(self, obj: 'BaseCategory') -> None: + """ + Copy relevant properties from object. + + Args: + obj (BaseCategory): the object to "copy" + + """ + self.name = obj.name + self.normalized_name = obj.normalized_name + self.unique_id = obj.unique_id + self.icons = obj.icons + self.subcategories = obj.subcategories + self.version = obj.version + self.owner_id = obj.owner_id + self.empty = obj.empty + + +class ResourceCategory(BaseCategory): + """Resource category class.""" + + @classmethod + def _get_objects_list(cls, + result: Dict[str, Any]) -> List[Dict[str, Any]]: + """ + Get list of resource categories. + + Args: + result (List[Dict[str, Any]]): the result returned by SDC + in a list of dicts + + Raises: + KeyError: Invalid result dictionary + + """ + return result["categories"]["resourceCategories"] + + @classmethod + def _base_create_url(cls) -> str: + """Url to create resource category. + + Returns: + str: Creation url + + """ + return f"{cls._base_url()}/resources" + + @classmethod + def category_name(cls) -> str: + """Resource category name. + + Used for logging. + + Returns: + str: Resource category name + + """ + return "Resource Category" + + @classmethod + def get(cls, name: str, subcategory: str = None) -> "ResourceCategory": # pylint: disable=arguments-differ + """Get resource category with given name. + + It returns resource category with all subcategories by default. You can + get resource category with only one subcategory if you provide it's + name as `subcategory` parameter. + + Args: + name (str): Resource category name. + subcategory (str, optional): Name of subcategory. Defaults to None. + + Raises: + ResourceNotFound: Subcategory with given name does not exist + + Returns: + BaseCategory: BaseCategory instance + + """ + category_obj: "ResourceCategory" = super().get(name=name) + if not subcategory: + return category_obj + filtered_subcategories: Dict[str, str] = list(filter(lambda x: x["name"] == subcategory, + category_obj.subcategories)) + if not filtered_subcategories: + raise ResourceNotFound(f"Subcategory {subcategory} does not exist.") + category_obj.subcategories = filtered_subcategories + return category_obj + + +class ServiceCategory(BaseCategory): + """Service category class.""" + + @classmethod + def _get_objects_list(cls, + result: Dict[str, Any]) -> List[Dict[str, Any]]: + """ + Get list of service categories. + + Args: + result (List[Dict[str, Any]]): the result returned by SDC + in a list of dicts + + Raises: + KeyError: Invalid result dictionary + + """ + return result["categories"]["serviceCategories"] + + @classmethod + def _base_create_url(cls) -> str: + """Url to create service category. + + Returns: + str: Creation url + + """ + return f"{cls._base_url()}/services" + + @classmethod + def category_name(cls) -> str: + """Service category name. + + Used for logging. + + Returns: + str: Service category name + + """ + return "Service Category" diff --git a/src/onapsdk/sdc/component.py b/src/onapsdk/sdc/component.py new file mode 100644 index 0000000..9b26bef --- /dev/null +++ b/src/onapsdk/sdc/component.py @@ -0,0 +1,162 @@ +"""SDC Component module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 dataclasses import dataclass +from typing import Any, Dict, Iterator, List, Optional +from onapsdk.exceptions import ParameterError + +from onapsdk.sdc.properties import ComponentProperty +from onapsdk.utils.jinja import jinja_env + + +@dataclass +class Component: # pylint: disable=too-many-instance-attributes + """Component dataclass.""" + + created_from_csar: bool + actual_component_uid: str + unique_id: str + normalized_name: str + name: str + origin_type: str + customization_uuid: str + component_uid: str + component_version: str + tosca_component_name: str + component_name: str + group_instances: Optional[List[Dict[str, Any]]] + sdc_resource: "SdcResource" + parent_sdc_resource: "SdcResource" + + @classmethod + def create_from_api_response(cls, + api_response: Dict[str, Any], + sdc_resource: "SdcResource", + parent_sdc_resource: "SdcResource") -> "Component": + """Create component from api response. + + Args: + api_response (Dict[str, Any]): component API response + sdc_resource (SdcResource): component's SDC resource + parent_sdc_resource (SdcResource): component's parent SDC resource + + Returns: + Component: Component created using api_response and SDC resource + + """ + return cls(created_from_csar=api_response["createdFromCsar"], + actual_component_uid=api_response["actualComponentUid"], + unique_id=api_response["uniqueId"], + normalized_name=api_response["normalizedName"], + name=api_response["name"], + origin_type=api_response["originType"], + customization_uuid=api_response["customizationUUID"], + component_uid=api_response["componentUid"], + component_version=api_response["componentVersion"], + tosca_component_name=api_response["toscaComponentName"], + component_name=api_response["componentName"], + group_instances=api_response["groupInstances"], + sdc_resource=sdc_resource, + parent_sdc_resource=parent_sdc_resource) + + @property + def properties_url(self) -> str: + """Url to get component's properties. + + Returns: + str: Compoent's properties url + + """ + return self.parent_sdc_resource.get_component_properties_url(self) + + @property + def properties_value_url(self) -> str: + """Url to set component property value. + + Returns: + str: Url to set component property value + + """ + return self.parent_sdc_resource.get_component_properties_value_set_url(self) + + @property + def properties(self) -> Iterator["ComponentProperty"]: + """Component properties. + + In SDC it's named as properties, but we uses "inputs" endpoint to fetch them. + Structure is also input's like, but it's a property. + + Yields: + ComponentProperty: Component property object + + """ + for component_property in self.sdc_resource.send_message_json(\ + "GET", + f"Get {self.name} component properties", + self.properties_url): + yield ComponentProperty(unique_id=component_property["uniqueId"], + name=component_property["name"], + property_type=component_property["type"], + _value=component_property.get("value"), + component=self) + + def get_property(self, property_name: str) -> "ComponentProperty": + """Get component property by it's name. + + Args: + property_name (str): property name + + Raises: + ParameterError: Component has no property with given name + + Returns: + ComponentProperty: Component's property object + + """ + for property_obj in self.properties: + if property_obj.name == property_name: + return property_obj + msg = f"Component has no property with {property_name} name" + raise ParameterError(msg) + + def set_property_value(self, property_obj: "ComponentProperty", value: Any) -> None: + """Set property value. + + Set given value to component property + + Args: + property_obj (ComponentProperty): Component property object + value (Any): Property value to set + + """ + self.sdc_resource.send_message_json( + "POST", + f"Set {self.name} component property {property_obj.name} value", + self.properties_value_url, + data=jinja_env().get_template(\ + "sdc_resource_component_set_property_value.json.j2").\ + render( + component=self, + value=value, + property=property_obj + ) + ) + + def delete(self) -> None: + """Delete component.""" + self.sdc_resource.send_message_json( + "DELETE", + f"Delete {self.name} component", + f"{self.parent_sdc_resource.resource_inputs_url}/resourceInstance/{self.unique_id}" + ) diff --git a/src/onapsdk/sdc/pnf.py b/src/onapsdk/sdc/pnf.py new file mode 100644 index 0000000..3fe4657 --- /dev/null +++ b/src/onapsdk/sdc/pnf.py @@ -0,0 +1,74 @@ +"""Pnf module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Dict, List, Union +from onapsdk.exceptions import ParameterError + +from onapsdk.sdc.sdc_resource import SdcResource +from onapsdk.sdc.properties import NestedInput, Property +import onapsdk.constants as const +from onapsdk.sdc.vendor import Vendor +from onapsdk.sdc.vsp import Vsp + + +class Pnf(SdcResource): + """ + ONAP PNF Object used for SDC operations. + + Attributes: + name (str): the name of the pnf. Defaults to "ONAP-test-PNF". + identifier (str): the unique ID of the pnf from SDC. + status (str): the status of the pnf from SDC. + version (str): the version ID of the vendor from SDC. + uuid (str): the UUID of the PNF (which is different from identifier, + don't ask why...) + unique_identifier (str): Yet Another ID, just to puzzle us... + vendor (optional): the vendor of the PNF + vsp (optional): the vsp related to the PNF + + """ + + def __init__(self, name: str = None, version: str = None, vendor: Vendor = None, # pylint: disable=too-many-arguments + sdc_values: Dict[str, str] = None, vsp: Vsp = None, + properties: List[Property] = None, inputs: Union[Property, NestedInput] = None, + category: str = None, subcategory: str = None): + """ + Initialize pnf object. + + Args: + name (optional): the name of the pnf + version (str, optional): the version of a PNF object + + """ + super().__init__(sdc_values=sdc_values, version=version, properties=properties, + inputs=inputs, category=category, subcategory=subcategory) + self.name: str = name or "ONAP-test-PNF" + self.vendor: Vendor = vendor + self.vsp: Vsp = vsp + + def create(self) -> None: + """Create the PNF in SDC if not already existing.""" + if not self.vsp and not self.vendor: + raise ParameterError("Neither Vsp nor Vendor provided.") + self._create("pnf_create.json.j2", + name=self.name, + vsp=self.vsp, + vendor=self.vendor, + category=self.category) + + def _really_submit(self) -> None: + """Really submit the SDC PNF in order to enable it.""" + result = self._action_to_sdc(const.CERTIFY, "lifecycleState") + if result: + self.load() diff --git a/src/onapsdk/sdc/properties.py b/src/onapsdk/sdc/properties.py new file mode 100644 index 0000000..f7e07a0 --- /dev/null +++ b/src/onapsdk/sdc/properties.py @@ -0,0 +1,202 @@ +"""Service properties module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 dataclasses import dataclass, field +from typing import Any, Dict, List, Optional + +from onapsdk.exceptions import ParameterError + + +@dataclass +class Input: + """Property input dataclass.""" + + unique_id: str + input_type: str + name: str + sdc_resource: "SdcResource" + _default_value: Optional[Any] = field(repr=False, default=None) + + @property + def default_value(self) -> Any: + """Input default value. + + Returns: + Any: Input default value + + """ + return self._default_value + + @default_value.setter + def default_value(self, value: Any) -> None: + """Set input default value. + + Use related sdc_resource "set_input_default_value" + method to set default value in SDC. We use that + because different types of SDC resources has different + urls to call SDC API. + + Args: + value (Any): Default value to set + + """ + self.sdc_resource.set_input_default_value(self, value) + self._default_value = value + +@dataclass +class NestedInput: + """Dataclass used for nested input declaration.""" + + sdc_resource: "SdcResource" + input_obj: Input + + +class Property: # pylint: disable=too-many-instance-attributes, too-few-public-methods + """Service property class.""" + + def __init__(self, # pylint: disable=too-many-arguments + name: str, + property_type: str, + description: Optional[str] = None, + unique_id: Optional[str] = None, + parent_unique_id: Optional[str] = None, + sdc_resource: Optional["SdcResource"] = None, + value: Optional[Any] = None, + get_input_values: Optional[List[Dict[str, str]]] = None) -> None: + """Property class initialization. + + Args: + property_type (str): [description] + description (Optional[str], optional): [description]. Defaults to None. + unique_id (Optional[str], optional): [description]. Defaults to None. + parent_unique_id (Optional[str], optional): [description]. Defaults to None. + sdc_resource (Optional[, optional): [description]. Defaults to None. + value (Optional[Any], optional): [description]. Defaults to None. + get_input_values (Optional[List[Dict[str, str]]], optional): [description]. + Defaults to None. + """ + self.name: str = name + self.property_type: str = property_type + self.description: str = description + self.unique_id: str = unique_id + self.parent_unique_id: str = parent_unique_id + self.sdc_resource: "SdcResource" = sdc_resource + self._value: Any = value + self.get_input_values: List[Dict[str, str]] = get_input_values + + def __repr__(self) -> str: + """Property object human readable representation. + + Returns: + str: Property human readable representation + + """ + return f"Property(name={self.name}, property_type={self.property_type})" + + def __eq__(self, obj: "Property") -> bool: + """Check if two Property object are equal. + + Args: + obj (Property): Object to compare + + Returns: + bool: True if objects are equal, False otherwise + + """ + return self.name == obj.name and self.property_type == obj.property_type + + @property + def input(self) -> Input: + """Property input. + + Returns property Input object. + Returns None if property has no associated input. + + Raises: + ParameterError: Input has no associated SdcResource + + ParameterError: Input for given property does not exits. + It shouldn't ever happen, but it's possible if after you + get property object someone delete input. + + Returns: + Input: Property input object. + + """ + if not self.sdc_resource: + raise ParameterError("Property has no associated SdcResource") + if not self.get_input_values: + return None + try: + return next(filter(lambda x: x.unique_id == self.get_input_values[0].get("inputId"), + self.sdc_resource.inputs)) + except StopIteration: + raise ParameterError("Property input does not exist") + + @property + def value(self) -> Any: + """Value property. + + Get property value. + + Returns: + Any: Property value + + """ + return self._value + + @value.setter + def value(self, val: Any) -> Any: + if self.sdc_resource: + self.sdc_resource.set_property_value(self, val) + self._value = val + + +@dataclass +class ComponentProperty: + """Component property dataclass. + + Component properties are inputs objects in SDC, but in logic + it's a property. + + """ + + unique_id: str + property_type: str + name: str + component: "Component" + _value: Optional[Any] = field(repr=False, default=None) + + @property + def value(self) -> Any: + """Property value getter. + + Returns: + Any: Property value + + """ + return self._value + + @value.setter + def value(self, val: Any) -> None: + """Property value setter. + + Set value both in an object and in SDC using it's HTTP API. + + Args: + val (Any): Property value to set + + """ + self.component.set_property_value(self, val) + self._value = val diff --git a/src/onapsdk/sdc/sdc_element.py b/src/onapsdk/sdc/sdc_element.py new file mode 100644 index 0000000..df513b7 --- /dev/null +++ b/src/onapsdk/sdc/sdc_element.py @@ -0,0 +1,227 @@ +"""SDC Element module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 abc import ABC, abstractmethod +from operator import itemgetter +from typing import Any, Dict, List, Optional + +from onapsdk.sdc import SdcOnboardable +import onapsdk.constants as const + + +class SdcElement(SdcOnboardable, ABC): + """Mother Class of all SDC elements.""" + + ACTION_TEMPLATE = 'sdc_element_action.json.j2' + ACTION_METHOD = 'PUT' + + def __init__(self, name: str = None) -> None: + """Initialize the object.""" + super().__init__(name=name) + self.human_readable_version: Optional[str] = None + + def _get_item_details(self) -> Dict[str, Any]: + """ + Get item details. + + Returns: + Dict[str, Any]: the description of the item + + """ + if self.created(): + url = "{}/items/{}/versions".format(self._base_url(), + self.identifier) + results: Dict[str, Any] = self.send_message_json('GET', 'get item', url) + if results["listCount"] > 1: + items: List[Dict[str, Any]] = results["results"] + return sorted(items, key=itemgetter("creationTime"), reverse=True)[0] + return results["results"][0] + return {} + + def load(self) -> None: + """Load Object information from SDC.""" + vsp_details = self._get_item_details() + if vsp_details: + self._logger.debug("details found, updating") + self.version = vsp_details['id'] + self.human_readable_version = vsp_details["name"] + self.update_informations_from_sdc(vsp_details) + else: + # exists() method check if exists AND update identifier + self.exists() + + def update_informations_from_sdc(self, details: Dict[str, Any]) -> None: + """ + + Update instance with details from SDC. + + Args: + details ([type]): [description] + + """ + def update_informations_from_sdc_creation(self, + details: Dict[str, Any]) -> None: + """ + + Update instance with details from SDC after creation. + + Args: + details ([type]): the details from SDC + + """ + @classmethod + def _base_url(cls) -> str: + """ + Give back the base url of Sdc. + + Returns: + str: the base url + + """ + return "{}/sdc1/feProxy/onboarding-api/v1.0".format(cls.base_front_url) + + @classmethod + def _base_create_url(cls) -> str: + """ + Give back the base url of Sdc. + + Returns: + str: the base url + + """ + return "{}/sdc1/feProxy/onboarding-api/v1.0".format(cls.base_front_url) + + def _generate_action_subpath(self, action: str) -> str: + """ + + Generate subpath part of SDC action url. + + Args: + action (str): the action that will be done + + Returns: + str: the subpath part + + """ + subpath = self._sdc_path() + if action == const.COMMIT: + subpath = "items" + return subpath + + def _version_path(self) -> str: + """ + Give the end of the path for a version. + + Returns: + str: the end of the path + + """ + return "{}/versions/{}".format(self.identifier, self.version) + + @staticmethod + def _action_url(base: str, + subpath: str, + version_path: str, + action_type: str = None) -> str: + """ + Generate action URL for SDC. + + Args: + base (str): base part of url + subpath (str): subpath of url + version_path (str): version path of the url + action_type (str, optional): the type of action. UNUSED here + + Returns: + str: the URL to use + + """ + return "{}/{}/{}/actions".format(base, subpath, version_path) + + @classmethod + def _get_objects_list(cls, result: List[Dict[str, Any]] + ) -> List[Dict[str, Any]]: + """ + Import objects created in SDC. + + Args: + result (Dict[str, Any]): the result returned by SDC in a Dict + + Return: + List[Dict[str, Any]]: the list of objects + + """ + return result['results'] + + @classmethod + def _get_all_url(cls) -> str: + """ + Get URL for all elements in SDC. + + Returns: + str: the url + + """ + return "{}/{}".format(cls._base_url(), cls._sdc_path()) + + def _copy_object(self, obj: 'SdcElement') -> None: + """ + Copy relevant properties from object. + + Args: + obj (SdcElement): the object to "copy" + + """ + self.identifier = obj.identifier + + def _get_version_from_sdc(self, sdc_infos: Dict[str, Any]) -> str: + """ + Get version from SDC results. + + Args: + sdc_infos (Dict[str, Any]): the result dict from SDC + + Returns: + str: the version + + """ + return sdc_infos['version']['id'] + + def _get_identifier_from_sdc(self, sdc_infos: Dict[str, Any]) -> str: + """ + Get identifier from SDC results. + + Args: + sdc_infos (Dict[str, Any]): the result dict from SDC + + Returns: + str: the identifier + + """ + return sdc_infos['itemId'] + + @classmethod + @abstractmethod + def import_from_sdc(cls, values: Dict[str, Any]) -> 'SdcElement': + """ + Import SdcElement from SDC. + + Args: + values (Dict[str, Any]): dict to parse returned from SDC. + + Raises: + NotImplementedError: this is an abstract method. + + """ + raise NotImplementedError("SdcElement is an abstract class") diff --git a/src/onapsdk/sdc/sdc_resource.py b/src/onapsdk/sdc/sdc_resource.py new file mode 100644 index 0000000..7e7dbb9 --- /dev/null +++ b/src/onapsdk/sdc/sdc_resource.py @@ -0,0 +1,960 @@ +"""SDC Element module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 logging +from abc import ABC +from typing import Any, Dict, Iterator, List, Union +import base64 +import time + +import onapsdk.constants as const +from onapsdk.exceptions import ParameterError, ResourceNotFound, StatusError +from onapsdk.sdc import SdcOnboardable +from onapsdk.sdc.category_management import ResourceCategory, ServiceCategory +from onapsdk.sdc.component import Component +from onapsdk.sdc.properties import Input, NestedInput, Property +from onapsdk.utils.headers_creator import (headers_sdc_creator, + headers_sdc_tester, + headers_sdc_artifact_upload) +from onapsdk.utils.jinja import jinja_env + + +# For an unknown reason, pylint keeps seeing _unique_uuid and +# _unique_identifier as attributes along with unique_uuid and unique_identifier +class SdcResource(SdcOnboardable, ABC): # pylint: disable=too-many-instance-attributes, too-many-public-methods + """Mother Class of all SDC resources.""" + + RESOURCE_PATH = 'resources' + ACTION_TEMPLATE = 'sdc_resource_action.json.j2' + ACTION_METHOD = 'POST' + headers = headers_sdc_creator(SdcOnboardable.headers) + + def __init__(self, name: str = None, version: str = None, # pylint: disable=too-many-arguments + sdc_values: Dict[str, str] = None, properties: List[Property] = None, + inputs: Union[Property, NestedInput] = None, + category: str = None, subcategory: str = None): + """Initialize the object.""" + super().__init__(name) + self.version_filter: str = version + self._unique_uuid: str = None + self._unique_identifier: str = None + self._resource_type: str = "resources" + self._properties_to_add: List[Property] = properties or [] + self._inputs_to_add: Union[Property, NestedInput] = inputs or [] + self._time_wait: int = 10 + self._category_name: str = category + self._subcategory_name: str = subcategory + if sdc_values: + self._logger.debug("SDC values given, using them") + self.identifier = sdc_values['uuid'] + self.version = sdc_values['version'] + self.unique_uuid = sdc_values['invariantUUID'] + distribitution_state = None + if 'distributionStatus' in sdc_values: + distribitution_state = sdc_values['distributionStatus'] + self.status = self._parse_sdc_status(sdc_values['lifecycleState'], + distribitution_state, + self._logger) + self._logger.debug("SDC resource %s status: %s", self.name, + self.status) + + def __repr__(self) -> str: + """SDC resource description. + + Returns: + str: SDC resource object description + + """ + return f"{self.__class__.__name__.upper()}(name={self.name})" + + @property + def unique_uuid(self) -> str: + """Return and lazy load the unique_uuid.""" + if not self._unique_uuid: + self.load() + return self._unique_uuid + + @property + def unique_identifier(self) -> str: + """Return and lazy load the unique_identifier.""" + if not self._unique_identifier: + self.deep_load() + return self._unique_identifier + + @unique_uuid.setter + def unique_uuid(self, value: str) -> None: + """Set value for unique_uuid.""" + self._unique_uuid = value + + @unique_identifier.setter + def unique_identifier(self, value: str) -> None: + """Set value for unique_identifier.""" + self._unique_identifier = value + + def load(self) -> None: + """Load Object information from SDC.""" + self.exists() + + def deep_load(self) -> None: + """Deep load Object informations from SDC.""" + url = ( + f"{self.base_front_url}/sdc1/feProxy/rest/v1/" + "screen?excludeTypes=VFCMT&excludeTypes=Configuration" + ) + headers = headers_sdc_creator(SdcResource.headers) + if self.status == const.UNDER_CERTIFICATION: + headers = headers_sdc_tester(SdcResource.headers) + + response = self.send_message_json("GET", + "Deep Load {}".format( + type(self).__name__), + url, + headers=headers) + + for resource in response[self._sdc_path()]: + if resource["invariantUUID"] == self.unique_uuid: + if resource["uuid"] == self.identifier: + self._logger.debug("Resource %s found in %s list", + resource["name"], self._sdc_path()) + self.unique_identifier = resource["uniqueId"] + self._category_name = resource["categories"][0]["name"] + subcategories = resource["categories"][0].get("subcategories", [{}]) + self._subcategory_name = None if subcategories is None else \ + subcategories[0].get("name") + return + if self._sdc_path() == "services": + for dependency in self.send_message_json("GET", + "Get service dependecies", + f"{self._base_create_url()}/services/" + f"{resource['uniqueId']}/" + "dependencies"): + if dependency["version"] == self.version: + self.unique_identifier = dependency["uniqueId"] + return + + def _generate_action_subpath(self, action: str) -> str: + """ + + Generate subpath part of SDC action url. + + Args: + action (str): the action that will be done + + Returns: + str: the subpath part + + """ + return action + + def _version_path(self) -> str: + """ + Give the end of the path for a version. + + Returns: + str: the end of the path + + """ + return self.unique_identifier + + def _action_url(self, + base: str, + subpath: str, + version_path: str, + action_type: str = None) -> str: + """ + Generate action URL for SDC. + + Args: + base (str): base part of url + subpath (str): subpath of url + version_path (str): version path of the url + action_type (str, optional): the type of action + ('distribution', 'distribution-state' + or 'lifecycleState'). Default to + 'lifecycleState'). + + Returns: + str: the URL to use + + """ + if not action_type: + action_type = "lifecycleState" + return "{}/{}/{}/{}/{}".format(base, self._resource_type, version_path, + action_type, subpath) + + @classmethod + def _base_create_url(cls) -> str: + """ + Give back the base url of Sdc. + + Returns: + str: the base url + + """ + return "{}/sdc1/feProxy/rest/v1/catalog".format(cls.base_front_url) + + @classmethod + def _base_url(cls) -> str: + """ + Give back the base url of Sdc. + + Returns: + str: the base url + + """ + return "{}/sdc/v1/catalog".format(cls.base_back_url) + + @classmethod + def _get_all_url(cls) -> str: + """ + Get URL for all elements in SDC. + + Returns: + str: the url + + """ + return "{}/{}?resourceType={}".format(cls._base_url(), cls._sdc_path(), + cls.__name__.upper()) + + @classmethod + def _get_objects_list(cls, result: List[Dict[str, Any]] + ) -> List[Dict[str, Any]]: + """ + Import objects created in SDC. + + Args: + result (Dict[str, Any]): the result returned by SDC in a Dict + + Return: + List[Dict[str, Any]]: the list of objects + + """ + return result + + def _get_version_from_sdc(self, sdc_infos: Dict[str, Any]) -> str: + """ + Get version from SDC results. + + Args: + sdc_infos (Dict[str, Any]): the result dict from SDC + + Returns: + str: the version + + """ + return sdc_infos['version'] + + def _get_identifier_from_sdc(self, sdc_infos: Dict[str, Any]) -> str: + """ + Get identifier from SDC results. + + Args: + sdc_infos (Dict[str, Any]): the result dict from SDC + + Returns: + str: the identifier + + """ + return sdc_infos['uuid'] + + @classmethod + def import_from_sdc(cls, values: Dict[str, Any]) -> 'SdcResource': + """ + Import SdcResource from SDC. + + Args: + values (Dict[str, Any]): dict to parse returned from SDC. + + Return: + SdcResource: the created resource + + """ + cls._logger.debug("importing SDC Resource %s from SDC", values['name']) + return cls(name=values['name'], sdc_values=values) + + def _copy_object(self, obj: 'SdcResource') -> None: + """ + Copy relevant properties from object. + + Args: + obj (SdcResource): the object to "copy" + + """ + self.identifier = obj.identifier + self.unique_uuid = obj.unique_uuid + self.status = obj.status + self.version = obj.version + self.unique_identifier = obj.unique_identifier + self._specific_copy(obj) + + def _specific_copy(self, obj: 'SdcResource') -> None: + """ + Copy specific properties from object. + + Args: + obj (SdcResource): the object to "copy" + + """ + + def update_informations_from_sdc(self, details: Dict[str, Any]) -> None: + """ + + Update instance with details from SDC. + + Args: + details ([type]): [description] + + """ + def update_informations_from_sdc_creation(self, + details: Dict[str, Any]) -> None: + """ + + Update instance with details from SDC after creation. + + Args: + details ([type]): the details from SDC + + """ + self.unique_uuid = details['invariantUUID'] + distribution_state = None + + if 'distributionStatus' in details: + distribution_state = details['distributionStatus'] + self.status = self._parse_sdc_status(details['lifecycleState'], + distribution_state, self._logger) + self.version = details['version'] + self.unique_identifier = details['uniqueId'] + + # Not my fault if SDC has so many states... + # pylint: disable=too-many-return-statements + @staticmethod + def _parse_sdc_status(sdc_status: str, distribution_state: str, + logger: logging.Logger) -> str: + """ + Parse SDC status in order to normalize it. + + Args: + sdc_status (str): the status found in SDC + distribution_state (str): the distribution status found in SDC. + Can be None. + + Returns: + str: the normalized status + + """ + logger.debug("Parse status for SDC Resource") + if sdc_status.capitalize() == const.CERTIFIED: + if distribution_state and distribution_state == const.SDC_DISTRIBUTED: + return const.DISTRIBUTED + return const.CERTIFIED + if sdc_status == const.NOT_CERTIFIED_CHECKOUT: + return const.DRAFT + if sdc_status == const.NOT_CERTIFIED_CHECKIN: + return const.CHECKED_IN + if sdc_status == const.READY_FOR_CERTIFICATION: + return const.SUBMITTED + if sdc_status == const.CERTIFICATION_IN_PROGRESS: + return const.UNDER_CERTIFICATION + if sdc_status != "": + return sdc_status + return None + + def _really_submit(self) -> None: + """Really submit the SDC Vf in order to enable it.""" + raise NotImplementedError("SDC is an abstract class") + + def onboard(self) -> None: + """Onboard resource in SDC.""" + if not self.status: + self.create() + time.sleep(self._time_wait) + self.onboard() + elif self.status == const.DRAFT: + for property_to_add in self._properties_to_add: + self.add_property(property_to_add) + for input_to_add in self._inputs_to_add: + self.declare_input(input_to_add) + self.submit() + time.sleep(self._time_wait) + self.onboard() + elif self.status == const.CHECKED_IN: + # Checked in status check added + self.certify() + time.sleep(self._time_wait) + self.onboard() + elif self.status == const.CERTIFIED: + self.load() + + @classmethod + def _sdc_path(cls) -> None: + """Give back the end of SDC path.""" + return cls.RESOURCE_PATH + + @property + def deployment_artifacts_url(self) -> str: + """Deployment artifacts url. + + Returns: + str: SdcResource Deployment artifacts url + + """ + return (f"{self._base_create_url()}/resources/" + f"{self.unique_identifier}/filteredDataByParams?include=deploymentArtifacts") + + @property + def add_deployment_artifacts_url(self) -> str: + """Add deployment artifacts url. + + Returns: + str: Url used to add deployment artifacts + + """ + return (f"{self._base_create_url()}/resources/" + f"{self.unique_identifier}/artifacts") + + @property + def properties_url(self) -> str: + """Properties url. + + Returns: + str: SdcResource properties url + + """ + return (f"{self._base_create_url()}/resources/" + f"{self.unique_identifier}/filteredDataByParams?include=properties") + + @property + def add_property_url(self) -> str: + """Add property url. + + Returns: + str: Url used to add property + + """ + return (f"{self._base_create_url()}/services/" + f"{self.unique_identifier}/properties") + + @property + def set_input_default_value_url(self) -> str: + """Url to set input default value. + + Returns: + str: SDC API url used to set input default value + + """ + return (f"{self._base_create_url()}/resources/" + f"{self.unique_identifier}/update/inputs") + + @property + def origin_type(self) -> str: + """Resource origin type. + + Value needed for composition. It's used for adding SDC resource + as an another SDC resource component. + + Returns: + str: SDC resource origin type + + """ + return type(self).__name__.upper() + + @property + def properties(self) -> Iterator[Property]: + """SDC resource properties. + + Iterate resource properties. + + Yields: + Property: Resource property + + """ + for property_data in self.send_message_json(\ + "GET", + f"Get {self.name} resource properties", + self.properties_url).get("properties", []): + yield Property( + sdc_resource=self, + unique_id=property_data["uniqueId"], + name=property_data["name"], + property_type=property_data["type"], + parent_unique_id=property_data["parentUniqueId"], + value=property_data.get("value"), + description=property_data.get("description"), + get_input_values=property_data.get("getInputValues"), + ) + + def get_property(self, property_name: str) -> Property: + """Get resource property by it's name. + + Args: + property_name (str): property name + + Raises: + ResourceNotFound: Resource has no property with given name + + Returns: + Property: Resource's property object + + """ + for property_obj in self.properties: + if property_obj.name == property_name: + return property_obj + + msg = f"Resource has no property with {property_name} name" + raise ResourceNotFound(msg) + + @property + def resource_inputs_url(self) -> str: + """Resource inputs url. + + Method which returns url which point to resource inputs. + + Returns: + str: Resource inputs url + + """ + return (f"{self._base_create_url()}/resources/" + f"{self.unique_identifier}") + + + def create(self) -> None: + """Create resource. + + Abstract method which should be implemented by subclasses and creates resource in SDC. + + Raises: + NotImplementedError: Method not implemented by subclasses. + + """ + raise NotImplementedError + + @property + def inputs(self) -> Iterator[Input]: + """SDC resource inputs. + + Iterate resource inputs. + + Yields: + Iterator[Input]: Resource input + + """ + url = f"{self.resource_inputs_url}/filteredDataByParams?include=inputs" + for input_data in self.send_message_json(\ + "GET", f"Get {self.name} resource inputs", + url).get("inputs", []): + + yield Input( + unique_id=input_data["uniqueId"], + input_type=input_data["type"], + name=input_data["name"], + sdc_resource=self, + _default_value=input_data.get("defaultValue") + ) + + def get_input(self, input_name: str) -> Input: + """Get input by it's name. + + Args: + input_name (str): Input name + + Raises: + ResourceNotFound: Resource doesn't have input with given name + + Returns: + Input: Found input object + + """ + for input_obj in self.inputs: + if input_obj.name == input_name: + return input_obj + raise ResourceNotFound(f"SDC resource has no {input_name} input") + + def add_deployment_artifact(self, artifact_type: str, artifact_label: str, + artifact_name: str, artifact: str): + """ + Add deployment artifact to resource. + + Add deployment artifact to resource using payload data. + + Args: + artifact_type (str): all SDC artifact types are supported (DCAE_*, HEAT_*, ...) + artifact_name (str): the artifact file name including its extension + artifact (str): artifact file to upload + artifact_label (str): Unique Identifier of the artifact within the VF / Service. + + Raises: + StatusError: Resource has not DRAFT status + + """ + data = open(artifact, 'rb').read() + artifact_string = base64.b64encode(data).decode('utf-8') + if self.status != const.DRAFT: + msg = "Can't add artifact to resource which is not in DRAFT status" + raise StatusError(msg) + self._logger.debug("Add deployment artifact to sdc resource") + my_data = jinja_env().get_template( + "sdc_resource_add_deployment_artifact.json.j2").\ + render(artifact_name=artifact_name, + artifact_label=artifact_label, + artifact_type=artifact_type, + b64_artifact=artifact_string) + my_header = headers_sdc_artifact_upload(base_header=self.headers, data=my_data) + + self.send_message_json("POST", + f"Add deployment artifact for {self.name} sdc resource", + self.add_deployment_artifacts_url, + data=my_data, + headers=my_header) + + @property + def components(self) -> Iterator[Component]: + """Resource components. + + Iterate resource components. + + Yields: + Component: Resource component object + + """ + for component_instance in self.send_message_json(\ + "GET", + f"Get {self.name} resource inputs", + f"{self.resource_inputs_url}/filteredDataByParams?include=componentInstances" + ).get("componentInstances", []): + sdc_resource: "SdcResource" = SdcResource.import_from_sdc(self.send_message_json(\ + "GET", + f"Get {self.name} component's SDC resource metadata", + (f"{self.base_front_url}/sdc1/feProxy/rest/v1/catalog/resources/" + f"{component_instance['actualComponentUid']}/" + "filteredDataByParams?include=metadata"))["metadata"]) + yield Component.create_from_api_response(api_response=component_instance, + sdc_resource=sdc_resource, + parent_sdc_resource=self) + + @property + def category(self) -> Union[ResourceCategory, ServiceCategory]: + """Sdc resource category. + + Depends on the resource type returns ResourceCategory or ServiceCategory. + + Returns: + Uniton[ResourceCategory, ServiceCategory]: resource category + + """ + if self.created(): + if not any([self._category_name, self._subcategory_name]): + self.deep_load() + if all([self._category_name, self._subcategory_name]): + return ResourceCategory.get(name=self._category_name, + subcategory=self._subcategory_name) + return ServiceCategory.get(name=self._category_name) + return self.get_category_for_new_resource() + + def get_category_for_new_resource(self) -> ResourceCategory: + """Get category for resource not created in SDC yet. + + If no category values are provided default category is going to be used. + + Returns: + ResourceCategory: Category of the new resource + + """ + if not all([self._category_name, self._subcategory_name]): + return ResourceCategory.get(name="Generic", subcategory="Abstract") + return ResourceCategory.get(name=self._category_name, subcategory=self._subcategory_name) + + def get_component_properties_url(self, component: "Component") -> str: + """Url to get component's properties. + + This method is here because component can have different url when + it's a component of another SDC resource type, eg. for service and + for VF components have different urls. + + Args: + component (Component): Component object to prepare url for + + Returns: + str: Component's properties url + + """ + return (f"{self.resource_inputs_url}/" + f"componentInstances/{component.unique_id}/properties") + + def get_component_properties_value_set_url(self, component: "Component") -> str: + """Url to set component property value. + + This method is here because component can have different url when + it's a component of another SDC resource type, eg. for service and + for VF components have different urls. + + Args: + component (Component): Component object to prepare url for + + Returns: + str: Component's properties url + + """ + return (f"{self.resource_inputs_url}/" + f"resourceInstance/{component.unique_id}/properties") + + def is_own_property(self, property_to_check: Property) -> bool: + """Check if given property is one of the resource's properties. + + Args: + property_to_check (Property): Property to check + + Returns: + bool: True if resource has given property, False otherwise + + """ + return any(( + prop == property_to_check for prop in self.properties + )) + + def get_component(self, sdc_resource: "SdcResource") -> Component: + """Get resource's component. + + Get component by SdcResource object. + + Args: + sdc_resource (SdcResource): Component's SdcResource + + Raises: + ResourceNotFound: Component with given SdcResource does not exist + + Returns: + Component: Component object + + """ + for component in self.components: + if component.sdc_resource.name == sdc_resource.name: + return component + msg = f"SDC resource {sdc_resource.name} is not a component" + raise ResourceNotFound(msg) + + def get_component_by_name(self, component_name: str) -> Component: + """Get resource's component by it's name. + + Get component by name. + + Args: + component_name (str): Component's name + + Raises: + ResourceNotFound: Component with given name does not exist + + Returns: + Component: Component object + + """ + for component in self.components: + if component.sdc_resource.name == component_name: + return component + msg = f"SDC resource {component_name} is not a component" + raise ResourceNotFound(msg) + + def declare_input_for_own_property(self, property_obj: Property) -> None: + """Declare input for resource's property. + + For each property input can be declared. + + Args: + property_obj (Property): Property to declare input + + """ + self._logger.debug("Declare input for SDC resource property") + self.send_message_json("POST", + f"Declare new input for {property_obj.name} property", + f"{self.resource_inputs_url}/create/inputs", + data=jinja_env().get_template(\ + "sdc_resource_add_input.json.j2").\ + render(\ + sdc_resource=self, + property=property_obj)) + + def declare_nested_input(self, + nested_input: NestedInput) -> None: + """Declare nested input for SDC resource. + + Nested input is an input of one of the components. + + Args: + nested_input (NestedInput): Nested input object + + """ + self._logger.debug("Declare input for SDC resource's component property") + component: Component = self.get_component(nested_input.sdc_resource) + self.send_message_json("POST", + f"Declare new input for {nested_input.input_obj.name} input", + f"{self.resource_inputs_url}/create/inputs", + data=jinja_env().get_template(\ + "sdc_resource_add_nested_input.json.j2").\ + render(\ + sdc_resource=self, + component=component, + input=nested_input.input_obj)) + + def declare_input(self, input_to_declare: Union[Property, NestedInput]) -> None: + """Declare input for given property or nested input object. + + Call SDC FE API to declare input for given property. + + Args: + input_declaration (Union[Property, NestedInput]): Property to declare input + or NestedInput object + + Raises: + ParameterError: if the given property is not SDC resource property + + """ + self._logger.debug("Declare input") + if isinstance(input_to_declare, Property): + if self.is_own_property(input_to_declare): + self.declare_input_for_own_property(input_to_declare) + else: + msg = "Given property is not SDC resource property" + raise ParameterError(msg) + else: + self.declare_nested_input(input_to_declare) + + def add_property(self, property_to_add: Property) -> None: + """Add property to resource. + + Call SDC FE API to add property to resource. + + Args: + property_to_add (Property): Property object to add to resource. + + Raises: + StatusError: Resource has not DRAFT status + + """ + if self.status != const.DRAFT: + msg = "Can't add property to resource which is not in DRAFT status" + raise StatusError(msg) + self._logger.debug("Add property to sdc resource") + self.send_message_json("POST", + f"Declare new property for {self.name} sdc resource", + self.add_property_url, + data=jinja_env().get_template( + "sdc_resource_add_property.json.j2").\ + render( + property=property_to_add + )) + + def set_property_value(self, property_obj: Property, value: Any) -> None: + """Set property value. + + Set given value to resource property + + Args: + property_obj (Property): Property object + value (Any): Property value to set + + Raises: + ParameterError: if the given property is not the resource's property + + """ + if not self.is_own_property(property_obj): + raise ParameterError("Given property is not a resource's property") + self._logger.debug("Set %s property value", property_obj.name) + self.send_message_json("PUT", + f"Set {property_obj.name} value to {value}", + self.add_property_url, + data=jinja_env().get_template( + "sdc_resource_set_property_value.json.j2").\ + render( + sdc_resource=self, + property=property_obj, + value=value + ) + ) + + def set_input_default_value(self, input_obj: Input, default_value: Any) -> None: + """Set input default value. + + Set given value as input default value + + Args: + input_obj (Input): Input object + value (Any): Default value to set + + """ + self._logger.debug("Set %s input default value", input_obj.name) + self.send_message_json("POST", + f"Set {input_obj.name} default value to {default_value}", + self.set_input_default_value_url, + data=jinja_env().get_template( + "sdc_resource_set_input_default_value.json.j2").\ + render( + sdc_resource=self, + input=input_obj, + default_value=default_value + ) + ) + + def checkout(self) -> None: + """Checkout SDC resource.""" + self._logger.debug("Checkout %s SDC resource", self.name) + result = self._action_to_sdc(const.CHECKOUT, "lifecycleState") + if result: + self.load() + + def undo_checkout(self) -> None: + """Undo Checkout SDC resource.""" + self._logger.debug("Undo Checkout %s SDC resource", self.name) + result = self._action_to_sdc(const.UNDOCHECKOUT, "lifecycleState") + if result: + self.load() + + def certify(self) -> None: + """Certify SDC resource.""" + self._logger.debug("Certify %s SDC resource", self.name) + result = self._action_to_sdc(const.CERTIFY, "lifecycleState") + if result: + self.load() + + def add_resource(self, resource: 'SdcResource') -> None: + """ + Add a Resource. + + Args: + resource (SdcResource): the resource to add + + """ + if self.status == const.DRAFT: + url = "{}/{}/{}/resourceInstance".format(self._base_create_url(), + self._sdc_path(), + self.unique_identifier) + + template = jinja_env().get_template( + "add_resource_to_service.json.j2") + data = template.render(resource=resource, + resource_type=resource.origin_type) + result = self.send_message("POST", + f"Add {resource.origin_type} to {self.origin_type}", + url, + data=data) + if result: + self._logger.info("Resource %s %s has been added on %s %s", + resource.origin_type, resource.name, + self.origin_type, self.name) + return result + self._logger.error(("an error occured during adding resource %s %s" + " on %s %s in SDC"), + resource.origin_type, resource.name, + self.origin_type, self.name) + return None + msg = f"Can't add resource to {self.origin_type} which is not in DRAFT status" + raise StatusError(msg) diff --git a/src/onapsdk/sdc/service.py b/src/onapsdk/sdc/service.py new file mode 100644 index 0000000..c866fa4 --- /dev/null +++ b/src/onapsdk/sdc/service.py @@ -0,0 +1,932 @@ +"""Service module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 base64 +import pathlib as Path +import time +from dataclasses import dataclass, field +from enum import Enum +from io import BytesIO, TextIOWrapper +from os import makedirs +from typing import Dict, List, Callable, Iterator, Optional, Type, Union, Any, BinaryIO +from zipfile import ZipFile, BadZipFile + +import oyaml as yaml +from requests import Response + +import onapsdk.constants as const +from onapsdk.exceptions import (ParameterError, RequestError, ResourceNotFound, + StatusError, ValidationError) +from onapsdk.sdc.category_management import ServiceCategory +from onapsdk.sdc.properties import NestedInput, Property +from onapsdk.sdc.sdc_resource import SdcResource +from onapsdk.utils.configuration import (components_needing_distribution, + tosca_path) +from onapsdk.utils.headers_creator import headers_sdc_creator, headers_sdc_artifact_upload +from onapsdk.utils.jinja import jinja_env + + +@dataclass +class VfModule: # pylint: disable=too-many-instance-attributes + """VfModule dataclass.""" + + name: str + group_type: str + model_name: str + model_version_id: str + model_invariant_uuid: str + model_version: str + model_customization_id: str + properties: Iterator[Property] + + +@dataclass +class NodeTemplate: # pylint: disable=too-many-instance-attributes + """Node template dataclass. + + Base class for Vnf, Pnf and Network classes. + """ + + name: str + node_template_type: str + model_name: str + model_version_id: str + model_invariant_id: str + model_version: str + model_customization_id: str + model_instance_name: str + component: "Component" + + @property + def properties(self) -> Iterator["Property"]: + """Node template properties. + + Returns: + Iterator[Property]: Node template properties iterator + + """ + return self.component.properties + + +@dataclass +class Vnf(NodeTemplate): + """Vnf dataclass.""" + + vf_modules: List[VfModule] = field(default_factory=list) + + +@dataclass +class Pnf(NodeTemplate): + """Pnf dataclass.""" + + +class Network(NodeTemplate): # pylint: disable=too-few-public-methods + """Network dataclass.""" + + +class ServiceInstantiationType(Enum): + """Service instantiation type enum class. + + Service can be instantiated using `A-la-carte` or `Macro` flow. + It has to be determined during design time. That class stores these + two values to set during initialization. + + """ + + A_LA_CARTE = "A-la-carte" + MACRO = "Macro" + + +class Service(SdcResource): # pylint: disable=too-many-instance-attributes, too-many-public-methods + """ + ONAP Service Object used for SDC operations. + + Attributes: + name (str): the name of the service. Defaults to "ONAP-test-Service". + identifier (str): the unique ID of the service from SDC. + status (str): the status of the service from SDC. + version (str): the version ID of the service from SDC. + uuid (str): the UUID of the Service (which is different from + identifier, don't ask why...) + distribution_status (str): the status of distribution in the different + ONAP parts. + distribution_id (str): the ID of the distribution when service is + distributed. + distributed (bool): True if the service is distributed + unique_identifier (str): Yet Another ID, just to puzzle us... + + """ + + SERVICE_PATH = "services" + + def __init__(self, name: str = None, version: str = None, sdc_values: Dict[str, str] = None, # pylint: disable=too-many-arguments + resources: List[SdcResource] = None, properties: List[Property] = None, + inputs: List[Union[Property, NestedInput]] = None, + instantiation_type: Optional[ServiceInstantiationType] = \ + None, + category: str = None, role: str = "", function: str = "", service_type: str = ""): + """ + Initialize service object. + + Args: + name (str, optional): the name of the service + version (str, optional): the version of the service + sdc_values (Dict[str, str], optional): dictionary of values + returned by SDC + resources (List[SdcResource], optional): list of SDC resources + properties (List[Property], optional): list of properties to add to service. + None by default. + inputs (List[Union[Property, NestedInput]], optional): list of inputs + to declare for service. It can be both Property or NestedInput object. + None by default. + instantiation_type (ServiceInstantiationType, optional): service instantiation + type. ServiceInstantiationType.A_LA_CARTE by default + category (str, optional): service category name + role (str, optional): service role + function (str, optional): service function. Empty by default + service_type (str, optional): service type. Empty by default + + """ + super().__init__(sdc_values=sdc_values, version=version, properties=properties, + inputs=inputs, category=category) + self.name: str = name or "ONAP-test-Service" + self.distribution_status = None + self.category_name: str = category + self.role: str = role + self.function: str = function + self.service_type: str = service_type + if sdc_values: + self.distribution_status = sdc_values['distributionStatus'] + self.category_name = sdc_values["category"] + self.resources = resources or [] + self._instantiation_type: Optional[ServiceInstantiationType] = instantiation_type + self._distribution_id: str = None + self._distributed: bool = False + self._resource_type: str = "services" + self._tosca_model: bytes = None + self._tosca_template: str = None + self._vnfs: list = None + self._pnfs: list = None + self._networks: list = None + self._vf_modules: list = None + + @classmethod + def get_by_unique_uuid(cls, unique_uuid: str) -> "Service": + """Get the service model using unique uuid. + + Returns: + Service: object with provided unique_uuid + + Raises: + ResourceNotFound: No service with given unique_uuid exists + + """ + services: List["Service"] = cls.get_all() + for service in services: + if service.unique_uuid == unique_uuid: + return service + raise ResourceNotFound("Service with given unique uuid doesn't exist") + + def onboard(self) -> None: + """Onboard the Service in SDC. + + Raises: + StatusError: service has an invalid status + ParameterError: no resources, no properties for service + in DRAFT status + + """ + # first Lines are equivalent for all onboard functions but it's more + # readable + if not self.status: + # equivalent step as in onboard-function in sdc_resource + self.create() + time.sleep(self._time_wait) + self.onboard() + elif self.status == const.DRAFT: + if not any([self.resources, self._properties_to_add]): + raise ParameterError("No resources nor properties were given") + self.declare_resources_and_properties() + self.checkin() + time.sleep(self._time_wait) + self.onboard() + elif self.status == const.CHECKED_IN: + self.certify() + time.sleep(self._time_wait) + self.onboard() + elif self.status == const.CERTIFIED: + self.distribute() + self.onboard() + elif self.status == const.DISTRIBUTED: + self._logger.info("Service %s onboarded", self.name) + else: + self._logger.error("Service has invalid status: %s", self.status) + raise StatusError(self.status) + + @property + def distribution_id(self) -> str: + """Return and lazy load the distribution_id.""" + if not self._distribution_id: + self.load_metadata() + return self._distribution_id + + @distribution_id.setter + def distribution_id(self, value: str) -> None: + """Set value for distribution_id.""" + self._distribution_id = value + + @property + def distributed(self) -> bool: + """Return and lazy load the distributed state.""" + if not self._distributed: + self._check_distributed() + return self._distributed + + @property + def tosca_template(self) -> str: + """Service tosca template file. + + Get tosca template from service tosca model bytes. + + Returns: + str: Tosca template file + + """ + if not self._tosca_template and self.tosca_model: + self._unzip_csar_file(BytesIO(self.tosca_model), + self._load_tosca_template) + return self._tosca_template + + @property + def tosca_model(self) -> bytes: + """Service's tosca model file. + + Send request to get service TOSCA model, + + Returns: + bytes: TOSCA model file bytes + + """ + if not self._tosca_model: + url = "{}/services/{}/toscaModel".format(self._base_url(), + self.identifier) + headers = self.headers.copy() + headers["Accept"] = "application/octet-stream" + self._tosca_model = self.send_message( + "GET", + "Download Tosca Model for {}".format(self.name), + url, + headers=headers).content + return self._tosca_model + + def create_node_template(self, + node_template_type: Type[NodeTemplate], + component: "Component") -> NodeTemplate: + """Create a node template type object. + + The base of the all node template types objects (Vnf, Pnf, Network) is the + same. The difference is only for the Vnf which can have vf modules associated with. + Vf modules could have "vf_module_label" property with"base_template_dummy_ignore" + value. These vf modules should be ignored/ + + Args: + node_template_type (Type[NodeTemplate]): Node template class type + component (Component): Component on which base node template object should be created + + Returns: + NodeTemplate: Node template object created from component + + """ + node_template: NodeTemplate = node_template_type( + name=component.name, + node_template_type=component.tosca_component_name, + model_name=component.component_name, + model_version_id=component.sdc_resource.identifier, + model_invariant_id=component.sdc_resource.unique_uuid, + model_version=component.sdc_resource.version, + model_customization_id=component.customization_uuid, + model_instance_name=self.name, + component=component + ) + if node_template_type is Vnf: + if component.group_instances: + for vf_module in component.group_instances: + if not any([property_def["name"] == "vf_module_label"] and \ + property_def["value"] == "base_template_dummy_ignore" for \ + property_def in vf_module["properties"]): + node_template.vf_modules.append(VfModule( + name=vf_module["name"], + group_type=vf_module["type"], + model_name=vf_module["groupName"], + model_version_id=vf_module["groupUUID"], + model_invariant_uuid=vf_module["invariantUUID"], + model_version=vf_module["version"], + model_customization_id=vf_module["customizationUUID"], + properties=( + Property( + name=property_def["name"], + property_type=property_def["type"], + description=property_def["description"], + value=property_def["value"] + ) for property_def in vf_module["properties"] \ + if property_def["value"] and not ( + property_def["name"] == "vf_module_label" and \ + property_def["value"] == "base_template_dummy_ignore" + ) + ) + )) + return node_template + + def __has_component_type(self, origin_type: str) -> bool: + """Check if any of Service's component type is provided origin type. + + In template generation is checked if Service has some types of components, + based on that blocks are added to the request template. It's not + the best option to get all components to check if at least one with + given type exists for conditional statement. + + Args: + origin_type (str): Type to check if any component exists. + + Returns: + bool: True if service has at least one component with given origin type, + False otherwise + + """ + return any((component.origin_type == origin_type for component in self.components)) + + @property + def has_vnfs(self) -> bool: + """Check if service has at least one VF component.""" + return self.__has_component_type("VF") + + @property + def has_pnfs(self) -> bool: + """Check if service has at least one PNF component.""" + return self.__has_component_type("PNF") + + @property + def has_vls(self) -> bool: + """Check if service has at least one VL component.""" + return self.__has_component_type("VL") + + @property + def vnfs(self) -> Iterator[Vnf]: + """Service Vnfs. + + Load VNFs from components generator. + It creates a generator of the vf modules as well, but without + vf modules which has "vf_module_label" property value equal + to "base_template_dummy_ignore". + + Returns: + Iterator[Vnf]: Vnf objects iterator + + """ + for component in self.components: + if component.origin_type == "VF": + yield self.create_node_template(Vnf, component) + + @property + def pnfs(self) -> Iterator[Pnf]: + """Service Pnfs. + + Load PNFS from components generator. + + Returns: + Iterator[Pnf]: Pnf objects generator + + """ + for component in self.components: + if component.origin_type == "PNF": + yield self.create_node_template(Pnf, component) + + @property + def networks(self) -> Iterator[Network]: + """Service networks. + + Load networks from service's components generator. + + Returns: + Iterator[Network]: Network objects generator + + """ + for component in self.components: + if component.origin_type == "VL": + yield self.create_node_template(Network, component) + + @property + def deployment_artifacts_url(self) -> str: + """Deployment artifacts url. + + Returns: + str: SdcResource Deployment artifacts url + + """ + return (f"{self._base_create_url()}/services/" + f"{self.unique_identifier}/filteredDataByParams?include=deploymentArtifacts") + + @property + def add_deployment_artifacts_url(self) -> str: + """Add deployment artifacts url. + + Returns: + str: Url used to add deployment artifacts + + """ + return (f"{self._base_create_url()}/services/" + f"{self.unique_identifier}/artifacts") + + @property + def properties_url(self) -> str: + """Properties url. + + Returns: + str: SdcResource properties url + + """ + return (f"{self._base_create_url()}/services/" + f"{self.unique_identifier}/filteredDataByParams?include=properties") + + @property + def metadata_url(self) -> str: + """Metadata url. + + Returns: + str: Service metadata url + + """ + return (f"{self._base_create_url()}/services/" + f"{self.unique_identifier}/filteredDataByParams?include=metadata") + + @property + def resource_inputs_url(self) -> str: + """Service inputs url. + + Returns: + str: Service inputs url + + """ + return (f"{self._base_create_url()}/services/" + f"{self.unique_identifier}") + + @property + def set_input_default_value_url(self) -> str: + """Url to set input default value. + + Returns: + str: SDC API url used to set input default value + + """ + return (f"{self._base_create_url()}/services/" + f"{self.unique_identifier}/update/inputs") + + @property + def origin_type(self) -> str: + """Service origin type. + + Value needed for composition. It's used for adding SDC resource + as an another SDC resource component. + For Service that value has to be set to "ServiceProxy". + + Returns: + str: Service resource origin type + + """ + return "ServiceProxy" + + @property + def instantiation_type(self) -> ServiceInstantiationType: + """Service instantiation type. + + One of `ServiceInstantiationType` enum value. + + Returns: + ServiceInstantiationType: Service instantiation type + + """ + if not self._instantiation_type: + if not self.created(): + self._instantiation_type = ServiceInstantiationType.A_LA_CARTE + else: + response: str = self.send_message_json("GET", + f"Get service {self.name} metadata", + self.metadata_url)["metadata"]\ + ["instantiationType"] + self._instantiation_type = ServiceInstantiationType(response) + return self._instantiation_type + + def create(self) -> None: + """Create the Service in SDC if not already existing.""" + self._create("service_create.json.j2", + name=self.name, + instantiation_type=self.instantiation_type.value, + category=self.category, + role=self.role, service_type=self.service_type, function=self.function) + + def declare_resources_and_properties(self) -> None: + """Delcare resources and properties. + + It declares also inputs. + + """ + for resource in self.resources: + self.add_resource(resource) + for property_to_add in self._properties_to_add: + self.add_property(property_to_add) + for input_to_add in self._inputs_to_add: + self.declare_input(input_to_add) + + def checkin(self) -> None: + """Checkin Service.""" + self._verify_lcm_to_sdc(const.DRAFT, const.CHECKIN) + + def submit(self) -> None: + """Really submit the SDC Service.""" + self._verify_lcm_to_sdc(const.CHECKED_IN, const.SUBMIT_FOR_TESTING) + + def start_certification(self) -> None: + """Start Certification on Service.""" + headers = headers_sdc_creator(SdcResource.headers) + self._verify_lcm_to_sdc(const.CHECKED_IN, + const.START_CERTIFICATION, + headers=headers) + + def certify(self) -> None: + """Certify Service in SDC.""" + headers = headers_sdc_creator(SdcResource.headers) + self._verify_lcm_to_sdc(const.CHECKED_IN, + const.CERTIFY, + headers=headers) + + def approve(self) -> None: + """Approve Service in SDC.""" + headers = headers_sdc_creator(SdcResource.headers) + self._verify_approve_to_sdc(const.CERTIFIED, + const.APPROVE, + headers=headers) + + def distribute(self) -> None: + """Apptove Service in SDC.""" + headers = headers_sdc_creator(SdcResource.headers) + self._verify_distribute_to_sdc(const.CERTIFIED, + const.DISTRIBUTE, + headers=headers) + + def redistribute(self) -> None: + """Apptove Service in SDC.""" + headers = headers_sdc_creator(SdcResource.headers) + self._verify_distribute_to_sdc(const.DISTRIBUTED, + const.DISTRIBUTE, + headers=headers) + + def get_tosca(self) -> None: + """Get Service tosca files and save it.""" + url = "{}/services/{}/toscaModel".format(self._base_url(), + self.identifier) + headers = self.headers.copy() + headers["Accept"] = "application/octet-stream" + result = self.send_message("GET", + "Download Tosca Model for {}".format( + self.name), + url, + headers=headers) + if result: + self._create_tosca_file(result) + + def _create_tosca_file(self, result: Response) -> None: + """Create Service Tosca files from HTTP response.""" + csar_filename = "service-{}-csar.csar".format(self.name) + makedirs(tosca_path(), exist_ok=True) + with open((tosca_path() + csar_filename), 'wb') as csar_file: + for chunk in result.iter_content(chunk_size=128): + csar_file.write(chunk) + try: + self._unzip_csar_file(tosca_path() + csar_filename, + self._write_csar_file) + except BadZipFile as exc: + self._logger.exception(exc) + + def _check_distributed(self) -> bool: + """Check if service is distributed and update status accordingly.""" + url = "{}/services/distribution/{}".format(self._base_create_url(), + self.distribution_id) + headers = headers_sdc_creator(SdcResource.headers) + + status = {} + for component in components_needing_distribution(): + status[component] = False + + try: + result = self.send_message_json("GET", + "Check distribution for {}".format( + self.name), + url, + headers=headers) + except ResourceNotFound: + msg = f"No distributions found for {self.name} of {self.__class__.__name__}." + self._logger.debug(msg) + else: + status = self._update_components_status(status, result) + + for state in status.values(): + if not state: + self._distributed = False + return + self._distributed = True + + def _update_components_status(self, status: Dict[str, bool], + result: Response) -> Dict[str, bool]: + """Update components distribution status.""" + distrib_list = result['distributionStatusList'] + self._logger.debug("[SDC][Get Distribution] distrib_list = %s", + distrib_list) + for elt in distrib_list: + status = self._parse_components_status(status, elt) + return status + + def _parse_components_status(self, status: Dict[str, bool], + element: Dict[str, Any]) -> Dict[str, bool]: + """Parse components distribution status.""" + for key in status: + if ((key in element['omfComponentID']) + and (const.DOWNLOAD_OK in element['status'])): + status[key] = True + self._logger.info(("[SDC][Get Distribution] Service " + "distributed in %s"), key) + return status + + def load_metadata(self) -> None: + """Load Metada of Service and retrieve informations.""" + url = "{}/services/{}/distribution".format(self._base_create_url(), + self.identifier) + headers = headers_sdc_creator(SdcResource.headers) + result = self.send_message_json("GET", + "Get Metadata for {}".format( + self.name), + url, + headers=headers) + if ('distributionStatusOfServiceList' in result + and len(result['distributionStatusOfServiceList']) > 0): + # API changed and the latest distribution is not added to the end + # of distributions list but inserted as the first one. + dist_status = result['distributionStatusOfServiceList'][0] + self._distribution_id = dist_status['distributionID'] + + @classmethod + def _get_all_url(cls) -> str: + """ + Get URL for all elements in SDC. + + Returns: + str: the url + + """ + return "{}/{}".format(cls._base_url(), cls._sdc_path()) + + def _really_submit(self) -> None: + """Really submit the SDC Service in order to enable it.""" + result = self._action_to_sdc(const.CERTIFY, + action_type="lifecycleState") + if result: + self.load() + + def _specific_copy(self, obj: 'Service') -> None: + """ + Copy specific properties from object. + + Args: + obj (Service): the object to "copy" + + """ + super()._specific_copy(obj) + self.category_name = obj.category_name + self.role = obj.role + + def _verify_distribute_to_sdc(self, desired_status: str, + desired_action: str, **kwargs) -> None: + self._verify_action_to_sdc(desired_status, desired_action, + "distribution", **kwargs) + + def _verify_approve_to_sdc(self, desired_status: str, desired_action: str, + **kwargs) -> None: + self._verify_action_to_sdc(desired_status, desired_action, + "distribution-state", **kwargs) + + def _verify_lcm_to_sdc(self, desired_status: str, desired_action: str, + **kwargs) -> None: + self._verify_action_to_sdc(desired_status, desired_action, + "lifecycleState", **kwargs) + + def _verify_action_to_sdc(self, desired_status: str, desired_action: str, + action_type: str, **kwargs) -> None: + """ + Verify action to SDC. + + Verify that object is in right state before launching the action on + SDC. + + Raises: + StatusError: if current status is not the desired status. + + Args: + desired_status (str): the status the object should be + desired_action (str): the action we want to perform + action_type (str): the type of action ('distribution-state' or + 'lifecycleState') + **kwargs: any specific stuff to give to requests + + """ + self._logger.info("attempting to %s Service %s in SDC", desired_action, + self.name) + if self.status == desired_status and self.created(): + self._action_to_sdc(desired_action, + action_type=action_type, + **kwargs) + self.load() + elif not self.created(): + self._logger.warning("Service %s in SDC is not created", self.name) + elif self.status != desired_status: + msg = (f"Service {self.name} in SDC is in status {self.status} " + f"and it should be in status {desired_status}") + raise StatusError(msg) + + @staticmethod + def _unzip_csar_file(zip_file: Union[str, BytesIO], + function: Callable[[str, + TextIOWrapper], None]) -> None: + """ + Unzip Csar File and perform an action on the file. + + Raises: + ValidationError: CSAR file has no service template + + """ + folder = "Definitions" + prefix = "service-" + suffix = "-template.yml" + with ZipFile(zip_file) as myzip: + service_template = None + for name in myzip.namelist(): + if (name[-13:] == suffix + and name[:20] == f"{folder}/{prefix}"): + service_template = name + + if not service_template: + msg = (f"CSAR file has no service template. " + f"Valid path: {folder}/{prefix}*{suffix}") + raise ValidationError(msg) + + with myzip.open(service_template) as template_file: + function(service_template, template_file) + + @staticmethod + def _write_csar_file(service_template: str, + template_file: TextIOWrapper) -> None: + """Write service temple into a file.""" + with open(tosca_path() + service_template[12:], 'wb') as file: + file.write(template_file.read()) + + # _service_template is not used but function generation is generic + # pylint: disable-unused-argument + def _load_tosca_template(self, _service_template: str, + template_file: TextIOWrapper) -> None: + """Load Tosca template.""" + self._tosca_template = yaml.safe_load(template_file.read()) + + @classmethod + def _sdc_path(cls) -> None: + """Give back the end of SDC path.""" + return cls.SERVICE_PATH + + def get_nf_unique_id(self, nf_name: str) -> str: + """ + Get nf (network function) uniqueID. + + Get nf uniqueID from service nf in sdc. + + Args: + nf_name (str): the nf from which we extract the unique ID + + Returns: + the nf unique ID + + Raises: + ResourceNotFound: Couldn't find NF by name. + + """ + url = f"{self._base_create_url()}/services/{self.unique_identifier}" + request_return = self.send_message_json('GET', + 'Get nf unique ID', + url) + + for instance in filter(lambda x: x["componentName"] == nf_name, + request_return["componentInstances"]): + return instance["uniqueId"] + + raise ResourceNotFound(f"NF '{nf_name}'") + + + def add_artifact_to_vf(self, vnf_name: str, artifact_type: str, + artifact_name: str, artifact: BinaryIO = None): + """ + Add artifact to vf. + + Add artifact to vf using payload data. + + Raises: + RequestError: file upload (POST request) for an artifact fails. + + Args: + vnf_name (str): the vnf which we want to add the artifact + artifact_type (str): all SDC artifact types are supported (DCAE_*, HEAT_*, ...) + artifact_name (str): the artifact file name including its extension + artifact (str): binary data to upload + + """ + missing_identifier = self.get_nf_unique_id(vnf_name) + url = (f"{self._base_create_url()}/services/{self.unique_identifier}/" + f"resourceInstance/{missing_identifier}/artifacts") + template = jinja_env().get_template("add_artifact_to_vf.json.j2") + data = template.render(artifact_name=artifact_name, + artifact_label=f"sdk{Path.PurePosixPath(artifact_name).stem}", + artifact_type=artifact_type, + b64_artifact=base64.b64encode(artifact).decode('utf-8')) + headers = headers_sdc_artifact_upload(base_header=self.headers, + data=data) + try: + self.send_message('POST', + 'Add artifact to vf', + url, + headers=headers, + data=data) + except RequestError as exc: + self._logger.error(("an error occured during file upload for an Artifact" + "to VNF %s"), vnf_name) + raise exc + + def get_component_properties_url(self, component: "Component") -> str: + """Url to get component's properties. + + This method is here because component can have different url when + it's a component of another SDC resource type, eg. for service and + for VF components have different urls. + Also for VL origin type components properties url is different than + for the other types. + + Args: + component (Component): Component object to prepare url for + + Returns: + str: Component's properties url + + """ + if component.origin_type == "VL": + return super().get_component_properties_url(component) + return (f"{self.resource_inputs_url}/" + f"componentInstances/{component.unique_id}/{component.actual_component_uid}/inputs") + + def get_component_properties_value_set_url(self, component: "Component") -> str: + """Url to set component property value. + + This method is here because component can have different url when + it's a component of another SDC resource type, eg. for service and + for VF components have different urls. + Also for VL origin type components properties url is different than + for the other types. + + Args: + component (Component): Component object to prepare url for + + Returns: + str: Component's properties url + + """ + if component.origin_type == "VL": + return super().get_component_properties_value_set_url(component) + return (f"{self.resource_inputs_url}/" + f"resourceInstance/{component.unique_id}/inputs") + + def get_category_for_new_resource(self) -> ServiceCategory: + """Get category for service not created in SDC yet. + + If no category values are provided default category is going to be used. + + Returns: + ServiceCategory: Category of the new service + + """ + if not self._category_name: + return ServiceCategory.get(name="Network Service") + return ServiceCategory.get(name=self._category_name) diff --git a/src/onapsdk/sdc/templates/add_artifact_to_vf.json.j2 b/src/onapsdk/sdc/templates/add_artifact_to_vf.json.j2 new file mode 100644 index 0000000..2b0446c --- /dev/null +++ b/src/onapsdk/sdc/templates/add_artifact_to_vf.json.j2 @@ -0,0 +1,9 @@ +{ + "artifactGroupType": "DEPLOYMENT", + "artifactName": "{{artifact_name}}", + "artifactLabel": "{{artifact_label}}", + "artifactType": "{{artifact_type}}", + "description": "test", + "payloadData": "{{b64_artifact}}", + "heatParameters": [] +} diff --git a/src/onapsdk/sdc/templates/add_resource_to_service.json.j2 b/src/onapsdk/sdc/templates/add_resource_to_service.json.j2 new file mode 100644 index 0000000..d6676e9 --- /dev/null +++ b/src/onapsdk/sdc/templates/add_resource_to_service.json.j2 @@ -0,0 +1,10 @@ +{ + "name": "{{ resource.name }}", + "componentVersion": "{{ resource.version }}", + "posY": {{ posY| default(100) }}, + "posX": {{ posX| default(200) }}, + "uniqueId": "{{ resource.unique_identifier }}", + "originType": "{{ resource_type }}", + "componentUid": "{{ resource.unique_identifier }}", + "icon": "defaulticon" +} diff --git a/src/onapsdk/sdc/templates/component_declare_input.json.j2 b/src/onapsdk/sdc/templates/component_declare_input.json.j2 new file mode 100644 index 0000000..fd0ee03 --- /dev/null +++ b/src/onapsdk/sdc/templates/component_declare_input.json.j2 @@ -0,0 +1,37 @@ +{ + "componentInstanceInputsMap": {}, + "componentInstanceProperties": { + "{{ component.unique_id }}": [ + { + "constraints": null, + "defaultValue": null, + "description": "", + "name": "{{ property.name }}", + "origName": "{{ property.name }}", + "parentUniqueId": null, + "password": false, + "required": true, + "schema": { + "property": {} + }, + "schemaType": null, + "type": "{{ property.property_type }}", + "uniqueId": "{{ property.unique_id }}", + {% if property.value is not none %} + "value":"{{ property.value }}", + {% else %} + "value":null, + {% endif %} + "definition": false, + "getInputValues": null, + "parentPropertyType": null, + "subPropertyInputPath": null, + "getPolicyValues": null, + "inputPath": null, + "metadata": null + } + ] + }, + "groupProperties": {}, + "policyProperties": {} +} \ No newline at end of file diff --git a/src/onapsdk/sdc/templates/pnf_create.json.j2 b/src/onapsdk/sdc/templates/pnf_create.json.j2 new file mode 100644 index 0000000..fe7e60c --- /dev/null +++ b/src/onapsdk/sdc/templates/pnf_create.json.j2 @@ -0,0 +1,29 @@ +{ + "artifacts": {}, + "attributes": [], + "capabilities": {}, + {% include "sdc_resource_category.json.j2" %}, + "componentInstances": [], + "componentInstancesAttributes": {}, + "componentInstancesProperties": {}, + "componentType": "RESOURCE", + "contactId": "cs0008", + {% if vsp is not none %} + "csarUUID": "{{ vsp.csar_uuid }}", + "csarVersion": "1.0", + "vendorName": "{{ vsp.vendor.name }}", + {% else %} + "vendorName": "{{ vendor.name }}", + {% endif %} + "deploymentArtifacts": {}, + "description": "PNF", + "icon": "defaulticon", + "name": "{{ name }}", + "properties": [], + "groups": [], + "requirements": {}, + "resourceType": "PNF", + "tags": ["{{ name }}"], + "toscaArtifacts": {}, + "vendorRelease": "1.0" +} diff --git a/src/onapsdk/sdc/templates/sdc_element_action.json.j2 b/src/onapsdk/sdc/templates/sdc_element_action.json.j2 new file mode 100644 index 0000000..04fd946 --- /dev/null +++ b/src/onapsdk/sdc/templates/sdc_element_action.json.j2 @@ -0,0 +1,6 @@ +{ +{% if action == const.COMMIT %} + "commitRequest":{"message":"ok"}, +{% endif %} + "action": "{{ action }}" +} diff --git a/src/onapsdk/sdc/templates/sdc_resource_action.json.j2 b/src/onapsdk/sdc/templates/sdc_resource_action.json.j2 new file mode 100644 index 0000000..742d076 --- /dev/null +++ b/src/onapsdk/sdc/templates/sdc_resource_action.json.j2 @@ -0,0 +1,3 @@ +{ + "userRemarks": "{{ action | lower }}" +} diff --git a/src/onapsdk/sdc/templates/sdc_resource_add_deployment_artifact.json.j2 b/src/onapsdk/sdc/templates/sdc_resource_add_deployment_artifact.json.j2 new file mode 100644 index 0000000..290c6d2 --- /dev/null +++ b/src/onapsdk/sdc/templates/sdc_resource_add_deployment_artifact.json.j2 @@ -0,0 +1,8 @@ +{ + "artifactGroupType": "DEPLOYMENT", + "artifactName": "{{artifact_name}}", + "artifactLabel": "{{artifact_label}}", + "artifactType": "{{artifact_type}}", + "description": "test", + "payloadData": "{{b64_artifact}}" +} diff --git a/src/onapsdk/sdc/templates/sdc_resource_add_input.json.j2 b/src/onapsdk/sdc/templates/sdc_resource_add_input.json.j2 new file mode 100644 index 0000000..1964f36 --- /dev/null +++ b/src/onapsdk/sdc/templates/sdc_resource_add_input.json.j2 @@ -0,0 +1,39 @@ +{ + "serviceProperties":{ + "{{ sdc_resource.unique_identifier }}":[ + { + "constraints":null, + "defaultValue":null, + "description":null, + "name":"{{ property.name }}", + "origName":"{{ property.name }}", + "parentUniqueId":"{{ sdc_resource.unique_identifier }}", + "password":false, + "required":false, + "schema":{ + "property":{ + "type":"", + "required":false, + "definition":false, + "description":null, + "password":false + } + }, + "schemaType":"", + "type":"{{ property.property_type }}", + "uniqueId":"{{ sdc_resource.unique_identifier }}.{{ property.name }}", + {% if property.value is not none %} + "value":"{{ property.value }}", + {% else %} + "value":null, + {% endif %} + "definition":false, + "getInputValues":null, + "parentPropertyType":null, + "subPropertyInputPath":null, + "getPolicyValues":null, + "inputPath":null + } + ] + } +} \ No newline at end of file diff --git a/src/onapsdk/sdc/templates/sdc_resource_add_nested_input.json.j2 b/src/onapsdk/sdc/templates/sdc_resource_add_nested_input.json.j2 new file mode 100644 index 0000000..9dc8261 --- /dev/null +++ b/src/onapsdk/sdc/templates/sdc_resource_add_nested_input.json.j2 @@ -0,0 +1,35 @@ +{ + "componentInstanceInputsMap":{ + "{{ component.unique_id }}":[ + { + {# "defaultValue":null, #} + "name":"{{ input.name }}", + "origName":"{{ input.name }}", + {# "parentUniqueId":"cs0008", #} + "password":false, + "required":false, + "schema":{ + "property":{ + {# "type":"", + "required":false, + "definition":false, + "password":false #} + } + }, + {# "schemaType":"", #} + "type":"{{ input.input_type }}", + "uniqueId":"{{ sdc_resource.unique_identifier }}.{{ input.name }}", + {% if input.default_value is not none %} + "value":"{{ input.default_value }}", + {% endif %} + "definition":false + {# "type":"{{ input.input_type }}", #} + } + ] + }, + "componentInstanceProperties":{ + "{{ component.unique_id }}":[] + }, + "groupProperties":{}, + "policyProperties":{} +} \ No newline at end of file diff --git a/src/onapsdk/sdc/templates/sdc_resource_add_property.json.j2 b/src/onapsdk/sdc/templates/sdc_resource_add_property.json.j2 new file mode 100644 index 0000000..bed49ca --- /dev/null +++ b/src/onapsdk/sdc/templates/sdc_resource_add_property.json.j2 @@ -0,0 +1,17 @@ +{ + "{{ property.name }}":{ + "schema":{ + "property":{ + "type":"" + } + }, + "name": "{{ property.name }}", + {% if property.description %} + "description": "{{ property.description }}", + {% endif %} + {% if property.value %} + "value": "{{ property.value }}", + {% endif %} + "type": "{{ property.property_type }}" + } +} \ No newline at end of file diff --git a/src/onapsdk/sdc/templates/sdc_resource_category.json.j2 b/src/onapsdk/sdc/templates/sdc_resource_category.json.j2 new file mode 100644 index 0000000..633aacd --- /dev/null +++ b/src/onapsdk/sdc/templates/sdc_resource_category.json.j2 @@ -0,0 +1,13 @@ + "categories": [ + { + "normalizedName": "{{ category.normalized_name }}", + "name": "{{ category.name }}", + "uniqueId": "{{ category.unique_id }}", + "subcategories": {% if category.subcategories %}{{ category.subcategories|tojson }}{% else %}null{% endif %}, + "version": {% if category.version %}"{{ category.version }}"{% else %}null{% endif %}, + "ownerId": {% if category.owner_id %}"{{ category.owner_id }}"{% else %}null{% endif %}, + "empty": {{ category.empty|tojson }}, + "type": {% if category.type %}"{{ category.type }}"{% else %}null{% endif %}, + "icons": {% if category.icons %}{{ category.icons|tojson }}{% else %}null{% endif %} + } + ] \ No newline at end of file diff --git a/src/onapsdk/sdc/templates/sdc_resource_component_set_property_value.json.j2 b/src/onapsdk/sdc/templates/sdc_resource_component_set_property_value.json.j2 new file mode 100644 index 0000000..46bd527 --- /dev/null +++ b/src/onapsdk/sdc/templates/sdc_resource_component_set_property_value.json.j2 @@ -0,0 +1,13 @@ +[ + { + "name":"{{ property.name }}", + "parentUniqueId":"{{ component.actual_component_uid }}", + "type":"{{ property.property_type }}", + "uniqueId":"{{ component.actual_component_uid }}.{{ property.name }}", + "value":"{{ value }}", + "definition":false, + "toscaPresentation":{ + "ownerId":"{{ component.actual_component_uid }}" + } + } +] \ No newline at end of file diff --git a/src/onapsdk/sdc/templates/sdc_resource_set_input_default_value.json.j2 b/src/onapsdk/sdc/templates/sdc_resource_set_input_default_value.json.j2 new file mode 100644 index 0000000..97c2cfd --- /dev/null +++ b/src/onapsdk/sdc/templates/sdc_resource_set_input_default_value.json.j2 @@ -0,0 +1,8 @@ +[ + { + "defaultValue":"{{ default_value }}", + "name":"{{ input.name }}", + "type":"{{ input.input_type }}", + "uniqueId":"{{ input.unique_id }}" + } +] \ No newline at end of file diff --git a/src/onapsdk/sdc/templates/sdc_resource_set_property_value.json.j2 b/src/onapsdk/sdc/templates/sdc_resource_set_property_value.json.j2 new file mode 100644 index 0000000..d0e73f7 --- /dev/null +++ b/src/onapsdk/sdc/templates/sdc_resource_set_property_value.json.j2 @@ -0,0 +1,13 @@ +[ + { + "name":"{{ property.name }}", + "parentUniqueId":"{{ sdc_resource.unique_identifier }}", + "type":"{{ property.property_type }}", + "uniqueId":"{{ sdc_resource.unique_identifier }}.{{ property.name }}", + "value":"{{ value }}", + "definition":false, + "toscaPresentation":{ + "ownerId":"{{ sdc_resource.unique_identifier }}" + } + } +] \ No newline at end of file diff --git a/src/onapsdk/sdc/templates/service_create.json.j2 b/src/onapsdk/sdc/templates/service_create.json.j2 new file mode 100644 index 0000000..f247059 --- /dev/null +++ b/src/onapsdk/sdc/templates/service_create.json.j2 @@ -0,0 +1,29 @@ + +{ + "componentType": "SERVICE", + "properties": [], + "requirements": {}, + "toscaArtifacts": {}, + "tags": ["{{ name }}"], + "artifacts": {}, + "description": "service", + "serviceApiArtifacts": {}, + "capabilities": {}, + "name": "{{ name }}", + "componentInstancesProperties": {}, + "componentInstancesAttributes": {}, + "contactId": "cs0008", + "groups": [], + "projectCode": "123456", + "deploymentArtifacts": {}, + "attributes": [], + "componentInstances": [], + "ecompGeneratedNaming": true, + "instantiationType": "{{ instantiation_type }}", + "environmentContext": "General_Revenue-Bearing", + {% include "sdc_resource_category.json.j2" %}, + "icon": "network_l_1-3", + "serviceFunction": "{{ function }}", + "serviceRole": "{{ role }}", + "serviceType": "{{ service_type }}" +} diff --git a/src/onapsdk/sdc/templates/vendor_create.json.j2 b/src/onapsdk/sdc/templates/vendor_create.json.j2 new file mode 100644 index 0000000..858f736 --- /dev/null +++ b/src/onapsdk/sdc/templates/vendor_create.json.j2 @@ -0,0 +1,5 @@ +{ + "iconRef": "icon", + "vendorName": "{{ name }}", + "description": "vendor" +} diff --git a/src/onapsdk/sdc/templates/vf_create.json.j2 b/src/onapsdk/sdc/templates/vf_create.json.j2 new file mode 100644 index 0000000..6f165e5 --- /dev/null +++ b/src/onapsdk/sdc/templates/vf_create.json.j2 @@ -0,0 +1,27 @@ +{ + "artifacts": {}, + "attributes": [], + "capabilities": {}, + {% include "sdc_resource_category.json.j2" %}, + "componentInstances": [], + "componentInstancesAttributes": {}, + "componentInstancesProperties": {}, + "componentType": "RESOURCE", + "contactId": "cs0008", + {% if category.name != "Allotted Resource" %} + "csarUUID": "{{ vsp.csar_uuid }}", + "csarVersion": "1.0", + {% endif %} + "deploymentArtifacts": {}, + "description": "VF", + "icon": "defaulticon", + "name": "{{ name }}", + "properties": [], + "groups": [], + "requirements": {}, + "resourceType": "VF", + "tags": ["{{ name }}"], + "toscaArtifacts": {}, + "vendorName": "{{ vendor.name }}", + "vendorRelease": "1.0" +} diff --git a/src/onapsdk/sdc/templates/vf_vsp_update.json.j2 b/src/onapsdk/sdc/templates/vf_vsp_update.json.j2 new file mode 100644 index 0000000..f862676 --- /dev/null +++ b/src/onapsdk/sdc/templates/vf_vsp_update.json.j2 @@ -0,0 +1,61 @@ +{ + "resourceType": "{{ resource_data['resourceType'] }}", + "componentType": "{{ resource_data['componentType'] }}", + "tags": {{ resource_data['tags'] | tojson }}, + "icon": "{{ resource_data['icon'] }}", + "uniqueId": "{{ resource_data['uniqueId'] }}", + "uuid": "{{ resource_data['uuid'] }}", + "invariantUUID": "{{ resource_data['invariantUUID'] }}", + "contactId": "{{ resource_data['contactId'] }}", + "categories": {{ resource_data['categories'] | tojson }}, + "creatorUserId": "{{ resource_data['creatorUserId'] }}", + "creationDate": {{ resource_data['creationDate'] }}, + "creatorFullName": "{{ resource_data['creatorFullName'] }}", + "description": "{{ resource_data['description'] }}", + "lastUpdateDate": {{ resource_data['lastUpdateDate'] }}, + "lastUpdaterUserId": "{{ resource_data['lastUpdaterUserId'] }}", + "lastUpdaterFullName": "{{ resource_data['lastUpdaterFullName'] }}", + "lifecycleState": "{{ resource_data['lifecycleState'] }}", + "name": "{{ resource_data['name'] }}", + "version": "{{ resource_data['version'] }}", + "allVersions": {{ resource_data['allVersions'] | tojson }}, + "vendorName": "{{ resource_data['vendorName'] }}", + "vendorRelease": "{{ resource_data['vendorRelease'] }}", + "normalizedName": "{{ resource_data['normalizedName'] }}", + "systemName": "{{ resource_data['systemName'] }}", + "archived": {{ resource_data['archived'] | tojson }}, + "componentMetadata": { + "resourceType": "{{ resource_data['resourceType'] }}", + "componentType": "{{ resource_data['componentType'] }}", + "tags": {{ resource_data['tags'] | tojson }}, + "icon": "{{ resource_data['icon'] }}", + "uniqueId": "{{ resource_data['uniqueId'] }}", + "uuid": "{{ resource_data['uuid'] }}", + "invariantUUID": "{{ resource_data['invariantUUID'] }}", + "contactId": "{{ resource_data['contactId'] }}", + "categories": {{ resource_data['categories'] | tojson }}, + "creatorUserId": "{{ resource_data['creatorUserId'] }}", + "creationDate": {{ resource_data['creationDate'] }}, + "creatorFullName": "{{ resource_data['creatorFullName'] }}", + "description": "{{ resource_data['description'] }}", + "lastUpdateDate": {{ resource_data['lastUpdateDate'] }}, + "lastUpdaterUserId": "{{ resource_data['lastUpdaterUserId'] }}", + "lastUpdaterFullName": "{{ resource_data['lastUpdaterFullName'] }}", + "lifecycleState": "{{ resource_data['lifecycleState'] }}", + "name": "{{ resource_data['name'] }}", + "version": "{{ resource_data['version'] }}", + "allVersions": {{ resource_data['allVersions'] | tojson }}, + "vendorName": "{{ resource_data['vendorName'] }}", + "vendorRelease": "{{ resource_data['vendorRelease'] }}", + "normalizedName": "{{ resource_data['normalizedName'] }}", + "systemName": "{{ resource_data['systemName'] }}", + "csarUUID": "{{ resource_data['csarUUID'] }}", + "csarVersion": "{{ resource_data['csarVersion'] }}", + "derivedFrom": null, + "resourceVendorModelNumber": "{{ resource_data['resourceVendorModelNumber'] }}" + }, + "csarUUID": "{{ csarUUID }}", + "csarVersion": "{{ csarVersion }}", + "derivedFrom": null, + "resourceVendorModelNumber": "{{ resource_data['resourceVendorModelNumber'] }}" +} \ No newline at end of file diff --git a/src/onapsdk/sdc/templates/vsp_create.json.j2 b/src/onapsdk/sdc/templates/vsp_create.json.j2 new file mode 100644 index 0000000..30fa6b9 --- /dev/null +++ b/src/onapsdk/sdc/templates/vsp_create.json.j2 @@ -0,0 +1,11 @@ +{ + "name": "{{ name }}", + "description": "vendor software product", + "icon": "icon", + "category": "resourceNewCategory.generic", + "subCategory": "resourceNewCategory.generic.abstract", + "vendorName": "{{ vendor.name }}", + "vendorId": "{{ vendor.identifier }}", + "licensingData": {}, + "onboardingMethod": "NetworkPackage" +} diff --git a/src/onapsdk/sdc/vendor.py b/src/onapsdk/sdc/vendor.py new file mode 100644 index 0000000..a82e3b9 --- /dev/null +++ b/src/onapsdk/sdc/vendor.py @@ -0,0 +1,108 @@ +"""Vendor module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Any +from typing import Dict + +from onapsdk.sdc.sdc_element import SdcElement +import onapsdk.constants as const +from onapsdk.utils.headers_creator import headers_sdc_creator + + +class Vendor(SdcElement): + """ + ONAP Vendor Object used for SDC operations. + + Attributes: + name (str): the name of the vendor. Defaults to "Generic-Vendor". + identifier (str): the unique ID of the vendor from SDC. + status (str): the status of the vendor from SDC. + version (str): the version ID of the vendor from SDC. + + """ + + VENDOR_PATH = "vendor-license-models" + headers = headers_sdc_creator(SdcElement.headers) + + def __init__(self, name: str = None): + """ + Initialize vendor object. + + Args: + name (optional): the name of the vendor + + """ + super().__init__() + self.name: str = name or "Generic-Vendor" + + def onboard(self) -> None: + """Onboard the vendor in SDC.""" + if not self.status: + self.create() + self.onboard() + elif self.status == const.DRAFT: + self.submit() + + def create(self) -> None: + """Create the vendor in SDC if not already existing.""" + self._create("vendor_create.json.j2", name=self.name) + + def submit(self) -> None: + """Submit the SDC vendor in order to enable it.""" + self._logger.info("attempting to certify/sumbit vendor %s in SDC", + self.name) + if self.status != const.CERTIFIED and self.created(): + self._really_submit() + elif self.status == const.CERTIFIED: + self._logger.warning( + "vendor %s in SDC is already submitted/certified", self.name) + elif not self.created(): + self._logger.warning("vendor %s in SDC is not created", self.name) + + def update_informations_from_sdc(self, details: Dict[str, Any]) -> None: + """ + + Update instance with details from SDC. + + Args: + details (Dict[str, Any]): dict from SDC + + """ + self._status = details['status'] + + @classmethod + def import_from_sdc(cls, values: Dict[str, Any]) -> 'Vendor': + """ + Import Vendor from SDC. + + Args: + values (Dict[str, Any]): dict to parse returned from SDC. + + Returns: + a Vsp instance with right values + + """ + vendor = Vendor(values['name']) + vendor.identifier = values['id'] + return vendor + + def _really_submit(self) -> None: + """Really submit the SDC Vf in order to enable it.""" + self._action_to_sdc(const.SUBMIT) + self._status = const.CERTIFIED + + @classmethod + def _sdc_path(cls) -> None: + """Give back the end of SDC path.""" + return cls.VENDOR_PATH diff --git a/src/onapsdk/sdc/vf.py b/src/onapsdk/sdc/vf.py new file mode 100644 index 0000000..7e4e4a7 --- /dev/null +++ b/src/onapsdk/sdc/vf.py @@ -0,0 +1,164 @@ +"""Vf module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 __future__ import annotations +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union + +from onapsdk.exceptions import ParameterError +from onapsdk.sdc.properties import ComponentProperty, NestedInput, Property +from onapsdk.sdc.sdc_resource import SdcResource +from onapsdk.sdc.vendor import Vendor +from onapsdk.utils.jinja import jinja_env +import onapsdk.constants as const + +if TYPE_CHECKING: + from onapsdk.sdc.vsp import Vsp + + +class Vf(SdcResource): + """ + ONAP Vf Object used for SDC operations. + + Attributes: + name (str): the name of the vendor. Defaults to "Generic-Vendor". + identifier (str): the unique ID of the vendor from SDC. + status (str): the status of the vendor from SDC. + version (str): the version ID of the vendor from SDC. + vsp (Vsp): the Vsp used for VF creation + uuid (str): the UUID of the VF (which is different from identifier, + don't ask why...) + unique_identifier (str): Yet Another ID, just to puzzle us... + + """ + + def __init__(self, name: str = None, version: str = None, sdc_values: Dict[str, str] = None, # pylint: disable=too-many-arguments + vsp: Vsp = None, properties: List[Property] = None, + inputs: Union[Property, NestedInput] = None, + category: str = None, subcategory: str = None, + vendor: Vendor = None): + """ + Initialize vf object. + + Args: + name (optional): the name of the vf + version (str, optional): the version of the vf + vsp (Vsp, optional): VSP object related with the Vf object. + Defaults to None. + vendor (Vendor, optional): Vendor object used if VSP was not provided. + Defaults to None. + + """ + super().__init__(sdc_values=sdc_values, version=version, properties=properties, + inputs=inputs, category=category, subcategory=subcategory) + self.name: str = name or "ONAP-test-VF" + self.vsp: Vsp = vsp + self._vendor: Vendor = vendor + + @property + def vendor(self) -> Optional[Vendor]: + """Vendor related wth Vf. + + If Vf is created vendor is get from it's resource metadata. + Otherwise it's vendor provided by the user or the vendor from vsp. + It's possible that method returns None, but it won't be possible then + to create that Vf resource. + + Returns: + Optional[Vendor]: Vendor object related with Vf + + """ + if self._vendor: + return self._vendor + if self.created(): + resource_data: Dict[str, Any] = self.send_message_json( + "GET", + "Get VF data to update VSP", + self.resource_inputs_url + ) + self._vendor = Vendor(name=resource_data.get("vendorName")) + elif self.vsp: + self._vendor = self.vsp.vendor + return self._vendor + + + def create(self) -> None: + """Create the Vf in SDC if not already existing. + + Raises: + ParameterError: VSP is not provided during VF object initalization + + """ + if not any([self.vsp, self.vendor]): + raise ParameterError("At least vsp or vendor needs to be given") + self._create("vf_create.json.j2", name=self.name, vsp=self.vsp, + category=self.category, vendor=self.vendor) + + def _really_submit(self) -> None: + """Really submit the SDC Vf in order to enable it.""" + self._action_to_sdc(const.CERTIFY, "lifecycleState") + self.load() + + def update_vsp(self, vsp: Vsp) -> None: + """Update Vsp. + + Update VSP UUID and version for Vf object. + + Args: + vsp (Vsp): Object to be used in Vf + + Raises: + ValidationError: Vf object request has invalid structure. + + """ + resource_data: Dict[str, Any] = self.send_message_json( + "GET", + "Get VF data to update VSP", + self.resource_inputs_url + ) + self.send_message_json( + "PUT", + "Update vsp data", + self.resource_inputs_url, + data=jinja_env() + .get_template("vf_vsp_update.json.j2") + .render(resource_data=resource_data, + csarUUID=vsp.csar_uuid, + csarVersion=vsp.human_readable_version) + ) + + def declare_input(self, + input_to_declare: Union[Property, NestedInput, ComponentProperty]) -> None: + """Declare input for given property, nested input or component property object. + + Call SDC FE API to declare input for given property. + + Args: + input_declaration (Union[Property, NestedInput]): Property or ComponentProperty + to declare input or NestedInput object + + Raises: + ParameterError: if the given property is not SDC resource property + + """ + if not isinstance(input_to_declare, ComponentProperty): + super().declare_input(input_to_declare) + else: + self.send_message("POST", + f"Declare new input for {input_to_declare.name} property", + f"{self.resource_inputs_url}/create/inputs", + data=jinja_env().get_template(\ + "component_declare_input.json.j2").\ + render(\ + component=input_to_declare.component, + property=input_to_declare)) diff --git a/src/onapsdk/sdc/vfc.py b/src/onapsdk/sdc/vfc.py new file mode 100644 index 0000000..e2a6f57 --- /dev/null +++ b/src/onapsdk/sdc/vfc.py @@ -0,0 +1,46 @@ +"""VFC module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Dict + +from onapsdk.exceptions import ResourceNotFound + +from .sdc_resource import SdcResource + + +class Vfc(SdcResource): + """ONAP VFC Object used for SDC operations.""" + + def __init__(self, name: str, version: str = None, sdc_values: Dict[str, str] = None): + """Initialize VFC object. + + Vfc has to exist in SDC. + + Args: + name (str): Vfc name + version (str): Vfc version + sdc_values (Dict[str, str], optional): Sdc values of existing Vfc. Defaults to None. + + Raises: + ResourceNotFound: Vfc doesn't exist in SDC + + """ + super().__init__(name=name, version=version, sdc_values=sdc_values) + if not sdc_values and not self.exists(): + raise ResourceNotFound( + "This Vfc has to exist prior to object initialization.") + + def _really_submit(self) -> None: + """Really submit the SDC in order to enable it.""" + raise NotImplementedError("Vfc doesn't need _really_submit") diff --git a/src/onapsdk/sdc/vl.py b/src/onapsdk/sdc/vl.py new file mode 100644 index 0000000..22872f0 --- /dev/null +++ b/src/onapsdk/sdc/vl.py @@ -0,0 +1,46 @@ +"""Vl module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Dict + +from onapsdk.exceptions import ResourceNotFound + +from .sdc_resource import SdcResource + + +class Vl(SdcResource): + """ONAP Vl Object used for SDC operations.""" + + def __init__(self, name: str, version: str = None, sdc_values: Dict[str, str] = None): + """Initialize Vl object. + + Vl has to exists in SDC. + + Args: + name (str): Vl name + version (str): Vl version + sdc_values (Dict[str, str], optional): Sdc values of existing Vl. Defaults to None. + + Raises: + ResourceNotFound: Vl doesn't exist in SDC + + """ + super().__init__(name=name, version=version, sdc_values=sdc_values) + if not sdc_values and not self.exists(): + raise ResourceNotFound( + "This Vl has to exist prior to object initialization.") + + def _really_submit(self) -> None: + """Really submit the SDC in order to enable it.""" + raise NotImplementedError("Vl don't need _really_submit") diff --git a/src/onapsdk/sdc/vsp.py b/src/onapsdk/sdc/vsp.py new file mode 100644 index 0000000..a6c4c24 --- /dev/null +++ b/src/onapsdk/sdc/vsp.py @@ -0,0 +1,370 @@ +"""VSP module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 +from typing import Any, Optional +from typing import BinaryIO +from typing import Callable +from typing import Dict + +from onapsdk.exceptions import APIError, ParameterError +from onapsdk.sdc.sdc_element import SdcElement +from onapsdk.sdc.vendor import Vendor +import onapsdk.constants as const +from onapsdk.utils.headers_creator import headers_sdc_creator + +# Hard to do fewer attributes and still mapping SDC VSP object. +class Vsp(SdcElement): # pylint: disable=too-many-instance-attributes + """ + ONAP VSP Object used for SDC operations. + + Attributes: + name (str): the name of the vsp. Defaults to "ONAP-test-VSP". + identifier (str): the unique ID of the VSP from SDC. + status (str): the status of the VSP from SDC. + version (str): the version ID of the VSP from SDC. + csar_uuid (str): the CSAR ID of the VSP from SDC. + vendor (Vendor): The VSP Vendor + + """ + + VSP_PATH = "vendor-software-products" + headers = headers_sdc_creator(SdcElement.headers) + + def __init__(self, name: str = None, package: BinaryIO = None, + vendor: Vendor = None): + """ + Initialize vsp object. + + Args: + name (optional): the name of the vsp + + """ + super().__init__() + self._csar_uuid: str = None + self._vendor: Vendor = vendor or None + self.name: str = name or "ONAP-test-VSP" + self.package = package or None + + @property + def status(self): + """Return and load the status.""" + self.load_status() + return self._status + + def onboard(self) -> None: + """Onboard the VSP in SDC.""" + status: Optional[str] = self.status + if not status: + if not self._vendor: + raise ParameterError("No Vendor provided.") + self.create() + self.onboard() + elif status == const.DRAFT: + if not self.package: + raise ParameterError("No file/package provided.") + self.upload_package(self.package) + self.onboard() + elif status == const.UPLOADED: + self.validate() + self.onboard() + elif status == const.VALIDATED: + self.commit() + self.onboard() + elif status == const.COMMITED: + self.submit() + self.onboard() + elif status == const.CERTIFIED: + self.create_csar() + + def create(self) -> None: + """Create the Vsp in SDC if not already existing.""" + if self.vendor: + self._create("vsp_create.json.j2", + name=self.name, + vendor=self.vendor) + + def upload_package(self, package_to_upload: BinaryIO) -> None: + """ + Upload given zip file into SDC as artifacts for this Vsp. + + Args: + package_to_upload (file): the zip file to upload + + """ + self._action("upload package", + const.DRAFT, + self._upload_action, + package_to_upload=package_to_upload) + + def update_package(self, package_to_upload: BinaryIO) -> None: + """ + Upload given zip file into SDC as artifacts for this Vsp. + + Args: + package_to_upload (file): the zip file to upload + + """ + self._action("update package", + const.COMMITED, + self._upload_action, + package_to_upload=package_to_upload) + + def validate(self) -> None: + """Validate the artifacts uploaded.""" + self._action("validate", const.UPLOADED, self._validate_action) + + def commit(self) -> None: + """Commit the SDC Vsp.""" + self._action("commit", + const.VALIDATED, + self._generic_action, + action=const.COMMIT) + + def submit(self) -> None: + """Submit the SDC Vsp in order to enable it.""" + self._action("certify/sumbit", + const.COMMITED, + self._generic_action, + action=const.SUBMIT) + + def create_csar(self) -> None: + """Create the CSAR package in the SDC Vsp.""" + self._action("create CSAR package", const.CERTIFIED, + self._create_csar_action) + + @property + def vendor(self) -> Vendor: + """Return and lazy load the vendor.""" + if not self._vendor and self.created(): + details = self._get_vsp_details() + if details: + self._vendor = Vendor(name=details['vendorName']) + return self._vendor + + @vendor.setter + def vendor(self, vendor: Vendor) -> None: + """Set value for Vendor.""" + self._vendor = vendor + + @property + def csar_uuid(self) -> str: + """Return and lazy load the CSAR UUID.""" + if self.created() and not self._csar_uuid: + self.create_csar() + return self._csar_uuid + + @csar_uuid.setter + def csar_uuid(self, csar_uuid: str) -> None: + """Set value for csar uuid.""" + self._csar_uuid = csar_uuid + + def _get_item_version_details(self) -> Dict[Any, Any]: + """Get vsp item details.""" + if self.created() and self.version: + url = "{}/items/{}/versions/{}".format(self._base_url(), + self.identifier, + self.version) + return self.send_message_json('GET', 'get item version', url) + return {} + + def _upload_action(self, package_to_upload: BinaryIO): + """Do upload for real.""" + url = "{}/{}/{}/orchestration-template-candidate".format( + self._base_url(), Vsp._sdc_path(), self._version_path()) + headers = self.headers.copy() + headers.pop("Content-Type") + headers["Accept-Encoding"] = "gzip, deflate" + data = {'upload': package_to_upload} + upload_result = self.send_message('POST', + 'upload ZIP for Vsp', + url, + headers=headers, + files=data) + if upload_result: + # TODO https://jira.onap.org/browse/SDC-3505 pylint: disable=W0511 + response_json = json.loads(upload_result.text) + if response_json["status"] != "Success": + self._logger.error( + "an error occured during file upload for Vsp %s", + self.name) + raise APIError(response_json) + # End TODO https://jira.onap.org/browse/SDC-3505 + self._logger.info("Files for Vsp %s have been uploaded", + self.name) + else: + self._logger.error( + "an error occured during file upload for Vsp %s", + self.name) + + def _validate_action(self): + """Do validate for real.""" + url = "{}/{}/{}/orchestration-template-candidate/process".format( + self._base_url(), Vsp._sdc_path(), self._version_path()) + validate_result = self.send_message_json('PUT', + 'Validate artifacts for Vsp', + url) + if validate_result and validate_result['status'] == 'Success': + self._logger.info("Artifacts for Vsp %s have been validated", + self.name) + else: + self._logger.error( + "an error occured during artifacts validation for Vsp %s", + self.name) + + def _generic_action(self, action=None): + """Do a generic action for real.""" + if action: + self._action_to_sdc(action, action_type="lifecycleState") + + def _create_csar_action(self): + """Create CSAR package for real.""" + result = self._action_to_sdc(const.CREATE_PACKAGE, + action_type="lifecycleState") + if result: + self._logger.info("result: %s", result.text) + data = result.json() + self.csar_uuid = data['packageId'] + + def _action(self, action_name: str, right_status: str, + action_function: Callable[['Vsp'], None], **kwargs) -> None: + """ + Generate an action on the instance in order to send it to SDC. + + Args: + action_name (str): The name of the action (for the logs) + right_status (str): The status that the object must be + action_function (function): the function to perform if OK + + """ + self._logger.info("attempting to %s for %s in SDC", action_name, + self.name) + if self.status == right_status: + action_function(**kwargs) + else: + self._logger.warning( + "vsp %s in SDC is not created or not in %s status", self.name, + right_status) + + # VSP: DRAFT --> UPLOADED --> VALIDATED --> COMMITED --> CERTIFIED + def load_status(self) -> None: + """ + Load Vsp status from SDC. + + rules are following: + + * DRAFT: status == DRAFT and networkPackageName not present + + * UPLOADED: status == DRAFT and networkPackageName present and + validationData not present + + * VALIDATED: status == DRAFT and networkPackageName present and + validationData present and state.dirty = true + + * COMMITED: status == DRAFT and networkPackageName present and + validationData present and state.dirty = false + + * CERTIFIED: status == CERTIFIED + + status is found in sdc items + state is found in sdc version from items + networkPackageName and validationData is found in SDC vsp show + + """ + item_details = self._get_item_details() + if (item_details and item_details['status'] == const.CERTIFIED): + self._status = const.CERTIFIED + else: + self._check_status_not_certified() + + def _check_status_not_certified(self) -> None: + """Check a status when it's not certified.""" + vsp_version_details = self._get_item_version_details() + vsp_details = self._get_vsp_details() + if (vsp_version_details and 'state' in vsp_version_details + and not vsp_version_details['state']['dirty'] and vsp_details + and 'validationData' in vsp_details): + self._status = const.COMMITED + else: + self._check_status_not_commited() + + def _check_status_not_commited(self) -> None: + """Check a status when it's not certified or commited.""" + vsp_details = self._get_vsp_details() + if (vsp_details and 'validationData' in vsp_details): + self._status = const.VALIDATED + elif (vsp_details and 'validationData' not in vsp_details + and 'networkPackageName' in vsp_details): + self._status = const.UPLOADED + elif vsp_details: + self._status = const.DRAFT + + def _get_vsp_details(self) -> Dict[Any, Any]: + """Get vsp details.""" + if self.created() and self.version: + url = "{}/vendor-software-products/{}/versions/{}".format( + self._base_url(), self.identifier, self.version) + + return self.send_message_json('GET', 'get vsp version', url) + return {} + + @classmethod + def import_from_sdc(cls, values: Dict[str, Any]) -> 'Vsp': + """ + Import Vsp from SDC. + + Args: + values (Dict[str, Any]): dict to parse returned from SDC. + + Returns: + a Vsp instance with right values + + """ + cls._logger.debug("importing VSP %s from SDC", values['name']) + vsp = Vsp(values['name']) + vsp.identifier = values['id'] + vsp.vendor = Vendor(name=values['vendorName']) + return vsp + + def _really_submit(self) -> None: + """Really submit the SDC Vf in order to enable it. + + Raises: + NotImplementedError + + """ + raise NotImplementedError("VSP don't need _really_submit") + + @classmethod + def _sdc_path(cls) -> None: + """Give back the end of SDC path.""" + return cls.VSP_PATH + + def create_new_version(self) -> None: + """Create new version of VSP. + + Create a new major version of VSP so it would be possible to + update a package or do some changes in VSP. + + """ + self._logger.debug("Create new version of %s VSP", self.name) + self.send_message_json("POST", + "Create new VSP version", + (f"{self._base_url()}/items/{self.identifier}/" + f"versions/{self.version}"), + data=json.dumps({ + "creationMethod": "major", + "description": "New VSP version" + })) + self.load() diff --git a/src/onapsdk/sdnc/__init__.py b/src/onapsdk/sdnc/__init__.py new file mode 100644 index 0000000..8bc40e8 --- /dev/null +++ b/src/onapsdk/sdnc/__init__.py @@ -0,0 +1,15 @@ +"""SDNC package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 .preload import NetworkPreload, VfModulePreload diff --git a/src/onapsdk/sdnc/preload.py b/src/onapsdk/sdnc/preload.py new file mode 100644 index 0000000..8b00e5c --- /dev/null +++ b/src/onapsdk/sdnc/preload.py @@ -0,0 +1,148 @@ +"""SDNC preload module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Any, Dict, Iterable + +from onapsdk.utils.headers_creator import headers_sdnc_creator +from onapsdk.utils.jinja import jinja_env + +from .sdnc_element import SdncElement + + +class Preload(SdncElement): + """Preload base class.""" + + headers: Dict[str, str] = headers_sdnc_creator(SdncElement.headers) + + +class PreloadInformation(Preload): + """Preload information.""" + + def __init__(self, preload_id: str, preload_type: str, preload_data: Dict[str, Any]) -> None: + """Preload information initialization. + + Args: + preload_id (str): Preload id + preload_type (str): Preload type + preload_data (Dict[str, Any]): Preload data + """ + super().__init__() + self.preload_id: str = preload_id + self.preload_type: str = preload_type + self.preload_data: Dict[str, Any] = preload_data + + def __repr__(self) -> str: # noqa + """Preload information human readble string. + + Returns: + str: Preload information description + + """ + return (f"PreloadInformation(preload_id={self.preload_id}, " + f"preload_type={self.preload_type}, " + f"preload_data={self.preload_data})") + + @classmethod + def get_all(cls) -> Iterable["PreloadInformation"]: + """Get all preload informations. + + Get all uploaded preloads. + + Yields: + PreloadInformation: Preload information object + + """ + for preload_information in \ + cls.send_message_json(\ + "GET",\ + "Get SDNC preload information",\ + f"{cls.base_url}/restconf/operational/GENERIC-RESOURCE-API:preload-information" + ).get('preload-information', {}).get('preload-list', []): + yield PreloadInformation(preload_id=preload_information["preload-id"], + preload_type=preload_information["preload-type"], + preload_data=preload_information["preload-data"]) + + +class NetworkPreload(Preload): + """Class to upload network module preload.""" + + @classmethod + def upload_network_preload(cls, + network: "Network", + network_instance_name: str, + subnets: Iterable["Subnet"] = None) -> None: + """Upload network preload. + + Args: + network: Network object + network_instance_name (str): network instance name + subnets (Iterable[Subnet], optional): Iterable object of Subnet. + Defaults to None. + + """ + cls.send_message_json( + "POST", + "Upload Network preload using GENERIC-RESOURCE-API", + (f"{cls.base_url}/restconf/operations/" + "GENERIC-RESOURCE-API:preload-network-topology-operation"), + data=jinja_env().get_template( + "instantiate_network_ala_carte_upload_preload_gr_api.json.j2"). + render( + network=network, + network_instance_name=network_instance_name, + subnets=subnets if subnets else [] + ) + ) + + +class VfModulePreload(Preload): + """Class to upload vf module preload.""" + + @classmethod + def upload_vf_module_preload(cls, # pylint: disable=too-many-arguments + vnf_instance: "VnfInstance", + vf_module_instance_name: str, + vf_module: "VfModule", + vnf_parameters: Iterable["InstantiationParameter"] = None) -> None: + """Upload vf module preload. + + Args: + vnf_instance: VnfInstance object + vf_module_instance_name (str): VF module instance name + vf_module (VfModule): VF module + vnf_parameters (Iterable[InstantiationParameter], optional): Iterable object + of InstantiationParameter. Defaults to None. + + """ + vnf_para = [] + if vnf_parameters: + for vnf_parameter in vnf_parameters: + vnf_para.append({ + "name": vnf_parameter.name, + "value": vnf_parameter.value + }) + cls.send_message_json( + "POST", + "Upload VF module preload using GENERIC-RESOURCE-API", + (f"{cls.base_url}/restconf/operations/" + "GENERIC-RESOURCE-API:preload-vf-module-topology-operation"), + data=jinja_env().get_template( + "instantiate_vf_module_ala_carte_upload_preload_gr_api.json.j2"). + render( + vnf_instance=vnf_instance, + vf_module_instance_name=vf_module_instance_name, + vf_module=vf_module, + vnf_parameters=vnf_para + ) + ) diff --git a/src/onapsdk/sdnc/sdnc_element.py b/src/onapsdk/sdnc/sdnc_element.py new file mode 100644 index 0000000..84f56d9 --- /dev/null +++ b/src/onapsdk/sdnc/sdnc_element.py @@ -0,0 +1,48 @@ +"""SDNC base module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.configuration import settings +from onapsdk.onap_service import OnapService +from onapsdk.utils.gui import GuiItem, GuiList + + +class SdncElement(OnapService): + """SDNC base class.""" + + base_url = settings.SDNC_URL + + @classmethod + def get_guis(cls) -> GuiItem: + """Retrieve the status of the SDNC GUIs. + + There are 2 GUIS + - SDNC DG Builder + - SDNC ODL + + Return the list of GUIs + """ + guilist = GuiList([]) + url = settings.SDNC_DG_GUI_SERVICE + response = cls.send_message( + "GET", "Get SDNC GUI DG Status", url) + guilist.add(GuiItem( + url, + response.status_code)) + url = settings.SDNC_ODL_GUI_SERVICE + response = cls.send_message( + "GET", "Get SDNC ODL GUI Status", url) + guilist.add(GuiItem( + url, + response.status_code)) + return guilist diff --git a/src/onapsdk/sdnc/templates/instantiate_network_ala_carte_upload_preload_gr_api.json.j2 b/src/onapsdk/sdnc/templates/instantiate_network_ala_carte_upload_preload_gr_api.json.j2 new file mode 100644 index 0000000..9eead38 --- /dev/null +++ b/src/onapsdk/sdnc/templates/instantiate_network_ala_carte_upload_preload_gr_api.json.j2 @@ -0,0 +1,42 @@ +{ + "input": { + "preload-network-topology-information": { + "network-policy": [], + "route-table-reference": [], + "vpn-bindings": [], + "network-topology-identifier-structure": { + "network-role": "integration_test_net", + "network-technology": "neutron", + "network-name": "{{ network_instance_name }}", + "network-type": "Generic NeutronNet" + }, + "is-external-network": false, + "is-shared-network": false, + "is-provider-network": false, + "physical-network-name": "Not Aplicable", + "subnets": [ + {% for subnet in subnets %} + { + "subnet-name": "{{ subnet.name }}", + "start-address": "{{ subnet.start_address }}", + "cidr-mask": "{{ subnet.cidr_mask }}", + "ip-version": "{{ subnet.ip_version }}", + {% if subnet.dhcp_enabled %} + "dhcp-enabled": "Y", + "dhcp-start-address": "{{ subnet.dhcp_start_address }}", + "dhcp-end-address": "{{ subnet.dhcp_end_address }}", + {% else %} + "dhcp-enabled": "N", + {% endif %} + "gateway-address": "{{ subnet.gateway_address }}" + }{% if not loop.last %},{% endif %} + {% endfor %} + ] + }, + "sdnc-request-header": { + "svc-request-id": "test", + "svc-notification-url": "http:\/\/onap.org:8080\/adapters\/rest\/SDNCNotify", + "svc-action": "reserve" + } + } +} diff --git a/src/onapsdk/sdnc/templates/instantiate_vf_module_ala_carte_upload_preload_gr_api.json.j2 b/src/onapsdk/sdnc/templates/instantiate_vf_module_ala_carte_upload_preload_gr_api.json.j2 new file mode 100644 index 0000000..eaa5936 --- /dev/null +++ b/src/onapsdk/sdnc/templates/instantiate_vf_module_ala_carte_upload_preload_gr_api.json.j2 @@ -0,0 +1,41 @@ +{ + "input":{ + "preload-vf-module-topology-information":{ + "vf-module-topology":{ + "vf-module-topology-identifier":{ + "vf-module-name":"{{vf_module_instance_name}}" + }, + "vf-module-parameters": { + "param": {{ vnf_parameters }} + } + }, + "vnf-topology-identifier-structure":{ + "vnf-name":"{{vnf_instance.vnf_name}}", + "vnf-type":"{{vnf_instance.vnf_type}}" + }, + "vnf-resource-assignments":{ + "availability-zones":{ + "availability-zone":[ + "nova" + ], + "max-count":1 + }, + "vnf-networks":{ + "vnf-network":[] + } + } + }, + "request-information":{ + "request-id":"test", + "order-version":"1", + "notification-url":"onap.org", + "order-number":"1", + "request-action":"PreloadVfModuleRequest" + }, + "sdnc-request-header":{ + "svc-request-id":"test", + "svc-notification-url":"http:\/\/onap.org:8080\/adapters\/rest\/SDNCNotify", + "svc-action":"reserve" + } + } +} diff --git a/src/onapsdk/so/__init__.py b/src/onapsdk/so/__init__.py new file mode 100644 index 0000000..15a483f --- /dev/null +++ b/src/onapsdk/so/__init__.py @@ -0,0 +1,14 @@ +"""ONAP SDK SO package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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. diff --git a/src/onapsdk/so/deletion.py b/src/onapsdk/so/deletion.py new file mode 100644 index 0000000..35ff0ee --- /dev/null +++ b/src/onapsdk/so/deletion.py @@ -0,0 +1,167 @@ +"""Deletion module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 abc import ABC + +from onapsdk.onap_service import OnapService +from onapsdk.utils.headers_creator import headers_so_creator +from onapsdk.utils.jinja import jinja_env + +from onapsdk.so.so_element import OrchestrationRequest + + +class DeletionRequest(OrchestrationRequest, ABC): + """Deletion request base class.""" + + @classmethod + def send_request(cls, instance: "AaiResource", a_la_carte: bool = True) -> "Deletion": + """Abstract method to send instance deletion request. + + Raises: + NotImplementedError: Needs to be implemented in inheriting classes + + """ + raise NotImplementedError + + +class VfModuleDeletionRequest(DeletionRequest): # pytest: disable=too-many-ancestors + """VF module deletion class.""" + + @classmethod + def send_request(cls, + instance: "VfModuleInstance", + a_la_carte: bool = True) -> "VfModuleDeletion": + """Send request to SO to delete VNF instance. + + Args: + instance (VfModuleInstance): Vf Module instance to delete + a_la_carte (boolean): deletion mode + + Returns: + VnfDeletionRequest: Deletion request object + + """ + cls._logger.debug("VF module %s deletion request", instance.vf_module_id) + response = cls.send_message_json("DELETE", + (f"Create {instance.vf_module_id} VF module" + "deletion request"), + (f"{cls.base_url}/onap/so/infra/" + f"serviceInstantiation/{cls.api_version}/" + "serviceInstances/" + f"{instance.vnf_instance.service_instance.instance_id}/" + f"vnfs/{instance.vnf_instance.vnf_id}/" + f"vfModules/{instance.vf_module_id}"), + data=jinja_env(). + get_template("deletion_vf_module.json.j2"). + render(vf_module_instance=instance, + a_la_carte=a_la_carte), + headers=headers_so_creator(OnapService.headers)) + return cls(request_id=response["requestReferences"]["requestId"]) + + +class VnfDeletionRequest(DeletionRequest): # pytest: disable=too-many-ancestors + """VNF deletion class.""" + + @classmethod + def send_request(cls, + instance: "VnfInstance", + a_la_carte: bool = True) -> "VnfDeletionRequest": + """Send request to SO to delete VNF instance. + + Args: + instance (VnfInstance): VNF instance to delete + a_la_carte (boolean): deletion mode + + Returns: + VnfDeletionRequest: Deletion request object + + """ + cls._logger.debug("VNF %s deletion request", instance.vnf_id) + response = cls.send_message_json("DELETE", + f"Create {instance.vnf_id} VNF deletion request", + (f"{cls.base_url}/onap/so/infra/" + f"serviceInstantiation/{cls.api_version}/" + "serviceInstances/" + f"{instance.service_instance.instance_id}/" + f"vnfs/{instance.vnf_id}"), + data=jinja_env(). + get_template("deletion_vnf.json.j2"). + render(vnf_instance=instance, + a_la_carte=a_la_carte), + headers=headers_so_creator(OnapService.headers)) + return cls(request_id=response["requestReferences"]["requestId"]) + + +class ServiceDeletionRequest(DeletionRequest): # pytest: disable=too-many-ancestors + """Service deletion request class.""" + + @classmethod + def send_request(cls, + instance: "ServiceInstance", + a_la_carte: bool = True) -> "ServiceDeletionRequest": + """Send request to SO to delete service instance. + + Args: + instance (ServiceInstance): service instance to delete + a_la_carte (boolean): deletion mode + + Returns: + ServiceDeletionRequest: Deletion request object + + """ + cls._logger.debug("Service %s deletion request", instance.instance_id) + response = cls.send_message_json("DELETE", + f"Create {instance.instance_id} Service deletion request", + (f"{cls.base_url}/onap/so/infra/" + f"serviceInstantiation/{cls.api_version}/" + f"serviceInstances/{instance.instance_id}"), + data=jinja_env(). + get_template("deletion_service.json.j2"). + render(service_instance=instance, + a_la_carte=a_la_carte), + headers=headers_so_creator(OnapService.headers)) + return cls(request_id=response["requestReferences"]["requestId"]) + + +class NetworkDeletionRequest(DeletionRequest): # pylint: disable=too-many-ancestors + """Network deletion request class.""" + + @classmethod + def send_request(cls, + instance: "NetworkInstance", + a_la_carte: bool = True) -> "VnfDeletionRequest": + """Send request to SO to delete Network instance. + + Args: + instance (NetworkInstance): Network instance to delete + a_la_carte (boolean): deletion mode + + Returns: + NetworkDeletionRequest: Deletion request object + + """ + cls._logger.debug("Network %s deletion request", instance.network_id) + response = cls.send_message_json("DELETE", + f"Create {instance.network_id} Network deletion request", + (f"{cls.base_url}/onap/so/infra/" + f"serviceInstantiation/{cls.api_version}/" + "serviceInstances/" + f"{instance.service_instance.instance_id}/" + f"networks/{instance.network_id}"), + data=jinja_env(). + get_template("deletion_network.json.j2"). + render(network_instance=instance, + a_la_carte=a_la_carte), + headers=headers_so_creator(OnapService.headers)) + return cls(request_id=response["requestReferences"]["requestId"]) diff --git a/src/onapsdk/so/instantiation.py b/src/onapsdk/so/instantiation.py new file mode 100644 index 0000000..bb0bde2 --- /dev/null +++ b/src/onapsdk/so/instantiation.py @@ -0,0 +1,957 @@ +"""Instantion module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 abc import ABC +from dataclasses import dataclass, field +from typing import Any, Dict, Iterable, List, Optional +from uuid import uuid4 +from dacite import from_dict + +from onapsdk.aai.business.owning_entity import OwningEntity +from onapsdk.exceptions import ( + APIError, InvalidResponse, ParameterError, ResourceNotFound, StatusError +) +from onapsdk.onap_service import OnapService +from onapsdk.sdnc import NetworkPreload, VfModulePreload +from onapsdk.sdc.service import Network, Service as SdcService, Vnf, VfModule +from onapsdk.utils.jinja import jinja_env +from onapsdk.utils.headers_creator import headers_so_creator +from onapsdk.configuration import settings + +from .so_element import OrchestrationRequest + + +@dataclass +class Operation: + """Operation class with data about method and suffix for VnfOperation.""" + + request_method: str + request_suffix: str + + +class VnfOperation(Operation): # pylint: disable=too-few-public-methods + """Class to store possible operations' data for vnfs (request method and suffix).""" + + UPDATE = Operation("PUT", "") + HEALTHCHECK = Operation("POST", "/healthcheck") + + +@dataclass +class SoServiceVfModule: + """Class to store a VfModule instance parameters.""" + + model_name: str + instance_name: str + parameters: Dict[str, Any] = field(default_factory=dict) + processing_priority: Optional[int] = None + + +@dataclass +class SoServiceXnf: + """Class to store a Xnf instance parameters.""" + + model_name: str + instance_name: str + parameters: Dict[str, Any] = field(default_factory=dict) + processing_priority: Optional[int] = None + + @classmethod + def load(cls, data: Dict[str, Any]) -> "SoServiceVnf": + """Create a vnf instance description object from the dict. + + Useful if you keep your instance data in file. + + Returns: + SoServiceVnf: SoServiceVnf object created from the dictionary + + """ + return from_dict(data_class=cls, data=data) + + +@dataclass +class SoServiceVnf(SoServiceXnf): + """Class to store a Vnf instance parameters.""" + + vf_modules: List[SoServiceVfModule] = field(default_factory=list) + + +@dataclass +class SoServicePnf(SoServiceXnf): + """Class to store a Pnf instance parameters.""" + + +@dataclass +class SoService: + """Class to store SO Service parameters used for macro instantiation. + + Contains value list: List of vnfs to instantiate + Contains value: subscription service type + """ + + subscription_service_type: str + vnfs: List[SoServiceVnf] = field(default_factory=list) + pnfs: List[SoServicePnf] = field(default_factory=list) + instance_name: Optional[str] = None + + @classmethod + def load(cls, data: Dict[str, Any]) -> "SoService": + """Create a service instance description object from the dict. + + Useful if you keep your instance data in file. + + Returns: + SoService: SoService object created from the dictionary + + """ + return from_dict(data_class=cls, data=data) + + + +@dataclass +class VnfParameters: + """Class to store vnf parameters used for macro instantiation. + + Contains value lists: List vnf Instantiation parameters and list of + vfModule parameters + """ + + name: str + vnf_parameters: Iterable["InstantiationParameter"] = None + vfmodule_parameters: Iterable["VfmoduleParameters"] = None + +@dataclass +class VfmoduleParameters: + """Class to store vfmodule parameters used for macro instantiation. + + Contains value lists: List of vfModule parameters + """ + + name: str + vfmodule_parameters: Iterable["InstantiationParameter"] = None + + +@dataclass +class InstantiationParameter: + """Class to store instantiation parameters used for preload or macro instantiation. + + Contains two values: name of parameter and it's value + """ + + name: str + value: str + + +@dataclass +class Subnet: # pylint: disable=too-many-instance-attributes + """Class to store subnet parameters used for preload.""" + + name: str + start_address: str + gateway_address: str + role: str = None + cidr_mask: str = "24" + ip_version: str = "4" + dhcp_enabled: bool = False + dhcp_start_address: Optional[str] = None + dhcp_end_address: Optional[str] = None + + def __post_init__(self) -> None: + """Post init subnet method. + + Checks if both dhcp_start_address and dhcp_end_address values are + provided if dhcp is enabled. + + Raises: + ParameterError: Neither dhcp_start_addres + nor dhcp_end_address are provided + + """ + if self.dhcp_enabled and \ + not all([self.dhcp_start_address, + self.dhcp_end_address]): + msg = "DHCP is enabled but neither DHCP " \ + "start nor end adresses are provided." + raise ParameterError(msg) + + +class Instantiation(OrchestrationRequest, ABC): + """Abstract class used for instantiation.""" + + def __init__(self, + name: str, + request_id: str, + instance_id: str) -> None: + """Instantiate object initialization. + + Initializator used by classes inherited from this abstract class. + + Args: + name (str): instantiated object name + request_id (str): request ID + instance_id (str): instance ID + """ + super().__init__(request_id) + self.name: str = name + self.instance_id: str = instance_id + + +class VfModuleInstantiation(Instantiation): # pytest: disable=too-many-ancestors + """VF module instantiation class.""" + + def __init__(self, + name: str, + request_id: str, + instance_id: str, + vf_module: VfModule) -> None: + """Initialize class object. + + Args: + name (str): vf module name + request_id (str): request ID + instance_id (str): instance ID + vnf_instantiation (VnfInstantiation): VNF instantiation class object + vf_module (VfModule): VF module used for instantiation + """ + super().__init__(name, request_id, instance_id) + self.vf_module: VfModule = vf_module + + @classmethod + def instantiate_ala_carte(cls, # pylint: disable=too-many-arguments + vf_module: "VfModule", + vnf_instance: "VnfInstance", + cloud_region: "CloudRegion", + tenant: "Tenant", + vf_module_instance_name: str = None, + vnf_parameters: Iterable["InstantiationParameter"] = None, + use_preload: bool = True) -> "VfModuleInstantiation": + """Instantiate VF module. + + Iterate throught vf modules from service Tosca file and instantiate vf modules. + + Args: + vf_module (VfModule): VfModule to instantiate + vnf_instance (VnfInstance): VnfInstance object + cloud_region (CloudRegion, optional): Cloud region to use in instantiation request. + Defaults to None. + tenant (Tenant, optional): Tenant to use in instnatiation request. + Defaults to None. + vf_module_instance_name_factory (str, optional): Factory to create VF module names. + It's going to be a prefix of name. Index of vf module in Tosca file will be + added to it. + If no value is provided it's going to be + "Python_ONAP_SDK_vf_module_service_instance_{str(uuid4())}". + Defaults to None. + vnf_parameters (Iterable[InstantiationParameter], optional): Parameters which are + going to be used in preload upload for vf modules or passed in "userParams". + Defaults to None. + use_preload (bool, optional): This flag determines whether instantiation parameters + are used as preload or "userParams" content. Defaults to True + + Yields: + Iterator[VfModuleInstantiation]: VfModuleInstantiation class object. + + """ + if vf_module_instance_name is None: + vf_module_instance_name = \ + f"Python_ONAP_SDK_vf_module_instance_{str(uuid4())}" + if use_preload: + VfModulePreload.upload_vf_module_preload( + vnf_instance, + vf_module_instance_name, + vf_module, + vnf_parameters + ) + vnf_parameters = None + sdc_service: SdcService = vnf_instance.service_instance.sdc_service + response: dict = cls.send_message_json( + "POST", + (f"Instantiate {sdc_service.name} " + f"service vf module {vf_module.name}"), + (f"{cls.base_url}/onap/so/infra/serviceInstantiation/{cls.api_version}/" + f"serviceInstances/{vnf_instance.service_instance.instance_id}/vnfs/" + f"{vnf_instance.vnf_id}/vfModules"), + data=jinja_env().get_template("instantiate_vf_module_ala_carte.json.j2"). + render( + vf_module_instance_name=vf_module_instance_name, + vf_module=vf_module, + service=sdc_service, + cloud_region=cloud_region, + tenant=tenant, + vnf_instance=vnf_instance, + vf_module_parameters=vnf_parameters or [] + ), + headers=headers_so_creator(OnapService.headers) + ) + return VfModuleInstantiation( + name=vf_module_instance_name, + request_id=response["requestReferences"].get("requestId"), + instance_id=response["requestReferences"].get("instanceId"), + vf_module=vf_module + ) + + +class NodeTemplateInstantiation(Instantiation, ABC): # pytest: disable=too-many-ancestors + """Base class for service's node_template object instantiation.""" + + def __init__(self, # pylint: disable=too-many-arguments + name: str, + request_id: str, + instance_id: str, + line_of_business: str, + platform: str) -> None: + """Node template object initialization. + + Args: + name (str): Node template name + request_id (str): Node template instantiation request ID + instance_id (str): Node template instance ID + line_of_business (str): LineOfBusiness name + platform (str): Platform name + """ + super().__init__(name, request_id, instance_id) + self.line_of_business = line_of_business + self.platform = platform + + +class VnfInstantiation(NodeTemplateInstantiation): # pylint: disable=too-many-ancestors + """VNF instantiation class.""" + + def __init__(self, # pylint: disable=too-many-arguments + name: str, + request_id: str, + instance_id: str, + line_of_business: str, + platform: str, + vnf: Vnf) -> None: + """Class VnfInstantion object initialization. + + Args: + name (str): VNF name + request_id (str): request ID + instance_id (str): instance ID + service_instantiation ([type]): ServiceInstantiation class object + line_of_business (str): LineOfBusiness name + platform (str): Platform name + vnf (Vnf): Vnf class object + """ + super().__init__(name, request_id, instance_id, line_of_business, platform) + self.vnf = vnf + + @classmethod + def create_from_request_response(cls, request_response: dict) -> "VnfInstantiation": + """Create VNF instantiation object based on request details. + + Raises: + ResourceNotFound: Service related with given object doesn't exist + ResourceNotFound: No ServiceInstantiation related with given VNF instantiation + ResourceNotFound: VNF related with given object doesn't exist + InvalidResponse: Invalid dictionary - couldn't create VnfInstantiation object + + Returns: + VnfInstantiation: VnfInstantiation object + + """ + if request_response.get("request", {}).get("requestScope") == "vnf" and \ + request_response.get("request", {}).get("requestType") == "createInstance": + service: SdcService = None + for related_instance in request_response.get("request", {}).get("requestDetails", {})\ + .get("relatedInstanceList", []): + if related_instance.get("relatedInstance", {}).get("modelInfo", {})\ + .get("modelType") == "service": + service = SdcService(related_instance.get("relatedInstance", {})\ + .get("modelInfo", {}).get("modelName")) + if not service: + raise ResourceNotFound("No related service in Vnf instance details response") + vnf: Vnf = None + for service_vnf in service.vnfs: + if service_vnf.name == request_response.get("request", {})\ + .get("requestDetails", {}).get("modelInfo", {}).get("modelCustomizationName"): + vnf = service_vnf + if not vnf: + raise ResourceNotFound("No vnf in service vnfs list") + return cls( + name=request_response.get("request", {})\ + .get("instanceReferences", {}).get("vnfInstanceName"), + request_id=request_response.get("request", {}).get("requestId"), + instance_id=request_response.get("request", {})\ + .get("instanceReferences", {}).get("vnfInstanceId"), + line_of_business=request_response.get("request", {})\ + .get("requestDetails", {}).get("lineOfBusiness", {}).get("lineOfBusinessName"), + platform=request_response.get("request", {})\ + .get("requestDetails", {}).get("platform", {}).get("platformName"), + vnf=vnf + ) + raise InvalidResponse("Invalid vnf instantions in response dictionary's requestList") + + @classmethod + def get_by_vnf_instance_name(cls, vnf_instance_name: str) -> "VnfInstantiation": + """Get VNF instantiation request by instance name. + + Raises: + InvalidResponse: Vnf instance with given name does not contain + requestList or the requestList does not contain any details. + + Returns: + VnfInstantiation: Vnf instantiation request object + + """ + response: dict = cls.send_message_json( + "GET", + f"Check {vnf_instance_name} service instantiation status", + (f"{cls.base_url}/onap/so/infra/orchestrationRequests/{cls.api_version}?" + f"filter=vnfInstanceName:EQUALS:{vnf_instance_name}"), + headers=headers_so_creator(OnapService.headers) + ) + key = "requestList" + if not response.get(key, []): + raise InvalidResponse(f"{key} of a Vnf instance is missing.") + for details in response[key]: + return cls.create_from_request_response(details) + msg = f"No details available in response dictionary's {key}." + raise InvalidResponse(msg) + + @classmethod + def instantiate_ala_carte(cls, # pylint: disable=too-many-arguments + aai_service_instance: "ServiceInstance", + vnf_object: "Vnf", + line_of_business: str, + platform: str, + cloud_region: "CloudRegion", + tenant: "Tenant", + sdc_service: "SdcService", + vnf_instance_name: str = None, + vnf_parameters: Iterable["InstantiationParameter"] = None + ) -> "VnfInstantiation": + """Instantiate Vnf using a'la carte method. + + Args: + vnf_object (Vnf): Vnf to instantiate + line_of_business_object (LineOfBusiness): LineOfBusiness to use in instantiation request + platform_object (Platform): Platform to use in instantiation request + cloud_region (CloudRegion): Cloud region to use in instantiation request. + tenant (Tenant): Tenant to use in instnatiation request. + vnf_instance_name (str, optional): Vnf instance name. Defaults to None. + vnf_parameters (Iterable[InstantiationParameter], optional): Instantiation parameters + that are sent in the request. Defaults to None + + Returns: + VnfInstantiation: VnfInstantiation object + + """ + if vnf_instance_name is None: + vnf_instance_name = \ + f"Python_ONAP_SDK_vnf_instance_{str(uuid4())}" + response: dict = cls.send_message_json( + "POST", + (f"Instantiate {sdc_service.name} " + f"service vnf {vnf_object.name}"), + (f"{cls.base_url}/onap/so/infra/serviceInstantiation/{cls.api_version}/" + f"serviceInstances/{aai_service_instance.instance_id}/vnfs"), + data=jinja_env().get_template("instantiate_vnf_ala_carte.json.j2"). + render( + instance_name=vnf_instance_name, + vnf=vnf_object, + service=sdc_service, + cloud_region=cloud_region or \ + next(aai_service_instance.service_subscription.cloud_regions), + tenant=tenant or next(aai_service_instance.service_subscription.tenants), + line_of_business=line_of_business, + platform=platform, + service_instance=aai_service_instance, + vnf_parameters=vnf_parameters or [] + ), + headers=headers_so_creator(OnapService.headers) + ) + return VnfInstantiation( + name=vnf_instance_name, + request_id=response["requestReferences"]["requestId"], + instance_id=response["requestReferences"]["instanceId"], + line_of_business=line_of_business, + platform=platform, + vnf=vnf_object + ) + + @classmethod + def instantiate_macro(cls, # pylint: disable=too-many-arguments, too-many-locals + aai_service_instance: "ServiceInstance", + vnf_object: "Vnf", + line_of_business: str, + platform: str, + cloud_region: "CloudRegion", + tenant: "Tenant", + sdc_service: "SdcService", + vnf_instance_name: str = None, + vnf_parameters: Iterable["InstantiationParameter"] = None, + so_vnf: "SoServiceVnf" = None + ) -> "VnfInstantiation": + """Instantiate Vnf using macro method. + + Args: + aai_service_instance (ServiceInstance): Service instance associated with request + vnf_object (Vnf): Vnf to instantiate + line_of_business (LineOfBusiness): LineOfBusiness to use in instantiation request + platform (Platform): Platform to use in instantiation request + cloud_region (CloudRegion): Cloud region to use in instantiation request. + tenant (Tenant): Tenant to use in instantiation request. + vnf_instance_name (str, optional): Vnf instance name. Defaults to None. + vnf_parameters (Iterable[InstantiationParameter], optional): Instantiation parameters + that are sent in the request. Defaults to None + so_vnf (SoServiceVnf): object with vnf instance parameters + + Returns: + VnfInstantiation: VnfInstantiation object + + """ + owning_entity_id = None + project = settings.PROJECT + + for relationship in aai_service_instance.relationships: + if relationship.related_to == "owning-entity": + owning_entity_id = relationship.relationship_data.pop().get("relationship-value") + if relationship.related_to == "project": + project = relationship.relationship_data.pop().get("relationship-value") + + owning_entity = OwningEntity.get_by_owning_entity_id( + owning_entity_id=owning_entity_id) + + if so_vnf: + template_file = "instantiate_vnf_macro_so_vnf.json.j2" + if so_vnf.instance_name: + vnf_instance_name = so_vnf.instance_name + else: + template_file = "instantiate_vnf_macro.json.j2" + if vnf_instance_name is None: + vnf_instance_name = \ + f"Python_ONAP_SDK_vnf_instance_{str(uuid4())}" + + response: dict = cls.send_message_json( + "POST", + (f"Instantiate {sdc_service.name} " + f"service vnf {vnf_object.name}"), + (f"{cls.base_url}/onap/so/infra/serviceInstantiation/{cls.api_version}/" + f"serviceInstances/{aai_service_instance.instance_id}/vnfs"), + data=jinja_env().get_template(template_file).render( + instance_name=vnf_instance_name, + vnf=vnf_object, + service=sdc_service, + cloud_region=cloud_region or \ + next(aai_service_instance.service_subscription.cloud_regions), + tenant=tenant or next(aai_service_instance.service_subscription.tenants), + project=project, + owning_entity=owning_entity, + line_of_business=line_of_business, + platform=platform, + service_instance=aai_service_instance, + vnf_parameters=vnf_parameters or [], + so_vnf=so_vnf + ), + headers=headers_so_creator(OnapService.headers) + ) + + return VnfInstantiation( + name=vnf_instance_name, + request_id=response["requestReferences"]["requestId"], + instance_id=response["requestReferences"]["instanceId"], + line_of_business=line_of_business, + platform=platform, + vnf=vnf_object + ) + + @classmethod + def so_action(cls, # pylint: disable=too-many-arguments, too-many-locals + vnf_instance: "VnfInstance", + operation_type: VnfOperation, + aai_service_instance: "ServiceInstance", + line_of_business: str, + platform: str, + sdc_service: "SdcService", + so_service: "SoService" = None + ) -> "VnfInstantiation": + """Execute SO action (update or healthcheck) for selected vnf with SO macro request. + + Args: + vnf_instance (VnfInstance): vnf instance object + operation_type (VnfOperation): name of the operation to trigger + aai_service_instance (AaiService): Service Instance object from aai + line_of_business (LineOfBusiness): LineOfBusiness name to use + in instantiation request + platform (Platform): Platform name to use in instantiation request + sdc_service (SdcService): Service model information + so_service (SoService, optional): SO values to use in SO request + + Raises: + StatusError: if the provided operation is not supported + + Returns: + VnfInstantiation: VnfInstantiation object + + """ + if operation_type not in (VnfOperation.HEALTHCHECK, VnfOperation.UPDATE): + raise StatusError("Operation not supported!") + + owning_entity_id = None + project = settings.PROJECT + + for relationship in aai_service_instance.relationships: + if relationship.related_to == "owning-entity": + owning_entity_id = relationship.relationship_data.pop().get("relationship-value") + if relationship.related_to == "project": + project = relationship.relationship_data.pop().get("relationship-value") + + owning_entity = OwningEntity.get_by_owning_entity_id( + owning_entity_id=owning_entity_id) + + response: dict = cls.send_message_json( + operation_type.request_method, + (f"So Action {sdc_service.name} " + f" vnf instance {vnf_instance.vnf_id}"), + (f"{cls.base_url}/onap/so/infra/serviceInstantiation/{cls.api_version}/" + f"serviceInstances/{aai_service_instance.instance_id}/vnfs/{vnf_instance.vnf_id}" + f"{operation_type.request_suffix}"), + data=jinja_env().get_template("instantiate_multi_vnf_service_macro.json.j2").render( + sdc_service=sdc_service, + cloud_region=next(aai_service_instance.service_subscription.cloud_regions), + tenant=next(aai_service_instance.service_subscription.tenants), + customer=aai_service_instance.service_subscription.customer, + project=project, + owning_entity=owning_entity, + line_of_business=line_of_business, + platform=platform, + service_instance_name=aai_service_instance.instance_name, + so_service=so_service + ), + headers=headers_so_creator(OnapService.headers) + ) + + return VnfInstantiation( + name=vnf_instance.vnf_name, + request_id=response["requestReferences"]["requestId"], + instance_id=response["requestReferences"]["instanceId"], + line_of_business=line_of_business, + platform=platform, + vnf=vnf_instance + ) + + +class ServiceInstantiation(Instantiation): # pylint: disable=too-many-ancestors + """Service instantiation class.""" + + def __init__(self, # pylint: disable=too-many-arguments + name: str, + request_id: str, + instance_id: str, + sdc_service: "SdcService", + cloud_region: "CloudRegion", + tenant: "Tenant", + customer: "Customer", + owning_entity: OwningEntity, + project: str) -> None: + """Class ServiceInstantiation object initialization. + + Args: + name (str): service instance name + request_id (str): service instantiation request ID + instance_id (str): service instantiation ID + sdc_service (SdcService): SdcService class object + cloud_region (CloudRegion): CloudRegion class object + tenant (Tenant): Tenant class object + customer (Customer): Customer class object + owning_entity (OwningEntity): OwningEntity class object + project (str): Project name + + """ + super().__init__(name, request_id, instance_id) + self.sdc_service = sdc_service + self.cloud_region = cloud_region + self.tenant = tenant + self.customer = customer + self.owning_entity = owning_entity + self.project = project + + @classmethod + def instantiate_ala_carte(cls, # pylint: disable=too-many-arguments + sdc_service: "SdcService", + cloud_region: "CloudRegion", + tenant: "Tenant", + customer: "Customer", + owning_entity: OwningEntity, + project: str, + service_subscription: "ServiceSubscription", + service_instance_name: str = None, + enable_multicloud: bool = False) -> "ServiceInstantiation": + """Instantiate service using SO a'la carte request. + + Args: + sdc_service (SdcService): Service to instantiate + cloud_region (CloudRegion): Cloud region to use in instantiation request + tenant (Tenant): Tenant to use in instantiation request + customer (Customer): Customer to use in instantiation request + owning_entity (OwningEntity): Owning entity to use in instantiation request + project (str): Project name to use in instantiation request + service_subscription (ServiceSubscription): Customer's service subsription. + service_instance_name (str, optional): Service instance name. Defaults to None. + enable_multicloud (bool, optional): Determines if Multicloud should be enabled + for instantiation request. Defaults to False. + + Raises: + StatusError: if a service is not distributed. + + Returns: + ServiceInstantiation: instantiation request object + + """ + if not sdc_service.distributed: + msg = f"Service {sdc_service.name} is not distributed." + raise StatusError(msg) + if service_instance_name is None: + service_instance_name = f"Python_ONAP_SDK_service_instance_{str(uuid4())}" + response: dict = cls.send_message_json( + "POST", + f"Instantiate {sdc_service.name} service a'la carte", + (f"{cls.base_url}/onap/so/infra/" + f"serviceInstantiation/{cls.api_version}/serviceInstances"), + data=jinja_env().get_template("instantiate_service_ala_carte.json.j2"). + render( + sdc_service=sdc_service, + cloud_region=cloud_region, + tenant=tenant, + customer=customer, + owning_entity=owning_entity, + service_instance_name=service_instance_name, + project=project, + enable_multicloud=enable_multicloud, + service_subscription=service_subscription + ), + headers=headers_so_creator(OnapService.headers) + ) + return cls( + name=service_instance_name, + request_id=response["requestReferences"].get("requestId"), + instance_id=response["requestReferences"].get("instanceId"), + sdc_service=sdc_service, + cloud_region=cloud_region, + tenant=tenant, + customer=customer, + owning_entity=owning_entity, + project=project + ) + + # pylint: disable=too-many-arguments, too-many-locals + @classmethod + def instantiate_macro(cls, + sdc_service: "SdcService", + customer: "Customer", + owning_entity: OwningEntity, + project: str, + line_of_business: str, + platform: str, + aai_service: "AaiService" = None, + cloud_region: "CloudRegion" = None, + tenant: "Tenant" = None, + service_instance_name: str = None, + vnf_parameters: Iterable["VnfParameters"] = None, + enable_multicloud: bool = False, + so_service: "SoService" = None, + service_subscription: "ServiceSubscription" = None + ) -> "ServiceInstantiation": + """Instantiate service using SO macro request. + + Args: + sdc_service (SdcService): Service to instantiate + customer (Customer): Customer to use in instantiation request + owning_entity (OwningEntity): Owning entity to use in instantiation request + project (Project): Project name to use in instantiation request + line_of_business_object (LineOfBusiness): LineOfBusiness name to use + in instantiation request + platform_object (Platform): Platform name to use in instantiation request + aai_service (AaiService): Service object from aai sdc + cloud_region (CloudRegion): Cloud region to use in instantiation request + tenant (Tenant): Tenant to use in instantiation request + service_instance_name (str, optional): Service instance name. Defaults to None. + vnf_parameters: (Iterable[VnfParameters]): Parameters which are + going to be used for vnfs instantiation. Defaults to None. + enable_multicloud (bool, optional): Determines if Multicloud should be enabled + for instantiation request. Defaults to False. + so_service (SoService, optional): SO values to use in instantiation request + service_subscription(ServiceSubscription, optional): Customer service subscription + for the instantiated service. Required if so_service is not provided. + + Raises: + StatusError: if a service is not distributed. + + Returns: + ServiceInstantiation: instantiation request object + + """ + template_file = "instantiate_service_macro.json.j2" + if so_service: + template_file = "instantiate_multi_vnf_service_macro.json.j2" + if so_service.instance_name: + service_instance_name = so_service.instance_name + else: + if not service_subscription: + raise ParameterError("If no so_service is provided, " + "service_subscription parameter is required!") + if service_instance_name is None: + service_instance_name = f"Python_ONAP_SDK_service_instance_{str(uuid4())}" + if not sdc_service.distributed: + msg = f"Service {sdc_service.name} is not distributed." + raise StatusError(msg) + + response: dict = cls.send_message_json( + "POST", + f"Instantiate {sdc_service.name} service macro", + (f"{cls.base_url}/onap/so/infra/" + f"serviceInstantiation/{cls.api_version}/serviceInstances"), + data=jinja_env().get_template(template_file). \ + render( + so_service=so_service, + sdc_service=sdc_service, + cloud_region=cloud_region, + tenant=tenant, + customer=customer, + owning_entity=owning_entity, + project=project, + aai_service=aai_service, + line_of_business=line_of_business, + platform=platform, + service_instance_name=service_instance_name, + vnf_parameters=vnf_parameters, + enable_multicloud=enable_multicloud, + service_subscription=service_subscription + ), + headers=headers_so_creator(OnapService.headers) + ) + return cls( + name=service_instance_name, + request_id=response["requestReferences"].get("requestId"), + instance_id=response["requestReferences"].get("instanceId"), + sdc_service=sdc_service, + cloud_region=cloud_region, + tenant=tenant, + customer=customer, + owning_entity=owning_entity, + project=project + ) + + @property + def aai_service_instance(self) -> "ServiceInstance": + """Service instance associated with service instantiation request. + + Raises: + StatusError: if a service is not instantiated - + not in COMPLETE status. + APIError: A&AI resource is not created + + Returns: + ServiceInstance: ServiceInstance + + """ + required_status = self.StatusEnum.COMPLETED + if self.status != required_status: + msg = (f"Service {self.name} is not instantiated - " + f"not in {required_status} status.") + raise StatusError(msg) + try: + service_subscription: "ServiceSubscription" = \ + self.customer.get_service_subscription_by_service_type(self.sdc_service.name) + return service_subscription.get_service_instance_by_name(self.name) + except APIError as exc: + self._logger.error("A&AI resources not created properly") + raise exc + + +class NetworkInstantiation(NodeTemplateInstantiation): # pylint: disable=too-many-ancestors + """Network instantiation class.""" + + def __init__(self, # pylint: disable=too-many-arguments + name: str, + request_id: str, + instance_id: str, + line_of_business: str, + platform: str, + network: Network) -> None: + """Class NetworkInstantiation object initialization. + + Args: + name (str): VNF name + request_id (str): request ID + instance_id (str): instance ID + service_instantiation ([type]): ServiceInstantiation class object + line_of_business (str): LineOfBusiness name + platform (str): Platform name + vnf (Network): Network class object + """ + super().__init__(name, request_id, instance_id, line_of_business, platform) + self.network = network + + @classmethod + def instantiate_ala_carte(cls, # pylint: disable=too-many-arguments + aai_service_instance: "ServiceInstance", + network_object: "Network", + line_of_business: str, + platform: str, + cloud_region: "CloudRegion", + tenant: "Tenant", + network_instance_name: str = None, + subnets: Iterable[Subnet] = None) -> "NetworkInstantiation": + """Instantiate Network using a'la carte method. + + Args: + network_object (Network): Network to instantiate + line_of_business (str): LineOfBusiness name to use in instantiation request + platform (str): Platform name to use in instantiation request + cloud_region (CloudRegion): Cloud region to use in instantiation request. + tenant (Tenant): Tenant to use in instnatiation request. + network_instance_name (str, optional): Network instance name. Defaults to None. + + Returns: + NetworkInstantiation: NetworkInstantiation object + + """ + if network_instance_name is None: + network_instance_name = \ + f"Python_ONAP_SDK_network_instance_{str(uuid4())}" + NetworkPreload.upload_network_preload(network=network_object, + network_instance_name=network_instance_name, + subnets=subnets) + response: dict = cls.send_message_json( + "POST", + (f"Instantiate {aai_service_instance.sdc_service.name} " + f"service network {network_object.name}"), + (f"{cls.base_url}/onap/so/infra/serviceInstantiation/{cls.api_version}/" + f"serviceInstances/{aai_service_instance.instance_id}/networks"), + data=jinja_env().get_template("instantiate_network_ala_carte.json.j2"). + render( + instance_name=network_instance_name, + network=network_object, + service=aai_service_instance.sdc_service, + cloud_region=cloud_region or \ + next(aai_service_instance.service_subscription.cloud_regions), + tenant=tenant or next(aai_service_instance.service_subscription.tenants), + line_of_business=line_of_business, + platform=platform, + service_instance=aai_service_instance, + subnets=subnets + ), + headers=headers_so_creator(OnapService.headers) + ) + return cls( + name=network_instance_name, + request_id=response["requestReferences"]["requestId"], + instance_id=response["requestReferences"]["instanceId"], + line_of_business=line_of_business, + platform=platform, + network=network_object + ) diff --git a/src/onapsdk/so/so_db_adapter.py b/src/onapsdk/so/so_db_adapter.py new file mode 100644 index 0000000..b3694d1 --- /dev/null +++ b/src/onapsdk/so/so_db_adapter.py @@ -0,0 +1,94 @@ +"""Database Adapter module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 abc import ABC +from dataclasses import dataclass +from typing import Dict, Any + +from onapsdk.so.so_element import SoElement +from onapsdk.onap_service import OnapService +from onapsdk.utils.headers_creator import headers_so_creator, headers_so_catelog_db_creator +from onapsdk.utils.jinja import jinja_env + + +@dataclass +class IdentityService: # pylint: disable=too-many-instance-attributes + """Class to store identity service details.""" + + identity_id: str + url: str = "http://1.2.3.4:5000/v2.0" + mso_id: str = "onapsdk_user" + mso_pass: str = "mso_pass_onapsdk" + project_domain_name: str = "NULL" + user_domain_name: str = "NULL" + admin_tenant: str = "service" + member_role: str = "admin" + identity_server_type: str = "KEYSTONE" + identity_authentication_type: str = "USERNAME_PASSWORD" + hibernate_lazy_initializer = {} + server_type_as_string: str = "KEYSTONE" + tenant_metadata: bool = True + + +class SoDbAdapter(SoElement, ABC): + """DB Adapter class.""" + + @classmethod + def add_cloud_site(cls, + cloud_region_id: str, + complex_id: str, + identity_service: IdentityService, + orchestrator: str = "multicloud" + ): + """Add cloud_site data with identity_service to SO db. + + Args: + cloud_region_id (str): The id of cloud region + complex_id (str): The id of complex + identity_service (IdentityService): Identity service related to the cloud region + orchestrator (str, optional): Orchestrator type. Defaults to multicloud. + + Important: + identity_services data will be overwrite, but in the same time + cloud_sites data will not (shouldn't) be overwrite! + SOCatalogDB REST API has some limitations reported: https://jira.onap.org/browse/SO-2727 + + Return: + response object + """ + response = cls.send_message_json( + "POST", + "Create a region in SO db", + f"{cls.base_url}/cloudSite", + data=jinja_env().get_template("add_cloud_site_with_identity_service.json.j2"). + render( + cloud_region_id=cloud_region_id, + complex_id=complex_id, + orchestrator=orchestrator, + identity_service=identity_service + ), + headers=headers_so_creator(OnapService.headers) + ) + return response + @classmethod + def get_service_vnf_info(cls, identifier: str) -> Dict[Any, Any]: + """Get Service VNF and VF details. + + Returns: + The response in a dict format + + """ + url = f"{cls.base_url}/ecomp/mso/catalog/v2/serviceVnfs?serviceModelUuid={identifier}" + headers = headers_so_catelog_db_creator(OnapService.headers) + return cls.send_message_json("GET", "Get Service Details", url, headers=headers) diff --git a/src/onapsdk/so/so_element.py b/src/onapsdk/so/so_element.py new file mode 100644 index 0000000..fca6ba7 --- /dev/null +++ b/src/onapsdk/so/so_element.py @@ -0,0 +1,223 @@ +"""SO Element module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 +from abc import ABC +from dataclasses import dataclass +from enum import Enum +from typing import Dict + +from onapsdk.configuration import settings +from onapsdk.sdc.service import Service +from onapsdk.sdc.vf import Vf +from onapsdk.onap_service import OnapService +from onapsdk.utils.headers_creator import headers_so_creator +from onapsdk.utils.jinja import jinja_env +from onapsdk.utils.mixins import WaitForFinishMixin +from onapsdk.utils.tosca_file_handler import get_modules_list_from_tosca_file +from onapsdk.utils.gui import GuiItem, GuiList + +@dataclass +class SoElement(OnapService): + """Mother Class of all SO elements.""" + + name: str = None + _server: str = "SO" + base_url = settings.SO_URL + api_version = settings.SO_API_VERSION + _status: str = None + + @property + def headers(self): + """Create headers for SO request. + + It is used as a property because x-transactionid header should be unique for each request. + """ + return headers_so_creator(OnapService.headers) + + @classmethod + def get_subscription_service_type(cls, vf_name): + """Retrieve the model info of the VFs.""" + vf_object = Vf(name=vf_name) + return vf_object.name + + @classmethod + def get_service_model_info(cls, service_name): + """Retrieve Service Model info.""" + service = Service(name=service_name) + template_service = jinja_env().get_template("service_instance_model_info.json.j2") + # Get service instance model + parsed = json.loads( + template_service.render( + model_invariant_id=service.unique_uuid, + model_name_version_id=service.identifier, + model_name=service.name, + model_version=service.version, + ) + ) + return json.dumps(parsed, indent=4) + + @classmethod + def get_vnf_model_info(cls, vf_name): + """Retrieve the model info of the VFs.""" + vf_object = Vf(name=vf_name) + template_service = jinja_env().get_template("vnf_model_info.json.j2") + parsed = json.loads( + template_service.render( + vnf_model_invariant_uuid=vf_object.unique_uuid, + vnf_model_customization_id="????", + vnf_model_version_id=vf_object.identifier, + vnf_model_name=vf_object.name, + vnf_model_version=vf_object.version, + vnf_model_instance_name=(vf_object.name + " 0"), + ) + ) + # we need also a vnf instance Name + # Usually it is found like that + # name: toto + # instance name: toto 0 + # it can be retrieved from the toscafrom onapsdk.configuration import settings + return json.dumps(parsed, indent=4) + + @classmethod + def get_vf_model_info(cls, vf_model: str) -> str: + """Retrieve the VF model info From Tosca?.""" + modules: Dict = get_modules_list_from_tosca_file(vf_model) + template_service = jinja_env().get_template("vf_model_info.json.j2") + parsed = json.loads(template_service.render(modules=modules)) + return json.dumps(parsed, indent=4) + + @classmethod + def _base_create_url(cls) -> str: + """ + Give back the base url of SO. + + Returns: + str: the base url + + """ + return "{}/onap/so/infra/serviceInstantiation/{}/serviceInstances".format( + cls.base_url, cls.api_version + ) + + @classmethod + def get_guis(cls) -> GuiItem: + """Retrieve the status of the SO GUIs. + + Only one GUI is referenced for SO: SO monitor + + Return the list of GUIs + """ + gui_url = settings.SO_MONITOR_GUI_SERVICE + so_gui_response = cls.send_message( + "GET", "Get SO GUI Status", gui_url) + guilist = GuiList([]) + guilist.add(GuiItem( + gui_url, + so_gui_response.status_code)) + return guilist + + +class OrchestrationRequest(SoElement, WaitForFinishMixin, ABC): + """Base SO orchestration request class.""" + + WAIT_FOR_SLEEP_TIME = 10 + + def __init__(self, + request_id: str) -> None: + """Instantiate object initialization. + + Initializator used by classes inherited from this abstract class. + + Args: + request_id (str): request ID + """ + super().__init__() + self.request_id: str = request_id + + class StatusEnum(Enum): + """Status enum. + + Store possible statuses for instantiation: + - IN_PROGRESS, + - FAILED, + - COMPLETE. + If instantiation has status which is not covered by these values + UNKNOWN value is used. + + """ + + IN_PROGRESS = "IN_PROGRESS" + FAILED = "FAILED" + COMPLETED = "COMPLETE" + UNKNOWN = "UNKNOWN" + + @property + def status(self) -> "StatusEnum": + """Object instantiation status. + + It's populated by call SO orchestation request endpoint. + + Returns: + StatusEnum: Instantiation status. + + """ + response: dict = self.send_message_json( + "GET", + f"Check {self.request_id} orchestration request status", + (f"{self.base_url}/onap/so/infra/" + f"orchestrationRequests/{self.api_version}/{self.request_id}"), + headers=headers_so_creator(OnapService.headers) + ) + try: + return self.StatusEnum(response["request"]["requestStatus"]["requestState"]) + except (KeyError, ValueError): + self._logger.exception("Invalid status") + return self.StatusEnum.UNKNOWN + + @property + def finished(self) -> bool: + """Store an information if instantion is finished or not. + + Instantiation is finished if it's status is COMPLETED or FAILED. + + Returns: + bool: True if instantiation is finished, False otherwise. + + """ + return self.status in [self.StatusEnum.COMPLETED, self.StatusEnum.FAILED] + + @property + def completed(self) -> bool: + """Store an information if instantion is completed or not. + + Instantiation is completed if it's status is COMPLETED. + + Returns: + bool: True if instantiation is completed, False otherwise. + + """ + return self.finished and self.status == self.StatusEnum.COMPLETED + + @property + def failed(self) -> bool: + """Store an information if instantion is failed or not. + + Instantiation is failed if it's status is FAILED. + + Returns: + bool: True if instantiation is failed, False otherwise. + + """ + return self.finished and self.status == self.StatusEnum.FAILED diff --git a/src/onapsdk/so/templates/add_cloud_site_with_identity_service.json.j2 b/src/onapsdk/so/templates/add_cloud_site_with_identity_service.json.j2 new file mode 100644 index 0000000..31599f5 --- /dev/null +++ b/src/onapsdk/so/templates/add_cloud_site_with_identity_service.json.j2 @@ -0,0 +1,22 @@ +{ + "id": "{{ cloud_region_id }}", + "region_id": "{{ cloud_region_id }}", + "aic_version": "2.5", + "clli": "{{ complex_id }}", + "orchestrator": "{{ orchestrator }}", + "identityService": { + "id": "{{ identity_service.identity_id }}", + "identityServerTypeAsString": "{{ identity_service.server_type_as_string }}", + "hibernateLazyInitializer": {{ identity_service.hibernate_lazy_initializer }}, + "identity_url": "{{ identity_service.url }}", + "mso_id": "{{ identity_service.mso_id }}", + "mso_pass": "{{ identity_service.mso_pass }}", + "project_domain_name": "{{ identity_service.project_domain_name }}", + "user_domain_name": "{{ identity_service.user_domain_name }}", + "admin_tenant": "{{ identity_service.admin_tenant }}", + "member_role": "{{ identity_service.member_role }}", + "tenant_metadata": "{{ identity_service.tenant_metadata }}", + "identity_server_type": "{{ identity_service.identity_server_type }}", + "identity_authentication_type": "{{ identity_service.identity_authentication_type }}" + } +} \ No newline at end of file diff --git a/src/onapsdk/so/templates/deletion_network.json.j2 b/src/onapsdk/so/templates/deletion_network.json.j2 new file mode 100644 index 0000000..93f0990 --- /dev/null +++ b/src/onapsdk/so/templates/deletion_network.json.j2 @@ -0,0 +1,22 @@ +{ + "requestDetails": { + "requestInfo": { + "source": "VID", + "requestorId": "demo" + }, + "modelInfo": { + "modelType": "network" + }, + "requestParameters": { + "testApi": "GR_API", + "aLaCarte": {{ a_la_carte | tojson }} + }, + {# the code below is needed to be refactored #} + {# https://gitlab.com/Orange-OpenSource/lfn/onap/python-onapsdk/-/issues/133 #} + "cloudConfiguration": { + "cloudOwner": "{{ network_instance.service_instance.service_subscription.cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ network_instance.service_instance.service_subscription.cloud_region.cloud_region_id }}", + "tenantId": "{{ network_instance.service_instance.service_subscription.tenant.tenant_id }}" + } + } +} \ No newline at end of file diff --git a/src/onapsdk/so/templates/deletion_service.json.j2 b/src/onapsdk/so/templates/deletion_service.json.j2 new file mode 100644 index 0000000..1244e97 --- /dev/null +++ b/src/onapsdk/so/templates/deletion_service.json.j2 @@ -0,0 +1,26 @@ +{ + "requestDetails": { + "requestInfo": { + "source": "VID", + "requestorId": "demo" + }, + "modelInfo": { + "modelType": "service", + "modelName": "{{ service_instance.sdc_service.name }}", + "modelInvariantId": "{{ service_instance.sdc_service.unique_uuid }}", + "modelVersion": "1.0", + "modelVersionId": "{{ service_instance.sdc_service.identifier }}" + }, + "requestParameters": { + "testApi": "GR_API", + "aLaCarte": {{ a_la_carte | tojson }} + }{% if service_instance.sdc_service.has_vnfs %}, + {# the code below is needed to be refactored #} + {# https://gitlab.com/Orange-OpenSource/lfn/onap/python-onapsdk/-/issues/133 #} + "cloudConfiguration": { + "cloudOwner": "{{ service_instance.service_subscription.cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ service_instance.service_subscription.cloud_region.cloud_region_id }}", + "tenantId": "{{ service_instance.service_subscription.tenant.tenant_id }}" + }{% endif %} + } +} \ No newline at end of file diff --git a/src/onapsdk/so/templates/deletion_vf_module.json.j2 b/src/onapsdk/so/templates/deletion_vf_module.json.j2 new file mode 100644 index 0000000..8f83717 --- /dev/null +++ b/src/onapsdk/so/templates/deletion_vf_module.json.j2 @@ -0,0 +1,27 @@ +{ + "requestDetails": { + "requestInfo": { + "source": "VID", + "requestorId": "demo" + }, + "modelInfo": { + "modelType": "vfModule", + "modelInvariantId": "{{ vf_module_instance.model_invariant_id }}", + "modelVersionId": "{{ vf_module_instance.model_version_id }}", + "modelName": "{{ vf_module_instance.vf_module_name }}", + "modelVersion": "{{ vf_module_instance.resource_version }}", + "modelCustomizationId": "{{ vf_module_instance.model_customization_id }}" + }, + "requestParameters": { + "testApi": "GR_API", + "aLaCarte": {{ a_la_carte | tojson }} + }, + {# the code below is needed to be refactored #} + {# https://gitlab.com/Orange-OpenSource/lfn/onap/python-onapsdk/-/issues/133 #} + "cloudConfiguration": { + "cloudOwner": "{{ vf_module_instance.vnf_instance.service_instance.service_subscription.cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ vf_module_instance.vnf_instance.service_instance.service_subscription.cloud_region.cloud_region_id }}", + "tenantId": "{{ vf_module_instance.vnf_instance.service_instance.service_subscription.tenant.tenant_id }}" + } + } +} \ No newline at end of file diff --git a/src/onapsdk/so/templates/deletion_vnf.json.j2 b/src/onapsdk/so/templates/deletion_vnf.json.j2 new file mode 100644 index 0000000..fb640ec --- /dev/null +++ b/src/onapsdk/so/templates/deletion_vnf.json.j2 @@ -0,0 +1,28 @@ +{ + "requestDetails": { + "requestInfo": { + "source": "VID", + "requestorId": "demo" + }, + "modelInfo": { + "modelType": "vnf", + "modelName": "{{ vnf_instance.vnf.model_name }}", + "modelInvariantId": "{{ vnf_instance.vnf.model_invariant_id }}", + "modelVersion": "{{ vnf_instance.vnf.model_version }}", + "modelVersionId": "{{ vnf_instance.vnf.model_version_id }}", + "modelCustomizationId": "{{ vnf_instance.vnf.model_customization_id }}", + "modelCustomizationName": "{{ vnf_instance.vnf.name }}" + }, + "requestParameters": { + "testApi": "GR_API", + "aLaCarte": {{ a_la_carte | tojson }} + }, + {# the code below is needed to be refactored #} + {# https://gitlab.com/Orange-OpenSource/lfn/onap/python-onapsdk/-/issues/133 #} + "cloudConfiguration": { + "cloudOwner": "{{ vnf_instance.service_instance.service_subscription.cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ vnf_instance.service_instance.service_subscription.cloud_region.cloud_region_id }}", + "tenantId": "{{ vnf_instance.service_instance.service_subscription.tenant.tenant_id }}" + } + } +} \ No newline at end of file diff --git a/src/onapsdk/so/templates/instantiate_multi_vnf_service_macro.json.j2 b/src/onapsdk/so/templates/instantiate_multi_vnf_service_macro.json.j2 new file mode 100644 index 0000000..32e1b68 --- /dev/null +++ b/src/onapsdk/so/templates/instantiate_multi_vnf_service_macro.json.j2 @@ -0,0 +1,121 @@ +{% extends "instantiate_service_macro.json.j2" %} + +{% block subscriptionServiceType %} + "subscriptionServiceType": "{{ so_service.subscription_service_type }}", +{% endblock %} + +{% block pnfs %} + {% if so_service.pnfs %} + "pnfs": [ + {% for pnf in so_service.pnfs %} + { + "modelInfo":{ + {% for sdc_pnf in sdc_service.pnfs %} + {% if sdc_pnf.model_name == pnf.model_name %} + "modelCustomizationName":"{{ sdc_pnf.name }}", + "modelCustomizationId":"{{ sdc_pnf.model_customization_id }}", + "modelInvariantId":"{{ sdc_service.unique_uuid }}", + "modelVersionId":"{{ sdc_service.identifier }}", + "modelName":"{{ sdc_service.name }}", + "modelType":"pnf", + "modelVersion":"{{ sdc_pnf.model_version }}" + {% endif %} + {% endfor %} + }, + "platform":{ + "platformName":"{{ platform }}" + }, + "lineOfBusiness":{ + "lineOfBusinessName":"{{ line_of_business }}" + }, + "productFamilyId":"{{ aai_service.service_id }}", + "instanceParams":[], + {% if pnf.processing_priority %} + "processingPriority": "{{ pnf.processing_priority }}", + {% endif %} + "instanceName": "{{ pnf.instance_name }}" + }{% if not loop.last %},{% endif %} + {% endfor %} + ]{% if so_service.vnfs %},{% endif %} + {% endif %} +{% endblock %} + +{% block vnfs %} + {% if so_service.vnfs %} + "vnfs": [ + {% for vnf in so_service.vnfs %} + { + "modelInfo": { + {% for sdc_vnf in sdc_service.vnfs %} + {% if sdc_vnf.model_name == vnf.model_name %} + "modelName": "{{ sdc_vnf.model_name }}", + "modelVersionId": "{{ sdc_vnf.model_version_id }}", + "modelInvariantUuid": "{{ sdc_vnf.model_invariant_uuid }}", + "modelVersion": "{{ sdc_vnf.model_version }}", + "modelCustomizationId": "{{ sdc_vnf.model_customization_id }}", + "modelInstanceName": "{{ sdc_vnf.model_name }}" + {% endif %} + {% endfor %} + }, + "cloudConfiguration": { + "tenantId": "{{ tenant.tenant_id }}", + "cloudOwner": "{{ cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ cloud_region.cloud_region_id }}" + }, + "platform": { + "platformName": "{{ platform }}" + }, + "lineOfBusiness": { + "lineOfBusinessName": "{{ line_of_business }}" + }, + "productFamilyId": "1234", + "instanceName": "{{ vnf.instance_name }}", + "instanceParams": [ + { + {% for key, value in vnf.parameters.items() %} + "{{ key }}": "{{ value }}"{% if not loop.last %},{% endif %} + {% endfor %} + } + ], + {% if vnf.processing_priority %} + "processingPriority": "{{ vnf.processing_priority }}", + {% endif %} + "vfModules": [ + {% for vf_module in vnf.vf_modules %} + { + "modelInfo": { + {% for sdc_vnf in sdc_service.vnfs %} + {% if sdc_vnf.model_name == vnf.model_name %} + {% for sdc_vf_module in sdc_vnf.vf_modules %} + {% set mylist = sdc_vf_module.name.split('..') %} + {% set item = mylist|length-2 %} + {% if vf_module.model_name == mylist[item] %} + "modelName": "{{ sdc_vf_module.model_name }}", + "modelVersionId": "{{ sdc_vf_module.model_version_id }}", + "modelInvariantUuid": "{{ sdc_vf_module.model_invariant_uuid }}", + "modelVersion": "{{ sdc_vf_module.model_version }}", + "modelCustomizationId": "{{ sdc_vf_module.model_customization_id }}" + {% endif %} + {% endfor %} + {% endif %} + {% endfor %} + }, + "instanceName": "{{ vf_module.instance_name }}", + {% if vf_module.processing_priority %} + "processingPriority": "{{ vf_module.processing_priority }}", + {% endif %} + "instanceParams": [ + { + {% for key, value in vf_module.parameters.items() %} + "{{ key }}": "{{ value }}"{% if not loop.last %},{% endif %} + {% endfor %} + } + ] + }{% if not loop.last %},{% endif %} + {% endfor %} + ] + }{% if not loop.last %},{% endif %} + {% endfor %} + ] + {% endif %} +{% endblock %} diff --git a/src/onapsdk/so/templates/instantiate_network_ala_carte.json.j2 b/src/onapsdk/so/templates/instantiate_network_ala_carte.json.j2 new file mode 100644 index 0000000..90b3c16 --- /dev/null +++ b/src/onapsdk/so/templates/instantiate_network_ala_carte.json.j2 @@ -0,0 +1,10 @@ +{% extends "instantiate_network_vnf_ala_carte_base.json.j2" %} +{% block model_info %} + "modelType": "network", + "modelInvariantId": "{{ network.model_invariant_id }}", + "modelVersionId": "{{ network.model_version_id }}", + "modelName": "{{ network.model_name }}", + "modelVersion": "{{ network.model_version }}", + "modelCustomizationId": "{{ network.model_customization_id }}", + "modelCustomizationName": "{{ network.name }}" +{% endblock %} \ No newline at end of file diff --git a/src/onapsdk/so/templates/instantiate_network_vnf_ala_carte_base.json.j2 b/src/onapsdk/so/templates/instantiate_network_vnf_ala_carte_base.json.j2 new file mode 100644 index 0000000..757cdd8 --- /dev/null +++ b/src/onapsdk/so/templates/instantiate_network_vnf_ala_carte_base.json.j2 @@ -0,0 +1,44 @@ +{ + "requestDetails": { + "requestInfo": { + "instanceName": "{{ instance_name }}", + "source": "VID", + "suppressRollback": false, + "requestorId": "test", + "productFamilyId": "{{ service_instance.model_invariant_id }}" + }, + "modelInfo": { + {% block model_info %}{% endblock %} + }, + "requestParameters": { + "userParams": [ + {% block user_params %}{% endblock %} + ], + "aLaCarte": true, + "testApi": "GR_API" + }, + "cloudConfiguration": { + "tenantId": "{{ tenant.tenant_id }}", + "cloudOwner": "{{ cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ cloud_region.cloud_region_id }}" + }, + "lineOfBusiness": { + "lineOfBusinessName": "{{ line_of_business }}" + }, + "platform": { + "platformName": "{{ platform }}" + }, + "relatedInstanceList": [{ + "relatedInstance": { + "instanceId": "{{ service_instance.instance_id }}", + "modelInfo": { + "modelType": "service", + "modelName": "{{ service.name }}", + "modelInvariantId": "{{ service.unique_uuid }}", + "modelVersion": "1.0", + "modelVersionId": "{{ service.identifier }}" + } + } + }] + } +} \ No newline at end of file diff --git a/src/onapsdk/so/templates/instantiate_service_ala_carte.json.j2 b/src/onapsdk/so/templates/instantiate_service_ala_carte.json.j2 new file mode 100644 index 0000000..4954cde --- /dev/null +++ b/src/onapsdk/so/templates/instantiate_service_ala_carte.json.j2 @@ -0,0 +1,45 @@ +{ + "requestDetails": { + "requestInfo": { + "instanceName": "{{ service_instance_name }}", + "source": "VID", + "suppressRollback": false, + "requestorId": "demo" + }, + "modelInfo": { + "modelType": "service", + "modelInvariantId": "{{ sdc_service.unique_uuid }}", + "modelVersionId": "{{ sdc_service.identifier }}", + "modelName": "{{ sdc_service.name }}", + "modelVersion": "1.0" + }, + "cloudConfiguration": { + "tenantId": "{{ tenant.tenant_id }}", + "cloudOwner": "{{ cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ cloud_region.cloud_region_id }}" + }, + "requestParameters": { + "userParams": [ + {% if enable_multicloud %} + { + "name":"orchestrator", + "value":"multicloud" + } + {% endif %} + ], + "testApi": "GR_API", + "subscriptionServiceType": "{{ service_subscription.service_type }}", + "aLaCarte": true + }, + "subscriberInfo": { + "globalSubscriberId": "{{ customer.global_customer_id }}" + }, + "project": { + "projectName": "{{ project }}" + }, + "owningEntity": { + "owningEntityId": "{{ owning_entity.owning_entity_id }}", + "owningEntityName": "{{ owning_entity.name }}" + } + } +} \ No newline at end of file diff --git a/src/onapsdk/so/templates/instantiate_service_macro.json.j2 b/src/onapsdk/so/templates/instantiate_service_macro.json.j2 new file mode 100644 index 0000000..43b92ee --- /dev/null +++ b/src/onapsdk/so/templates/instantiate_service_macro.json.j2 @@ -0,0 +1,173 @@ +{ + "requestDetails": { + "requestInfo": { + "suppressRollback": false, + {% if aai_service %} + "productFamilyId":"{{ aai_service.service_id }}", + {% else %} + "productFamilyId": "1234", + {% endif %} + "requestorId": "demo", + "instanceName": "{{ service_instance_name }}", + "source": "VID" + }, + "modelInfo": { + "modelType": "service", + "modelInvariantId": "{{ sdc_service.unique_uuid }}", + "modelVersionId": "{{ sdc_service.identifier }}", + "modelName": "{{ sdc_service.name }}", + "modelVersion": "1.0" + }, + {% if sdc_service.has_vnfs %} + "cloudConfiguration": { + "tenantId": "{{ tenant.tenant_id }}", + "cloudOwner": "{{ cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ cloud_region.cloud_region_id }}" + }, + {% endif %} + "subscriberInfo": { + "globalSubscriberId": "{{ customer.global_customer_id }}" + }, + "requestParameters": { + {% block subscriptionServiceType %} + "subscriptionServiceType": "{{ service_subscription.service_type }}", + {% endblock %} + "userParams": [ + { + "Homing_Solution": "none" + }, + {% if enable_multicloud %} + { + "name":"orchestrator", + "value":"multicloud" + }, + {% endif %} + { + "service": { + "instanceParams": [], + "instanceName": "{{ service_instance_name }}", + "resources": { + {% block pnfs %} + {% if sdc_service.pnfs %} + "pnfs":[ + {% for pnf in sdc_service.pnfs %} + { + "modelInfo":{ + "modelCustomizationName":"{{ pnf.name }}", + "modelCustomizationId":"{{ pnf.model_customization_id }}", + "modelInvariantId":"{{ sdc_service.unique_uuid }}", + "modelVersionId":"{{ sdc_service.identifier }}", + "modelName":"{{ sdc_service.name }}", + "modelType":"pnf", + "modelVersion":"1.0" + }, + "platform":{ + "platformName":"{{ platform }}" + }, + "lineOfBusiness":{ + "lineOfBusinessName":"{{ line_of_business }}" + }, + "productFamilyId":"{{ aai_service.service_id }}", + "instanceParams":[], + "instanceName":"{{ service_instance_name }}" + }{% if not loop.last %},{% endif %} + {% endfor %} + ] + {% if sdc_service.vnfs %},{% endif %} + {% endif %} + {% endblock %} + {% block vnfs %} + {% if sdc_service.vnfs %} + "vnfs": [ + {% for vnf in sdc_service.vnfs %} + { + "modelInfo": { + "modelName": "{{ vnf.model_name }}", + "modelVersionId": "{{ vnf.model_version_id }}", + "modelInvariantUuid": "{{ vnf.model_invariant_id }}", + "modelVersion": "{{ vnf.model_version }}", + "modelCustomizationId": "{{ vnf.model_customization_id }}", + "modelInstanceName": "{{ vnf.model_name }}" + }, + "cloudConfiguration": { + "tenantId": "{{ tenant.tenant_id }}", + "cloudOwner": "{{ cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ cloud_region.cloud_region_id }}" + }, + "platform": { + "platformName": "{{ platform }}" + }, + "lineOfBusiness": { + "lineOfBusinessName": "{{ line_of_business }}" + }, + "productFamilyId": "1234", + "instanceName": "{{ vnf.model_name }}", + "instanceParams": [ + { + {% for vnf_parameter in vnf_parameters %} + {% if vnf_parameter.name == vnf.model_name %} + {% for parameter in vnf_parameter.vnf_parameters %} + "{{ parameter.name }}": "{{ parameter.value }}"{% if not loop.last %},{% endif %} + {% endfor %} + {% endif %} + {% endfor %} + } + ], + "vfModules": [ + {% for vf_module in vnf.vf_modules %} + { + "modelInfo": { + "modelName": "{{ vf_module.model_name }}", + "modelVersionId": "{{ vf_module.model_version_id }}", + "modelInvariantUuid": "{{ vf_module.model_invariant_uuid }}", + "modelVersion": "{{ vf_module.model_version }}", + "modelCustomizationId": "{{ vf_module.model_customization_id }}" + }, + "instanceName": "{{ service_instance_name }}_{{ vf_module.name }}", + "instanceParams": [ + { + {% for vnf_parameter in vnf_parameters %} + {% if vnf_parameter.name == vnf.model_name %} + {% set mylist = vf_module.name.split('..') %} + {% set item = mylist|length-2 %} + {% for vf_module_parameter in vnf_parameter.vfmodule_parameters %} + {% if vf_module_parameter.name == mylist[item] %} + {% for parameter in vf_module_parameter.vfmodule_parameters %} + "{{ parameter.name }}": "{{ parameter.value }}"{% if not loop.last %},{% endif %} + {% endfor %} + {% endif %} + {% endfor %} + {% endif %} + {% endfor %} + } + ] + }{% if not loop.last %},{% endif %} + {% endfor %} + ] + }{% if not loop.last %},{% endif %} + {% endfor %} + ] + {% endif %} + {% endblock %} + }, + "modelInfo": { + "modelVersion": "1.0", + "modelVersionId": "{{ sdc_service.identifier }}", + "modelInvariantId": "{{ sdc_service.unique_uuid }}", + "modelName": "{{ sdc_service.name }}", + "modelType": "service" + } + } + } + ], + "aLaCarte": false + }, + "project": { + "projectName": "{{ project }}" + }, + "owningEntity": { + "owningEntityId": "{{ owning_entity.owning_entity_id }}", + "owningEntityName": "{{ owning_entity.name }}" + } + } +} diff --git a/src/onapsdk/so/templates/instantiate_vf_module_ala_carte.json.j2 b/src/onapsdk/so/templates/instantiate_vf_module_ala_carte.json.j2 new file mode 100644 index 0000000..0738379 --- /dev/null +++ b/src/onapsdk/so/templates/instantiate_vf_module_ala_carte.json.j2 @@ -0,0 +1,66 @@ +{ + "requestDetails": { + "requestInfo": + { + "instanceName": "{{ vf_module_instance_name }}", + "source": "VID", + "suppressRollback": false, + "requestorId": "test" + }, + "modelInfo": { + "modelType": "vfModule", + "modelInvariantId": "{{ vf_module.model_invariant_uuid }}", + "modelVersionId": "{{ vf_module.model_version_id }}", + "modelName": "{{ vf_module.model_name }}", + "modelVersion": "{{ vf_module.model_version }}", + "modelCustomizationId": "{{ vf_module.model_customization_id }}", + "modelCustomizationName": "{{ vf_module.model_name }}" + }, + "requestParameters": { + "userParams": [ + {% for parameter in vf_module_parameters %} + { + "name": "{{ parameter.name }}", + "value": "{{ parameter.value }}" + }{% if not loop.last %},{% endif %} + {% endfor %} + ], + "testApi": "GR_API", + "usePreload": true, + "aLaCarte": true + }, + "cloudConfiguration": { + "tenantId": "{{ tenant.tenant_id }}", + "cloudOwner": "{{ cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ cloud_region.cloud_region_id }}" + }, + "relatedInstanceList": [ + { + "relatedInstance": { + "instanceId": "{{ vnf_instance.service_instance.instance_id }}", + "modelInfo": { + "modelType": "service", + "modelName": "{{ service.name }}", + "modelInvariantId": "{{ service.unique_uuid }}", + "modelVersion": "1.0", + "modelVersionId": "{{ service.identifier }}" + } + } + }, + { + "relatedInstance": { + "instanceId": "{{ vnf_instance.vnf_id }}", + "modelInfo": { + "modelType": "vnf", + "modelName": "{{ vnf_instance.vnf.model_name }}", + "modelInvariantId": "{{ vnf_instance.vnf.model_invariant_id }}", + "modelVersion": "{{ vnf_instance.vnf.model_version }}", + "modelVersionId": "{{ vnf_instance.vnf.model_version_id }}", + "modelCustomizationId": "{{ vnf_instance.vnf.model_customization_id }}", + "modelCustomizationName": "{{ vnf_instance.vnf.name }}" + } + } + } + ] + } +} diff --git a/src/onapsdk/so/templates/instantiate_vnf_ala_carte.json.j2 b/src/onapsdk/so/templates/instantiate_vnf_ala_carte.json.j2 new file mode 100644 index 0000000..9fbf989 --- /dev/null +++ b/src/onapsdk/so/templates/instantiate_vnf_ala_carte.json.j2 @@ -0,0 +1,18 @@ +{% extends "instantiate_network_vnf_ala_carte_base.json.j2" %} +{% block model_info %} + "modelType": "vnf", + "modelInvariantId": "{{ vnf.model_invariant_id }}", + "modelVersionId": "{{ vnf.model_version_id }}", + "modelName": "{{ vnf.model_name }}", + "modelVersion": "{{ vnf.model_version }}", + "modelCustomizationId": "{{ vnf.model_customization_id }}", + "modelCustomizationName": "{{ vnf.name }}" +{% endblock %} +{% block user_params %} +{% for parameter in vnf_parameters %} +{ + "name": "{{ parameter.name }}", + "value": "{{ parameter.value }}" +}{% if not loop.last %},{% endif %} +{% endfor %} +{% endblock %} \ No newline at end of file diff --git a/src/onapsdk/so/templates/instantiate_vnf_macro.json.j2 b/src/onapsdk/so/templates/instantiate_vnf_macro.json.j2 new file mode 100644 index 0000000..2d7aeee --- /dev/null +++ b/src/onapsdk/so/templates/instantiate_vnf_macro.json.j2 @@ -0,0 +1,153 @@ +{ + "requestDetails": { + "requestInfo": { + "instanceName": "{{ service_instance.instance_name }}", + "source": "VID", + "suppressRollback": false, + "requestorId": "demo", + "productFamilyId": "1234" + }, + "modelInfo": { + "modelType": "vnf", + "modelInvariantId": "{{ vnf.model_invariant_id }}", + "modelVersionId": "{{ vnf.model_version_id }}", + "modelName": "{{ vnf.model_name }}", + "modelVersion": "{{ vnf.model_version }}", + "modelCustomizationId": "{{ vnf.model_customization_id }}", + "modelInstanceName": "{{ vnf.name }}" + }, + "cloudConfiguration": { + "tenantId": "{{ tenant.tenant_id }}", + "cloudOwner": "{{ cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ cloud_region.cloud_region_id }}" + }, + "platform": { + "platformName": "{{ platform }}" + }, + "lineOfBusiness": { + "lineOfBusinessName": "{{ line_of_business }}" + }, + "subscriberInfo": { + "globalSubscriberId": "{{ service_instance.service_subscription.customer.global_customer_id }}" + }, + "requestParameters": { + {% block subscriptionServiceType %} + "subscriptionServiceType": "{{ service.name }}", + {% endblock %} + "userParams": [ + { + "Homing_Solution": "none" + }, + { + "service": { + "instanceParams": [], + "resources": { + {% block vnfs %} + "vnfs": [ + { + "modelInfo": { + "modelName": "{{ vnf.model_name }}", + "modelVersionId": "{{ vnf.model_version_id }}", + "modelInvariantUuid": "{{ vnf.model_invariant_id }}", + "modelVersion": "{{ vnf.model_version }}", + "modelCustomizationId": "{{ vnf.model_customization_id }}", + "modelInstanceName": "{{ vnf.name }}" + }, + "cloudConfiguration": { + "tenantId": "{{ tenant.tenant_id }}", + "cloudOwner": "{{ cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ cloud_region.cloud_region_id }}" + }, + "platform": { + "platformName": "{{ platform }}" + }, + "lineOfBusiness": { + "lineOfBusinessName": "{{ line_of_business }}" + }, + "productFamilyId": "1234", + "instanceName": "{{ instance_name }}", + "instanceParams": [ + { + {% for vnf_parameter in vnf_parameters %} + {% if vnf_parameter.name == vnf.model_name %} + {% for parameter in vnf_parameter.vnf_parameters %} + "{{ parameter.name }}": "{{ parameter.value }}"{% if not loop.last %},{% endif %} + {% endfor %} + {% endif %} + {% endfor %} + } + ], + "vfModules": [ + {% for vf_module in vnf.vf_modules %} + + { + "modelInfo": { + "modelName": "{{ vf_module.model_name }}", + "modelVersionId": "{{ vf_module.model_version_id }}", + "modelInvariantUuid": "{{ vf_module.model_invariant_uuid }}", + "modelVersion": "{{ vf_module.model_version }}", + "modelCustomizationId": "{{ vf_module.model_customization_id }}" + }, + "instanceName": "{{instance_name}}_{{ vf_module.name }}", + "instanceParams": [ + { + {% for vnf_parameter in vnf_parameters %} + {% if vnf_parameter.name == vnf.model_name %} + {% set mylist = vf_module.name.split('..') %} + {% set item = mylist|length-2 %} + {% for vf_module_parameter in vnf_parameter.vfmodule_parameters %} + {% if vf_module_parameter.name == mylist[item] %} + {% for parameter in vf_module_parameter.vfmodule_parameters %} + "{{ parameter.name }}": "{{ parameter.value }}"{% if not loop.last %},{% endif %} + {% endfor %} + {% endif %} + {% endfor %} + {% endif %} + {% endfor %} + } + ] + }{% if not loop.last %},{% endif %} + {% endfor %} + ] + } + ] + {% endblock %} + }, + "modelInfo": { + "modelType": "vnf", + "modelInvariantId": "{{ vnf.model_invariant_id }}", + "modelVersionId": "{{ vnf.model_version_id }}", + "modelName": "{{ vnf.model_name }}", + "modelVersion": "{{ vnf.model_version }}", + "modelCustomizationId": "{{ vnf.model_customization_id }}", + "modelCustomizationName": "{{ vnf.name }}" + } + } + } + ], + "aLaCarte": false + }, + "project": { + "projectName": "{{ project }}" + }, + "owningEntity": { + "owningEntityId": "{{ owning_entity.owning_entity_id }}", + "owningEntityName": "{{ owning_entity.name }}" + }, + "relatedInstanceList": [ + { + "relatedInstance": { + "instanceId": "{{ service_instance.instance_id }}", + "modelInfo": { + "modelType": "service", + "modelInvariantId": "{{ service.unique_uuid }}", + "modelVersionId": "{{ service.identifier }}", + "modelName": "{{ service.name }}", + "modelVersion": "1.0" + } + } + } + ] + }, + "serviceInstanceId": "{{ service_instance.instance_id }}" +} diff --git a/src/onapsdk/so/templates/instantiate_vnf_macro_so_vnf.json.j2 b/src/onapsdk/so/templates/instantiate_vnf_macro_so_vnf.json.j2 new file mode 100644 index 0000000..c7f4356 --- /dev/null +++ b/src/onapsdk/so/templates/instantiate_vnf_macro_so_vnf.json.j2 @@ -0,0 +1,151 @@ +{ + "requestDetails": { + "requestInfo": { + "instanceName": "{{ service_instance.instance_name }}", + "source": "VID", + "suppressRollback": false, + "requestorId": "demo", + "productFamilyId": "1234" + }, + "modelInfo": { + "modelType": "vnf", + "modelInvariantId": "{{ vnf.model_invariant_id }}", + "modelVersionId": "{{ vnf.model_version_id }}", + "modelName": "{{ vnf.model_name }}", + "modelVersion": "{{ vnf.model_version }}", + "modelCustomizationId": "{{ vnf.model_customization_id }}", + "modelInstanceName": "{{ vnf.name }}" + }, + "cloudConfiguration": { + "tenantId": "{{ tenant.tenant_id }}", + "cloudOwner": "{{ cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ cloud_region.cloud_region_id }}" + }, + "platform": { + "platformName": "{{ platform }}" + }, + "lineOfBusiness": { + "lineOfBusinessName": "{{ line_of_business }}" + }, + "subscriberInfo": { + "globalSubscriberId": "{{ service_instance.service_subscription.customer.global_customer_id }}" + }, + "requestParameters": { + {% block subscriptionServiceType %} + "subscriptionServiceType": "{{ service.name }}", + {% endblock %} + "userParams": [ + { + "Homing_Solution": "none" + }, + { + "service": { + "instanceParams": [], + "resources": { + {% block vnfs %} + "vnfs": [ + { + "modelInfo": { + "modelName": "{{ vnf.model_name }}", + "modelVersionId": "{{ vnf.model_version_id }}", + "modelInvariantUuid": "{{ vnf.model_invariant_id }}", + "modelVersion": "{{ vnf.model_version }}", + "modelCustomizationId": "{{ vnf.model_customization_id }}", + "modelInstanceName": "{{ vnf.name }}" + }, + "cloudConfiguration": { + "tenantId": "{{ tenant.tenant_id }}", + "cloudOwner": "{{ cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ cloud_region.cloud_region_id }}" + }, + "platform": { + "platformName": "{{ platform }}" + }, + "lineOfBusiness": { + "lineOfBusinessName": "{{ line_of_business }}" + }, + "productFamilyId": "1234", + "instanceName": "{{ instance_name }}", + "instanceParams": [ + { + {% for key, value in so_vnf.parameters.items() %} + "{{ key }}": "{{ value }}"{% if not loop.last %},{% endif %} + {% endfor %} + } + ], + "vfModules": [ + {% for vf_module in so_vnf.vf_modules %} + { + "modelInfo": { + + {% if vnf.model_name == so_vnf.model_name %} + {% for sdc_vf_module in vnf.vf_modules %} + {% set mylist = sdc_vf_module.name.split('..') %} + {% set item = mylist|length-2 %} + {% if vf_module.model_name == mylist[item] %} + "modelName": "{{ sdc_vf_module.model_name }}", + "modelVersionId": "{{ sdc_vf_module.model_version_id}}", + "modelInvariantUuid": "{{ sdc_vf_module.model_invariant_uuid }}", + "modelVersion": "{{ sdc_vf_module.model_version }}", + "modelCustomizationId": "{{ sdc_vf_module.model_customization_id }}" + {% endif %} + {% endfor %} + {% endif %} + + }, + "instanceName": "{{ vf_module.instance_name }}", + {% if vf_module.processing_priority %} + "processingPriority": "{{ vf_module.processing_priority }}", + {% endif %} + "instanceParams": [ + { + {% for key, value in vf_module.parameters.items() %} + "{{ key }}": "{{ value }}"{% if not loop.last %},{% endif %} + {% endfor %} + } + ] + }{% if not loop.last %},{% endif %} + {% endfor %} + ] + } + ] + {% endblock %} + }, + "modelInfo": { + "modelType": "vnf", + "modelInvariantId": "{{ vnf.model_invariant_id }}", + "modelVersionId": "{{ vnf.model_version_id }}", + "modelName": "{{ vnf.model_name }}", + "modelVersion": "{{ vnf.model_version }}", + "modelCustomizationId": "{{ vnf.model_customization_id }}", + "modelCustomizationName": "{{ vnf.name }}" + } + } + } + ], + "aLaCarte": false + }, + "project": { + "projectName": "{{ project }}" + }, + "owningEntity": { + "owningEntityId": "{{ owning_entity.owning_entity_id }}", + "owningEntityName": "{{ owning_entity.name }}" + }, + "relatedInstanceList": [ + { + "relatedInstance": { + "instanceId": "{{ service_instance.instance_id }}", + "modelInfo": { + "modelType": "service", + "modelInvariantId": "{{ service.unique_uuid }}", + "modelVersionId": "{{ service.identifier }}", + "modelName": "{{ service.name }}", + "modelVersion": "1.0" + } + } + } + ] + }, + "serviceInstanceId": "{{ service_instance.instance_id }}" +} diff --git a/src/onapsdk/so/templates/service_instance_model_info.json.j2 b/src/onapsdk/so/templates/service_instance_model_info.json.j2 new file mode 100644 index 0000000..fc66de4 --- /dev/null +++ b/src/onapsdk/so/templates/service_instance_model_info.json.j2 @@ -0,0 +1,7 @@ +{ + "modelType": "service", + "modelInvariantId": "{{ model_invariant_id }}", + "modelName": "{{ model_name }}", + "modelVersion": "{{ model_version }}", + "modelVersionId": "{{ model_name_version_id }}" +} diff --git a/src/onapsdk/so/templates/vf_model_info.json.j2 b/src/onapsdk/so/templates/vf_model_info.json.j2 new file mode 100644 index 0000000..4b40898 --- /dev/null +++ b/src/onapsdk/so/templates/vf_model_info.json.j2 @@ -0,0 +1,15 @@ +[ +{% for _, module in modules.items() %} + { + "modelInfo": { + "modelName": "{{ module["metadata"]["vfModuleModelName"] }}", + "modelVersion": "{{ module.metadata.vfModuleModelVersion }}", + "modelVersionId": "{{ module.metadata.vfModuleModelUUID }}", + "modelInvariantUuid": "{{ module.metadata.vfModuleInvariantUUID }}", + "modelCustomizationId": "{{ module.metadata.vfModuleModelCustomizationUUID }}" + }, + "instanceName": "{{ module.metadata.vfModuleModelName }}", + "instanceParams": [] + }{% if not loop.last %},{% endif %} +{% endfor %} +] \ No newline at end of file diff --git a/src/onapsdk/so/templates/vnf_model_info.json.j2 b/src/onapsdk/so/templates/vnf_model_info.json.j2 new file mode 100644 index 0000000..ecc788b --- /dev/null +++ b/src/onapsdk/so/templates/vnf_model_info.json.j2 @@ -0,0 +1,9 @@ +{ + "modelType": "vnf", + "modelName": "{{ vnf_model_name }}", + "modelVersion": "{{ vnf_model_version }}", + "modelVersionId": "{{ vnf_model_version_id }}", + "modelInvariantUuid": "{{ vnf_model_invariant_uuid }}", + "modelCustomizationId": "{{ vnf_model_customization_id }}", + "modelInstanceName": "{{ vnf_model_instance_name }}" +} diff --git a/src/onapsdk/utils/__init__.py b/src/onapsdk/utils/__init__.py new file mode 100644 index 0000000..bd7f9f5 --- /dev/null +++ b/src/onapsdk/utils/__init__.py @@ -0,0 +1,40 @@ +"""ONAP SDK utils package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 +from datetime import datetime + + +def get_zulu_time_isoformat() -> str: + """Get zulu time in accepted by ONAP modules format. + + Returns: + str: Actual Zulu time. + + """ + return datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ') + + +def load_json_file(path_to_json_file: str) -> str: + """ + Return json as string from selected file. + + Args: + path_to_json_file: (str) path to file with json + Returns: + File content as string (str) + """ + with open(path_to_json_file) as json_file: + data = json.load(json_file) + return json.dumps(data) diff --git a/src/onapsdk/utils/configuration.py b/src/onapsdk/utils/configuration.py new file mode 100644 index 0000000..89bee5a --- /dev/null +++ b/src/onapsdk/utils/configuration.py @@ -0,0 +1,25 @@ +"""Configuration package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import List + + +def tosca_path() -> str: + """Return tosca file paths.""" + return '/tmp/tosca_files/' + + +def components_needing_distribution() -> List[str]: + """Return the list of components needing distribution.""" + return ["SO", "sdnc", "aai"] diff --git a/src/onapsdk/utils/gui.py b/src/onapsdk/utils/gui.py new file mode 100644 index 0000000..421e966 --- /dev/null +++ b/src/onapsdk/utils/gui.py @@ -0,0 +1,35 @@ +"""Definition of GUI objects.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 dataclasses import dataclass +from typing import List + +@dataclass +class GuiItem: + """Class for keeping track of a GUI.""" + + url: str + status: int + +@dataclass +class GuiList: + """Class to list all the GUIs.""" + + guilist: List[GuiItem] + + def add(self, element): + """Add a GUi to GUI list.""" + if not isinstance(element, GuiItem): + raise AttributeError + self.guilist.append(element) diff --git a/src/onapsdk/utils/headers_creator.py b/src/onapsdk/utils/headers_creator.py new file mode 100644 index 0000000..adb0609 --- /dev/null +++ b/src/onapsdk/utils/headers_creator.py @@ -0,0 +1,245 @@ +"""Header creator package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 typing import Dict +from uuid import uuid4 +import base64 +import hashlib + +from onapsdk.configuration import settings + + +def headers_sdc_creator(base_header: Dict[str, str], + user: str = "cs0008", + authorization: str = None): + """ + Create the right headers for SDC creator type. + + Args: + base_header (Dict[str, str]): the base header to use + user (str, optional): the user to use. Default to cs0008 + authorization (str, optional): the basic auth to use. + Default to "classic" one + + Returns: + Dict[str, str]: the needed headers + + """ + return headers_sdc_generic(base_header, user, authorization=authorization) + + +def headers_sdc_tester(base_header: Dict[str, str], + user: str = "jm0007", + authorization: str = None): + """ + Create the right headers for SDC tester type. + + Args: + base_header (Dict[str, str]): the base header to use + user (str, optional): the user to use. Default to jm0007 + authorization (str, optional): the basic auth to use. + Default to "classic" one + + Returns: + Dict[str, str]: the needed headers + + """ + return headers_sdc_generic(base_header, user, authorization=authorization) + + +def headers_sdc_governor(base_header: Dict[str, str], + user: str = "gv0001", + authorization: str = None): + """ + Create the right headers for SDC governor type. + + Args: + base_header (Dict[str, str]): the base header to use + user (str, optional): the user to use. Default to gv0001 + authorization (str, optional): the basic auth to use. + Default to "classic" one + + Returns: + Dict[str, str]: the needed headers + + """ + return headers_sdc_generic(base_header, user, authorization=authorization) + + +def headers_sdc_operator(base_header: Dict[str, str], + user: str = "op0001", + authorization: str = None): + """ + Create the right headers for SDC operator type. + + Args: + base_header (Dict[str, str]): the base header to use + user (str, optional): the user to use. Default to op0001 + authorization (str, optional): the basic auth to use. + Default to "classic" one + + Returns: + Dict[str, str]: the needed headers + + """ + return headers_sdc_generic(base_header, user, authorization=authorization) + + +def headers_sdc_generic(base_header: Dict[str, str], + user: str, + authorization: str = None): + """ + Create the right headers for SDC generic type. + + Args: + base_header (Dict[str, str]): the base header to use + user (str): the user to use. + authorization (str, optional): the basic auth to use. + Default to "classic" one + + Returns: + Dict[str, str]: the needed headers + + """ + headers = base_header.copy() + headers["USER_ID"] = user + headers["Authorization"] = authorization or settings.SDC_AUTH + headers["X-ECOMP-InstanceID"] = "onapsdk" + return headers + + +def headers_aai_creator(base_header: Dict[str, str]): + """ + Create the right headers for AAI creator type. + + Args: + base_header (Dict[str, str]): the base header to use + + Returns: + Dict[str, str]: the needed headers + + """ + headers = base_header.copy() + headers["x-fromappid"] = "AAI" + headers["x-transactionid"] = "0a3f6713-ba96-4971-a6f8-c2da85a3176e" + headers["authorization"] = settings.AAI_AUTH + return headers + + +def headers_so_creator(base_header: Dict[str, str]): + """ + Create the right headers for SO creator type. + + Args: + base_header (Dict[str, str]): the base header to use + + Returns: + Dict[str, str]: the needed headers + + """ + headers = base_header.copy() + headers["x-fromappid"] = "AAI" + headers["x-transactionid"] = str(uuid4()) + headers["authorization"] = settings.SO_AUTH + headers["cache-control"] = "no-cache" + return headers + +def headers_so_catelog_db_creator(base_header: Dict[str, str]): + """ + Create the right headers for SO creator type. + + Args: + base_header (Dict[str, str]): the base header to use + + Returns: + Dict[str, str]: the needed headers + + """ + headers = base_header.copy() + headers["x-fromappid"] = "AAI" + headers["x-transactionid"] = str(uuid4()) + headers["authorization"] = settings.SO_CAT_DB_AUTH + headers["cache-control"] = "no-cache" + return headers + +def headers_msb_creator(base_header: Dict[str, str]): + """ + Create the right headers for MSB. + + Args: + base_header (Dict[str, str]): the base header to use + + Returns: + Dict[str, str]: the needed headers + + """ + headers = base_header.copy() + headers["cache-control"] = "no-cache" + return headers + + +def headers_sdnc_creator(base_header: Dict[str, str]): + """ + Create the right headers for SDNC. + + Args: + base_header (Dict[str, str]): the base header to use + + Returns: + Dict[str, str]: the needed headers + + """ + headers = base_header.copy() + headers["authorization"] = settings.SDNC_AUTH + headers["x-transactionid"] = str(uuid4()) + headers["x-fromappid"] = "API client" + return headers + + +def headers_sdc_artifact_upload(base_header: Dict[str, str], data: str): + """ + Create the right headers for sdc artifact upload. + + Args: + base_header (Dict[str, str]): the base header to use + data (str): payload data used to create an md5 content header + + Returns: + Dict[str, str]: the needed headers + + """ + headers = base_header.copy() + headers["Accept"] = "application/json, text/plain, */*" + headers["Accept-Encoding"] = "gzip, deflate, br" + headers["Content-Type"] = "application/json; charset=UTF-8" + md5_content = hashlib.md5(data.encode('UTF-8')).hexdigest() + content = base64.b64encode(md5_content.encode('ascii')).decode('UTF-8') + headers["Content-MD5"] = content + return headers + +def headers_clamp_creator(base_header: Dict[str, str]): + """ + Create the right headers for CLAMP generic type. + + base_header (Dict[str, str]): the base header to use + data (str): payload data used to create an md5 content header + + Returns: + Dict[str, str]: the needed headers + + """ + headers = base_header.copy() + headers["Authorization"] = settings.CLAMP_AUTH + headers["X-ECOMP-InstanceID"] = "onapsdk" + return headers diff --git a/src/onapsdk/utils/jinja.py b/src/onapsdk/utils/jinja.py new file mode 100644 index 0000000..fe59eae --- /dev/null +++ b/src/onapsdk/utils/jinja.py @@ -0,0 +1,50 @@ +"""Jinja module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 jinja2 import Environment, PackageLoader, select_autoescape, ChoiceLoader + + +def jinja_env() -> Environment: + """Create Jinja environment. + + jinja_env allow to fetch simply jinja templates where they are. + by default jinja engine will look for templates in `templates` directory of + the package. So to load a template, you just have to do: + + Example: + >>> template = jinja_env().get_template('vendor_create.json.j2') + >>> data = template.render(name="vendor") + + See also: + SdcElement.create() for real use + + Returns: + Environment: the Jinja environment to use + + """ + return Environment(autoescape=select_autoescape(['html', 'htm', 'xml']), + loader=ChoiceLoader([ + PackageLoader("onapsdk.aai"), + PackageLoader("onapsdk.cds"), + PackageLoader("onapsdk.clamp"), + PackageLoader("onapsdk.msb"), + PackageLoader("onapsdk.nbi"), + PackageLoader("onapsdk.sdc"), + PackageLoader("onapsdk.sdnc"), + PackageLoader("onapsdk.sdnc"), + PackageLoader("onapsdk.so"), + PackageLoader("onapsdk.ves"), + PackageLoader("onapsdk.vid") + ])) diff --git a/src/onapsdk/utils/mixins.py b/src/onapsdk/utils/mixins.py new file mode 100644 index 0000000..7a64a15 --- /dev/null +++ b/src/onapsdk/utils/mixins.py @@ -0,0 +1,99 @@ +"""Mixins module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 abc import ABC, abstractmethod +from ctypes import c_bool +from multiprocessing import Process, Value +from time import sleep + + +class WaitForFinishMixin(ABC): + """Wait for finish mixin. + + Mixin with wait_for_finish method and two properties: + - completed, + - finished. + + Can be used to wait for result of asynchronous tasks. + + """ + + WAIT_FOR_SLEEP_TIME = 10 + + @property + @abstractmethod + def completed(self) -> bool: + """Store an information if object task is completed or not. + + Returns: + bool: True if object task is completed, False otherwise. + + """ + + @property + @abstractmethod + def finished(self) -> bool: + """Store an information if object task is finished or not. + + Returns: + bool: True if object task is finished, False otherwise. + + """ + + def _wait_for_finish(self, return_value: Value) -> bool: + """Wait until object task is finished. + + Method called in another process. + + Args: + return_value(Value): value shared with main process to pass there + if object task was completed or not + + """ + while not self.finished: + sleep(self.WAIT_FOR_SLEEP_TIME) + self._logger.info(f"{self.__class__.__name__} task finished") + return_value.value = self.completed + + def wait_for_finish(self, timeout: float = None) -> bool: + """Wait until object task is finished. + + It uses time.sleep with WAIT_FOR_SLEEP_TIME value as a parameter to + wait unitl request is finished (object's finished property is + equal to True). + + It runs another process to control time of the function. If process timed out + TimeoutError is going to be raised. + + Args: + timeout(float, optional): positive number, wait at most timeout seconds + + Raises: + TimeoutError: Raised when function timed out + + Returns: + bool: True if object's task is successfully completed, False otherwise + + """ + self._logger.debug(f"Wait until {self.__class__.__name__} task is not finished") + return_value: Value = Value(c_bool) + wait_for_process: Process = Process(target=self._wait_for_finish, args=(return_value,)) + try: + wait_for_process.start() + wait_for_process.join(timeout) + return return_value.value + finally: + if wait_for_process.is_alive(): + wait_for_process.terminate() + raise TimeoutError diff --git a/src/onapsdk/utils/tosca_file_handler.py b/src/onapsdk/utils/tosca_file_handler.py new file mode 100644 index 0000000..921b868 --- /dev/null +++ b/src/onapsdk/utils/tosca_file_handler.py @@ -0,0 +1,106 @@ +"""Utils class.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 string +import random +from typing import Dict, List + +from onapsdk.exceptions import ValidationError + +def get_parameter_from_yaml(parameter: str, config_file: str): + """Get the value of a given parameter in file.yaml. + + Parameter must be given in string format with dots + Example: general.openstack.image_name + + Args: + parameter (str): + config_file (str): configuration yaml file formtatted as string + + Raises: + ParameterError: parameter not defined + + Returns: + the value of the parameter + + """ + value = json.loads(config_file) + + # Workaround for the .. within the params in the yaml file + ugly_param = parameter.replace("..", "##") + for element in ugly_param.split("."): + value = value.get(element.replace("##", "..")) + if value is None: + msg = f"{element} in the {parameter} is not in YAML config file." + raise ValidationError(msg) + + return value + +def get_vf_list_from_tosca_file(model: str) -> List: + """Get the list of Vfs of a VNF based on the tosca file. + + Args: + model (str): the model retrieved from the tosca file at Vnf + instantiation + + Returns: + list: a list of Vfs + + """ + newlist = [] + node_list = get_parameter_from_yaml( + "topology_template.node_templates", model) + + for node in node_list: + value = get_parameter_from_yaml( + "topology_template.node_templates." + node + ".type", + model) + if "org.openecomp.resource.vf" in value: + print(node, value) + if node not in newlist: + search_value = str(node).split(" ")[0] + newlist.append(search_value) + return newlist + +def get_modules_list_from_tosca_file(model: str) -> Dict: + """Get the list of modules from tosca file. + + Modules are stored on topology_template.groups TOSCA file section. + + Args: + model (str): the model retrieved from the tosca file at Vnf + instantiation. + + Returns: + dict: a list of modules + + """ + return get_parameter_from_yaml( + "topology_template.groups", model + ) + +def random_string_generator(size=6, + chars=string.ascii_uppercase + string.digits) -> str: + """Get a random String for VNF. + + Args: + size (int): the number of alphanumerical chars for CI + chars (str): alphanumerical characters (ASCII uppercase and digits) + + Returns: + str: a sequence of random characters + + """ + return ''.join(random.choice(chars) for _ in range(size)) diff --git a/src/onapsdk/version.py b/src/onapsdk/version.py new file mode 100644 index 0000000..9cf4f97 --- /dev/null +++ b/src/onapsdk/version.py @@ -0,0 +1,16 @@ +"""Version module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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. + +__version__ = "10.2.0" diff --git a/src/onapsdk/ves/__init__.py b/src/onapsdk/ves/__init__.py new file mode 100644 index 0000000..186f0e7 --- /dev/null +++ b/src/onapsdk/ves/__init__.py @@ -0,0 +1,14 @@ +"""ONAP SDK VES package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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. diff --git a/src/onapsdk/ves/templates/ves7_batch_with_stndDefined_valid.json.j2 b/src/onapsdk/ves/templates/ves7_batch_with_stndDefined_valid.json.j2 new file mode 100644 index 0000000..662e55e --- /dev/null +++ b/src/onapsdk/ves/templates/ves7_batch_with_stndDefined_valid.json.j2 @@ -0,0 +1,109 @@ +{ + "eventList": [ + { + "commonEventHeader": { + "version": "4.1", + "vesEventListenerVersion": "7.2", + "domain": "stndDefined", + "eventId": "stndDefined-gNB_Nokia000001", + "eventName": "stndDefined-gNB-Nokia-PowerLost", + "stndDefinedNamespace": "3GPP-FaultSupervision", + "startEpochMicrosec": 1413378172000000, + "lastEpochMicrosec": 1413378172000000, + "reportingEntityName": "ibcx0001vm002oam001", + "sourceName": "scfx0001vm002cap001", + "sequence": 1, + "priority": "High" + }, + "stndDefinedFields": { + "schemaReference": "https://forge.3gpp.org/rep/sa5/MnS/blob/SA88-Rel16/OpenAPI/faultMnS.yaml#components/schemas/NotifyNewAlarm", + "data": { + "href": 1, + "uri": "1", + "notificationId": 1, + "notificationType": "notifyNewAlarm", + "eventTime": "xyz", + "systemDN": "xyz", + "probableCause": 1, + "perceivedSeverity": "INDETERMINATE", + "rootCauseIndicator": false, + "specificProblem": "xyz", + "correlatedNotifications": [], + "backedUpStatus": true, + "backUpObject": "xyz", + "trendIndication": "MORE_SEVERE", + "thresholdInfo": { + "observedMeasurement": "new", + "observedValue": 123 + }, + "stateChangeDefinition": { + }, + "monitoredAttributes": { + "newAtt": "new" + }, + "proposedRepairActions": "xyz", + "additionalText": "xyz", + "additionalInformation": { + "addInfo": "new" + }, + "alarmId": "1", + "alarmType": "COMMUNICATIONS_ALARM" + }, + "stndDefinedFieldsVersion": "1.0" + } + }, + { + "commonEventHeader": { + "version": "4.1", + "vesEventListenerVersion": "7.2", + "domain": "stndDefined", + "eventId": "stndDefined-gNB_Nokia000001", + "eventName": "stndDefined-gNB-Nokia-PowerLost", + "stndDefinedNamespace": "3GPP-FaultSupervision", + "startEpochMicrosec": 1413378172000000, + "lastEpochMicrosec": 1413378172000000, + "reportingEntityName": "ibcx0001vm002oam001", + "sourceName": "scfx0001vm002cap001", + "sequence": 1, + "priority": "High" + }, + "stndDefinedFields": { + "schemaReference": "https://forge.3gpp.org/rep/sa5/MnS/blob/SA88-Rel16/OpenAPI/faultMnS.yaml#components/schemas/NotifyNewAlarm", + "data": { + "href": 1, + "uri": "1", + "notificationId": 1, + "notificationType": "notifyNewAlarm", + "eventTime": "xyz", + "systemDN": "xyz", + "probableCause": 1, + "perceivedSeverity": "INDETERMINATE", + "rootCauseIndicator": false, + "specificProblem": "xyz", + "correlatedNotifications": [], + "backedUpStatus": true, + "backUpObject": "xyz", + "trendIndication": "MORE_SEVERE", + "thresholdInfo": { + "observedMeasurement": "new", + "observedValue": 123 + }, + "stateChangeDefinition": { + }, + "monitoredAttributes": { + "newAtt": "new" + }, + "proposedRepairActions": "xyz", + "additionalText": "xyz", + "additionalInformation": { + "addInfo": "new" + }, + "alarmId": "1", + "alarmType": "COMMUNICATIONS_ALARM" + }, + "stndDefinedFieldsVersion": "1.0" + } + } + ] +} + diff --git a/src/onapsdk/ves/templates/ves_stnd_event.json.j2 b/src/onapsdk/ves/templates/ves_stnd_event.json.j2 new file mode 100644 index 0000000..fd1ce98 --- /dev/null +++ b/src/onapsdk/ves/templates/ves_stnd_event.json.j2 @@ -0,0 +1,54 @@ +{ + "event": { + "commonEventHeader": { + "version": "4.1", + "vesEventListenerVersion": "7.2", + "domain": "stndDefined", + "eventId": "stndDefined-gNB_Nokia000001", + "eventName": "stndDefined-gNB-Nokia-PowerLost", + "stndDefinedNamespace": "3GPP-FaultSupervision", + "startEpochMicrosec": 1413378172000000, + "lastEpochMicrosec": 1413378172000000, + "reportingEntityName": "ibcx0001vm002oam001", + "sourceName": "scfx0001vm002cap001", + "sequence": 1, + "priority": "High" + }, + "stndDefinedFields": { + "schemaReference": "https://forge.3gpp.org/rep/sa5/MnS/blob/SA88-Rel16/OpenAPI/faultMnS.yaml#components/schemas/NotifyNewAlarm", + "data": { + "href": 1, + "uri": "1", + "notificationId": 1, + "notificationType": "notifyNewAlarm", + "eventTime": "xyz", + "systemDN": "xyz", + "probableCause": 1, + "perceivedSeverity": "INDETERMINATE", + "rootCauseIndicator": false, + "specificProblem": "xyz", + "correlatedNotifications": [], + "backedUpStatus": true, + "backUpObject": "xyz", + "trendIndication": "MORE_SEVERE", + "thresholdInfo": { + "observedMeasurement": "new", + "observedValue": 123 + }, + "stateChangeDefinition": { + }, + "monitoredAttributes": { + "newAtt": "new" + }, + "proposedRepairActions": "xyz", + "additionalText": "xyz", + "additionalInformation": { + "addInfo": "new" + }, + "alarmId": "1", + "alarmType": "COMMUNICATIONS_ALARM" + }, + "stndDefinedFieldsVersion": "1.0" + } + } +} diff --git a/src/onapsdk/ves/templates/ves_stnd_valid_event.json.j2 b/src/onapsdk/ves/templates/ves_stnd_valid_event.json.j2 new file mode 100644 index 0000000..08d335b --- /dev/null +++ b/src/onapsdk/ves/templates/ves_stnd_valid_event.json.j2 @@ -0,0 +1,54 @@ +{ + "event": { + "commonEventHeader": { + "version": "4.1", + "vesEventListenerVersion": "7.2", + "domain": "stndDefined", + "eventId": "12", + "eventName": "someEventName", + "stndDefinedNamespace": "{{ header.namespace }}", + "startEpochMicrosec": 1413378172000000, + "lastEpochMicrosec": 1413378172000000, + "reportingEntityName": "ibcx0001vm002oam001", + "sourceName": "scfx0001vm002cap001", + "sequence": 1, + "priority": "High" + }, + "stndDefinedFields": { + "schemaReference": "{{ schema_reference }}", + "data": { + "href": 1, + "uri": "1", + "notificationId": 1, + "notificationType": "notifyNewAlarm", + "eventTime": "xyz", + "systemDN": "xyz", + "probableCause": 1, + "perceivedSeverity": "INDETERMINATE", + "rootCauseIndicator": false, + "specificProblem": "xyz", + "correlatedNotifications": [], + "backedUpStatus": true, + "backUpObject": "xyz", + "trendIndication": "MORE_SEVERE", + "thresholdInfo": { + "observedMeasurement": "new", + "observedValue": 123 + }, + "stateChangeDefinition": { + }, + "monitoredAttributes": { + "newAtt": "new" + }, + "proposedRepairActions": "xyz", + "additionalText": "xyz", + "additionalInformation": { + "addInfo": "new" + }, + "alarmId": "1", + "alarmType": "COMMUNICATIONS_ALARM" + }, + "stndDefinedFieldsVersion": "1.0" + } + } +} diff --git a/src/onapsdk/ves/ves.py b/src/onapsdk/ves/ves.py new file mode 100644 index 0000000..1f3f592 --- /dev/null +++ b/src/onapsdk/ves/ves.py @@ -0,0 +1,84 @@ +"""Base VES event sender.""" +# Copyright 2022 Orange, Deutsche Telekom AG, Nokia +# +# 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 typing import Dict, Union + +import json +import requests + +from onapsdk.ves.ves_service import VesService + +ACTION = "Send event to Ves" +POST_HTTP_METHOD = "POST" + + +class Ves(VesService): + """Ves library provides functions for sending events to VES.""" + + event_endpoint_url: str = "{}/eventListener/{}" + event_batch_endpoint_url: str = "{}/eventListener/{}/eventBatch" + + @classmethod + def send_event(cls, + version: str, + json_event: str, + basic_auth: Dict[str, str]) -> Union[requests.Response, None]: + """ + Send an event stored in a file to VES. + + Args: + version: (str) version of VES data format + json_event: (str) event to send + basic_auth: Dict[str, str], for example:{ 'username': 'bob', 'password': 'secret' } + Returns: + (requests.Response) HTTP response status + + """ + return Ves.__send_event_message(cls.event_endpoint_url.format(VesService._url, version), + json_event, basic_auth) + + @classmethod + def send_batch_event(cls, + version: str, + json_event: str, + basic_auth: Dict[str, str]) -> Union[requests.Response, None]: + """ + Send a batch event stored in a file to VES. + + Args: + version: (str) version of VES data format + json_event: (str) event to send + basic_auth: Dict[str, str], for example:{ 'username': 'bob', 'password': 'secret' } + Returns: + (requests.Response) HTTP response status + + """ + return Ves.__send_event_message(cls.event_batch_endpoint_url. + format(VesService._url, version), + json_event, basic_auth) + + @classmethod + def __send_event_message(cls, + base_url: str, + json_event: str, + basic_auth: Dict[str, str] + ) -> Union[requests.Response, None]: + cls._logger.debug("Event to send %s", json_event) + return cls.send_message( + POST_HTTP_METHOD, + ACTION, + f"{base_url}", + basic_auth=basic_auth, + json=json.loads(json_event) + ) diff --git a/src/onapsdk/ves/ves_service.py b/src/onapsdk/ves/ves_service.py new file mode 100644 index 0000000..c4bc8ed --- /dev/null +++ b/src/onapsdk/ves/ves_service.py @@ -0,0 +1,27 @@ +"""Base VES module.""" +# Copyright 2022 Orange, Deutsche Telekom AG, Nokia +# +# 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 onapsdk.configuration import settings +from onapsdk.onap_service import OnapService + + +class VesService(OnapService): + """Base VES class. + + Stores url to VES API (edit if you want to use other) and authentication tuple + (username, password). + """ + + _url: str = settings.VES_URL diff --git a/src/onapsdk/vid/__init__.py b/src/onapsdk/vid/__init__.py new file mode 100644 index 0000000..fd58581 --- /dev/null +++ b/src/onapsdk/vid/__init__.py @@ -0,0 +1,16 @@ +"""VID package.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 .vid import LineOfBusiness, OwningEntity, Platform, Project, Vid diff --git a/src/onapsdk/vid/templates/vid_declare_resource.json.j2 b/src/onapsdk/vid/templates/vid_declare_resource.json.j2 new file mode 100644 index 0000000..b14e6f4 --- /dev/null +++ b/src/onapsdk/vid/templates/vid_declare_resource.json.j2 @@ -0,0 +1,3 @@ +{ + "options": ["{{ name }}"] +} \ No newline at end of file diff --git a/src/onapsdk/vid/vid.py b/src/onapsdk/vid/vid.py new file mode 100644 index 0000000..31cb92e --- /dev/null +++ b/src/onapsdk/vid/vid.py @@ -0,0 +1,134 @@ +"""VID module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 abc import ABC +from warnings import warn + +from onapsdk.configuration import settings +from onapsdk.onap_service import OnapService +from onapsdk.utils.jinja import jinja_env + + +class Vid(OnapService, ABC): + """VID base class.""" + + base_url = settings.VID_URL + api_version = settings.VID_API_VERSION + + def __init__(self, name: str) -> None: + """VID resource object initialization. + + Args: + name (str): Resource name + """ + warn("VID is deprecated and shouldn't be used! " + "It's not a part of the ONAP release since Istanbul.") + super().__init__() + self.name: str = name + + @classmethod + def get_create_url(cls) -> str: + """Resource url. + + Used to create resources + + Returns: + str: Url used for resource creation + + """ + raise NotImplementedError + + @classmethod + def create(cls, name: str) -> "Vid": + """Create VID resource. + + Returns: + Vid: Created VID resource + + """ + warn("VID is deprecated and shouldn't be used! " + "It's not a part of the ONAP release since Istanbul.") + cls.send_message( + "POST", + f"Declare VID resource with {name} name", + cls.get_create_url(), + data=jinja_env().get_template("vid_declare_resource.json.j2").render( + name=name + ) + ) + return cls(name) + + +class OwningEntity(Vid): + """VID owning entity class.""" + + @classmethod + def get_create_url(cls) -> str: + """Owning entity creation url. + + Returns: + str: Url used for ownint entity creation + + """ + warn("VID is deprecated and shouldn't be used! " + "It's not a part of the ONAP release since Istanbul.") + return f"{cls.base_url}{cls.api_version}/maintenance/category_parameter/owningEntity" + + +class Project(Vid): + """VID project class.""" + + @classmethod + def get_create_url(cls) -> str: + """Project creation url. + + Returns: + str: Url used for project creation + + """ + warn("VID is deprecated and shouldn't be used! " + "It's not a part of the ONAP release since Istanbul.") + return f"{cls.base_url}{cls.api_version}/maintenance/category_parameter/project" + + +class LineOfBusiness(Vid): + """VID line of business class.""" + + @classmethod + def get_create_url(cls) -> str: + """Line of business creation url. + + Returns: + str: Url used for line of business creation + + """ + warn("VID is deprecated and shouldn't be used! " + "It's not a part of the ONAP release since Istanbul.") + return f"{cls.base_url}{cls.api_version}/maintenance/category_parameter/lineOfBusiness" + + +class Platform(Vid): + """VID platform class.""" + + @classmethod + def get_create_url(cls) -> str: + """Platform creation url. + + Returns: + str: Url used for platform creation + + """ + warn("VID is deprecated and shouldn't be used! " + "It's not a part of the ONAP release since Istanbul.") + return f"{cls.base_url}{cls.api_version}/maintenance/category_parameter/platform" diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..b1ef494 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,7 @@ +-r requirements.txt +bandit +pylint==2.4.4 +pytest +pytest-cov +pydocstyle +requests-mock diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..d985dca --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,21 @@ +"""Main test module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 logging + +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)-23s - %(levelname)-8s - %(message)s') +logging.captureWarnings(True) diff --git a/tests/data/__init__.py b/tests/data/__init__.py new file mode 100644 index 0000000..5b3f765 --- /dev/null +++ b/tests/data/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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. \ No newline at end of file diff --git a/tests/data/bad.csar b/tests/data/bad.csar new file mode 100644 index 0000000..6a445b6 --- /dev/null +++ b/tests/data/bad.csar @@ -0,0 +1 @@ +bad is bad diff --git a/tests/data/bad_no_service.csar b/tests/data/bad_no_service.csar new file mode 100644 index 0000000..f0a0627 Binary files /dev/null and b/tests/data/bad_no_service.csar differ diff --git a/tests/data/csar.meta b/tests/data/csar.meta new file mode 100644 index 0000000..bc3359c --- /dev/null +++ b/tests/data/csar.meta @@ -0,0 +1,2 @@ +SDC-TOSCA-Meta-File-Version: 1.0 +SDC-TOSCA-Definitions-Version: 9.0 diff --git a/tests/data/service-Foo-template.yml b/tests/data/service-Foo-template.yml new file mode 100644 index 0000000..12ba4f4 --- /dev/null +++ b/tests/data/service-Foo-template.yml @@ -0,0 +1,1228 @@ +tosca_definitions_version: tosca_simple_yaml_1_1 +metadata: + invariantUUID: 6a081157-7d17-4369-a1f2-099322bfa9bc + UUID: 862d4c8a-bb0e-40dc-ad4c-7768fc03530a + name: vFW-service + description: vFW-service + type: Service + category: Network L4+ + serviceType: '' + serviceRole: '' + serviceEcompNaming: true + ecompGeneratedNaming: true + namingPolicy: '' +imports: + - nodes: + file: nodes.yml + - datatypes: + file: data.yml + - capabilities: + file: capabilities.yml + - relationships: + file: relationships.yml + - groups: + file: groups.yml + - policies: + file: policies.yml + - service-vFW-service-interface: + file: service-VfwService-template-interface.yml + - resource-vFWCL_vPKG-vf: + file: resource-VfwclVpkgVf-template.yml + - resource-vFWCL_vPKG-vf-interface: + file: resource-VfwclVpkgVf-template-interface.yml + - resource-vFWCL_vFWSNK-vf: + file: resource-VfwclVfwsnkVf-template.yml + - resource-vFWCL_vFWSNK-vf-interface: + file: resource-VfwclVfwsnkVf-template-interface.yml +topology_template: + node_templates: + vFWCL_vPKG-vf 0: + type: org.openecomp.resource.vf.VfwclVpkgVf + metadata: + invariantUUID: 696c4c49-b5d2-4d6f-ac2d-be2cd09fc356 + UUID: 9530fa98-94ca-41c3-bb67-bae3f3dae4a2 + customizationUUID: a2e4b36f-bcea-4b22-a383-0d4dc0a7dd65 + version: '1.0' + name: vFWCL_vPKG-vf + description: vPacketGenerator function + type: VF + category: Application L4+ + subcategory: Firewall + resourceVendor: Generic-Vendor + resourceVendorRelease: '1.0' + resourceVendorModelNumber: '' + properties: + vf_module_id: vTrafficPNG + repo_url_blob: https://nexus.onap.org/content/sites/raw + unprotected_private_subnet_id: zdfw1fwl01_unprotected_sub + public_net_id: 715a1ca1-cbc6-4d00-84bb-0f8667a748ce + vfw_private_ip_0: 192.168.10.100 + onap_private_subnet_id: 51a5a838-7318-464d-858a-974bef8d49e3 + onap_private_net_cidr: 10.4.2.0/24 + image_name: ubuntu-14.04-daily + flavor_name: onap.medium + vnf_id: vPNG_Firewall_demo_app + vpg_name_0: zdfw1fwl01pgn01 + vpg_private_ip_1: 10.4.2.200 + vsn_private_ip_0: 192.168.20.250 + vpg_private_ip_0: 192.168.10.200 + protected_private_net_cidr: 192.168.20.0/24 + unprotected_private_net_cidr: 192.168.10.0/24 + nf_naming: + ecomp_generated_naming: true + onap_private_net_id: 715a1ca1-cbc6-4d00-84bb-0f8667a748ce + unprotected_private_net_id: zdfw1fwl01_unprotected + availability_zone_max_count: 1 + demo_artifacts_version: 1.2.0 + key_name: onap_key_LnHa + repo_url_artifacts: https://nexus.onap.org/content/groups/staging + install_script_version: 1.2.0-SNAPSHOT + cloud_env: openstack + vFWCL_vFWSNK-vf 0: + type: org.openecomp.resource.vf.VfwclVfwsnkVf + metadata: + invariantUUID: f37bfc8b-5d9f-4471-a799-1508f0d8fab5 + UUID: 1fb229a7-0bd9-4358-9072-60501374bcc2 + customizationUUID: 2b98b1f4-498c-4bc9-ab6c-b76af29cb981 + version: '1.0' + name: vFWCL_vFWSNK-vf + description: vFirewall et vSink functions + type: VF + category: Application L4+ + subcategory: Firewall + resourceVendor: Generic-Vendor + resourceVendorRelease: '1.0' + resourceVendorModelNumber: '' + properties: + vf_module_id: vFirewallCL + repo_url_blob: https://nexus.onap.org/content/sites/raw + vfw_private_ip_1: 192.168.20.100 + unprotected_private_subnet_id: zdfw1fwl01_unprotected_sub + public_net_id: 715a1ca1-cbc6-4d00-84bb-0f8667a748ce + vfw_private_ip_0: 192.168.10.100 + onap_private_subnet_id: 51a5a838-7318-464d-858a-974bef8d49e3 + vfw_private_ip_2: 10.4.2.201 + vfw_name_0: zdfw1fwl01fwl01 + onap_private_net_cidr: 10.4.2.0/24 + image_name: ubuntu-14.04-daily + flavor_name: onap.medium + dcae_collector_ip: 10.4.2.38 + vnf_id: vFirewall_demo_app + dcae_collector_port: '8080' + protected_private_subnet_id: zdfw1fwl01_protected_sub + vsn_private_ip_0: 192.168.20.250 + vsn_private_ip_1: 10.4.2.202 + vpg_private_ip_0: 192.168.10.200 + protected_private_net_cidr: 192.168.20.0/24 + unprotected_private_net_cidr: 192.168.10.0/24 + nf_naming: + ecomp_generated_naming: true + vsn_name_0: zdfw1fwl01snk01 + onap_private_net_id: 715a1ca1-cbc6-4d00-84bb-0f8667a748ce + unprotected_private_net_id: zdfw1fwl01_unprotected + availability_zone_max_count: 1 + demo_artifacts_version: 1.2.0 + key_name: onap_key_LnHa + repo_url_artifacts: https://nexus.onap.org/content/groups/staging + install_script_version: 1.2.0-SNAPSHOT + protected_private_net_id: zdfw1fwl01_protected + cloud_env: openstack + capabilities: + network.outgoing.bytes_vfw_vfw_private_0_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + end_point: + properties: + protocol: tcp + initiator: source + network_name: PRIVATE + secure: false + network.outgoing.bytes.rate_vfw_vfw_private_2_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + memory.usage_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.incoming.packets_vfw_vfw_private_0_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.device.read.requests_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + instance_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.latency_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + memory.resident_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.outpoing.packets_vfw_vfw_private_0_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.outgoing.bytes.rate_vfw_vfw_private_1_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.allocation_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.write.requests.rate_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.incoming.bytes.rate_vfw_vfw_private_1_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.incoming.bytes_vfw_vfw_private_2_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.device.iops_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.incoming.packets.rate_vfw_vfw_private_0_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + scalable_vfw: + properties: + min_instances: 1 + max_instances: 1 + network.incoming.packets_vfw_vfw_private_1_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.device.usage_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.outgoing.bytes_vfw_vfw_private_1_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.usage_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.ephemeral.size_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.write.bytes.rate_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.outpoing.packets_vfw_vfw_private_2_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + cpu.delta_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.device.write.bytes.rate_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.read.bytes_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.capacity_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.write.bytes_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.device.allocation_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.device.read.requests.rate_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.read.requests_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + vcpus_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.incoming.bytes_vfw_vfw_private_0_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.device.read.bytes_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.device.read.bytes.rate_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + endpoint_vfw: + properties: + secure: true + network.outgoing.packets.rate_vfw_vfw_private_1_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.outgoing.packets.rate_vfw_vfw_private_0_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.read.bytes.rate_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.incoming.packets.rate_vfw_vfw_private_1_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.incoming.bytes.rate_vfw_vfw_private_0_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.write.requests_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + memory_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.device.write.requests_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.root.size_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.outgoing.packets.rate_vfw_vfw_private_2_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.incoming.packets.rate_vfw_vfw_private_2_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.device.capacity_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.outgoing.bytes_vfw_vfw_private_2_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.outpoing.packets_vfw_vfw_private_1_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.incoming.packets_vfw_vfw_private_2_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.device.write.requests.rate_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.incoming.bytes_vfw_vfw_private_1_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.device.latency_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + cpu_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.iops_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.incoming.bytes.rate_vfw_vfw_private_2_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + disk.device.write.bytes_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + network.outgoing.bytes.rate_vfw_vfw_private_0_port: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + cpu_util_vfw: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + groups: + vfwcl_vfwsnkvf0..VfwclVfwsnkVf..base_vfw..module-0: + type: org.openecomp.groups.VfModule + metadata: + vfModuleModelName: VfwclVfwsnkVf..base_vfw..module-0 + vfModuleModelInvariantUUID: 0cf42138-9514-4667-af27-19e04eae09ea + vfModuleModelUUID: fd867a01-04a0-4733-a0dd-38293bcb5b1e + vfModuleModelVersion: '1' + vfModuleModelCustomizationUUID: 14f54459-417e-4608-adf3-e173b25bf26c + properties: + min_vf_module_instances: 1 + vf_module_label: base_vfw + max_vf_module_instances: 1 + vfc_list: + vf_module_type: Base + vf_module_description: + initial_count: 1 + volume_group: false + availability_zone_count: + vfwcl_vpkgvf0..VfwclVpkgVf..base_vpkg..module-0: + type: org.openecomp.groups.VfModule + metadata: + vfModuleModelName: VfwclVpkgVf..base_vpkg..module-0 + vfModuleModelInvariantUUID: 8154de96-1f48-42ec-b8a0-9d2eb60cd6b3 + vfModuleModelUUID: d44761cf-c1e6-429f-8cbd-bd9923827965 + vfModuleModelVersion: '1' + vfModuleModelCustomizationUUID: a567f92e-d8ac-4547-bd7e-958419df6b86 + properties: + min_vf_module_instances: 1 + vf_module_label: base_vpkg + max_vf_module_instances: 1 + vfc_list: + vf_module_type: Base + vf_module_description: + initial_count: 1 + volume_group: false + availability_zone_count: + substitution_mappings: + node_type: org.openecomp.service.VfwService + capabilities: + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.device.write.bytes_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.device.write.bytes_vpg + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.device.write.requests.rate_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.device.write.requests.rate_vfw + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.latency_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.latency_vfw + vfwcl_vpkgvf0.vpg_vpg_private_0_port.abstract_vpg.network.incoming.bytes_vpg_vpg_private_0_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_0_port.abstract_vpg.network.incoming.bytes_vpg_vpg_private_0_port + vfwcl_vpkgvf0.vpg.abstract_vpg.cpu_util_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.cpu_util_vpg + vfwcl_vfwsnkvf0.protected_private_network.attachment: + - vfwcl_vfwsnkvf0 + - protected_private_network.attachment + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.device.iops_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.device.iops_vfw + vfwcl_vpkgvf0.vpg_vpg_private_0_port.abstract_vpg.binding_vpg_vpg_private_0_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_0_port.abstract_vpg.binding_vpg_vpg_private_0_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.os_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.os_vsn + vfwcl_vpkgvf0.vpg.abstract_vpg.scalable_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.scalable_vpg + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.device.write.requests_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.device.write.requests_vfw + vfwcl_vfwsnkvf0.vfw_vfw_private_2_port.abstract_vfw.network.incoming.bytes.rate_vfw_vfw_private_2_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_2_port.abstract_vfw.network.incoming.bytes.rate_vfw_vfw_private_2_port + vfwcl_vfwsnkvf0.vfw_vfw_private_2_port.abstract_vfw.network.incoming.bytes_vfw_vfw_private_2_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_2_port.abstract_vfw.network.incoming.bytes_vfw_vfw_private_2_port + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.device.read.requests.rate_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.device.read.requests.rate_vpg + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.capacity_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.capacity_vfw + vfwcl_vpkgvf0.vpg_vpg_private_0_port.abstract_vpg.network.incoming.packets_vpg_vpg_private_0_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_0_port.abstract_vpg.network.incoming.packets_vpg_vpg_private_0_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.root.size_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.root.size_vfw + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.device.write.bytes_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.device.write.bytes_vsn + vfwcl_vfwsnkvf0.vsn.abstract_vsn.cpu_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.cpu_vsn + vfwcl_vfwsnkvf0.vfw.abstract_vfw.vcpus_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.vcpus_vfw + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.device.iops_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.device.iops_vpg + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.device.usage_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.device.usage_vfw + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.device.capacity_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.device.capacity_vpg + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.read.requests_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.read.requests_vsn + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.ephemeral.size_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.ephemeral.size_vpg + vfwcl_vfwsnkvf0.vfw_vfw_private_1_port.abstract_vfw.binding_vfw_vfw_private_1_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_1_port.abstract_vfw.binding_vfw_vfw_private_1_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.device.write.requests_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.device.write.requests_vsn + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.device.write.requests_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.device.write.requests_vpg + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.device.read.bytes_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.device.read.bytes_vfw + vfwcl_vpkgvf0.vpg_vpg_private_0_port.abstract_vpg.network.outgoing.bytes_vpg_vpg_private_0_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_0_port.abstract_vpg.network.outgoing.bytes_vpg_vpg_private_0_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.read.bytes.rate_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.read.bytes.rate_vfw + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.device.usage_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.device.usage_vsn + vfwcl_vfwsnkvf0.vsn.abstract_vsn.feature_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.feature_vsn + vfwcl_vfwsnkvf0.vsn_vsn_private_0_port.abstract_vsn.binding_vsn_vsn_private_0_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_0_port.abstract_vsn.binding_vsn_vsn_private_0_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.write.requests.rate_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.write.requests.rate_vsn + vfwcl_vfwsnkvf0.vsn_vsn_private_1_port.abstract_vsn.network.outgoing.packets.rate_vsn_vsn_private_1_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_1_port.abstract_vsn.network.outgoing.packets.rate_vsn_vsn_private_1_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.host_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.host_vfw + vfwcl_vfwsnkvf0.unprotected_private_network.feature: + - vfwcl_vfwsnkvf0 + - unprotected_private_network.feature + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.device.iops_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.device.iops_vsn + vfwcl_vpkgvf0.vpg.abstract_vpg.host_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.host_vpg + vfwcl_vfwsnkvf0.vfw_vfw_private_2_port.abstract_vfw.network.outgoing.bytes_vfw_vfw_private_2_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_2_port.abstract_vfw.network.outgoing.bytes_vfw_vfw_private_2_port + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.device.latency_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.device.latency_vpg + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.device.read.bytes_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.device.read.bytes_vpg + vfwcl_vfwsnkvf0.vsn.abstract_vsn.memory_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.memory_vsn + vfwcl_vpkgvf0.vpg_vpg_private_1_port.abstract_vpg.network.outgoing.packets.rate_vpg_vpg_private_1_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_1_port.abstract_vpg.network.outgoing.packets.rate_vpg_vpg_private_1_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.instance_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.instance_vfw + vfwcl_vfwsnkvf0.unprotected_private_network.end_point: + - vfwcl_vfwsnkvf0 + - unprotected_private_network.end_point + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.iops_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.iops_vfw + vfwcl_vfwsnkvf0.vfw.abstract_vfw.memory_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.memory_vfw + vfwcl_vfwsnkvf0.vsn_vsn_private_1_port.abstract_vsn.network.incoming.bytes.rate_vsn_vsn_private_1_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_1_port.abstract_vsn.network.incoming.bytes.rate_vsn_vsn_private_1_port + vfwcl_vfwsnkvf0.vsn_vsn_private_1_port.abstract_vsn.network.outpoing.packets_vsn_vsn_private_1_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_1_port.abstract_vsn.network.outpoing.packets_vsn_vsn_private_1_port + vfwcl_vpkgvf0.vpg_vpg_private_0_port.abstract_vpg.network.outpoing.packets_vpg_vpg_private_0_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_0_port.abstract_vpg.network.outpoing.packets_vpg_vpg_private_0_port + vfwcl_vfwsnkvf0.vfw_vfw_private_2_port.abstract_vfw.network.outgoing.packets.rate_vfw_vfw_private_2_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_2_port.abstract_vfw.network.outgoing.packets.rate_vfw_vfw_private_2_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.device.allocation_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.device.allocation_vfw + vfwcl_vfwsnkvf0.vfw_vfw_private_0_port.abstract_vfw.network.outgoing.bytes_vfw_vfw_private_0_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_0_port.abstract_vfw.network.outgoing.bytes_vfw_vfw_private_0_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.write.requests_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.write.requests_vsn + vfwcl_vpkgvf0.vpg.abstract_vpg.vcpus_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.vcpus_vpg + vfwcl_vpkgvf0.vpg_vpg_private_1_port.abstract_vpg.network.outgoing.bytes.rate_vpg_vpg_private_1_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_1_port.abstract_vpg.network.outgoing.bytes.rate_vpg_vpg_private_1_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.write.bytes_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.write.bytes_vsn + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.device.write.bytes.rate_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.device.write.bytes.rate_vpg + vfwcl_vfwsnkvf0.vfw_vfw_private_2_port.abstract_vfw.network.outpoing.packets_vfw_vfw_private_2_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_2_port.abstract_vfw.network.outpoing.packets_vfw_vfw_private_2_port + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.write.requests.rate_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.write.requests.rate_vpg + vfwcl_vfwsnkvf0.vfw_vfw_private_0_port.abstract_vfw.attachment_vfw_vfw_private_0_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_0_port.abstract_vfw.attachment_vfw_vfw_private_0_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.latency_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.latency_vsn + vfwcl_vpkgvf0.vpg.abstract_vpg.cpu_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.cpu_vpg + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.root.size_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.root.size_vpg + vfwcl_vfwsnkvf0.vsn.abstract_vsn.host_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.host_vsn + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.capacity_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.capacity_vpg + vfwcl_vfwsnkvf0.vfw.abstract_vfw.memory.resident_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.memory.resident_vfw + vfwcl_vpkgvf0.vpg.abstract_vpg.memory.resident_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.memory.resident_vpg + vfwcl_vpkgvf0.vpg.abstract_vpg.binding_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.binding_vpg + vfwcl_vfwsnkvf0.vfw.abstract_vfw.cpu_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.cpu_vfw + vfwcl_vfwsnkvf0.vsn_vsn_private_1_port.abstract_vsn.attachment_vsn_vsn_private_1_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_1_port.abstract_vsn.attachment_vsn_vsn_private_1_port + vfwcl_vpkgvf0.vpg_vpg_private_0_port.abstract_vpg.attachment_vpg_vpg_private_0_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_0_port.abstract_vpg.attachment_vpg_vpg_private_0_port + vfwcl_vfwsnkvf0.vfw_vfw_private_1_port.abstract_vfw.network.outgoing.bytes_vfw_vfw_private_1_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_1_port.abstract_vfw.network.outgoing.bytes_vfw_vfw_private_1_port + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.read.requests_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.read.requests_vpg + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.root.size_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.root.size_vsn + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.read.requests_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.read.requests_vfw + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.device.read.bytes.rate_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.device.read.bytes.rate_vfw + vfwcl_vfwsnkvf0.vfw_vfw_private_2_port.abstract_vfw.network.incoming.packets_vfw_vfw_private_2_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_2_port.abstract_vfw.network.incoming.packets_vfw_vfw_private_2_port + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.device.read.bytes.rate_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.device.read.bytes.rate_vpg + vfwcl_vfwsnkvf0.vsn_vsn_private_0_port.abstract_vsn.network.incoming.bytes_vsn_vsn_private_0_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_0_port.abstract_vsn.network.incoming.bytes_vsn_vsn_private_0_port + vfwcl_vfwsnkvf0.vsn_vsn_private_0_port.abstract_vsn.network.outgoing.packets.rate_vsn_vsn_private_0_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_0_port.abstract_vsn.network.outgoing.packets.rate_vsn_vsn_private_0_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.device.read.bytes.rate_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.device.read.bytes.rate_vsn + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.read.bytes_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.read.bytes_vsn + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.read.bytes_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.read.bytes_vpg + vfwcl_vpkgvf0.vpg_vpg_private_0_port.abstract_vpg.feature_vpg_vpg_private_0_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_0_port.abstract_vpg.feature_vpg_vpg_private_0_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.read.bytes_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.read.bytes_vfw + vfwcl_vfwsnkvf0.vfw_vfw_private_0_port.abstract_vfw.network.incoming.packets_vfw_vfw_private_0_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_0_port.abstract_vfw.network.incoming.packets_vfw_vfw_private_0_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.device.latency_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.device.latency_vfw + vfwcl_vfwsnkvf0.vfw.abstract_vfw.endpoint_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.endpoint_vfw + vfwcl_vfwsnkvf0.vfw_vfw_private_0_port.abstract_vfw.feature_vfw_vfw_private_0_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_0_port.abstract_vfw.feature_vfw_vfw_private_0_port + vfwcl_vpkgvf0.vpg_vpg_private_0_port.abstract_vpg.network.outgoing.bytes.rate_vpg_vpg_private_0_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_0_port.abstract_vpg.network.outgoing.bytes.rate_vpg_vpg_private_0_port + vfwcl_vfwsnkvf0.vsn_vsn_private_1_port.abstract_vsn.network.incoming.packets.rate_vsn_vsn_private_1_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_1_port.abstract_vsn.network.incoming.packets.rate_vsn_vsn_private_1_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.device.capacity_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.device.capacity_vfw + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.write.requests.rate_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.write.requests.rate_vfw + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.device.allocation_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.device.allocation_vsn + vfwcl_vfwsnkvf0.vfw_vfw_private_1_port.abstract_vfw.network.incoming.packets_vfw_vfw_private_1_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_1_port.abstract_vfw.network.incoming.packets_vfw_vfw_private_1_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.usage_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.usage_vfw + vfwcl_vpkgvf0.vpg_vpg_private_0_port.abstract_vpg.network.outgoing.packets.rate_vpg_vpg_private_0_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_0_port.abstract_vpg.network.outgoing.packets.rate_vpg_vpg_private_0_port + vfwcl_vfwsnkvf0.vfw_vfw_private_2_port.abstract_vfw.network.incoming.packets.rate_vfw_vfw_private_2_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_2_port.abstract_vfw.network.incoming.packets.rate_vfw_vfw_private_2_port + vfwcl_vpkgvf0.vpg.abstract_vpg.memory.usage_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.memory.usage_vpg + vfwcl_vfwsnkvf0.vfw_vfw_private_1_port.abstract_vfw.feature_vfw_vfw_private_1_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_1_port.abstract_vfw.feature_vfw_vfw_private_1_port + vfwcl_vfwsnkvf0.protected_private_network.feature: + - vfwcl_vfwsnkvf0 + - protected_private_network.feature + vfwcl_vfwsnkvf0.vfw_vfw_private_2_port.abstract_vfw.attachment_vfw_vfw_private_2_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_2_port.abstract_vfw.attachment_vfw_vfw_private_2_port + vfwcl_vpkgvf0.vpg_vpg_private_0_port.abstract_vpg.network.incoming.bytes.rate_vpg_vpg_private_0_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_0_port.abstract_vpg.network.incoming.bytes.rate_vpg_vpg_private_0_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.memory.usage_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.memory.usage_vsn + vfwcl_vpkgvf0.vpg_vpg_private_1_port.abstract_vpg.network.incoming.packets.rate_vpg_vpg_private_1_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_1_port.abstract_vpg.network.incoming.packets.rate_vpg_vpg_private_1_port + vfwcl_vfwsnkvf0.vfw_vfw_private_1_port.abstract_vfw.network.outgoing.packets.rate_vfw_vfw_private_1_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_1_port.abstract_vfw.network.outgoing.packets.rate_vfw_vfw_private_1_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.device.write.bytes_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.device.write.bytes_vfw + vfwcl_vfwsnkvf0.vfw.abstract_vfw.scalable_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.scalable_vfw + vfwcl_vfwsnkvf0.vfw.abstract_vfw.memory.usage_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.memory.usage_vfw + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.usage_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.usage_vpg + vfwcl_vfwsnkvf0.vfw_vfw_private_1_port.abstract_vfw.network.incoming.bytes_vfw_vfw_private_1_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_1_port.abstract_vfw.network.incoming.bytes_vfw_vfw_private_1_port + vfwcl_vpkgvf0.vpg_vpg_private_1_port.abstract_vpg.network.incoming.bytes.rate_vpg_vpg_private_1_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_1_port.abstract_vpg.network.incoming.bytes.rate_vpg_vpg_private_1_port + vfwcl_vfwsnkvf0.vsn_vsn_private_1_port.abstract_vsn.network.incoming.bytes_vsn_vsn_private_1_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_1_port.abstract_vsn.network.incoming.bytes_vsn_vsn_private_1_port + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.device.read.requests_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.device.read.requests_vpg + vfwcl_vpkgvf0.vpg.abstract_vpg.feature_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.feature_vpg + vfwcl_vfwsnkvf0.vfw.abstract_vfw.cpu_util_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.cpu_util_vfw + vfwcl_vfwsnkvf0.vfw_vfw_private_2_port.abstract_vfw.feature_vfw_vfw_private_2_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_2_port.abstract_vfw.feature_vfw_vfw_private_2_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.device.read.requests.rate_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.device.read.requests.rate_vsn + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.ephemeral.size_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.ephemeral.size_vsn + vfwcl_vfwsnkvf0.vfw.abstract_vfw.binding_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.binding_vfw + vfwcl_vfwsnkvf0.vsn_vsn_private_0_port.abstract_vsn.attachment_vsn_vsn_private_0_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_0_port.abstract_vsn.attachment_vsn_vsn_private_0_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.allocation_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.allocation_vfw + vfwcl_vfwsnkvf0.vsn_vsn_private_1_port.abstract_vsn.binding_vsn_vsn_private_1_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_1_port.abstract_vsn.binding_vsn_vsn_private_1_port + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.read.bytes.rate_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.read.bytes.rate_vpg + vfwcl_vfwsnkvf0.vsn.abstract_vsn.memory.resident_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.memory.resident_vsn + vfwcl_vpkgvf0.vpg_vpg_private_1_port.abstract_vpg.feature_vpg_vpg_private_1_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_1_port.abstract_vpg.feature_vpg_vpg_private_1_port + vfwcl_vpkgvf0.vpg_vpg_private_1_port.abstract_vpg.network.incoming.bytes_vpg_vpg_private_1_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_1_port.abstract_vpg.network.incoming.bytes_vpg_vpg_private_1_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.binding_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.binding_vsn + vfwcl_vfwsnkvf0.vfw.abstract_vfw.cpu.delta_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.cpu.delta_vfw + vfwcl_vpkgvf0.vpg.abstract_vpg.os_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.os_vpg + vfwcl_vfwsnkvf0.vsn_vsn_private_0_port.abstract_vsn.network.outpoing.packets_vsn_vsn_private_0_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_0_port.abstract_vsn.network.outpoing.packets_vsn_vsn_private_0_port + vfwcl_vpkgvf0.vpg_vpg_private_1_port.abstract_vpg.network.incoming.packets_vpg_vpg_private_1_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_1_port.abstract_vpg.network.incoming.packets_vpg_vpg_private_1_port + vfwcl_vfwsnkvf0.vsn_vsn_private_0_port.abstract_vsn.network.incoming.packets.rate_vsn_vsn_private_0_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_0_port.abstract_vsn.network.incoming.packets.rate_vsn_vsn_private_0_port + vfwcl_vfwsnkvf0.vsn_vsn_private_0_port.abstract_vsn.network.incoming.bytes.rate_vsn_vsn_private_0_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_0_port.abstract_vsn.network.incoming.bytes.rate_vsn_vsn_private_0_port + vfwcl_vfwsnkvf0.vsn_vsn_private_1_port.abstract_vsn.network.outgoing.bytes_vsn_vsn_private_1_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_1_port.abstract_vsn.network.outgoing.bytes_vsn_vsn_private_1_port + vfwcl_vpkgvf0.vpg_vpg_private_1_port.abstract_vpg.network.outpoing.packets_vpg_vpg_private_1_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_1_port.abstract_vpg.network.outpoing.packets_vpg_vpg_private_1_port + vfwcl_vpkgvf0.vpg.abstract_vpg.cpu.delta_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.cpu.delta_vpg + vfwcl_vfwsnkvf0.vfw_vfw_private_0_port.abstract_vfw.binding_vfw_vfw_private_0_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_0_port.abstract_vfw.binding_vfw_vfw_private_0_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.os_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.os_vfw + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.latency_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.latency_vpg + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.read.bytes.rate_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.read.bytes.rate_vsn + vfwcl_vfwsnkvf0.vsn_vsn_private_1_port.abstract_vsn.network.outgoing.bytes.rate_vsn_vsn_private_1_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_1_port.abstract_vsn.network.outgoing.bytes.rate_vsn_vsn_private_1_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.device.write.bytes.rate_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.device.write.bytes.rate_vfw + vfwcl_vfwsnkvf0.vfw_vfw_private_1_port.abstract_vfw.network.incoming.packets.rate_vfw_vfw_private_1_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_1_port.abstract_vfw.network.incoming.packets.rate_vfw_vfw_private_1_port + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.device.allocation_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.device.allocation_vpg + vfwcl_vpkgvf0.vpg_vpg_private_1_port.abstract_vpg.network.outgoing.bytes_vpg_vpg_private_1_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_1_port.abstract_vpg.network.outgoing.bytes_vpg_vpg_private_1_port + vfwcl_vpkgvf0.vpg_vpg_private_0_port.abstract_vpg.network.incoming.packets.rate_vpg_vpg_private_0_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_0_port.abstract_vpg.network.incoming.packets.rate_vpg_vpg_private_0_port + vfwcl_vfwsnkvf0.vfw_vfw_private_0_port.abstract_vfw.network.outgoing.packets.rate_vfw_vfw_private_0_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_0_port.abstract_vfw.network.outgoing.packets.rate_vfw_vfw_private_0_port + vfwcl_vpkgvf0.vpg.abstract_vpg.instance_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.instance_vpg + vfwcl_vfwsnkvf0.vsn.abstract_vsn.vcpus_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.vcpus_vsn + vfwcl_vfwsnkvf0.vsn_vsn_private_0_port.abstract_vsn.network.outgoing.bytes_vsn_vsn_private_0_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_0_port.abstract_vsn.network.outgoing.bytes_vsn_vsn_private_0_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.endpoint_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.endpoint_vsn + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.usage_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.usage_vsn + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.write.bytes.rate_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.write.bytes.rate_vfw + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.device.latency_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.device.latency_vsn + vfwcl_vfwsnkvf0.vfw_vfw_private_2_port.abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_private_2_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_2_port.abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_private_2_port + vfwcl_vpkgvf0.vpg.abstract_vpg.memory_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.memory_vpg + vfwcl_vfwsnkvf0.vsn.abstract_vsn.cpu.delta_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.cpu.delta_vsn + vfwcl_vfwsnkvf0.unprotected_private_network.attachment: + - vfwcl_vfwsnkvf0 + - unprotected_private_network.attachment + vfwcl_vfwsnkvf0.vfw_vfw_private_1_port.abstract_vfw.network.outpoing.packets_vfw_vfw_private_1_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_1_port.abstract_vfw.network.outpoing.packets_vfw_vfw_private_1_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.ephemeral.size_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.ephemeral.size_vfw + vfwcl_vfwsnkvf0.vfw_vfw_private_1_port.abstract_vfw.network.incoming.bytes.rate_vfw_vfw_private_1_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_1_port.abstract_vfw.network.incoming.bytes.rate_vfw_vfw_private_1_port + vfwcl_vfwsnkvf0.vfw_vfw_private_2_port.abstract_vfw.binding_vfw_vfw_private_2_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_2_port.abstract_vfw.binding_vfw_vfw_private_2_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.iops_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.iops_vsn + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.device.capacity_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.device.capacity_vsn + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.device.write.requests.rate_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.device.write.requests.rate_vpg + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.write.bytes.rate_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.write.bytes.rate_vpg + vfwcl_vfwsnkvf0.vfw_vfw_private_0_port.abstract_vfw.network.outpoing.packets_vfw_vfw_private_0_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_0_port.abstract_vfw.network.outpoing.packets_vfw_vfw_private_0_port + vfwcl_vpkgvf0.vpg_vpg_private_1_port.abstract_vpg.attachment_vpg_vpg_private_1_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_1_port.abstract_vpg.attachment_vpg_vpg_private_1_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.device.write.requests.rate_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.device.write.requests.rate_vsn + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.write.requests_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.write.requests_vpg + vfwcl_vfwsnkvf0.protected_private_network.link: + - vfwcl_vfwsnkvf0 + - protected_private_network.link + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.device.usage_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.device.usage_vpg + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.device.write.bytes.rate_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.device.write.bytes.rate_vsn + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.write.bytes.rate_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.write.bytes.rate_vsn + vfwcl_vfwsnkvf0.vsn_vsn_private_0_port.abstract_vsn.network.outgoing.bytes.rate_vsn_vsn_private_0_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_0_port.abstract_vsn.network.outgoing.bytes.rate_vsn_vsn_private_0_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.device.read.requests.rate_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.device.read.requests.rate_vfw + vfwcl_vfwsnkvf0.vfw.abstract_vfw.feature_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.feature_vfw + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.allocation_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.allocation_vsn + vfwcl_vfwsnkvf0.vsn.abstract_vsn.instance_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.instance_vsn + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.device.read.requests_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.device.read.requests_vfw + vfwcl_vfwsnkvf0.vsn.abstract_vsn.cpu_util_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.cpu_util_vsn + vfwcl_vfwsnkvf0.vsn_vsn_private_1_port.abstract_vsn.feature_vsn_vsn_private_1_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_1_port.abstract_vsn.feature_vsn_vsn_private_1_port + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.allocation_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.allocation_vpg + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.capacity_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.capacity_vsn + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.write.bytes_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.write.bytes_vpg + vfwcl_vfwsnkvf0.vfw_vfw_private_0_port.abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_private_0_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_0_port.abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_private_0_port + vfwcl_vfwsnkvf0.vsn_vsn_private_1_port.abstract_vsn.network.incoming.packets_vsn_vsn_private_1_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_1_port.abstract_vsn.network.incoming.packets_vsn_vsn_private_1_port + vfwcl_vpkgvf0.vpg.abstract_vpg.endpoint_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.endpoint_vpg + vfwcl_vfwsnkvf0.unprotected_private_network.link: + - vfwcl_vfwsnkvf0 + - unprotected_private_network.link + vfwcl_vpkgvf0.vpg.abstract_vpg.disk.iops_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.disk.iops_vpg + vfwcl_vfwsnkvf0.vfw_vfw_private_0_port.abstract_vfw.network.incoming.packets.rate_vfw_vfw_private_0_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_0_port.abstract_vfw.network.incoming.packets.rate_vfw_vfw_private_0_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.scalable_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.scalable_vsn + vfwcl_vfwsnkvf0.vfw_vfw_private_1_port.abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_private_1_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_1_port.abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_private_1_port + vfwcl_vfwsnkvf0.vfw_vfw_private_1_port.abstract_vfw.attachment_vfw_vfw_private_1_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_1_port.abstract_vfw.attachment_vfw_vfw_private_1_port + vfwcl_vfwsnkvf0.vsn_vsn_private_0_port.abstract_vsn.feature_vsn_vsn_private_0_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_0_port.abstract_vsn.feature_vsn_vsn_private_0_port + vfwcl_vfwsnkvf0.vsn_vsn_private_0_port.abstract_vsn.network.incoming.packets_vsn_vsn_private_0_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_0_port.abstract_vsn.network.incoming.packets_vsn_vsn_private_0_port + vfwcl_vfwsnkvf0.vfw_vfw_private_0_port.abstract_vfw.network.incoming.bytes.rate_vfw_vfw_private_0_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_0_port.abstract_vfw.network.incoming.bytes.rate_vfw_vfw_private_0_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.device.read.bytes_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.device.read.bytes_vsn + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.write.requests_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.write.requests_vfw + vfwcl_vfwsnkvf0.protected_private_network.end_point: + - vfwcl_vfwsnkvf0 + - protected_private_network.end_point + vfwcl_vfwsnkvf0.vfw_vfw_private_0_port.abstract_vfw.network.incoming.bytes_vfw_vfw_private_0_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_0_port.abstract_vfw.network.incoming.bytes_vfw_vfw_private_0_port + vfwcl_vpkgvf0.vpg_vpg_private_1_port.abstract_vpg.binding_vpg_vpg_private_1_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_1_port.abstract_vpg.binding_vpg_vpg_private_1_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.disk.write.bytes_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.disk.write.bytes_vfw + vfwcl_vfwsnkvf0.vsn.abstract_vsn.disk.device.read.requests_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.disk.device.read.requests_vsn + requirements: + vfwcl_vfwsnkvf0.vfw.abstract_vfw.local_storage_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.local_storage_vfw + vfwcl_vfwsnkvf0.protected_private_network.dependency: + - vfwcl_vfwsnkvf0 + - protected_private_network.dependency + vfwcl_vfwsnkvf0.unprotected_private_network.dependency: + - vfwcl_vfwsnkvf0 + - unprotected_private_network.dependency + vfwcl_vfwsnkvf0.vsn.abstract_vsn.dependency_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.dependency_vsn + vfwcl_vpkgvf0.vpg.abstract_vpg.local_storage_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.local_storage_vpg + vfwcl_vpkgvf0.vpg_vpg_private_1_port.abstract_vpg.link_vpg_vpg_private_1_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_1_port.abstract_vpg.link_vpg_vpg_private_1_port + vfwcl_vpkgvf0.vpg_vpg_private_1_port.abstract_vpg.dependency_vpg_vpg_private_1_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_1_port.abstract_vpg.dependency_vpg_vpg_private_1_port + vfwcl_vfwsnkvf0.vfw_vfw_private_2_port.abstract_vfw.link_vfw_vfw_private_2_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_2_port.abstract_vfw.link_vfw_vfw_private_2_port + vfwcl_vfwsnkvf0.vfw.abstract_vfw.dependency_vfw: + - vfwcl_vfwsnkvf0 + - vfw.abstract_vfw.dependency_vfw + vfwcl_vfwsnkvf0.vsn_vsn_private_0_port.abstract_vsn.dependency_vsn_vsn_private_0_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_0_port.abstract_vsn.dependency_vsn_vsn_private_0_port + vfwcl_vfwsnkvf0.vfw_vfw_private_0_port.abstract_vfw.link_vfw_vfw_private_0_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_0_port.abstract_vfw.link_vfw_vfw_private_0_port + vfwcl_vfwsnkvf0.vsn_vsn_private_1_port.abstract_vsn.link_vsn_vsn_private_1_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_1_port.abstract_vsn.link_vsn_vsn_private_1_port + vfwcl_vfwsnkvf0.vfw_vfw_private_2_port.abstract_vfw.dependency_vfw_vfw_private_2_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_2_port.abstract_vfw.dependency_vfw_vfw_private_2_port + vfwcl_vfwsnkvf0.vfw_vfw_private_0_port.abstract_vfw.dependency_vfw_vfw_private_0_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_0_port.abstract_vfw.dependency_vfw_vfw_private_0_port + vfwcl_vpkgvf0.vpg_vpg_private_0_port.abstract_vpg.dependency_vpg_vpg_private_0_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_0_port.abstract_vpg.dependency_vpg_vpg_private_0_port + vfwcl_vfwsnkvf0.vsn.abstract_vsn.local_storage_vsn: + - vfwcl_vfwsnkvf0 + - vsn.abstract_vsn.local_storage_vsn + vfwcl_vfwsnkvf0.vfw_vfw_private_1_port.abstract_vfw.link_vfw_vfw_private_1_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_1_port.abstract_vfw.link_vfw_vfw_private_1_port + vfwcl_vfwsnkvf0.vsn_vsn_private_0_port.abstract_vsn.link_vsn_vsn_private_0_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_0_port.abstract_vsn.link_vsn_vsn_private_0_port + vfwcl_vpkgvf0.vpg_vpg_private_0_port.abstract_vpg.link_vpg_vpg_private_0_port: + - vfwcl_vpkgvf0 + - vpg_vpg_private_0_port.abstract_vpg.link_vpg_vpg_private_0_port + vfwcl_vfwsnkvf0.vsn_vsn_private_1_port.abstract_vsn.dependency_vsn_vsn_private_1_port: + - vfwcl_vfwsnkvf0 + - vsn_vsn_private_1_port.abstract_vsn.dependency_vsn_vsn_private_1_port + vfwcl_vfwsnkvf0.vfw_vfw_private_1_port.abstract_vfw.dependency_vfw_vfw_private_1_port: + - vfwcl_vfwsnkvf0 + - vfw_vfw_private_1_port.abstract_vfw.dependency_vfw_vfw_private_1_port + vfwcl_vpkgvf0.vpg.abstract_vpg.dependency_vpg: + - vfwcl_vpkgvf0 + - vpg.abstract_vpg.dependency_vpg diff --git a/tests/data/service-TestPnfVsp-template.yml b/tests/data/service-TestPnfVsp-template.yml new file mode 100644 index 0000000..b1ffa37 --- /dev/null +++ b/tests/data/service-TestPnfVsp-template.yml @@ -0,0 +1,146 @@ +tosca_definitions_version: tosca_simple_yaml_1_1 +metadata: + invariantUUID: 25871897-1960-445b-8c4f-78111fab1e32 + UUID: fda7e33f-775d-496c-a2a9-76bb8814fed7 + name: test_service_pnf_vsp + description: service + type: Service + category: Network Service + serviceType: '' + serviceRole: '' + instantiationType: A-la-carte + serviceEcompNaming: true + ecompGeneratedNaming: true + namingPolicy: '' + environmentContext: General_Revenue-Bearing + serviceFunction: '' +imports: +- nodes: + file: nodes.yml +- datatypes: + file: data.yml +- capabilities: + file: capabilities.yml +- relationships: + file: relationships.yml +- groups: + file: groups.yml +- policies: + file: policies.yml +- annotations: + file: annotations.yml +- service-test_service_pnf_vsp-interface: + file: service-TestServicePnfVsp-template-interface.yml +- resource-test_pnf_vsp: + file: resource-TestPnfVsp-template.yml +- resource-test_pnf_vsp-interface: + file: resource-TestPnfVsp-template-interface.yml +topology_template: + inputs: + skip_post_instantiation_configuration: + default: true + type: boolean + required: false + controller_actor: + default: SO-REF-DATA + type: string + required: false + cds_model_version: + type: string + required: false + cds_model_name: + type: string + required: false + node_templates: + test_pnf_vsp 0: + type: org.openecomp.resource.pnf.TestPnfVsp + metadata: + invariantUUID: d52250cb-f4e4-4e16-880b-de0c3a9a3273 + UUID: 081bc1b2-0283-44f6-80a2-f0f3672fec05 + customizationUUID: dc0627b2-cdb6-40a7-8510-3ee70d98ce66 + version: '1.0' + name: test_pnf_vsp + description: PNF + type: PNF + category: Generic + subcategory: Abstract + resourceVendor: test-vendor + resourceVendorRelease: '1.0' + resourceVendorModelNumber: '' + properties: + skip_post_instantiation_configuration: false + controller_actor: CDS + sdnc_model_version: 2.0.0 + sdnc_artifact_name: vnf + sdnc_model_name: vSIM_CNF_CDS + capabilities: + pnfextcp_1.network.incoming.packets.rate: + properties: + name: network.incoming.packets.rate + pnfextcp_1.network.outgoing.packets.rate: + properties: + name: network.outgoing.packets.rate + pnfextcp_1.network.incoming.bytes: + properties: + name: network.incoming.bytes + pnfextcp_1.network.outgoing.bytes.rate: + properties: + name: network.outgoing.bytes.rate + pnfextcp_1.network.outpoing.packets: + properties: + name: network.outpoing.packets + pnfextcp_1.network.outgoing.bytes: + properties: + name: network.outgoing.bytes + pnfextcp_1.network.incoming.bytes.rate: + properties: + name: network.incoming.bytes.rate + pnfextcp_1.network.incoming.packets: + properties: + name: network.incoming.packets + substitution_mappings: + node_type: org.openecomp.service.TestServicePnfVsp8 + capabilities: + test_pnf_vsp0.pnfextcp_1.network.outpoing.packets: + - test_pnf_vsp 0 + - pnfextcp_1.network.outpoing.packets + test_pnf_vsp0.pnfextcp_1.feature: + - test_pnf_vsp 0 + - pnfextcp_1.feature + test_pnf_vsp0.pnfextcp_1.network.incoming.bytes: + - test_pnf_vsp 0 + - pnfextcp_1.network.incoming.bytes + test_pnf_vsp0.pnfextcp_1.network.incoming.packets: + - test_pnf_vsp 0 + - pnfextcp_1.network.incoming.packets + test_pnf_vsp0.pnfextcp_1.port_mirroring: + - test_pnf_vsp 0 + - pnfextcp_1.port_mirroring + test_pnf_vsp0.pnfextcp_1.network.incoming.packets.rate: + - test_pnf_vsp 0 + - pnfextcp_1.network.incoming.packets.rate + test_pnf_vsp0.pnfextcp_1.network.outgoing.bytes.rate: + - test_pnf_vsp 0 + - pnfextcp_1.network.outgoing.bytes.rate + test_pnf_vsp0.pnfextcp_1.network.incoming.bytes.rate: + - test_pnf_vsp 0 + - pnfextcp_1.network.incoming.bytes.rate + test_pnf_vsp0.pnfextcp_1.forwarder: + - test_pnf_vsp 0 + - pnfextcp_1.forwarder + test_pnf_vsp0.pnfextcp_1.network.outgoing.packets.rate: + - test_pnf_vsp 0 + - pnfextcp_1.network.outgoing.packets.rate + test_pnf_vsp0.pnfextcp_1.network.outgoing.bytes: + - test_pnf_vsp 0 + - pnfextcp_1.network.outgoing.bytes + requirements: + test_pnf_vsp0.pnfextcp_1.dependency: + - test_pnf_vsp 0 + - pnfextcp_1.dependency + test_pnf_vsp0.pnfextcp_1.binding: + - test_pnf_vsp 0 + - pnfextcp_1.binding + test_pnf_vsp0.pnfextcp_1.link: + - test_pnf_vsp 0 + - pnfextcp_1.link diff --git a/tests/data/service-TestServiceFyx-template.yml b/tests/data/service-TestServiceFyx-template.yml new file mode 100644 index 0000000..179ff43 --- /dev/null +++ b/tests/data/service-TestServiceFyx-template.yml @@ -0,0 +1,646 @@ +tosca_definitions_version: tosca_simple_yaml_1_1 +metadata: + invariantUUID: 0ca2fd47-4f5b-4d0f-ad83-f458a0e6c4cd + UUID: 2ad116d1-5219-41de-b2ee-d00f100d7f00 + name: freeRadiusInnova_SERVICE123 + description: service + type: Service + category: Network Service + serviceType: '' + serviceRole: '' + instantiationType: A-la-carte + serviceEcompNaming: true + ecompGeneratedNaming: true + namingPolicy: '' + environmentContext: General_Revenue-Bearing + serviceFunction: '' +imports: +- nodes: + file: nodes.yml +- datatypes: + file: data.yml +- capabilities: + file: capabilities.yml +- relationships: + file: relationships.yml +- groups: + file: groups.yml +- policies: + file: policies.yml +- annotations: + file: annotations.yml +- service-freeRadiusInnova_SERVICE123-interface: + file: service-FreeradiusinnovaService123-template-interface.yml +- resource-NeutronNet: + file: resource-Neutronnet-template.yml +- resource-freeRadiusInnova_VF: + file: resource-FreeradiusinnovaVf-template.yml +- resource-freeRadiusInnova_VF-interface: + file: resource-FreeradiusinnovaVf-template-interface.yml +topology_template: + inputs: + skip_post_instantiation_configuration: + default: true + type: boolean + required: false + controller_actor: + default: SO-REF-DATA + type: string + required: false + cds_model_version: + type: string + required: false + cds_model_name: + type: string + required: false + node_templates: + NeutronNet 0: + type: org.openecomp.resource.vl.nodes.heat.network.neutron.Net + metadata: + invariantUUID: 4084c513-5149-456d-9be0-efc503058799 + UUID: e12cedf4-fd3f-4d76-ae2a-0368eaee40dc + customizationUUID: ae1df985-3313-4a8f-93e6-efbc71fd3938 + version: '1.0' + name: NeutronNet + description: Represents a network service with optional subnets and advanced configurations. + type: VL + category: Generic + subcategory: Network Elements + resourceVendor: ONAP (Tosca) + resourceVendorRelease: 1.0.0.wd03 + resourceVendorModelNumber: '' + properties: + dhcp_enabled: true + shared: false + ip_version: 4 + admin_state_up: true + capabilities: + end_point: + properties: + protocol: tcp + initiator: source + network_name: PRIVATE + secure: false + freeRadiusInnova_VF 0: + type: org.openecomp.resource.vf.FreeradiusinnovaVf + metadata: + invariantUUID: e7ba6a8b-ece2-4f4e-bd1a-0f5ca461eedc + UUID: e65f6295-82d1-4e34-8ce9-4df5a81f98a0 + customizationUUID: 674140ec-1aba-4c59-b775-9cb239fc5c70 + version: '1.0' + name: freeRadiusInnova_VF + description: VF + type: VF + category: Generic + subcategory: Abstract + resourceVendor: VNFVendor + resourceVendorRelease: '1.0' + resourceVendorModelNumber: '' + properties: + vf_module_id: FreeRadiusInnova-VF-module + freeRadius_image_name: ubuntu-16.04-daily + skip_post_instantiation_configuration: true + nf_naming: + ecomp_generated_naming: true + user_plane_net_name: user-plane-net, + multi_stage_design: 'false' + controller_actor: SO-REF-DATA + availability_zone_max_count: 1 + control_plane_net_name: control-plane-net + freeRadius_key_name: innova + freeRadius_flavor_name: onap.small + vnf_name: FreeRadiusInnova + freeRadius_pub_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCifYw9iPtGH/enR6rigILYu3djnyT+6g+bOnzSSNLVffDsU6QAv7i3Girc+JgXGjxCtWAzZTHx3qjniKixhIpyzt65J0RDeyZCS9WnMfOqkgIlj+85yxlQDLZdkS0xaQYzeqDEPsT6N0i+gxGWu52NxKYazVqyRXrqwiXZvOMyY4hcgtbNiWRy9Vqdkeippo/+3Jg2gawOz54fIF5zbWMqiJAALBCBl5w1LGTJIaJ+f6vQPq05kLKZOOs3zlfcUY5u1bEzulm/ZNyfhh3rrnJD20jCp8KETgg9sGHQdWdRkbsqT5AGeJQhwK47BEk7ckwZH3lmKwgRw8S/J604tpUh Generated-by-Nova + oam_plane_net_name: admin + freeRadius_name_0: FreeRadiusInnova-VM + vnf_id: FreeRadiusInnova-Id + capabilities: + abstract_freeradius.network.outgoing.bytes.rate_freeRadius_freeRadius_control_plane_port_0: + properties: + name: network.outgoing.bytes.rate + abstract_freeradius.cpu_util_freeRadius: + properties: + name: cpu_util + abstract_freeradius.memory.usage_freeRadius: + properties: + name: memory.usage + abstract_freeradius.network.incoming.bytes_freeRadius_freeRadius_control_plane_port_0: + properties: + name: network.incoming.bytes + abstract_freeradius.memory_freeRadius: + properties: + name: memory + abstract_freeradius.network.outgoing.packets.rate_freeRadius_freeRadius_oam_port_0: + properties: + name: network.outgoing.packets.rate + abstract_freeradius.network.incoming.packets.rate_freeRadius_freeRadius_control_plane_port_0: + properties: + name: network.incoming.packets.rate + abstract_freeradius.disk.device.read.bytes.rate_freeRadius: + properties: + name: disk.device.read.bytes.rate + abstract_freeradius.disk.read.requests_freeRadius: + properties: + name: disk.read.requests + abstract_freeradius.disk.device.usage_freeRadius: + properties: + name: disk.device.usage + abstract_freeradius.network.incoming.packets_freeRadius_freeRadius_user_plane_port_0: + properties: + name: network.incoming.packets + abstract_freeradius.scalable_freeRadius: + properties: + min_instances: 1 + max_instances: 1 + abstract_freeradius.disk.latency_freeRadius: + properties: + name: disk.latency + abstract_freeradius.disk.write.requests.rate_freeRadius: + properties: + name: disk.write.requests.rate + abstract_freeradius.disk.write.requests_freeRadius: + properties: + name: disk.write.requests + abstract_freeradius.network.incoming.bytes_freeRadius_freeRadius_user_plane_port_0: + properties: + name: network.incoming.bytes + abstract_freeradius.disk.device.write.bytes.rate_freeRadius: + properties: + name: disk.device.write.bytes.rate + abstract_freeradius.disk.device.iops_freeRadius: + properties: + name: disk.device.iops + abstract_freeradius.network.outpoing.packets_freeRadius_freeRadius_user_plane_port_0: + properties: + name: network.outpoing.packets + abstract_freeradius.network.incoming.packets_freeRadius_freeRadius_control_plane_port_0: + properties: + name: network.incoming.packets + abstract_freeradius.disk.iops_freeRadius: + properties: + name: disk.iops + abstract_freeradius.disk.device.write.requests_freeRadius: + properties: + name: disk.device.write.requests + abstract_freeradius.disk.device.read.requests.rate_freeRadius: + properties: + name: disk.device.read.requests.rate + abstract_freeradius.network.incoming.bytes.rate_freeRadius_freeRadius_user_plane_port_0: + properties: + name: network.incoming.bytes.rate + abstract_freeradius.disk.device.write.requests.rate_freeRadius: + properties: + name: disk.device.write.requests.rate + abstract_freeradius.network.outgoing.bytes.rate_freeRadius_freeRadius_user_plane_port_0: + properties: + name: network.outgoing.bytes.rate + abstract_freeradius.network.outpoing.packets_freeRadius_freeRadius_oam_port_0: + properties: + name: network.outpoing.packets + abstract_freeradius.disk.device.write.bytes_freeRadius: + properties: + name: disk.device.write.bytes + abstract_freeradius.instance_freeRadius: + properties: + name: instance + abstract_freeradius.disk.device.capacity_freeRadius: + properties: + name: disk.device.capacity + abstract_freeradius.port_mirroring_freeRadius_freeRadius_oam_port_0: + properties: + connection_point: + network_role: + get_input: port_freeRadius_oam_port_0_network_role + nfc_naming_code: freeRadius + abstract_freeradius.disk.usage_freeRadius: + properties: + name: disk.usage + abstract_freeradius.network.outgoing.bytes_freeRadius_freeRadius_oam_port_0: + properties: + name: network.outgoing.bytes + abstract_freeradius.disk.device.latency_freeRadius: + properties: + name: disk.device.latency + abstract_freeradius.disk.root.size_freeRadius: + properties: + name: disk.root.size + abstract_freeradius.disk.capacity_freeRadius: + properties: + name: disk.capacity + abstract_freeradius.network.incoming.bytes.rate_freeRadius_freeRadius_oam_port_0: + properties: + name: network.incoming.bytes.rate + abstract_freeradius.network.incoming.bytes.rate_freeRadius_freeRadius_control_plane_port_0: + properties: + name: network.incoming.bytes.rate + abstract_freeradius.memory.resident_freeRadius: + properties: + name: memory.resident + abstract_freeradius.network.incoming.packets.rate_freeRadius_freeRadius_oam_port_0: + properties: + name: network.incoming.packets.rate + abstract_freeradius.disk.write.bytes.rate_freeRadius: + properties: + name: disk.write.bytes.rate + abstract_freeradius.network.outgoing.bytes_freeRadius_freeRadius_control_plane_port_0: + properties: + name: network.outgoing.bytes + abstract_freeradius.disk.device.read.bytes_freeRadius: + properties: + name: disk.device.read.bytes + abstract_freeradius.network.outgoing.bytes.rate_freeRadius_freeRadius_oam_port_0: + properties: + name: network.outgoing.bytes.rate + abstract_freeradius.disk.allocation_freeRadius: + properties: + name: disk.allocation + abstract_freeradius.network.outgoing.packets.rate_freeRadius_freeRadius_user_plane_port_0: + properties: + name: network.outgoing.packets.rate + abstract_freeradius.cpu.delta_freeRadius: + properties: + name: cpu.delta + abstract_freeradius.network.outgoing.packets.rate_freeRadius_freeRadius_control_plane_port_0: + properties: + name: network.outgoing.packets.rate + abstract_freeradius.disk.read.bytes.rate_freeRadius: + properties: + name: disk.read.bytes.rate + abstract_freeradius.vcpus_freeRadius: + properties: + name: vcpus + abstract_freeradius.network.incoming.packets_freeRadius_freeRadius_oam_port_0: + properties: + name: network.incoming.packets + abstract_freeradius.disk.device.allocation_freeRadius: + properties: + name: disk.device.allocation + abstract_freeradius.disk.write.bytes_freeRadius: + properties: + name: disk.write.bytes + abstract_freeradius.endpoint_freeRadius: + properties: + secure: true + abstract_freeradius.network.incoming.packets.rate_freeRadius_freeRadius_user_plane_port_0: + properties: + name: network.incoming.packets.rate + abstract_freeradius.disk.read.bytes_freeRadius: + properties: + name: disk.read.bytes + abstract_freeradius.cpu_freeRadius: + properties: + name: cpu + abstract_freeradius.port_mirroring_freeRadius_freeRadius_control_plane_port_0: + properties: + connection_point: + network_role: + get_input: port_freeRadius_control_plane_port_0_network_role + nfc_naming_code: freeRadius + abstract_freeradius.network.outpoing.packets_freeRadius_freeRadius_control_plane_port_0: + properties: + name: network.outpoing.packets + abstract_freeradius.disk.device.read.requests_freeRadius: + properties: + name: disk.device.read.requests + abstract_freeradius.port_mirroring_freeRadius_freeRadius_user_plane_port_0: + properties: + connection_point: + network_role: + get_input: port_freeRadius_user_plane_port_0_network_role + nfc_naming_code: freeRadius + abstract_freeradius.disk.ephemeral.size_freeRadius: + properties: + name: disk.ephemeral.size + abstract_freeradius.network.incoming.bytes_freeRadius_freeRadius_oam_port_0: + properties: + name: network.incoming.bytes + abstract_freeradius.network.outgoing.bytes_freeRadius_freeRadius_user_plane_port_0: + properties: + name: network.outgoing.bytes + groups: + freeradiusinnova_vf0..FreeradiusinnovaVf..base_freeRadiusInnova..module-0: + type: org.openecomp.groups.VfModule + metadata: + vfModuleModelName: FreeradiusinnovaVf..base_freeRadiusInnova..module-0 + vfModuleModelInvariantUUID: bd638287-e1a0-47d0-a0b1-825a4ad23fef + vfModuleModelUUID: 9bc52324-3e35-4795-9181-804a4eee0806 + vfModuleModelVersion: '1' + vfModuleModelCustomizationUUID: 0a633c1a-c122-4e0a-8ed6-233ac56c132f + properties: + min_vf_module_instances: 1 + vf_module_label: base_freeRadiusInnova + max_vf_module_instances: 1 + vf_module_type: Base + isBase: true + initial_count: 1 + volume_group: false + substitution_mappings: + node_type: org.openecomp.service.FreeradiusinnovaService123 + capabilities: + freeradiusinnova_vf0.abstract_freeradius.disk.latency_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.latency_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.usage_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.usage_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.read.bytes_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.read.bytes_freeRadius + freeradiusinnova_vf0.abstract_freeradius.os_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.os_freeRadius + freeradiusinnova_vf0.abstract_freeradius.attachment_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.attachment_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.feature_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.feature_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.device.read.bytes_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.device.read.bytes_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.device.latency_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.device.latency_freeRadius + freeradiusinnova_vf0.abstract_freeradius.binding_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.binding_freeRadius + freeradiusinnova_vf0.abstract_freeradius.binding_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.binding_freeRadius_freeRadius_user_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.forwarder_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.forwarder_freeRadius_freeRadius_user_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.disk.device.usage_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.device.usage_freeRadius + freeradiusinnova_vf0.abstract_freeradius.endpoint_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.endpoint_freeRadius + freeradiusinnova_vf0.abstract_freeradius.port_mirroring_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.port_mirroring_freeRadius_freeRadius_oam_port_0 + freeradiusinnova_vf0.abstract_freeradius.cpu.delta_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.cpu.delta_freeRadius + freeradiusinnova_vf0.abstract_freeradius.feature_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.feature_freeRadius_freeRadius_user_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.disk.write.bytes_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.write.bytes_freeRadius + freeradiusinnova_vf0.abstract_freeradius.forwarder_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.forwarder_freeRadius_freeRadius_oam_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.incoming.packets_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.incoming.packets_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.disk.device.write.bytes_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.device.write.bytes_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.device.write.requests.rate_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.device.write.requests.rate_freeRadius + freeradiusinnova_vf0.abstract_freeradius.host_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.host_freeRadius + freeradiusinnova_vf0.abstract_freeradius.attachment_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.attachment_freeRadius_freeRadius_user_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.incoming.bytes.rate_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.incoming.bytes.rate_freeRadius_freeRadius_user_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.outgoing.bytes_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.outgoing.bytes_freeRadius_freeRadius_user_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.incoming.bytes_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.incoming.bytes_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.disk.device.write.bytes.rate_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.device.write.bytes.rate_freeRadius + neutronnet0.end_point: + - NeutronNet 0 + - end_point + freeradiusinnova_vf0.abstract_freeradius.network.outgoing.bytes_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.outgoing.bytes_freeRadius_freeRadius_oam_port_0 + freeradiusinnova_vf0.abstract_freeradius.cpu_util_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.cpu_util_freeRadius + freeradiusinnova_vf0.abstract_freeradius.network.incoming.packets_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.incoming.packets_freeRadius_freeRadius_oam_port_0 + neutronnet0.attachment: + - NeutronNet 0 + - attachment + freeradiusinnova_vf0.abstract_freeradius.disk.device.iops_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.device.iops_freeRadius + freeradiusinnova_vf0.freeradius_control_plane_security_group.feature: + - freeRadiusInnova_VF 0 + - freeradius_control_plane_security_group.feature + freeradiusinnova_vf0.freeradius_user_plane_security_group.feature: + - freeRadiusInnova_VF 0 + - freeradius_user_plane_security_group.feature + freeradiusinnova_vf0.abstract_freeradius.disk.capacity_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.capacity_freeRadius + freeradiusinnova_vf0.abstract_freeradius.network.incoming.bytes.rate_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.incoming.bytes.rate_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.outgoing.bytes.rate_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.outgoing.bytes.rate_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.outpoing.packets_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.outpoing.packets_freeRadius_freeRadius_oam_port_0 + neutronnet0.link: + - NeutronNet 0 + - link + freeradiusinnova_vf0.abstract_freeradius.disk.write.bytes.rate_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.write.bytes.rate_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.device.read.requests_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.device.read.requests_freeRadius + freeradiusinnova_vf0.abstract_freeradius.network.outgoing.bytes_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.outgoing.bytes_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.vcpus_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.vcpus_freeRadius + freeradiusinnova_vf0.abstract_freeradius.network.incoming.bytes.rate_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.incoming.bytes.rate_freeRadius_freeRadius_oam_port_0 + freeradiusinnova_vf0.abstract_freeradius.disk.device.allocation_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.device.allocation_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.device.read.requests.rate_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.device.read.requests.rate_freeRadius + freeradiusinnova_vf0.abstract_freeradius.attachment_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.attachment_freeRadius_freeRadius_oam_port_0 + freeradiusinnova_vf0.abstract_freeradius.memory_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.memory_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.device.write.requests_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.device.write.requests_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.read.bytes.rate_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.read.bytes.rate_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.root.size_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.root.size_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.allocation_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.allocation_freeRadius + freeradiusinnova_vf0.abstract_freeradius.network.outgoing.packets.rate_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.outgoing.packets.rate_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.incoming.packets_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.incoming.packets_freeRadius_freeRadius_user_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.disk.device.capacity_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.device.capacity_freeRadius + freeradiusinnova_vf0.abstract_freeradius.network.outgoing.bytes.rate_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.outgoing.bytes.rate_freeRadius_freeRadius_user_plane_port_0 + neutronnet0.feature: + - NeutronNet 0 + - feature + freeradiusinnova_vf0.abstract_freeradius.network.outpoing.packets_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.outpoing.packets_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.port_mirroring_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.port_mirroring_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.instance_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.instance_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.write.requests.rate_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.write.requests.rate_freeRadius + freeradiusinnova_vf0.abstract_freeradius.memory.resident_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.memory.resident_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.write.requests_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.write.requests_freeRadius + freeradiusinnova_vf0.abstract_freeradius.network.incoming.packets.rate_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.incoming.packets.rate_freeRadius_freeRadius_user_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.incoming.packets.rate_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.incoming.packets.rate_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.forwarder_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.forwarder_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.incoming.packets.rate_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.incoming.packets.rate_freeRadius_freeRadius_oam_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.incoming.bytes_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.incoming.bytes_freeRadius_freeRadius_oam_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.outgoing.packets.rate_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.outgoing.packets.rate_freeRadius_freeRadius_user_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.binding_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.binding_freeRadius_freeRadius_oam_port_0 + freeradiusinnova_vf0.abstract_freeradius.binding_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.binding_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.outgoing.packets.rate_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.outgoing.packets.rate_freeRadius_freeRadius_oam_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.outpoing.packets_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.outpoing.packets_freeRadius_freeRadius_user_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.network.outgoing.bytes.rate_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.outgoing.bytes.rate_freeRadius_freeRadius_oam_port_0 + freeradiusinnova_vf0.abstract_freeradius.scalable_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.scalable_freeRadius + freeradiusinnova_vf0.abstract_freeradius.feature_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.feature_freeRadius_freeRadius_oam_port_0 + freeradiusinnova_vf0.freeradius_oam_security_group.feature: + - freeRadiusInnova_VF 0 + - freeradius_oam_security_group.feature + freeradiusinnova_vf0.abstract_freeradius.feature_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.feature_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.port_mirroring_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.port_mirroring_freeRadius_freeRadius_user_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.disk.device.read.bytes.rate_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.device.read.bytes.rate_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.read.requests_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.read.requests_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.iops_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.iops_freeRadius + freeradiusinnova_vf0.abstract_freeradius.memory.usage_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.memory.usage_freeRadius + freeradiusinnova_vf0.abstract_freeradius.disk.ephemeral.size_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.disk.ephemeral.size_freeRadius + freeradiusinnova_vf0.abstract_freeradius.network.incoming.bytes_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.network.incoming.bytes_freeRadius_freeRadius_user_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.cpu_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.cpu_freeRadius + requirements: + freeradiusinnova_vf0.abstract_freeradius.dependency_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.dependency_freeRadius_freeRadius_user_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.dependency_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.dependency_freeRadius_freeRadius_oam_port_0 + freeradiusinnova_vf0.abstract_freeradius.link_freeRadius_freeRadius_oam_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.link_freeRadius_freeRadius_oam_port_0 + freeradiusinnova_vf0.freeradius_control_plane_security_group.port: + - freeRadiusInnova_VF 0 + - freeradius_control_plane_security_group.port + freeradiusinnova_vf0.freeradius_oam_security_group.dependency: + - freeRadiusInnova_VF 0 + - freeradius_oam_security_group.dependency + freeradiusinnova_vf0.freeradius_control_plane_security_group.dependency: + - freeRadiusInnova_VF 0 + - freeradius_control_plane_security_group.dependency + neutronnet0.dependency: + - NeutronNet 0 + - dependency + freeradiusinnova_vf0.freeradius_user_plane_security_group.port: + - freeRadiusInnova_VF 0 + - freeradius_user_plane_security_group.port + freeradiusinnova_vf0.freeradius_user_plane_security_group.dependency: + - freeRadiusInnova_VF 0 + - freeradius_user_plane_security_group.dependency + freeradiusinnova_vf0.abstract_freeradius.dependency_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.dependency_freeRadius + freeradiusinnova_vf0.abstract_freeradius.dependency_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.dependency_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.link_freeRadius_freeRadius_control_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.link_freeRadius_freeRadius_control_plane_port_0 + freeradiusinnova_vf0.abstract_freeradius.local_storage_freeRadius: + - freeRadiusInnova_VF 0 + - abstract_freeradius.local_storage_freeRadius + freeradiusinnova_vf0.freeradius_oam_security_group.port: + - freeRadiusInnova_VF 0 + - freeradius_oam_security_group.port + freeradiusinnova_vf0.abstract_freeradius.link_freeRadius_freeRadius_user_plane_port_0: + - freeRadiusInnova_VF 0 + - abstract_freeradius.link_freeRadius_freeRadius_user_plane_port_0 \ No newline at end of file diff --git a/tests/data/service-Ubuntu16-template.yml b/tests/data/service-Ubuntu16-template.yml new file mode 100644 index 0000000..63cee88 --- /dev/null +++ b/tests/data/service-Ubuntu16-template.yml @@ -0,0 +1,543 @@ +tosca_definitions_version: tosca_simple_yaml_1_1 +metadata: + invariantUUID: d65b4e82-06e7-424a-8944-284558b94ff4 + UUID: b54a8c68-d520-42c3-8f8e-08f3866f4fc1 + name: ubuntu16 + description: ubuntu16 + type: Service + category: Network Service + serviceType: '' + serviceRole: '' + instantiationType: A-la-carte + serviceEcompNaming: true + ecompGeneratedNaming: true + namingPolicy: '' + environmentContext: General_Revenue-Bearing +imports: +- nodes: + file: nodes.yml +- datatypes: + file: data.yml +- capabilities: + file: capabilities.yml +- relationships: + file: relationships.yml +- groups: + file: groups.yml +- policies: + file: policies.yml +- annotations: + file: annotations.yml +- service-ubuntu16-interface: + file: service-Ubuntu16-template-interface.yml +- resource-ubuntu16_VF: + file: resource-Ubuntu16Vf-template.yml +- resource-ubuntu16_VF-interface: + file: resource-Ubuntu16Vf-template-interface.yml +topology_template: + node_templates: + ubuntu16_VF 0: + type: org.openecomp.resource.vf.Ubuntu16Vf + metadata: + invariantUUID: 712e6e88-404e-49cb-99db-19460b29c2ac + UUID: dc4f02bb-f318-49c4-bd70-2a1ee95b439a + customizationUUID: 1066c03b-0aab-43b3-a661-7543de231e7c + version: '1.0' + name: ubuntu16_VF + description: VF + type: VF + category: Generic + subcategory: Abstract + resourceVendor: Generic-Vendor + resourceVendorRelease: '1.0' + resourceVendorModelNumber: '' + properties: + vf_module_id: Ubuntu16-VF-module + ubuntu16_name_0: ubuntu16 + nf_naming: + ecomp_generated_naming: true + ubuntu16_flavor_name: onap.small + multi_stage_design: false + ubuntu16_pub_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDY15cdBmIs2XOpe4EiFCsaY6bmUmK/GysMoLl4UG51JCfJwvwoWCoA+6mDIbymZxhxq9IGxilp/yTA6WQ9s/5pBag1cUMJmFuda9PjOkXl04jgqh5tR6I+GZ97AvCg93KAECis5ubSqw1xOCj4utfEUtPoF1OuzqM/lE5mY4N6VKXn+fT7pCD6cifBEs6JHhVNvs5OLLp/tO8Pa3kKYQOdyS0xc3rh+t2lrzvKUSWGZbX+dLiFiEpjsUL3tDqzkEMNUn4pdv69OJuzWHCxRWPfdrY9Wg0j3mJesP29EBht+w+EC9/kBKq+1VKdmsXUXAcjEvjovVL8l1BrX3BY0R8D imported-openssh-key + availability_zone_max_count: 1 + vnf_id: Ubuntu16-VNF + vnf_name: Ubuntu16-VNF-name + ubuntu16_image_name: ubuntu-16.04-daily + admin_plane_net_name: admin + ubuntu16_key_name: cleouverte + capabilities: + abstract_ubuntu16.port_mirroring_ubuntu16_ubuntu16_admin_plane_port: + properties: + connection_point: + network_role: + get_input: port_ubuntu16_admin_plane_port_network_role + nfc_naming_code: ubuntu16 + abstract_ubuntu16.cpu_util_ubuntu16: + properties: + unit: '%' + description: Average CPU utilization + type: Gauge + category: compute + abstract_ubuntu16.network.outpoing.packets_ubuntu16_ubuntu16_admin_plane_port: + properties: + unit: packet + description: Number of outgoing packets + type: Cumulative + category: network + abstract_ubuntu16.disk.device.read.bytes.rate_ubuntu16: + properties: + unit: B/s + description: Average rate of reads + type: Gauge + category: disk + abstract_ubuntu16.endpoint_ubuntu16: + properties: + secure: true + abstract_ubuntu16.disk.ephemeral.size_ubuntu16: + properties: + unit: GB + description: Size of ephemeral disk + type: Gauge + category: compute + abstract_ubuntu16.disk.write.bytes_ubuntu16: + properties: + unit: B + description: Volume of writes + type: Cumulative + category: compute + abstract_ubuntu16.cpu.delta_ubuntu16: + properties: + unit: ns + description: CPU time used since previous datapoint + type: Delta + category: compute + abstract_ubuntu16.network.incoming.bytes.rate_ubuntu16_ubuntu16_admin_plane_port: + properties: + unit: B/s + description: Average rate of incoming bytes + type: Gauge + category: network + abstract_ubuntu16.network.incoming.bytes_ubuntu16_ubuntu16_admin_plane_port: + properties: + unit: B + description: Number of incoming bytes + type: Cumulative + category: network + abstract_ubuntu16.disk.write.requests.rate_ubuntu16: + properties: + unit: request/s + description: Average rate of write requests + type: Gauge + category: compute + abstract_ubuntu16.memory_ubuntu16: + properties: + unit: MB + description: Volume of RAM allocated to the instance + type: Gauge + category: compute + abstract_ubuntu16.disk.root.size_ubuntu16: + properties: + unit: GB + description: Size of root disk + type: Gauge + category: compute + abstract_ubuntu16.disk.device.usage_ubuntu16: + properties: + unit: B + description: The physical size in bytes of the image container on the host per device + type: Gauge + category: disk + abstract_ubuntu16.disk.write.requests_ubuntu16: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: compute + abstract_ubuntu16.disk.device.read.bytes_ubuntu16: + properties: + unit: B + description: Volume of reads + type: Cumulative + category: disk + abstract_ubuntu16.vcpus_ubuntu16: + properties: + unit: vcpu + description: Number of virtual CPUs allocated to the instance + type: Gauge + category: compute + abstract_ubuntu16.network.incoming.packets.rate_ubuntu16_ubuntu16_admin_plane_port: + properties: + unit: packet/s + description: Average rate of incoming packets + type: Gauge + category: network + abstract_ubuntu16.network.incoming.packets_ubuntu16_ubuntu16_admin_plane_port: + properties: + unit: packet + description: Number of incoming packets + type: Cumulative + category: network + abstract_ubuntu16.disk.read.bytes_ubuntu16: + properties: + unit: B + description: Volume of reads + type: Cumulative + category: compute + abstract_ubuntu16.disk.latency_ubuntu16: + properties: + unit: ms + description: Average disk latency + type: Gauge + category: disk + abstract_ubuntu16.disk.device.read.requests.rate_ubuntu16: + properties: + unit: request/s + description: Average rate of read requests + type: Gauge + category: disk + abstract_ubuntu16.network.outgoing.bytes_ubuntu16_ubuntu16_admin_plane_port: + properties: + unit: B + description: Number of outgoing bytes + type: Cumulative + category: network + abstract_ubuntu16.scalable_ubuntu16: + properties: + max_instances: 1 + min_instances: 1 + abstract_ubuntu16.disk.device.write.requests.rate_ubuntu16: + properties: + unit: request/s + description: Average rate of write requests + type: Gauge + category: disk + abstract_ubuntu16.disk.device.allocation_ubuntu16: + properties: + unit: B + description: The amount of disk per device occupied by the instance on the host machine + type: Gauge + category: disk + abstract_ubuntu16.disk.device.write.bytes_ubuntu16: + properties: + unit: B + description: Volume of writes + type: Cumulative + category: disk + abstract_ubuntu16.disk.device.capacity_ubuntu16: + properties: + unit: B + description: The amount of disk per device that the instance can see + type: Gauge + category: disk + abstract_ubuntu16.disk.device.latency_ubuntu16: + properties: + unit: ms + description: Average disk latency per device + type: Gauge + category: disk + abstract_ubuntu16.disk.write.bytes.rate_ubuntu16: + properties: + unit: B/s + description: Average rate of writes + type: Gauge + category: compute + abstract_ubuntu16.instance_ubuntu16: + properties: + unit: instance + description: Existence of instance + type: Gauge + category: compute + abstract_ubuntu16.disk.iops_ubuntu16: + properties: + unit: count/s + description: Average disk iops + type: Gauge + category: disk + abstract_ubuntu16.disk.capacity_ubuntu16: + properties: + unit: B + description: The amount of disk that the instance can see + type: Gauge + category: disk + abstract_ubuntu16.memory.resident_ubuntu16: + properties: + unit: MB + description: Volume of RAM used by the instance on the physical machine + type: Gauge + category: compute + abstract_ubuntu16.disk.allocation_ubuntu16: + properties: + unit: B + description: The amount of disk occupied by the instance on the host machine + type: Gauge + category: disk + abstract_ubuntu16.network.outgoing.packets.rate_ubuntu16_ubuntu16_admin_plane_port: + properties: + unit: packet/s + description: Average rate of outgoing packets + type: Gauge + category: network + abstract_ubuntu16.disk.read.requests_ubuntu16: + properties: + unit: request + description: Number of read requests + type: Cumulative + category: compute + abstract_ubuntu16.network.outgoing.bytes.rate_ubuntu16_ubuntu16_admin_plane_port: + properties: + unit: B/s + description: Average rate of outgoing bytes + type: Gauge + category: network + abstract_ubuntu16.cpu_ubuntu16: + properties: + unit: ns + description: CPU time used + type: Cumulative + category: compute + abstract_ubuntu16.disk.device.iops_ubuntu16: + properties: + unit: count/s + description: Average disk iops per device + type: Gauge + category: disk + abstract_ubuntu16.disk.device.read.requests_ubuntu16: + properties: + unit: request + description: Number of read requests + type: Cumulative + category: disk + abstract_ubuntu16.memory.usage_ubuntu16: + properties: + unit: MB + description: Volume of RAM used by the instance from the amount of its allocated memory + type: Gauge + category: compute + abstract_ubuntu16.disk.usage_ubuntu16: + properties: + unit: B + description: The physical size in bytes of the image container on the host + type: Gauge + category: disk + abstract_ubuntu16.disk.device.write.bytes.rate_ubuntu16: + properties: + unit: B/s + description: Average rate of writes + type: Gauge + category: disk + abstract_ubuntu16.disk.read.bytes.rate_ubuntu16: + properties: + unit: B/s + description: Average rate of reads + type: Gauge + category: compute + abstract_ubuntu16.disk.device.write.requests_ubuntu16: + properties: + unit: request + description: Number of write requests + type: Cumulative + category: disk + groups: + ubuntu16_vf0..Ubuntu16Vf..base_ubuntu16..module-0: + type: org.openecomp.groups.VfModule + metadata: + vfModuleModelName: Ubuntu16Vf..base_ubuntu16..module-0 + vfModuleModelInvariantUUID: f47c3a9b-6a5f-4d1a-8a0b-b7f56ebb9a90 + vfModuleModelUUID: ed041b38-63fc-486d-9d4d-4e2531bc7e54 + vfModuleModelVersion: '1' + vfModuleModelCustomizationUUID: d946ea06-ec4b-4ed2-921a-117e1379b913 + properties: + min_vf_module_instances: 1 + vf_module_label: base_ubuntu16 + max_vf_module_instances: 1 + vf_module_type: Base + isBase: true + initial_count: 1 + volume_group: false + substitution_mappings: + node_type: org.openecomp.service.Ubuntu16 + capabilities: + ubuntu16_vf0.abstract_ubuntu16.disk.device.write.bytes_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.device.write.bytes_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.memory.resident_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.memory.resident_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.forwarder_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.forwarder_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.abstract_ubuntu16.binding_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.binding_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.abstract_ubuntu16.vcpus_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.vcpus_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.latency_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.latency_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.device.latency_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.device.latency_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.read.bytes_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.read.bytes_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.network.outgoing.bytes.rate_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.network.outgoing.bytes.rate_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.abstract_ubuntu16.disk.device.usage_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.device.usage_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.cpu.delta_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.cpu.delta_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.endpoint_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.endpoint_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.device.read.bytes_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.device.read.bytes_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.device.write.requests_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.device.write.requests_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.network.outgoing.bytes_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.network.outgoing.bytes_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.abstract_ubuntu16.disk.capacity_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.capacity_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.attachment_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.attachment_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.abstract_ubuntu16.network.incoming.bytes.rate_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.network.incoming.bytes.rate_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.abstract_ubuntu16.disk.device.iops_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.device.iops_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.device.capacity_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.device.capacity_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.write.bytes.rate_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.write.bytes.rate_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.device.write.requests.rate_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.device.write.requests.rate_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.feature_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.feature_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.abstract_ubuntu16.port_mirroring_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.port_mirroring_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.abstract_ubuntu16.disk.write.requests.rate_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.write.requests.rate_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.root.size_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.root.size_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.allocation_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.allocation_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.feature_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.feature_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.device.read.bytes.rate_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.device.read.bytes.rate_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.device.read.requests_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.device.read.requests_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.network.incoming.packets.rate_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.network.incoming.packets.rate_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.ubuntu16_admin_security_group.feature: + - ubuntu16_vf0 + - ubuntu16_admin_security_group.feature + ubuntu16_vf0.abstract_ubuntu16.host_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.host_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.write.bytes_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.write.bytes_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.memory_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.memory_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.cpu_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.cpu_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.usage_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.usage_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.cpu_util_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.cpu_util_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.write.requests_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.write.requests_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.os_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.os_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.network.outgoing.packets.rate_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.network.outgoing.packets.rate_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.abstract_ubuntu16.disk.ephemeral.size_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.ephemeral.size_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.network.outpoing.packets_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.network.outpoing.packets_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.abstract_ubuntu16.disk.device.write.bytes.rate_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.device.write.bytes.rate_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.binding_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.binding_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.read.requests_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.read.requests_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.memory.usage_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.memory.usage_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.read.bytes.rate_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.read.bytes.rate_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.disk.device.allocation_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.device.allocation_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.scalable_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.scalable_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.network.incoming.bytes_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.network.incoming.bytes_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.abstract_ubuntu16.disk.device.read.requests.rate_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.device.read.requests.rate_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.instance_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.instance_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.network.incoming.packets_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.network.incoming.packets_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.abstract_ubuntu16.disk.iops_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.disk.iops_ubuntu16 + requirements: + ubuntu16_vf0.ubuntu16_admin_security_group.port: + - ubuntu16_vf0 + - ubuntu16_admin_security_group.port + ubuntu16_vf0.abstract_ubuntu16.local_storage_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.local_storage_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.dependency_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.dependency_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.abstract_ubuntu16.dependency_ubuntu16: + - ubuntu16_vf0 + - abstract_ubuntu16.dependency_ubuntu16 + ubuntu16_vf0.abstract_ubuntu16.link_ubuntu16_ubuntu16_admin_plane_port: + - ubuntu16_vf0 + - abstract_ubuntu16.link_ubuntu16_ubuntu16_admin_plane_port + ubuntu16_vf0.ubuntu16_admin_security_group.dependency: + - ubuntu16_vf0 + - ubuntu16_admin_security_group.dependency diff --git a/tests/data/service-VfwcdsService-template.yml b/tests/data/service-VfwcdsService-template.yml new file mode 100644 index 0000000..15e0beb --- /dev/null +++ b/tests/data/service-VfwcdsService-template.yml @@ -0,0 +1,1439 @@ +tosca_definitions_version: tosca_simple_yaml_1_1 +metadata: + invariantUUID: 40f5261c-38dd-4e7b-9ef4-4af383982efe + UUID: 7227d3dc-fa6c-470c-b752-9879395e3560 + name: vfwcds_SERVICE + description: service + type: Service + category: Network Service + serviceType: '' + serviceRole: '' + instantiationType: Macro + serviceEcompNaming: true + ecompGeneratedNaming: true + namingPolicy: '' + environmentContext: General_Revenue-Bearing + serviceFunction: '' +imports: +- nodes: + file: nodes.yml +- datatypes: + file: data.yml +- capabilities: + file: capabilities.yml +- relationships: + file: relationships.yml +- groups: + file: groups.yml +- policies: + file: policies.yml +- annotations: + file: annotations.yml +- service-vfwcds_SERVICE-interface: + file: service-VfwcdsService-template-interface.yml +- resource-vfwcds_VF: + file: resource-VfwcdsVf-template.yml +- resource-vfwcds_VF-interface: + file: resource-VfwcdsVf-template-interface.yml +topology_template: + inputs: + skip_post_instantiation_configuration: + default: true + type: boolean + required: false + controller_actor: + default: SO-REF-DATA + type: string + required: false + cds_model_version: + type: string + required: false + cds_model_name: + type: string + required: false + node_templates: + vfwcds_VF 0: + type: org.openecomp.resource.vf.VfwcdsVf + metadata: + invariantUUID: 1d96a236-7c64-4bfe-9eeb-5026e060e6cd + UUID: 8726a1fd-e84e-4477-bffb-319defb84b76 + customizationUUID: eabe78af-3a24-45ed-a50a-24ad778d0bac + version: '1.0' + name: vfwcds_VF + description: VF + type: VF + category: Generic + subcategory: Abstract + resourceVendor: VNFVendor + resourceVendorRelease: '1.0' + resourceVendorModelNumber: '' + properties: + vf_module_id: vFirewallCL + skip_post_instantiation_configuration: false + controller_actor: CDS + vsn_flavor_name: PUT THE VM FLAVOR NAME HERE (m1.medium suggested) + vfw_int_private2_ip_0: 192.168.20.100 + int_private1_subnet_id: zdfw1fwl01_unprotected_sub + public_net_id: PUT THE PUBLIC NETWORK ID HERE + vnf_name: vFW_NextGen + onap_private_subnet_id: PUT THE ONAP PRIVATE NETWORK NAME HERE + vsn_int_private2_ip_0: 192.168.20.250 + sec_group: PUT THE ONAP SECURITY GROUP HERE + vfw_name_0: zdfw1fwl01fwl01 + nexus_artifact_repo: https://nexus.onap.org + onap_private_net_cidr: 10.0.0.0/16 + vpg_onap_private_ip_0: 10.0.100.2 + dcae_collector_ip: 10.0.4.1 + vsn_image_name: PUT THE VM IMAGE NAME HERE (UBUNTU 1404) + vnf_id: vSink_demo_app + vpg_flavor_name: PUT THE VM FLAVOR NAME HERE (m1.medium suggested) + dcae_collector_port: '30417' + vfw_int_private2_floating_ip: 192.168.10.200 + vpg_name_0: zdfw1fwl01pgn01 + int_private2_subnet_id: zdfw1fwl01_protected_sub + int_private2_net_cidr: 192.168.20.0/24 + nf_naming: + ecomp_generated_naming: true + vsn_name_0: zdfw1fwl01snk01 + multi_stage_design: 'false' + vpg_image_name: PUT THE VM IMAGE NAME HERE (UBUNTU 1404) + onap_private_net_id: PUT THE ONAP PRIVATE NETWORK NAME HERE + availability_zone_max_count: 1 + sdnc_artifact_name: vfwcds + vsn_onap_private_ip_0: 10.0.100.3 + vfw_flavor_name: PUT THE VM FLAVOR NAME HERE (m1.medium suggested) + demo_artifacts_version: 1.6.0-SNAPSHOT + pub_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQXYJYYi3/OUZXUiCYWdtc7K0m5C0dJKVxPG0eI8EWZrEHYdfYe6WoTSDJCww+1qlBSpA5ac/Ba4Wn9vh+lR1vtUKkyIC/nrYb90ReUd385Glkgzrfh5HdR5y5S2cL/Frh86lAn9r6b3iWTJD8wBwXFyoe1S2nMTOIuG4RPNvfmyCTYVh8XTCCE8HPvh3xv2r4egawG1P4Q4UDwk+hDBXThY2KS8M5/8EMyxHV0ImpLbpYCTBA6KYDIRtqmgS6iKyy8v2D1aSY5mc9J0T5t9S2Gv+VZQNWQDDKNFnxqYaAo1uEoq/i1q63XC5AD3ckXb2VT6dp23BQMdDfbHyUWfJN + key_name: vfw_key + vfw_int_private1_ip_0: 192.168.10.100 + sdnc_model_version: 1.0.0 + int_private1_net_cidr: 192.168.10.0/24 + install_script_version: 1.6.0-SNAPSHOT + vfw_image_name: PUT THE VM IMAGE NAME HERE (UBUNTU 1404) + vfw_onap_private_ip_0: 10.0.100.1 + vpg_int_private1_ip_0: 192.168.10.200 + int_private2_net_id: zdfw1fwl01_protected + cloud_env: PUT openstack OR rackspace HERE + sdnc_model_name: vFW-CDS + int_private1_net_id: zdfw1fwl01_unprotected + capabilities: + abstract_vfw.cpu_vfw: + properties: + name: cpu + abstract_vpg.memory.resident_vpg: + properties: + name: memory.resident + abstract_vfw.disk.device.read.requests_vfw: + properties: + name: disk.device.read.requests + abstract_vpg.disk.write.bytes_vpg: + properties: + name: disk.write.bytes + abstract_vsn.disk.device.write.requests.rate_vsn: + properties: + name: disk.device.write.requests.rate + abstract_vpg.disk.usage_vpg: + properties: + name: disk.usage + abstract_vfw.network.outpoing.packets_vfw_vfw_int_private1_port_0: + properties: + name: network.outpoing.packets + abstract_vfw.disk.device.latency_vfw: + properties: + name: disk.device.latency + abstract_vfw.network.outpoing.packets_vfw_vfw_int_private2_port_0: + properties: + name: network.outpoing.packets + abstract_vfw.network.outpoing.packets_vfw_vfw_onap_private_port_0: + properties: + name: network.outpoing.packets + abstract_vsn.disk.device.write.bytes_vsn: + properties: + name: disk.device.write.bytes + abstract_vfw.disk.read.requests_vfw: + properties: + name: disk.read.requests + abstract_vsn.memory.resident_vsn: + properties: + name: memory.resident + abstract_vfw.network.incoming.bytes_vfw_vfw_int_private1_port_0: + properties: + name: network.incoming.bytes + abstract_vfw.network.incoming.bytes_vfw_vfw_int_private2_port_0: + properties: + name: network.incoming.bytes + abstract_vfw.memory_vfw: + properties: + name: memory + abstract_vsn.disk.device.read.requests.rate_vsn: + properties: + name: disk.device.read.requests.rate + abstract_vfw.disk.device.allocation_vfw: + properties: + name: disk.device.allocation + abstract_vsn.network.outgoing.packets.rate_vsn_vsn_int_private2_port_0: + properties: + name: network.outgoing.packets.rate + abstract_vfw.disk.root.size_vfw: + properties: + name: disk.root.size + abstract_vpg.network.incoming.packets_vpg_vpg_int_private1_port_0: + properties: + name: network.incoming.packets + abstract_vpg.network.outpoing.packets_vpg_vpg_onap_private_port_0: + properties: + name: network.outpoing.packets + abstract_vpg.memory.usage_vpg: + properties: + name: memory.usage + abstract_vfw.disk.capacity_vfw: + properties: + name: disk.capacity + abstract_vfw.disk.allocation_vfw: + properties: + name: disk.allocation + abstract_vpg.disk.iops_vpg: + properties: + name: disk.iops + abstract_vpg.disk.device.capacity_vpg: + properties: + name: disk.device.capacity + abstract_vsn.scalable_vsn: + properties: + max_instances: 1 + min_instances: 1 + abstract_vsn.network.incoming.packets.rate_vsn_vsn_int_private2_port_0: + properties: + name: network.incoming.packets.rate + abstract_vpg.disk.write.requests.rate_vpg: + properties: + name: disk.write.requests.rate + abstract_vsn.disk.read.bytes_vsn: + properties: + name: disk.read.bytes + abstract_vsn.network.incoming.bytes.rate_vsn_vsn_onap_private_port_0: + properties: + name: network.incoming.bytes.rate + abstract_vfw.disk.device.iops_vfw: + properties: + name: disk.device.iops + abstract_vsn.disk.device.write.requests_vsn: + properties: + name: disk.device.write.requests + abstract_vfw.disk.write.requests_vfw: + properties: + name: disk.write.requests + abstract_vpg.network.outgoing.bytes_vpg_vpg_int_private1_port_0: + properties: + name: network.outgoing.bytes + abstract_vsn.disk.device.allocation_vsn: + properties: + name: disk.device.allocation + abstract_vpg.disk.read.bytes_vpg: + properties: + name: disk.read.bytes + abstract_vsn.disk.ephemeral.size_vsn: + properties: + name: disk.ephemeral.size + abstract_vsn.cpu_vsn: + properties: + name: cpu + abstract_vpg.network.outgoing.packets.rate_vpg_vpg_onap_private_port_0: + properties: + name: network.outgoing.packets.rate + abstract_vpg.network.incoming.packets.rate_vpg_vpg_int_private1_port_0: + properties: + name: network.incoming.packets.rate + abstract_vsn.disk.write.bytes_vsn: + properties: + name: disk.write.bytes + abstract_vfw.network.incoming.packets.rate_vfw_vfw_int_private2_port_0: + properties: + name: network.incoming.packets.rate + abstract_vpg.disk.device.read.bytes_vpg: + properties: + name: disk.device.read.bytes + int_private1_network.end_point: + properties: + protocol: tcp + initiator: source + network_name: PRIVATE + secure: false + abstract_vfw.network.incoming.bytes.rate_vfw_vfw_int_private2_port_0: + properties: + name: network.incoming.bytes.rate + abstract_vfw.network.outgoing.bytes_vfw_vfw_onap_private_port_0: + properties: + name: network.outgoing.bytes + abstract_vpg.disk.capacity_vpg: + properties: + name: disk.capacity + abstract_vfw.network.outgoing.packets.rate_vfw_vfw_onap_private_port_0: + properties: + name: network.outgoing.packets.rate + abstract_vpg.disk.device.read.bytes.rate_vpg: + properties: + name: disk.device.read.bytes.rate + abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_onap_private_port_0: + properties: + name: network.outgoing.bytes.rate + abstract_vfw.disk.usage_vfw: + properties: + name: disk.usage + abstract_vsn.disk.device.latency_vsn: + properties: + name: disk.device.latency + abstract_vpg.scalable_vpg: + properties: + max_instances: 1 + min_instances: 1 + abstract_vsn.instance_vsn: + properties: + name: instance + abstract_vpg.disk.device.allocation_vpg: + properties: + name: disk.device.allocation + abstract_vsn.disk.write.requests_vsn: + properties: + name: disk.write.requests + abstract_vfw.disk.write.bytes_vfw: + properties: + name: disk.write.bytes + abstract_vsn.disk.device.read.bytes.rate_vsn: + properties: + name: disk.device.read.bytes.rate + abstract_vsn.vcpus_vsn: + properties: + name: vcpus + abstract_vfw.disk.device.write.bytes_vfw: + properties: + name: disk.device.write.bytes + abstract_vsn.network.outgoing.bytes.rate_vsn_vsn_onap_private_port_0: + properties: + name: network.outgoing.bytes.rate + abstract_vfw.network.incoming.packets.rate_vfw_vfw_onap_private_port_0: + properties: + name: network.incoming.packets.rate + abstract_vsn.cpu.delta_vsn: + properties: + name: cpu.delta + abstract_vfw.memory.resident_vfw: + properties: + name: memory.resident + abstract_vpg.disk.device.iops_vpg: + properties: + name: disk.device.iops + abstract_vsn.cpu_util_vsn: + properties: + name: cpu_util + abstract_vpg.network.incoming.packets.rate_vpg_vpg_onap_private_port_0: + properties: + name: network.incoming.packets.rate + abstract_vpg.network.outgoing.packets.rate_vpg_vpg_int_private1_port_0: + properties: + name: network.outgoing.packets.rate + abstract_vpg.disk.latency_vpg: + properties: + name: disk.latency + abstract_vsn.port_mirroring_vsn_vsn_onap_private_port_0: + properties: + connection_point: + network_role: + get_input: port_vsn_onap_private_port_0_network_role + nfc_naming_code: vsn + abstract_vpg.instance_vpg: + properties: + name: instance + abstract_vfw.network.incoming.bytes_vfw_vfw_onap_private_port_0: + properties: + name: network.incoming.bytes + abstract_vpg.port_mirroring_vpg_vpg_onap_private_port_0: + properties: + connection_point: + network_role: + get_input: port_vpg_onap_private_port_0_network_role + nfc_naming_code: vpg + abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_int_private2_port_0: + properties: + name: network.outgoing.bytes.rate + abstract_vfw.network.outgoing.packets.rate_vfw_vfw_int_private2_port_0: + properties: + name: network.outgoing.packets.rate + abstract_vfw.port_mirroring_vfw_vfw_onap_private_port_0: + properties: + connection_point: + network_role: + get_input: port_vfw_onap_private_port_0_network_role + nfc_naming_code: vfw + abstract_vfw.network.incoming.bytes.rate_vfw_vfw_onap_private_port_0: + properties: + name: network.incoming.bytes.rate + abstract_vpg.network.incoming.packets_vpg_vpg_onap_private_port_0: + properties: + name: network.incoming.packets + abstract_vsn.disk.allocation_vsn: + properties: + name: disk.allocation + abstract_vsn.disk.capacity_vsn: + properties: + name: disk.capacity + abstract_vpg.network.incoming.bytes_vpg_vpg_int_private1_port_0: + properties: + name: network.incoming.bytes + abstract_vpg.disk.allocation_vpg: + properties: + name: disk.allocation + abstract_vsn.disk.device.capacity_vsn: + properties: + name: disk.device.capacity + abstract_vfw.disk.device.read.requests.rate_vfw: + properties: + name: disk.device.read.requests.rate + abstract_vsn.disk.root.size_vsn: + properties: + name: disk.root.size + abstract_vsn.disk.usage_vsn: + properties: + name: disk.usage + abstract_vsn.disk.write.bytes.rate_vsn: + properties: + name: disk.write.bytes.rate + abstract_vsn.network.outgoing.bytes_vsn_vsn_int_private2_port_0: + properties: + name: network.outgoing.bytes + abstract_vsn.endpoint_vsn: + properties: + secure: true + abstract_vpg.disk.read.bytes.rate_vpg: + properties: + name: disk.read.bytes.rate + abstract_vpg.disk.device.read.requests_vpg: + properties: + name: disk.device.read.requests + abstract_vsn.network.outgoing.bytes_vsn_vsn_onap_private_port_0: + properties: + name: network.outgoing.bytes + abstract_vfw.disk.ephemeral.size_vfw: + properties: + name: disk.ephemeral.size + abstract_vpg.network.incoming.bytes.rate_vpg_vpg_onap_private_port_0: + properties: + name: network.incoming.bytes.rate + abstract_vpg.network.incoming.bytes.rate_vpg_vpg_int_private1_port_0: + properties: + name: network.incoming.bytes.rate + abstract_vpg.disk.read.requests_vpg: + properties: + name: disk.read.requests + abstract_vfw.network.incoming.packets_vfw_vfw_onap_private_port_0: + properties: + name: network.incoming.packets + abstract_vsn.disk.device.write.bytes.rate_vsn: + properties: + name: disk.device.write.bytes.rate + abstract_vpg.network.incoming.bytes_vpg_vpg_onap_private_port_0: + properties: + name: network.incoming.bytes + abstract_vpg.disk.device.write.bytes_vpg: + properties: + name: disk.device.write.bytes + abstract_vpg.network.outgoing.bytes.rate_vpg_vpg_int_private1_port_0: + properties: + name: network.outgoing.bytes.rate + abstract_vsn.memory.usage_vsn: + properties: + name: memory.usage + abstract_vsn.network.outgoing.bytes.rate_vsn_vsn_int_private2_port_0: + properties: + name: network.outgoing.bytes.rate + abstract_vsn.network.incoming.packets_vsn_vsn_int_private2_port_0: + properties: + name: network.incoming.packets + abstract_vsn.disk.device.iops_vsn: + properties: + name: disk.device.iops + abstract_vfw.network.incoming.packets_vfw_vfw_int_private1_port_0: + properties: + name: network.incoming.packets + abstract_vpg.disk.device.write.bytes.rate_vpg: + properties: + name: disk.device.write.bytes.rate + abstract_vsn.disk.device.read.bytes_vsn: + properties: + name: disk.device.read.bytes + abstract_vfw.disk.latency_vfw: + properties: + name: disk.latency + abstract_vsn.disk.read.requests_vsn: + properties: + name: disk.read.requests + abstract_vpg.endpoint_vpg: + properties: + secure: true + abstract_vsn.network.incoming.bytes.rate_vsn_vsn_int_private2_port_0: + properties: + name: network.incoming.bytes.rate + abstract_vsn.disk.device.read.requests_vsn: + properties: + name: disk.device.read.requests + abstract_vsn.memory_vsn: + properties: + name: memory + abstract_vfw.disk.device.read.bytes.rate_vfw: + properties: + name: disk.device.read.bytes.rate + abstract_vfw.disk.device.read.bytes_vfw: + properties: + name: disk.device.read.bytes + abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_int_private1_port_0: + properties: + name: network.outgoing.bytes.rate + abstract_vsn.network.incoming.packets.rate_vsn_vsn_onap_private_port_0: + properties: + name: network.incoming.packets.rate + abstract_vfw.network.outgoing.packets.rate_vfw_vfw_int_private1_port_0: + properties: + name: network.outgoing.packets.rate + abstract_vfw.disk.read.bytes_vfw: + properties: + name: disk.read.bytes + abstract_vfw.disk.read.bytes.rate_vfw: + properties: + name: disk.read.bytes.rate + abstract_vfw.endpoint_vfw: + properties: + secure: true + abstract_vfw.disk.device.write.requests_vfw: + properties: + name: disk.device.write.requests + int_private2_network.end_point: + properties: + protocol: tcp + initiator: source + network_name: PRIVATE + secure: false + abstract_vpg.cpu_util_vpg: + properties: + name: cpu_util + abstract_vfw.network.outgoing.bytes_vfw_vfw_int_private2_port_0: + properties: + name: network.outgoing.bytes + abstract_vpg.vcpus_vpg: + properties: + name: vcpus + abstract_vpg.cpu_vpg: + properties: + name: cpu + abstract_vsn.network.incoming.packets_vsn_vsn_onap_private_port_0: + properties: + name: network.incoming.packets + abstract_vpg.network.outpoing.packets_vpg_vpg_int_private1_port_0: + properties: + name: network.outpoing.packets + abstract_vfw.disk.write.requests.rate_vfw: + properties: + name: disk.write.requests.rate + abstract_vpg.disk.root.size_vpg: + properties: + name: disk.root.size + abstract_vsn.disk.write.requests.rate_vsn: + properties: + name: disk.write.requests.rate + abstract_vpg.disk.device.usage_vpg: + properties: + name: disk.device.usage + abstract_vpg.disk.device.write.requests.rate_vpg: + properties: + name: disk.device.write.requests.rate + abstract_vfw.disk.device.capacity_vfw: + properties: + name: disk.device.capacity + abstract_vsn.network.outgoing.packets.rate_vsn_vsn_onap_private_port_0: + properties: + name: network.outgoing.packets.rate + abstract_vfw.network.incoming.packets.rate_vfw_vfw_int_private1_port_0: + properties: + name: network.incoming.packets.rate + abstract_vfw.network.incoming.bytes.rate_vfw_vfw_int_private1_port_0: + properties: + name: network.incoming.bytes.rate + abstract_vfw.cpu_util_vfw: + properties: + name: cpu_util + abstract_vfw.cpu.delta_vfw: + properties: + name: cpu.delta + abstract_vfw.vcpus_vfw: + properties: + name: vcpus + abstract_vsn.disk.iops_vsn: + properties: + name: disk.iops + abstract_vfw.disk.iops_vfw: + properties: + name: disk.iops + abstract_vfw.disk.write.bytes.rate_vfw: + properties: + name: disk.write.bytes.rate + abstract_vfw.scalable_vfw: + properties: + max_instances: 1 + min_instances: 1 + abstract_vpg.memory_vpg: + properties: + name: memory + abstract_vpg.disk.ephemeral.size_vpg: + properties: + name: disk.ephemeral.size + abstract_vfw.memory.usage_vfw: + properties: + name: memory.usage + abstract_vpg.disk.device.read.requests.rate_vpg: + properties: + name: disk.device.read.requests.rate + abstract_vpg.disk.device.write.requests_vpg: + properties: + name: disk.device.write.requests + abstract_vpg.disk.device.latency_vpg: + properties: + name: disk.device.latency + abstract_vfw.disk.device.usage_vfw: + properties: + name: disk.device.usage + abstract_vfw.network.incoming.packets_vfw_vfw_int_private2_port_0: + properties: + name: network.incoming.packets + abstract_vfw.disk.device.write.bytes.rate_vfw: + properties: + name: disk.device.write.bytes.rate + abstract_vpg.cpu.delta_vpg: + properties: + name: cpu.delta + abstract_vsn.network.outpoing.packets_vsn_vsn_onap_private_port_0: + properties: + name: network.outpoing.packets + abstract_vfw.instance_vfw: + properties: + name: instance + abstract_vsn.disk.latency_vsn: + properties: + name: disk.latency + abstract_vsn.network.incoming.bytes_vsn_vsn_onap_private_port_0: + properties: + name: network.incoming.bytes + abstract_vpg.disk.write.bytes.rate_vpg: + properties: + name: disk.write.bytes.rate + abstract_vfw.network.outgoing.bytes_vfw_vfw_int_private1_port_0: + properties: + name: network.outgoing.bytes + abstract_vpg.network.outgoing.bytes_vpg_vpg_onap_private_port_0: + properties: + name: network.outgoing.bytes + abstract_vsn.network.incoming.bytes_vsn_vsn_int_private2_port_0: + properties: + name: network.incoming.bytes + abstract_vfw.disk.device.write.requests.rate_vfw: + properties: + name: disk.device.write.requests.rate + abstract_vpg.network.outgoing.bytes.rate_vpg_vpg_onap_private_port_0: + properties: + name: network.outgoing.bytes.rate + abstract_vpg.disk.write.requests_vpg: + properties: + name: disk.write.requests + abstract_vsn.disk.read.bytes.rate_vsn: + properties: + name: disk.read.bytes.rate + abstract_vsn.disk.device.usage_vsn: + properties: + name: disk.device.usage + abstract_vsn.network.outpoing.packets_vsn_vsn_int_private2_port_0: + properties: + name: network.outpoing.packets + groups: + vfwcds_vf0..VfwcdsVf..vsn..module-1: + type: org.openecomp.groups.VfModule + metadata: + vfModuleModelName: VfwcdsVf..vsn..module-1 + vfModuleModelInvariantUUID: f4b9d91b-75b7-4d14-98e5-907d8034fc4c + vfModuleModelUUID: fda666d1-d0d0-47c4-82cf-d02ee041a021 + vfModuleModelVersion: '1' + vfModuleModelCustomizationUUID: 34040775-31da-4034-9387-e2bb3fab236c + properties: + min_vf_module_instances: 0 + vf_module_label: vsn + vf_module_type: Expansion + isBase: false + initial_count: 0 + volume_group: false + vfwcds_vf0..VfwcdsVf..vpg..module-2: + type: org.openecomp.groups.VfModule + metadata: + vfModuleModelName: VfwcdsVf..vpg..module-2 + vfModuleModelInvariantUUID: 048e1738-4cc7-49d4-99a4-fb2d75e7a8db + vfModuleModelUUID: cfd79d7d-e63a-46ab-a765-b6753c781f32 + vfModuleModelVersion: '1' + vfModuleModelCustomizationUUID: d134bf7f-af50-4ea2-aa9c-1220497a6ec7 + properties: + min_vf_module_instances: 0 + vf_module_label: vpg + vf_module_type: Expansion + isBase: false + initial_count: 0 + volume_group: false + vfwcds_vf0..VfwcdsVf..vfw..module-3: + type: org.openecomp.groups.VfModule + metadata: + vfModuleModelName: VfwcdsVf..vfw..module-3 + vfModuleModelInvariantUUID: 09d24ca5-b3ef-4f82-a3ca-532e565929ed + vfModuleModelUUID: adf1eae4-0bb1-456e-8162-3cbc0554349f + vfModuleModelVersion: '1' + vfModuleModelCustomizationUUID: 2d9a63ad-d364-471f-a780-29140f3a2d62 + properties: + min_vf_module_instances: 0 + vf_module_label: vfw + vf_module_type: Expansion + isBase: false + initial_count: 0 + volume_group: false + vfwcds_vf0..VfwcdsVf..base_template..module-0: + type: org.openecomp.groups.VfModule + metadata: + vfModuleModelName: VfwcdsVf..base_template..module-0 + vfModuleModelInvariantUUID: 1b1c6c52-b506-4a37-9b83-8fddab20a0e5 + vfModuleModelUUID: 49dc98c5-2e56-4d37-a8bb-5f814b772a42 + vfModuleModelVersion: '1' + vfModuleModelCustomizationUUID: 4533702f-7d3f-4205-a5d4-d1b213ab8f2e + properties: + min_vf_module_instances: 1 + vf_module_label: base_template + max_vf_module_instances: 1 + vf_module_type: Base + isBase: true + initial_count: 1 + volume_group: false + substitution_mappings: + node_type: org.openecomp.service.VfwcdsService + capabilities: + vfwcds_vf0.abstract_vsn.instance_vsn: + - vfwcds_VF 0 + - abstract_vsn.instance_vsn + vfwcds_vf0.abstract_vsn.disk.device.read.bytes_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.device.read.bytes_vsn + vfwcds_vf0.abstract_vpg.memory.usage_vpg: + - vfwcds_VF 0 + - abstract_vpg.memory.usage_vpg + vfwcds_vf0.abstract_vsn.network.outpoing.packets_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.outpoing.packets_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vfw.attachment_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.attachment_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vsn.host_vsn: + - vfwcds_VF 0 + - abstract_vsn.host_vsn + vfwcds_vf0.abstract_vsn.network.outgoing.packets.rate_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.outgoing.packets.rate_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vfw.cpu.delta_vfw: + - vfwcds_VF 0 + - abstract_vfw.cpu.delta_vfw + vfwcds_vf0.abstract_vfw.network.incoming.packets_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.incoming.packets_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vfw.forwarder_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.forwarder_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vsn.disk.device.usage_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.device.usage_vsn + vfwcds_vf0.abstract_vfw.feature_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.feature_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vfw.feature_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.feature_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vpg.port_mirroring_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.port_mirroring_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vfw.disk.device.read.bytes_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.device.read.bytes_vfw + vfwcds_vf0.abstract_vpg.network.incoming.packets_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.incoming.packets_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vfw.forwarder_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.forwarder_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vfw.disk.latency_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.latency_vfw + vfwcds_vf0.abstract_vpg.memory.resident_vpg: + - vfwcds_VF 0 + - abstract_vpg.memory.resident_vpg + vfwcds_vf0.abstract_vfw.os_vfw: + - vfwcds_VF 0 + - abstract_vfw.os_vfw + vfwcds_vf0.abstract_vpg.instance_vpg: + - vfwcds_VF 0 + - abstract_vpg.instance_vpg + vfwcds_vf0.abstract_vfw.disk.read.bytes_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.read.bytes_vfw + vfwcds_vf0.abstract_vfw.feature_vfw: + - vfwcds_VF 0 + - abstract_vfw.feature_vfw + vfwcds_vf0.abstract_vfw.disk.device.read.requests_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.device.read.requests_vfw + vfwcds_vf0.int_private2_network.link: + - vfwcds_VF 0 + - int_private2_network.link + vfwcds_vf0.abstract_vpg.disk.device.latency_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.device.latency_vpg + vfwcds_vf0.abstract_vpg.network.outgoing.bytes.rate_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.outgoing.bytes.rate_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vsn.forwarder_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.forwarder_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vfw.binding_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.binding_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vfw.network.incoming.packets.rate_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.incoming.packets.rate_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vpg.disk.root.size_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.root.size_vpg + vfwcds_vf0.abstract_vfw.memory.resident_vfw: + - vfwcds_VF 0 + - abstract_vfw.memory.resident_vfw + vfwcds_vf0.abstract_vpg.forwarder_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.forwarder_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vsn.vcpus_vsn: + - vfwcds_VF 0 + - abstract_vsn.vcpus_vsn + vfwcds_vf0.abstract_vpg.disk.device.read.bytes.rate_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.device.read.bytes.rate_vpg + vfwcds_vf0.abstract_vfw.disk.device.write.bytes.rate_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.device.write.bytes.rate_vfw + vfwcds_vf0.abstract_vpg.vcpus_vpg: + - vfwcds_VF 0 + - abstract_vpg.vcpus_vpg + vfwcds_vf0.abstract_vpg.disk.device.write.requests.rate_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.device.write.requests.rate_vpg + vfwcds_vf0.abstract_vsn.disk.device.latency_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.device.latency_vsn + vfwcds_vf0.abstract_vfw.disk.root.size_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.root.size_vfw + vfwcds_vf0.abstract_vfw.disk.device.capacity_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.device.capacity_vfw + vfwcds_vf0.abstract_vsn.disk.device.read.bytes.rate_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.device.read.bytes.rate_vsn + vfwcds_vf0.abstract_vsn.disk.write.requests.rate_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.write.requests.rate_vsn + vfwcds_vf0.abstract_vsn.cpu.delta_vsn: + - vfwcds_VF 0 + - abstract_vsn.cpu.delta_vsn + vfwcds_vf0.abstract_vpg.disk.capacity_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.capacity_vpg + vfwcds_vf0.abstract_vpg.network.outgoing.bytes.rate_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.outgoing.bytes.rate_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vsn.os_vsn: + - vfwcds_VF 0 + - abstract_vsn.os_vsn + vfwcds_vf0.abstract_vfw.binding_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.binding_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vsn.disk.root.size_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.root.size_vsn + vfwcds_vf0.abstract_vsn.network.outgoing.bytes_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.outgoing.bytes_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vfw.network.incoming.packets.rate_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.incoming.packets.rate_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vfw.network.outpoing.packets_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.outpoing.packets_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vfw.scalable_vfw: + - vfwcds_VF 0 + - abstract_vfw.scalable_vfw + vfwcds_vf0.abstract_vpg.network.incoming.bytes_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.incoming.bytes_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vsn.feature_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.feature_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vsn.disk.device.capacity_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.device.capacity_vsn + vfwcds_vf0.abstract_vsn.binding_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.binding_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vfw.binding_vfw: + - vfwcds_VF 0 + - abstract_vfw.binding_vfw + vfwcds_vf0.abstract_vpg.os_vpg: + - vfwcds_VF 0 + - abstract_vpg.os_vpg + vfwcds_vf0.abstract_vpg.disk.device.write.bytes.rate_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.device.write.bytes.rate_vpg + vfwcds_vf0.abstract_vsn.cpu_util_vsn: + - vfwcds_VF 0 + - abstract_vsn.cpu_util_vsn + vfwcds_vf0.abstract_vpg.disk.write.bytes.rate_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.write.bytes.rate_vpg + vfwcds_vf0.abstract_vsn.disk.device.allocation_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.device.allocation_vsn + vfwcds_vf0.abstract_vfw.network.outgoing.packets.rate_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.outgoing.packets.rate_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vsn.network.outgoing.bytes.rate_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.outgoing.bytes.rate_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vpg.network.outpoing.packets_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.outpoing.packets_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vpg.disk.read.bytes.rate_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.read.bytes.rate_vpg + vfwcds_vf0.int_private1_network.link: + - vfwcds_VF 0 + - int_private1_network.link + vfwcds_vf0.abstract_vfw.disk.device.iops_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.device.iops_vfw + vfwcds_vf0.abstract_vsn.network.outgoing.bytes.rate_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.outgoing.bytes.rate_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vsn.disk.device.write.requests.rate_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.device.write.requests.rate_vsn + vfwcds_vf0.abstract_vpg.disk.allocation_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.allocation_vpg + vfwcds_vf0.abstract_vsn.binding_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.binding_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vfw.cpu_util_vfw: + - vfwcds_VF 0 + - abstract_vfw.cpu_util_vfw + vfwcds_vf0.abstract_vfw.network.outpoing.packets_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.outpoing.packets_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vsn.endpoint_vsn: + - vfwcds_VF 0 + - abstract_vsn.endpoint_vsn + vfwcds_vf0.abstract_vfw.network.outgoing.bytes_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.outgoing.bytes_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vsn.disk.device.write.requests_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.device.write.requests_vsn + vfwcds_vf0.abstract_vpg.network.incoming.packets.rate_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.incoming.packets.rate_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vsn.network.incoming.packets.rate_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.incoming.packets.rate_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vpg.network.incoming.bytes_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.incoming.bytes_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vfw.network.incoming.bytes_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.incoming.bytes_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vsn.forwarder_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.forwarder_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vsn.disk.device.iops_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.device.iops_vsn + vfwcds_vf0.abstract_vfw.disk.read.requests_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.read.requests_vfw + vfwcds_vf0.abstract_vpg.network.incoming.packets_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.incoming.packets_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vpg.feature_vpg: + - vfwcds_VF 0 + - abstract_vpg.feature_vpg + vfwcds_vf0.abstract_vsn.disk.capacity_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.capacity_vsn + vfwcds_vf0.abstract_vfw.disk.read.bytes.rate_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.read.bytes.rate_vfw + vfwcds_vf0.abstract_vsn.network.incoming.packets.rate_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.incoming.packets.rate_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vsn.scalable_vsn: + - vfwcds_VF 0 + - abstract_vsn.scalable_vsn + vfwcds_vf0.abstract_vpg.binding_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.binding_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vpg.disk.device.read.bytes_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.device.read.bytes_vpg + vfwcds_vf0.abstract_vpg.feature_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.feature_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vpg.disk.device.write.bytes_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.device.write.bytes_vpg + vfwcds_vf0.abstract_vpg.attachment_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.attachment_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vsn.disk.device.write.bytes_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.device.write.bytes_vsn + vfwcds_vf0.abstract_vpg.disk.device.read.requests_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.device.read.requests_vpg + vfwcds_vf0.abstract_vsn.feature_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.feature_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vsn.feature_vsn: + - vfwcds_VF 0 + - abstract_vsn.feature_vsn + vfwcds_vf0.abstract_vsn.disk.usage_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.usage_vsn + vfwcds_vf0.abstract_vfw.network.outgoing.bytes_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.outgoing.bytes_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vpg.network.outgoing.packets.rate_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.outgoing.packets.rate_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vfw.disk.device.usage_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.device.usage_vfw + vfwcds_vf0.int_private2_network.feature: + - vfwcds_VF 0 + - int_private2_network.feature + vfwcds_vf0.abstract_vfw.network.incoming.bytes.rate_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.incoming.bytes.rate_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vfw.network.incoming.bytes_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.incoming.bytes_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vpg.host_vpg: + - vfwcds_VF 0 + - abstract_vpg.host_vpg + vfwcds_vf0.abstract_vfw.disk.device.read.requests.rate_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.device.read.requests.rate_vfw + vfwcds_vf0.abstract_vfw.network.incoming.bytes.rate_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.incoming.bytes.rate_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vsn.disk.iops_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.iops_vsn + vfwcds_vf0.abstract_vfw.disk.write.bytes_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.write.bytes_vfw + vfwcds_vf0.abstract_vfw.attachment_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.attachment_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vfw.network.incoming.packets_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.incoming.packets_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vfw.cpu_vfw: + - vfwcds_VF 0 + - abstract_vfw.cpu_vfw + vfwcds_vf0.abstract_vpg.disk.device.read.requests.rate_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.device.read.requests.rate_vpg + vfwcds_vf0.int_private1_network.feature: + - vfwcds_VF 0 + - int_private1_network.feature + vfwcds_vf0.abstract_vpg.disk.iops_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.iops_vpg + vfwcds_vf0.abstract_vfw.disk.device.latency_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.device.latency_vfw + vfwcds_vf0.abstract_vpg.disk.latency_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.latency_vpg + vfwcds_vf0.abstract_vfw.feature_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.feature_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vfw.forwarder_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.forwarder_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vfw.instance_vfw: + - vfwcds_VF 0 + - abstract_vfw.instance_vfw + vfwcds_vf0.abstract_vfw.disk.capacity_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.capacity_vfw + vfwcds_vf0.abstract_vpg.network.incoming.bytes.rate_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.incoming.bytes.rate_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vpg.disk.device.write.requests_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.device.write.requests_vpg + vfwcds_vf0.abstract_vsn.network.outgoing.packets.rate_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.outgoing.packets.rate_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vfw.disk.write.bytes.rate_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.write.bytes.rate_vfw + vfwcds_vf0.abstract_vfw.network.incoming.packets_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.incoming.packets_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vsn.disk.read.bytes.rate_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.read.bytes.rate_vsn + vfwcds_vf0.abstract_vsn.memory.resident_vsn: + - vfwcds_VF 0 + - abstract_vsn.memory.resident_vsn + vfwcds_vf0.abstract_vpg.disk.write.requests_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.write.requests_vpg + vfwcds_vf0.int_private1_network.end_point: + - vfwcds_VF 0 + - int_private1_network.end_point + vfwcds_vf0.abstract_vfw.memory_vfw: + - vfwcds_VF 0 + - abstract_vfw.memory_vfw + vfwcds_vf0.abstract_vfw.attachment_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.attachment_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vpg.disk.write.bytes_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.write.bytes_vpg + vfwcds_vf0.abstract_vpg.cpu_vpg: + - vfwcds_VF 0 + - abstract_vpg.cpu_vpg + vfwcds_vf0.abstract_vpg.network.incoming.bytes.rate_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.incoming.bytes.rate_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vfw.network.outgoing.bytes_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.outgoing.bytes_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vfw.memory.usage_vfw: + - vfwcds_VF 0 + - abstract_vfw.memory.usage_vfw + vfwcds_vf0.abstract_vpg.attachment_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.attachment_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vsn.disk.read.bytes_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.read.bytes_vsn + vfwcds_vf0.abstract_vsn.disk.write.requests_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.write.requests_vsn + vfwcds_vf0.int_private2_network.end_point: + - vfwcds_VF 0 + - int_private2_network.end_point + vfwcds_vf0.abstract_vsn.disk.device.read.requests_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.device.read.requests_vsn + vfwcds_vf0.abstract_vfw.endpoint_vfw: + - vfwcds_VF 0 + - abstract_vfw.endpoint_vfw + vfwcds_vf0.abstract_vpg.network.outgoing.bytes_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.outgoing.bytes_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vpg.network.incoming.packets.rate_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.incoming.packets.rate_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vpg.network.outgoing.bytes_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.outgoing.bytes_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vsn.network.outpoing.packets_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.outpoing.packets_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vpg.binding_vpg: + - vfwcds_VF 0 + - abstract_vpg.binding_vpg + vfwcds_vf0.abstract_vpg.disk.usage_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.usage_vpg + vfwcds_vf0.abstract_vfw.disk.write.requests.rate_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.write.requests.rate_vfw + vfwcds_vf0.abstract_vsn.binding_vsn: + - vfwcds_VF 0 + - abstract_vsn.binding_vsn + vfwcds_vf0.abstract_vpg.disk.write.requests.rate_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.write.requests.rate_vpg + vfwcds_vf0.abstract_vsn.disk.latency_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.latency_vsn + vfwcds_vf0.abstract_vpg.memory_vpg: + - vfwcds_VF 0 + - abstract_vpg.memory_vpg + vfwcds_vf0.abstract_vsn.network.outgoing.bytes_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.outgoing.bytes_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vfw.network.outpoing.packets_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.outpoing.packets_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vfw.disk.device.read.bytes.rate_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.device.read.bytes.rate_vfw + vfwcds_vf0.abstract_vsn.disk.ephemeral.size_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.ephemeral.size_vsn + vfwcds_vf0.abstract_vfw.binding_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.binding_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vpg.feature_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.feature_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vpg.binding_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.binding_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vfw.disk.device.allocation_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.device.allocation_vfw + vfwcds_vf0.abstract_vpg.scalable_vpg: + - vfwcds_VF 0 + - abstract_vpg.scalable_vpg + vfwcds_vf0.abstract_vsn.attachment_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.attachment_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vpg.network.outpoing.packets_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.outpoing.packets_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vsn.memory.usage_vsn: + - vfwcds_VF 0 + - abstract_vsn.memory.usage_vsn + vfwcds_vf0.abstract_vsn.network.incoming.bytes.rate_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.incoming.bytes.rate_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vsn.memory_vsn: + - vfwcds_VF 0 + - abstract_vsn.memory_vsn + vfwcds_vf0.abstract_vpg.disk.device.iops_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.device.iops_vpg + vfwcds_vf0.int_private1_network.attachment: + - vfwcds_VF 0 + - int_private1_network.attachment + vfwcds_vf0.abstract_vfw.disk.ephemeral.size_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.ephemeral.size_vfw + vfwcds_vf0.abstract_vsn.network.incoming.bytes_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.incoming.bytes_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vsn.attachment_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.attachment_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vfw.disk.device.write.requests.rate_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.device.write.requests.rate_vfw + vfwcds_vf0.abstract_vsn.disk.allocation_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.allocation_vsn + vfwcds_vf0.abstract_vfw.network.incoming.bytes_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.incoming.bytes_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vfw.network.outgoing.packets.rate_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.outgoing.packets.rate_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vsn.network.incoming.bytes_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.incoming.bytes_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vpg.cpu_util_vpg: + - vfwcds_VF 0 + - abstract_vpg.cpu_util_vpg + vfwcds_vf0.abstract_vpg.disk.device.usage_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.device.usage_vpg + vfwcds_vf0.abstract_vfw.disk.usage_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.usage_vfw + vfwcds_vf0.abstract_vsn.disk.device.write.bytes.rate_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.device.write.bytes.rate_vsn + vfwcds_vf0.abstract_vpg.network.outgoing.packets.rate_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.network.outgoing.packets.rate_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vfw.disk.allocation_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.allocation_vfw + vfwcds_vf0.abstract_vfw.network.incoming.packets.rate_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.incoming.packets.rate_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vfw.network.incoming.bytes.rate_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.incoming.bytes.rate_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vsn.disk.read.requests_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.read.requests_vsn + vfwcds_vf0.abstract_vsn.network.incoming.bytes.rate_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.incoming.bytes.rate_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vfw.host_vfw: + - vfwcds_VF 0 + - abstract_vfw.host_vfw + vfwcds_vf0.abstract_vsn.network.incoming.packets_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.incoming.packets_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vpg.disk.read.requests_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.read.requests_vpg + vfwcds_vf0.abstract_vsn.disk.device.read.requests.rate_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.device.read.requests.rate_vsn + vfwcds_vf0.abstract_vfw.port_mirroring_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.port_mirroring_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vfw.disk.device.write.bytes_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.device.write.bytes_vfw + vfwcds_vf0.int_private2_network.attachment: + - vfwcds_VF 0 + - int_private2_network.attachment + vfwcds_vf0.abstract_vfw.network.outgoing.packets.rate_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.outgoing.packets.rate_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vfw.vcpus_vfw: + - vfwcds_VF 0 + - abstract_vfw.vcpus_vfw + vfwcds_vf0.abstract_vpg.disk.read.bytes_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.read.bytes_vpg + vfwcds_vf0.abstract_vpg.disk.ephemeral.size_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.ephemeral.size_vpg + vfwcds_vf0.abstract_vsn.network.incoming.packets_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.network.incoming.packets_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vpg.endpoint_vpg: + - vfwcds_VF 0 + - abstract_vpg.endpoint_vpg + vfwcds_vf0.abstract_vpg.forwarder_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.forwarder_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vfw.disk.iops_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.iops_vfw + vfwcds_vf0.abstract_vpg.cpu.delta_vpg: + - vfwcds_VF 0 + - abstract_vpg.cpu.delta_vpg + vfwcds_vf0.abstract_vpg.disk.device.capacity_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.device.capacity_vpg + vfwcds_vf0.abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.network.outgoing.bytes.rate_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vsn.cpu_vsn: + - vfwcds_VF 0 + - abstract_vsn.cpu_vsn + vfwcds_vf0.abstract_vpg.disk.device.allocation_vpg: + - vfwcds_VF 0 + - abstract_vpg.disk.device.allocation_vpg + vfwcds_vf0.abstract_vsn.disk.write.bytes_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.write.bytes_vsn + vfwcds_vf0.abstract_vsn.disk.write.bytes.rate_vsn: + - vfwcds_VF 0 + - abstract_vsn.disk.write.bytes.rate_vsn + vfwcds_vf0.abstract_vfw.disk.device.write.requests_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.device.write.requests_vfw + vfwcds_vf0.abstract_vsn.port_mirroring_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.port_mirroring_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vfw.disk.write.requests_vfw: + - vfwcds_VF 0 + - abstract_vfw.disk.write.requests_vfw + requirements: + vfwcds_vf0.abstract_vpg.dependency_vpg: + - vfwcds_VF 0 + - abstract_vpg.dependency_vpg + vfwcds_vf0.abstract_vpg.link_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.link_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vsn.local_storage_vsn: + - vfwcds_VF 0 + - abstract_vsn.local_storage_vsn + vfwcds_vf0.abstract_vfw.dependency_vfw: + - vfwcds_VF 0 + - abstract_vfw.dependency_vfw + vfwcds_vf0.abstract_vfw.local_storage_vfw: + - vfwcds_VF 0 + - abstract_vfw.local_storage_vfw + vfwcds_vf0.int_private2_network.dependency: + - vfwcds_VF 0 + - int_private2_network.dependency + vfwcds_vf0.int_private1_network.dependency: + - vfwcds_VF 0 + - int_private1_network.dependency + vfwcds_vf0.abstract_vfw.dependency_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.dependency_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vpg.dependency_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.dependency_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vfw.dependency_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.dependency_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vpg.dependency_vpg_vpg_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vpg.dependency_vpg_vpg_onap_private_port_0 + vfwcds_vf0.abstract_vsn.link_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.link_vsn_vsn_onap_private_port_0 + vfwcds_vf0.abstract_vpg.link_vpg_vpg_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vpg.link_vpg_vpg_int_private1_port_0 + vfwcds_vf0.abstract_vfw.link_vfw_vfw_int_private1_port_0: + - vfwcds_VF 0 + - abstract_vfw.link_vfw_vfw_int_private1_port_0 + vfwcds_vf0.abstract_vpg.local_storage_vpg: + - vfwcds_VF 0 + - abstract_vpg.local_storage_vpg + vfwcds_vf0.abstract_vfw.link_vfw_vfw_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vfw.link_vfw_vfw_int_private2_port_0 + vfwcds_vf0.abstract_vsn.dependency_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.dependency_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vsn.link_vsn_vsn_int_private2_port_0: + - vfwcds_VF 0 + - abstract_vsn.link_vsn_vsn_int_private2_port_0 + vfwcds_vf0.abstract_vsn.dependency_vsn: + - vfwcds_VF 0 + - abstract_vsn.dependency_vsn + vfwcds_vf0.abstract_vfw.link_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.link_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vfw.dependency_vfw_vfw_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vfw.dependency_vfw_vfw_onap_private_port_0 + vfwcds_vf0.abstract_vsn.dependency_vsn_vsn_onap_private_port_0: + - vfwcds_VF 0 + - abstract_vsn.dependency_vsn_vsn_onap_private_port_0 diff --git a/tests/data/test.csar b/tests/data/test.csar new file mode 100644 index 0000000..adedc40 Binary files /dev/null and b/tests/data/test.csar differ diff --git a/tests/data/test_so_service_data.yaml b/tests/data/test_so_service_data.yaml new file mode 100644 index 0000000..594ff17 --- /dev/null +++ b/tests/data/test_so_service_data.yaml @@ -0,0 +1,35 @@ +myservice: + subscription_service_type: myservice + vnfs: + - model_name: myvfmodel + instance_name: myfirstvnf + parameters: + param1: value1 + processing_priority: 1 + vf_modules: + - instance_name: mysecondvfm + model_name: base + processing_priority: 2 + parameters: + param-vfm1: value-vfm1 + - instance_name: myfirstvfm + model_name: base + processing_priority: 1 + parameters: + param-vfm1: value-vfm1 + - model_name: myvfmodel + instance_name: mysecondvnf + parameters: + param1: value1 + processing_priority: 2 + vf_modules: + - instance_name: myfirstvfm + model_name: base + processing_priority: 1 + parameters: + param-vfm1: value-vfm1 + - instance_name: mysecondvfm + model_name: base + processing_priority: 2 + parameters: + param-vfm1: value-vfm1 \ No newline at end of file diff --git a/tests/data/tests_settings.py b/tests/data/tests_settings.py new file mode 100644 index 0000000..09437b6 --- /dev/null +++ b/tests/data/tests_settings.py @@ -0,0 +1,15 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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. +AAI_URL = "http://tests.settings.py:1234" +TEST_VALUE = "test" diff --git a/tests/data/utils_load_json_file_test.json b/tests/data/utils_load_json_file_test.json new file mode 100644 index 0000000..3b4000d --- /dev/null +++ b/tests/data/utils_load_json_file_test.json @@ -0,0 +1 @@ +{"event":{"test1":"val1"}} diff --git a/tests/data/vLB_CBA_Python.zip b/tests/data/vLB_CBA_Python.zip new file mode 100755 index 0000000..ddd41ac Binary files /dev/null and b/tests/data/vLB_CBA_Python.zip differ diff --git a/tests/test_aai_bulk.py b/tests/test_aai_bulk.py new file mode 100644 index 0000000..0567055 --- /dev/null +++ b/tests/test_aai_bulk.py @@ -0,0 +1,90 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.aai.bulk import AaiBulk, AaiBulkRequest, AaiBulkResponse + + +BULK_RESPONSES = { + "operation-responses": [ + { + "action": "put", + "uri": "test-uri", + "response-status-code": 400, + "response-body": None + }, + { + "action": "post", + "uri": "test-uri", + "response-status-code": 201, + "response-body": "blabla" + } + ] +} + + +@mock.patch("onapsdk.aai.bulk.AaiBulk.send_message_json") +def test_aai_bulk(mock_send_message_json): + assert AaiBulk().url.endswith("bulk") + mock_send_message_json.return_value = BULK_RESPONSES + responses = list(AaiBulk.single_transaction( + [ + AaiBulkRequest( + action="post", + uri="test-uri", + body={"blabla: blabla"} + ), + AaiBulkRequest( + action="get", + uri="test-uri", + body={} + ) + ] + )) + assert len(responses) == 2 + resp_1, resp_2 = responses + assert resp_1.action == "put" + assert resp_1.uri == "test-uri" + assert resp_1.status_code == 400 + assert resp_1.body is None + assert resp_2.action == "post" + assert resp_2.uri == "test-uri" + assert resp_2.status_code == 201 + assert resp_2.body == "blabla" + + # Check if requests was splitted into chunks for generator + mock_send_message_json.reset_mock() + responses = list(AaiBulk.single_transaction( + ( + AaiBulkRequest( + action="post", + uri=f"test-uri-{i}", + body={"blabla: blabla"} + ) for i in range(31) + ) + )) + assert mock_send_message_json.call_count == 2 + + # Check if requests was splitted into chunks for list + mock_send_message_json.reset_mock() + responses = list(AaiBulk.single_transaction( + [ + AaiBulkRequest( + action="post", + uri=f"test-uri-{i}", + body={"blabla: blabla"} + ) for i in range(31) + ] + )) + assert mock_send_message_json.call_count == 2 diff --git a/tests/test_aai_cloud_region.py b/tests/test_aai_cloud_region.py new file mode 100644 index 0000000..a821090 --- /dev/null +++ b/tests/test_aai_cloud_region.py @@ -0,0 +1,71 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.aai.cloud_infrastructure.cloud_region import CloudRegion, Tenant +from onapsdk.exceptions import ResourceNotFound + + +COUNT = { + "results":[ + { + "cloud-region":2 + } + ] +} + + +@mock.patch("onapsdk.aai.cloud_infrastructure.cloud_region.AaiResource.relationships", new_callable=mock.PropertyMock) +@mock.patch("onapsdk.aai.cloud_infrastructure.cloud_region.Complex.get_by_physical_location_id") +def test_cloud_region_complex_property(mock_complex_get, mock_relationships): + cr = CloudRegion("test_cloud_owner", "test_cloud_region_id", False, False) + + mock_relationships.return_value = [] + assert cr.complex is None + + mock_relationships.return_value = [mock.MagicMock()] + assert cr.complex is None + + relationship_mock = mock.MagicMock() + relationship_mock.related_to = "complex" + relationship_mock.get_relationship_data.return_value = None + mock_relationships.return_value = [relationship_mock] + assert cr.complex is None + + relationship_mock.get_relationship_data.return_value = "123" + mock_complex_get.side_effect = ResourceNotFound + assert cr.complex is None + + mock_complex_get.side_effect = None + mock_complex_get.return_value = mock.MagicMock() + assert cr.complex is not None + + mock_relationships.side_effect = ResourceNotFound + assert cr.complex is None + +@mock.patch("onapsdk.aai.cloud_infrastructure.cloud_region.CloudRegion.tenants", new_callable=mock.PropertyMock) +def test_cloud_region_get_tenants_by_name(mock_tenants): + cr = CloudRegion("test_cloud_owner", "test_cloud_region_id", False, False) + mock_tenants.return_value = iter([ + Tenant(cloud_region="test_cloud_region_id",tenant_id="test-tenant",tenant_name="test-tenant") + ]) + tenants = list(cr.get_tenants_by_name("test-tenant")) + assert len(tenants) == 1 + assert isinstance(tenants[0], Tenant) + assert tenants[0].name == "test-tenant" + +@mock.patch("onapsdk.aai.cloud_infrastructure.cloud_region.CloudRegion.send_message_json") +def test_cloud_region_count(mock_send_message_json): + mock_send_message_json.return_value = COUNT + assert CloudRegion.count() == 2 diff --git a/tests/test_aai_complex.py b/tests/test_aai_complex.py new file mode 100644 index 0000000..13d2cf5 --- /dev/null +++ b/tests/test_aai_complex.py @@ -0,0 +1,138 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.aai.cloud_infrastructure import Complex +from onapsdk.aai.cloud_infrastructure import CloudRegion + + +COMPLEXES = { + "complex":[ + { + "physical-location-id":"integration_test_complex", + "data-center-code":"1234", + "complex-name":"integration_test_complex", + "identity-url":"", + "resource-version":"1588244056133", + "physical-location-type":"", + "street1":"", + "street2":"", + "city":"", + "state":"", + "postal-code":"", + "country":"", + "region":"", + "latitude":"", + "longitude":"", + "elevation":"", + "lata":"", + "time-zone":"", + "data-owner":"", + "data-source":"", + "data-source-version":"" + } + ] +} + + +COMPLEXES_COUNT = { + "results":[ + { + "complex":12 + } + ] +} + + +@mock.patch.object(Complex, "send_message") +def test_complex(mock_send_message): + cmplx = Complex(name="test_complex_name", + physical_location_id="test_location_id", + resource_version="1234") + assert cmplx.name == "test_complex_name" + assert cmplx.physical_location_id == "test_location_id" + assert cmplx.url == (f"{Complex.base_url}{Complex.api_version}/cloud-infrastructure/" + "complexes/complex/test_location_id") + + cmplx2 = Complex.create(name="test_complex_name", + physical_location_id="test_location_id") + mock_send_message.assert_called_once() + assert cmplx2.name == "test_complex_name" + assert cmplx2.physical_location_id == "test_location_id" + assert cmplx2.url == (f"{Complex.base_url}{Complex.api_version}/cloud-infrastructure/" + "complexes/complex/test_location_id") + method, _, url = mock_send_message.call_args[0] + assert method == "PUT" + assert url == (f"{Complex.base_url}{Complex.api_version}/cloud-infrastructure/" + "complexes/complex/test_location_id") + + +@mock.patch.object(Complex, "send_message_json") +def test_complex_get_all(mock_send_message_json): + mock_send_message_json.return_value = COMPLEXES + complexes = list(Complex.get_all()) + assert len(complexes) == 1 + cmplx = complexes[0] + assert cmplx.name == "integration_test_complex" + assert cmplx.physical_location_id == "integration_test_complex" + + +@mock.patch.object(CloudRegion, "add_relationship") +def test_cloud_region_link_to_complex(mock_add_rel): + """Test Cloud Region linking with Complex. + + Test Relationship object creation + """ + cloud_region = CloudRegion(cloud_owner="test_cloud_owner", + cloud_region_id="test_cloud_region", + orchestration_disabled=True, + in_maint=False) + cmplx = Complex(name="test_complex_name", + physical_location_id="test_location_id", + resource_version="1234") + cloud_region.link_to_complex(cmplx) + mock_add_rel.assert_called_once() + relationship = mock_add_rel.call_args[0][0] + assert relationship.related_to == "complex" + assert relationship.related_link == (f"aai/v13/cloud-infrastructure/complexes/" + f"complex/test_location_id") + assert len(relationship.relationship_data) == 2 + + +@mock.patch.object(Complex, "send_message_json") +def test_complex_get_by_physical_location_id(mock_send_message_json): + """Test complex get_by_physical_location_id url creation.""" + Complex.get_by_physical_location_id("test") + assert mock_send_message_json.called_once_with( + "GET", + "Get complex with physical location id: test", + f"{Complex.base_url}{Complex.api_version}/cloud-infrastructure/" + f"complexes/complex/test" + ) + +@mock.patch.object(Complex, "send_message") +def test_complex_delete(mock_send_message): + cmplx = Complex(physical_location_id="test_location_id", + resource_version="1234") + cmplx.delete() + mock_send_message.assert_called_once_with( + "DELETE", + "Delete test_location_id complex", + f"{cmplx.url}?resource-version={cmplx.resource_version}" + ) + +@mock.patch.object(Complex, "send_message_json") +def test_complex_count(mock_send_message_json): + mock_send_message_json.return_value = COMPLEXES_COUNT + assert Complex.count() == 12 diff --git a/tests/test_aai_customer.py b/tests/test_aai_customer.py new file mode 100644 index 0000000..7e427bd --- /dev/null +++ b/tests/test_aai_customer.py @@ -0,0 +1,580 @@ +"""Test A&AI Customer module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +import pytest + +from onapsdk.aai.business import Customer, ServiceSubscription +from onapsdk.aai.cloud_infrastructure import CloudRegion, Tenant +from onapsdk.msb.multicloud import Multicloud +from onapsdk.sdc.service import Service as SdcService +from onapsdk.exceptions import ParameterError, ResourceNotFound + + +SIMPLE_CUSTOMER = { + "customer": [ + { + "global-customer-id": "generic", + "subscriber-name": "generic", + "subscriber-type": "INFRA", + "resource-version": "1561218640404", + } + ] +} + + +SERVICE_SUBSCRIPTION = { + "service-subscription": [ + { + "service-type": "freeradius", + "resource-version": "1562591478146", + "relationship-list": { + "relationship": [ + { + "related-to": "tenant", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v16/cloud-infrastructure/cloud-regions/cloud-region/OPNFV/RegionOne/tenants/tenant/4bdc6f0f2539430f9428c852ba606808", + "relationship-data": [ + { + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": "OPNFV", + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": "RegionOne", + }, + { + "relationship-key": "tenant.tenant-id", + "relationship-value": "4bdc6f0f2539430f9428c852ba606808", + }, + ], + "related-to-property": [ + { + "property-key": "tenant.tenant-name", + "property-value": "onap-dublin-daily-vnfs", + } + ], + } + ] + }, + }, + {"service-type": "ims"}, + ] +} + + +CUSTOMERS = { + "customer": [ + { + "subscriber-name": "generic", + "subscriber-type": "INFRA", + "global-customer-id": "generic", + "resource-version": "1581510772967", + } + ] +} + + +SIMPLE_CUSTOMER_2 = { + "global-customer-id": "generic", + "subscriber-name": "generic", + "subscriber-type": "INFRA", + "resource-version": "1561218640404", +} + + +SERVICE_INSTANCES = { + "service-instance":[ + { + "service-instance-id":"5410bf79-2aa3-450e-a324-ec5630dc18cf", + "service-instance-name":"test", + "environment-context":"General_Revenue-Bearing", + "workload-context":"Production", + "model-invariant-id":"2a51a89b-6f94-4417-8831-c468fb30ed02", + "model-version-id":"92a82807-b483-4579-86b1-c79b1286aab4", + "resource-version":"1589457727708", + "orchestration-status":"Active", + "relationship-list":{ + "relationship":[ + { + "related-to":"owning-entity", + "relationship-label":"org.onap.relationships.inventory.BelongsTo", + "related-link":"/aai/v16/business/owning-entities/owning-entity/ff6c945f-89ab-4f14-bafd-0cdd6eac791a", + "relationship-data":[ + { + "relationship-key":"owning-entity.owning-entity-id", + "relationship-value":"ff6c945f-89ab-4f14-bafd-0cdd6eac791a" + } + ] + }, + { + "related-to":"project", + "relationship-label":"org.onap.relationships.inventory.Uses", + "related-link":"/aai/v16/business/projects/project/python_onap_sdk_project", + "relationship-data":[ + { + "relationship-key":"project.project-name", + "relationship-value":"python_onap_sdk_project" + } + ] + } + ] + } + } + ] +} + + +SERVICE_SUBSCRIPTION_RELATIONSHIPS = { + "relationship": [ + { + "related-to": "tenant", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v16/cloud-infrastructure/cloud-regions/cloud-region/OPNFV/RegionOne/tenants/tenant/4bdc6f0f2539430f9428c852ba606808", + "relationship-data": [ + { + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": "OPNFV", + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": "RegionOne", + }, + { + "relationship-key": "tenant.tenant-id", + "relationship-value": "4bdc6f0f2539430f9428c852ba606808", + }, + ], + "related-to-property": [ + { + "property-key": "tenant.tenant-name", + "property-value": "onap-dublin-daily-vnfs", + } + ], + } + ] +} + + +CLOUD_REGION = { + "cloud-region": [ + { + "cloud-owner": "OPNFV", + "cloud-region-id": "RegionOne", + "cloud-type": "openstack", + "owner-defined-type": "N/A", + "cloud-region-version": "pike", + "identity-url": "http://msb-iag.onap:80/api/multicloud-pike/v0/OPNFV_RegionOne/identity/v2.0", + "cloud-zone": "OPNFV LaaS", + "complex-name": "Cruguil", + "resource-version": "1561217827955", + "orchestration-disabled": True, + "in-maint": False, + "relationship-list": { + "relationship": [ + { + "related-to": "complex", + "relationship-label": "org.onap.relationships.inventory.LocatedIn", + "related-link": "/aai/v13/cloud-infrastructure/complexes/complex/cruguil", + "relationship-data": [ + { + "relationship-key": "complex.physical-location-id", + "relationship-value": "cruguil", + } + ], + } + ] + }, + } + ] +} + + +TENANT = { + "tenant-id": "4bdc6f0f2539430f9428c852ba606808", + "tenant-name": "onap-dublin-daily-vnfs", + "resource-version": "1562591004273", + "relationship-list": { + "relationship": [ + { + "related-to": "service-subscription", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v16/business/customers/customer/generic/service-subscriptions/service-subscription/freeradius", + "relationship-data": [ + { + "relationship-key": "customer.global-customer-id", + "relationship-value": "generic", + }, + { + "relationship-key": "service-subscription.service-type", + "relationship-value": "freeradius", + }, + ], + }, + { + "related-to": "service-subscription", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v16/business/customers/customer/generic/service-subscriptions/service-subscription/ims", + "relationship-data": [ + { + "relationship-key": "customer.global-customer-id", + "relationship-value": "generic", + }, + { + "relationship-key": "service-subscription.service-type", + "relationship-value": "ims", + }, + ], + }, + { + "related-to": "service-subscription", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v16/business/customers/customer/generic/service-subscriptions/service-subscription/ubuntu16", + "relationship-data": [ + { + "relationship-key": "customer.global-customer-id", + "relationship-value": "generic", + }, + { + "relationship-key": "service-subscription.service-type", + "relationship-value": "ubuntu16", + }, + ], + }, + ] + }, +} + + +CUSTOMERS_COUNT = { + "results":[ + { + "customer":12 + } + ] +} + + +@mock.patch.object(Customer, 'send_message_json') +def test_customer_service_tenant_relations(mock_send): + """Test the retrieval of service/tenant relations in A&AI.""" + mock_send.return_value = SIMPLE_CUSTOMER + customer = next(Customer.get_all()) + mock_send.return_value = SERVICE_SUBSCRIPTION + res = list(customer.service_subscriptions) + assert len(res) == 2 + assert res[0].service_type == "freeradius" + + +@mock.patch.object(Customer, "send_message_json") +def test_customers_get_all(mock_send): + """Test get_all Customer class method.""" + mock_send.return_value = {} + customers = list(Customer.get_all()) + assert len(customers) == 0 + + mock_send.return_value = CUSTOMERS + customers = list(Customer.get_all()) + assert len(customers) == 1 + + +@mock.patch.object(Customer, "send_message_json") +def test_customer_get_service_subscription_by_service_type(mock_send): + """Test Customer's get_service_subscription_by_service_type method.""" + mock_send.return_value = CUSTOMERS + customer = next(Customer.get_all()) + + mock_send.return_value = SERVICE_SUBSCRIPTION + service_subscription = customer.get_service_subscription_by_service_type("freeradius") + assert service_subscription.service_type == "freeradius" + + +@mock.patch.object(Customer, "send_message_json") +@mock.patch.object(ServiceSubscription, "send_message_json") +def test_customer_service_subscription_service_instance(mock_send_serv_sub, mock_send): + """Test Customer's service subscription service instances.""" + mock_send.return_value = CUSTOMERS + customer = next(Customer.get_all()) + mock_send.return_value = SERVICE_SUBSCRIPTION + service_subscription = customer.get_service_subscription_by_service_type("freeradius") + + mock_send_serv_sub.return_value = SERVICE_INSTANCES + service_instances = list(service_subscription.service_instances) + assert len(service_instances) == 1 + service_instance = service_instances[0] + assert service_instance.instance_name == "test" + assert service_instance.instance_id == "5410bf79-2aa3-450e-a324-ec5630dc18cf" + assert service_instance.service_subscription == service_subscription + assert service_instance.url == (f"{service_subscription.url}/service-instances/" + f"service-instance/{service_instance.instance_id}") + + +@mock.patch.object(Customer, "send_message_json") +@mock.patch.object(ServiceSubscription, "send_message_json") +@mock.patch.object(CloudRegion, "send_message_json") +def test_customer_service_subscription_cloud_region(mock_cloud_region, mock_send_serv_sub, mock_send): + """Test Customer's service subscription cloud region object.""" + mock_send.return_value = CUSTOMERS + customer = next(Customer.get_all()) + mock_send.return_value = SERVICE_SUBSCRIPTION + service_subscription = customer.get_service_subscription_by_service_type("freeradius") + + mock_send_serv_sub.return_value = {} + relationships = list(service_subscription.relationships) + assert len(relationships) == 0 + with pytest.raises(ParameterError): + service_subscription.cloud_region + with pytest.raises(ParameterError): + service_subscription.tenant + with pytest.raises(StopIteration): + next(service_subscription.cloud_regions) + with pytest.raises(StopIteration): + next(service_subscription.tenants) + + mock_cloud_region.return_value = CLOUD_REGION + mock_send_serv_sub.return_value = SERVICE_SUBSCRIPTION_RELATIONSHIPS + relationships = list(service_subscription.relationships) + assert len(relationships) == 1 + cloud_region = next(service_subscription.cloud_regions) + assert cloud_region.cloud_owner == "OPNFV" + assert cloud_region.cloud_region_id == "RegionOne" + assert cloud_region.cloud_type == "openstack" + + mock_cloud_region.side_effect = ResourceNotFound + with pytest.raises(StopIteration): + next(service_subscription.tenants) + mock_cloud_region.side_effect = [CLOUD_REGION, TENANT] + tenant = next(service_subscription.tenants) + assert tenant.tenant_id == "4bdc6f0f2539430f9428c852ba606808" + assert tenant.name == "onap-dublin-daily-vnfs" + + +@mock.patch.object(Customer, "send_message_json") +def test_customer_get_by_global_customer_id(mock_send): + """Test Customer's get_by_global_customer_id method.""" + mock_send.return_value = SIMPLE_CUSTOMER_2 + customer = Customer.get_by_global_customer_id("generic") + assert customer.global_customer_id == "generic" + assert customer.subscriber_name == "generic" + assert customer.subscriber_type == "INFRA" + assert customer.resource_version is not None + + +@mock.patch.object(Customer, "send_message") +@mock.patch.object(Customer, "send_message_json") +def test_customer_create(mock_send_json, mock_send): + """Test Customer's create method.""" + mock_send_json.return_value = SIMPLE_CUSTOMER_2 + customer = Customer.create("generic", "generic", "INFRA") + assert customer.global_customer_id == "generic" + assert customer.subscriber_name == "generic" + assert customer.subscriber_type == "INFRA" + assert customer.resource_version is not None + + customer = Customer.create("generic", "generic", "INFRA", service_subscriptions=["test-service-type"]) + assert customer.global_customer_id == "generic" + assert customer.subscriber_name == "generic" + assert customer.subscriber_type == "INFRA" + assert customer.resource_version is not None + + +@mock.patch.object(Customer, "send_message") +def test_customer_delete(mock_send): + """Test Customer's delete method.""" + customer = Customer("test", "test", "test", "test") + customer.delete() + mock_send.assert_called_once_with( + "DELETE", + "Delete customer", + customer.url + ) + + +def test_customer_url(): + """Test Customer's url property.""" + customer = Customer("generic", "generic", "INFRA") + assert customer.url == (f"{customer.base_url}{customer.api_version}/business/customers/" + f"customer/{customer.global_customer_id}?" + f"resource-version={customer.resource_version}") + + +@mock.patch.object(ServiceSubscription, "add_relationship") +def test_service_subscription_link_cloud_region_and_tenant(mock_add_rel): + """Test service subscription linking with cloud region and tenant. + + Test Relationship object creation + """ + service_subscription = ServiceSubscription(customer=None, + service_type="test_service_type", + resource_version="test_resource_version") + cloud_region = CloudRegion(cloud_owner="test_cloud_owner", + cloud_region_id="test_cloud_region", + orchestration_disabled=True, + in_maint=False) + tenant = Tenant(cloud_region=cloud_region, + tenant_id="test_tenant_id", + tenant_name="test_tenant_name") + service_subscription.link_to_cloud_region_and_tenant(cloud_region, tenant) + mock_add_rel.assert_called_once() + relationship = mock_add_rel.call_args[0][0] + assert relationship.related_to == "tenant" + assert relationship.related_link == tenant.url + assert len(relationship.relationship_data) == 3 + + +@mock.patch.object(Customer, "send_message_json") +@mock.patch.object(Customer, "send_message") +def test_customer_subscribe_service(mock_send_message, mock_send_message_json): + customer = Customer(global_customer_id="test_customer_id", + subscriber_name="test_subscriber_name", + subscriber_type="test_subscriber_type") + mock_send_message_json.side_effect = (ResourceNotFound, SERVICE_SUBSCRIPTION) + customer.subscribe_service("test_service") + + +#test the Cloud Region Class +AVAILABILITY_ZONE = { + "availability-zone-name":"OPNFV LaaS", + "hypervisor-type":"1234", + "operational-status":"working", + "resource-version":"version1.0" +} + +AVAILABILITY_ZONES = { + "availability-zone":[ + { + "availability-zone-name":"OPNFV LaaS", + "hypervisor-type":"1234", + "operational-status":"working", + "resource-version":"version1.0" + } + ] +} + + +@mock.patch.object(CloudRegion, "send_message_json") +def test_availability_zones(mock_send_message_json): + """Test Cloud Region property""" + cloud_region = CloudRegion(cloud_owner="test_cloud_owner", + cloud_region_id="test_cloud_region", + orchestration_disabled=True, + in_maint=False) + mock_send_message_json.return_value = AVAILABILITY_ZONES + cloud_zones = cloud_region.availability_zones + zone1 = next(cloud_zones) + assert zone1.name == "OPNFV LaaS" + assert zone1.hypervisor_type == "1234" + +@mock.patch.object(CloudRegion, "send_message_json") +def test_get_availability_zone_from_name(mock_send_message_json): + """Test get Availability Zone by name""" + cloud_region = CloudRegion(cloud_owner="test_cloud_owner", + cloud_region_id="test_cloud_region", + orchestration_disabled=True, + in_maint=False) + mock_send_message_json.return_value = AVAILABILITY_ZONE + availability_zone = cloud_region.get_availability_zone_by_name("OPNFV LaaS") + assert availability_zone.name == "OPNFV LaaS" + assert availability_zone.hypervisor_type == "1234" + assert availability_zone.resource_version == "version1.0" + +@mock.patch.object(CloudRegion, "send_message") +def test_add_availability_zone(mock_send_message): + """Test Cloud Region class method""" + cloud_region = CloudRegion(cloud_owner="test_cloud_owner", + cloud_region_id="test_cloud_region", + orchestration_disabled=True, + in_maint=False) + cloud_region.add_availability_zone(availability_zone_name="test_zone", + availability_zone_hypervisor_type="1234") + mock_send_message.assert_called_once() + method, description, url = mock_send_message.call_args[0] + assert method == "PUT" + assert description == "Add availability zone to cloud region" + assert url == f"{cloud_region.url}/availability-zones/availability-zone/test_zone" + +@mock.patch.object(CloudRegion, "send_message") +def test_add_tenant_to_cloud(mock_send_message): + """Test Cloud Region class method""" + cloud_region = CloudRegion(cloud_owner="test_cloud_owner", + cloud_region_id="test_cloud_region", + orchestration_disabled=True, + in_maint=False) + cloud_region.add_tenant(tenant_id="123456", tenant_name="test_tenant") + mock_send_message.assert_called_once() + method, description, url = mock_send_message.call_args[0] + assert method == "PUT" + assert description == "add tenant to cloud region" + assert url == f"{cloud_region.url}/tenants/tenant/123456" + + +@mock.patch.object(CloudRegion, "send_message") +def test_add_esr_system_info(mock_send_message): + """Test Cloud Region class method""" + cloud_region = CloudRegion(cloud_owner="test_cloud_owner", + cloud_region_id="test_cloud_region", + orchestration_disabled=True, + in_maint=False) + cloud_region.add_esr_system_info(esr_system_info_id="123456", + user_name="test_user", + password="password", + system_type="test_type") + mock_send_message.assert_called_once() + method, description, url = mock_send_message.call_args[0] + assert method == "PUT" + assert description == "Add external system info to cloud region" + assert url == f"{cloud_region.url}/esr-system-info-list/esr-system-info/123456" + + +@mock.patch.object(Multicloud, "register_vim") +def test_register_to_multicloud(mock_register): + """Test register to multicloud""" + cloud_region = CloudRegion(cloud_owner="test_cloud_owner", + cloud_region_id="test_cloud_region", + orchestration_disabled=True, + in_maint=False) + cloud_region.register_to_multicloud() + mock_register.assert_called_once() + + +@mock.patch.object(Multicloud, "unregister_vim") +def test_unregister_from_multicloud(mock_unregister): + """Test register to multicloud""" + cloud_region = CloudRegion(cloud_owner="test_cloud_owner", + cloud_region_id="test_cloud_region", + orchestration_disabled=True, + in_maint=False) + cloud_region.unregister_from_multicloud() + mock_unregister.assert_called_once() + + +@mock.patch.object(CloudRegion, "send_message") +def test_delete_cloud_region(mock_send_message): + cloud_region = CloudRegion(cloud_owner="test_cloud_owner", + cloud_region_id="test_cloud_region", + orchestration_disabled=True, + in_maint=False) + cloud_region.delete() + mock_send_message.assert_called_once() + method, descritption, url = mock_send_message.call_args[0] + assert method == "DELETE" + assert descritption == f"Delete cloud region test_cloud_region" + assert url == cloud_region.url + +@mock.patch.object(Customer, "send_message_json") +def test_customer_count(mock_send_message_json): + mock_send_message_json.return_value = CUSTOMERS_COUNT + assert Customer.count() == 12 + diff --git a/tests/test_aai_geo_region.py b/tests/test_aai_geo_region.py new file mode 100644 index 0000000..b33f77a --- /dev/null +++ b/tests/test_aai_geo_region.py @@ -0,0 +1,54 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest.mock import patch + +from onapsdk.aai.cloud_infrastructure.geo_region import GeoRegion + +GEO_REGIONS = { + "geo-region": [ + { + "geo-region-id": "123" + }, + { + "geo-region-id": "321" + } + ] +} + +GEO_REGION = { + "geo-region-id": "123", + "resource-version": "123" +} + +@patch("onapsdk.aai.cloud_infrastructure.geo_region.GeoRegion.send_message_json") +def test_geo_region_get_all(mock_send_message_json): + mock_send_message_json.return_value = {} + assert len(list(GeoRegion.get_all())) == 0 + + mock_send_message_json.return_value = GEO_REGIONS + assert len(list(GeoRegion.get_all())) == 2 + +@patch("onapsdk.aai.cloud_infrastructure.geo_region.GeoRegion.send_message_json") +def test_geo_region_get_by_region_id(mock_send_message_json): + mock_send_message_json.return_value = GEO_REGION + geo_region = GeoRegion.get_by_geo_region_id("123") + assert geo_region.geo_region_id == "123" + assert geo_region.resource_version == "123" + +@patch("onapsdk.aai.cloud_infrastructure.geo_region.GeoRegion.send_message") +@patch("onapsdk.aai.cloud_infrastructure.geo_region.GeoRegion.get_by_geo_region_id") +def test_geo_region_create(mock_get_geo_region_by_id, mock_send_message): + GeoRegion.create("123") + mock_send_message.assert_called_once() + assert mock_get_geo_region_by_id.called_once_with("123") diff --git a/tests/test_aai_line_of_business.py b/tests/test_aai_line_of_business.py new file mode 100644 index 0000000..1bf4672 --- /dev/null +++ b/tests/test_aai_line_of_business.py @@ -0,0 +1,82 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.aai.business.line_of_business import LineOfBusiness + + +LINES_OF_BUSINESS = { + "line-of-business": [ + { + "line-of-business-name": "test-name", + "resource-version": "1234" + }, + { + "line-of-business-name": "test-name2", + "resource-version": "4321" + } + ] +} + + +COUNT = { + "results":[ + { + "line-of-business":1 + } + ] +} + + +@mock.patch("onapsdk.aai.business.line_of_business.LineOfBusiness.send_message_json") +def test_line_of_business_get_all(mock_send_message_json): + mock_send_message_json.return_value = {} + assert len(list(LineOfBusiness.get_all())) == 0 + + mock_send_message_json.return_value = LINES_OF_BUSINESS + lines_of_business = list(LineOfBusiness.get_all()) + assert len(lines_of_business) == 2 + lob1, lob2 = lines_of_business + assert lob1.name == "test-name" + assert lob1.resource_version == "1234" + assert lob2.name == "test-name2" + assert lob2.resource_version == "4321" + + +@mock.patch("onapsdk.aai.business.line_of_business.LineOfBusiness.send_message_json") +def test_line_of_business_get_by_name(mock_send): + LineOfBusiness.get_by_name(name="test-name") + mock_send.assert_called_once_with("GET", + "Get test-name line of business", + "https://aai.api.sparky.simpledemo.onap.org:30233/aai/v23/business/lines-of-business/line-of-business/test-name") + + +@mock.patch("onapsdk.aai.business.line_of_business.LineOfBusiness.send_message") +@mock.patch("onapsdk.aai.business.line_of_business.LineOfBusiness.get_by_name") +def test_line_of_business_create(_, mock_send): + LineOfBusiness.create(name="test-name") + mock_send.assert_called_once_with("PUT", + "Declare A&AI line of business", + "https://aai.api.sparky.simpledemo.onap.org:30233/aai/v23/business/lines-of-business/line-of-business/test-name", + data='{\n "line-of-business-name": "test-name"\n}') + + +@mock.patch("onapsdk.aai.business.line_of_business.LineOfBusiness.send_message_json") +def test_line_of_business_count(mock_send_message_json): + mock_send_message_json.return_value = COUNT + assert LineOfBusiness.count() == 1 + +def test_line_of_business_url(): + line_of_business = LineOfBusiness(name="test-lob", resource_version="123") + assert line_of_business.name in line_of_business.url diff --git a/tests/test_aai_network.py b/tests/test_aai_network.py new file mode 100644 index 0000000..7196d5a --- /dev/null +++ b/tests/test_aai_network.py @@ -0,0 +1,159 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.aai.business import NetworkInstance +from onapsdk.so.deletion import NetworkDeletionRequest + + +NETWORK_INSTANCE = { + 'network-id': '49dab38b-3a5b-47e5-9cd6-b8d069d6109d', + 'network-name': 'Python_ONAP_SDK_network_instance_0b4308ca-3fe0-4af1-9c4e-ed2c816b9496', + 'is-bound-to-vpn': False, + 'resource-version': '1593162237842', + 'orchestration-status': 'Inventoried', + 'model-invariant-id': 'cdbb2169-e638-4aab-a4e9-b9d2d6d62b04', + 'model-version-id': '51789f7b-5ffc-4c12-ac87-02363fdb40b1', + 'model-customization-id': 'db9c9a6c-2a1c-4cdd-8fbc-e10448d0e4cc', + 'is-provider-network': False, + 'is-shared-network': False, + 'is-external-network': False, + 'relationship-list': { + 'relationship': [ + { + 'related-to': 'service-instance', + 'relationship-label': 'org.onap.relationships.inventory.ComposedOf', + 'related-link': '/aai/v19/business/customers/customer/TestCustomer/service-subscriptions/service-subscription/vFW_with_net/service-instances/service-instance/72fd9ee9-077f-4d3d-8e86-08ed24514802', + 'relationship-data': [ + { + 'relationship-key': 'customer.global-customer-id', + 'relationship-value': 'TestCustomer' + }, + { + 'relationship-key': 'service-subscription.service-type', + 'relationship-value': 'vFW_with_net' + }, + { + 'relationship-key': 'service-instance.service-instance-id', + 'relationship-value': '72fd9ee9-077f-4d3d-8e86-08ed24514802' + } + ], + 'related-to-property': [ + { + 'property-key': 'service-instance.service-instance-name', + 'property-value': 'Python_ONAP_SDK_service_instance_7be66d06-c466-46cf-b84a-cd7af2d633ed' + } + ] + }, + { + 'related-to': 'cloud-region', + 'relationship-label': 'org.onap.relationships.inventory.Uses', + 'related-link': '/aai/v19/cloud-infrastructure/cloud-regions/cloud-region/TestCloudOwner/RegionOne', + 'relationship-data': [ + { + 'relationship-key': 'cloud-region.cloud-owner', + 'relationship-value': 'TestCloudOwner' + }, + { + 'relationship-key': 'cloud-region.cloud-region-id', + 'relationship-value': 'RegionOne' + } + ], + 'related-to-property': [ + { + 'property-key': 'cloud-region.owner-defined-type', + 'property-value': '' + } + ] + }, + { + 'related-to': 'line-of-business', + 'relationship-label': 'org.onap.relationships.inventory.Uses', + 'related-link': '/aai/v19/business/lines-of-business/line-of-business/Test-BusinessLine', + 'relationship-data': [ + { + 'relationship-key': 'line-of-business.line-of-business-name', + 'relationship-value': 'Test-BusinessLine' + } + ] + }, + { + 'related-to': 'tenant', + 'relationship-label': 'org.onap.relationships.inventory.Uses', + 'related-link': '/aai/v19/cloud-infrastructure/cloud-regions/cloud-region/TestCloudOwner/RegionOne/tenants/tenant/89788fdf49514f94963b12a6c0cfdc71', + 'relationship-data': [ + { + 'relationship-key': 'cloud-region.cloud-owner', + 'relationship-value': 'TestCloudOwner' + }, + { + 'relationship-key': 'cloud-region.cloud-region-id', + 'relationship-value': 'RegionOne' + }, + { + 'relationship-key': 'tenant.tenant-id', + 'relationship-value': '89788fdf49514f94963b12a6c0cfdc71' + } + ], + 'related-to-property': [ + { + 'property-key': 'tenant.tenant-name', + 'property-value': 'test-tenant' + } + ] + }, + { + 'related-to': 'platform', + 'relationship-label': 'org.onap.relationships.inventory.Uses', + 'related-link': '/aai/v19/business/platforms/platform/Test-Platform', + 'relationship-data': [ + { + 'relationship-key': 'platform.platform-name', + 'relationship-value': 'Test-Platform' + } + ] + } + ] + } +} + + +def test_create_network_instance_from_api_response(): + service_instance = mock.MagicMock() + network_instance = NetworkInstance.create_from_api_response( + NETWORK_INSTANCE, + service_instance + ) + assert network_instance.network_name == "Python_ONAP_SDK_network_instance_0b4308ca-3fe0-4af1-9c4e-ed2c816b9496" + assert network_instance.network_id == "49dab38b-3a5b-47e5-9cd6-b8d069d6109d" + assert network_instance.is_bound_to_vpn is False + assert network_instance.is_provider_network is False + assert network_instance.is_shared_network is False + assert network_instance.is_external_network is False + assert network_instance.resource_version == "1593162237842" + assert network_instance.model_invariant_id == "cdbb2169-e638-4aab-a4e9-b9d2d6d62b04" + assert network_instance.model_version_id == "51789f7b-5ffc-4c12-ac87-02363fdb40b1" + assert network_instance.model_customization_id == "db9c9a6c-2a1c-4cdd-8fbc-e10448d0e4cc" + + +@mock.patch.object(NetworkDeletionRequest, "send_message_json") +def test_network_instance_delete(mock_send_message_json): + network_instance = NetworkInstance(mock.MagicMock(), + network_id="test_network_id", + is_bound_to_vpn=True, + is_provider_network=False, + is_shared_network=True, + is_external_network=False) + network_instance.delete() + mock_send_message_json.assert_called_once() diff --git a/tests/test_aai_owning_entity.py b/tests/test_aai_owning_entity.py new file mode 100644 index 0000000..0f16044 --- /dev/null +++ b/tests/test_aai_owning_entity.py @@ -0,0 +1,87 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +import pytest + +from onapsdk.aai.business import OwningEntity +from onapsdk.exceptions import ResourceNotFound + + +OWNING_ENTITIES = { + "owning-entity":[ + { + "owning-entity-id":"ff6c945f-89ab-4f14-bafd-0cdd6eac791a", + "owning-entity-name":"OE-Generic", + "resource-version":"1588244348931", + }, + { + "owning-entity-id":"OE-generic", + "owning-entity-name":"OE-generic", + "resource-version":"1587388597761" + }, + { + "owning-entity-id":"b3dcdbb0-edae-4384-b91e-2f114472520c" + ,"owning-entity-name":"test", + "resource-version":"1588145971158" + } + ] +} + + +OWNING_ENTITY = { + "owning-entity-id":"OE-generic", + "owning-entity-name":"OE-generic", + "resource-version":"1587388597761" +} + + +@mock.patch.object(OwningEntity, "send_message_json") +def test_owning_entity_get_all(mock_send): + mock_send.return_value = OWNING_ENTITIES + owning_entities = list(OwningEntity.get_all()) + assert len(owning_entities) == 3 + owning_entity = owning_entities[0] + assert owning_entity.owning_entity_id == "ff6c945f-89ab-4f14-bafd-0cdd6eac791a" + assert owning_entity.name == "OE-Generic" + assert owning_entity.url == (f"{owning_entity.base_url}{owning_entity.api_version}/" + "business/owning-entities/owning-entity/" + f"{owning_entity.owning_entity_id}") + + +@mock.patch.object(OwningEntity, "send_message_json") +def test_owning_entity_get_by_name(mock_send): + mock_send.return_value = OWNING_ENTITIES + with pytest.raises(ResourceNotFound) as exc: + OwningEntity.get_by_owning_entity_name("invalid name") + assert exc.type == ResourceNotFound + owning_entity = OwningEntity.get_by_owning_entity_name("OE-Generic") + assert owning_entity.owning_entity_id == "ff6c945f-89ab-4f14-bafd-0cdd6eac791a" + assert owning_entity.name == "OE-Generic" + + +@mock.patch.object(OwningEntity, "send_message") +@mock.patch.object(OwningEntity, "send_message_json") +def test_owning_entity_create(mock_send_json, mock_send): + mock_send_json.return_value = OWNING_ENTITY + OwningEntity.create( + name="OE-generic", + ) + + owning_entity = OwningEntity.create( + name="OE-generic", + owning_entity_id="OE-generic" + ) + assert owning_entity.owning_entity_id == "OE-generic" + assert owning_entity.name == "OE-generic" diff --git a/tests/test_aai_platform.py b/tests/test_aai_platform.py new file mode 100644 index 0000000..ed20e50 --- /dev/null +++ b/tests/test_aai_platform.py @@ -0,0 +1,82 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.aai.business.platform import Platform + + +PLATFORMS = { + "platform": [ + { + "platform-name": "test-name", + "resource-version": "1234" + }, + { + "platform-name": "test-name2", + "resource-version": "4321" + } + ] +} + + +COUNT = { + "results":[ + { + "platform":1 + } + ] +} + + +@mock.patch("onapsdk.aai.business.platform.Platform.send_message_json") +def test_platform_get_all(mock_send_message_json): + mock_send_message_json.return_value = {} + assert len(list(Platform.get_all())) == 0 + + mock_send_message_json.return_value = PLATFORMS + platforms = list(Platform.get_all()) + assert len(platforms) == 2 + lob1, lob2 = platforms + assert lob1.name == "test-name" + assert lob1.resource_version == "1234" + assert lob2.name == "test-name2" + assert lob2.resource_version == "4321" + + +@mock.patch("onapsdk.aai.business.platform.Platform.send_message_json") +def test_platform_get_by_name(mock_send): + Platform.get_by_name(name="test-name") + mock_send.assert_called_once_with("GET", + "Get test-name platform", + "https://aai.api.sparky.simpledemo.onap.org:30233/aai/v23/business/platforms/platform/test-name") + + +@mock.patch("onapsdk.aai.business.platform.Platform.send_message") +@mock.patch("onapsdk.aai.business.platform.Platform.get_by_name") +def test_platform_create(_, mock_send): + Platform.create(name="test-name") + mock_send.assert_called_once_with("PUT", + "Declare A&AI platform", + "https://aai.api.sparky.simpledemo.onap.org:30233/aai/v23/business/platforms/platform/test-name", + data='{\n "platform-name": "test-name"\n}') + + +@mock.patch("onapsdk.aai.business.platform.Platform.send_message_json") +def test_line_of_business_count(mock_send_message_json): + mock_send_message_json.return_value = COUNT + assert Platform.count() == 1 + +def test_platform_url(): + platform = Platform(name="test-platform", resource_version="123") + assert platform.name in platform.url diff --git a/tests/test_aai_pnf.py b/tests/test_aai_pnf.py new file mode 100644 index 0000000..32e9ea7 --- /dev/null +++ b/tests/test_aai_pnf.py @@ -0,0 +1,140 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +import pytest + +from onapsdk.aai.business import PnfInstance, pnf +from onapsdk.exceptions import ResourceNotFound +# from onapsdk.so.deletion import NetworkDeletionRequest + + +PNF_INSTANCE = { + "pnf-name": "blablabla", + "pnf-id": "546b282b-2ff7-41a4-9329-55c9a2888477", + "equip-type": "pnf", + "equip-vendor": "PNF", + "equip-model": "Simulated Device", + "orchestration-status": "Active", + "ipaddress-v4-oam": "172.30.1.6", + "sw-version": "2.3.5", + "in-maint":False, + "serial-number": "123", + "ipaddress-v6-oam": "0:0:0:0:0:ffff:a0a:011", + "resource-version": "1610142659380", + "nf-role": "sdn controller", + "model-customization-id": "137ce8e8-bee9-465f-b7e1-0c006f10b443", + "model-invariant-id": "2ca7ea68-cf61-449c-a733-8122bcac1f9a", + "model-version-id": "da467f24-a26d-4620-b185-e1afa1d365ac", + "relationship-list": { + "relationship":[ + { + "related-to":"service-instance", + "relationship-label":"org.onap.relationships.inventory.ComposedOf", + "related-link":"/aai/v21/business/customers/customer/test/service-subscriptions/service-subscription/test/service-instances/service-instance/4c3ab996-afdb-4956-9c4d-038b4eed3db1", + "relationship-data":[ + { + "relationship-key":"customer.global-customer-id", + "relationship-value":"test" + }, + { + "relationship-key":"service-subscription.service-type", + "relationship-value":"test" + }, + { + "relationship-key":"service-instance.service-instance-id", + "relationship-value":"4c3ab996-afdb-4956-9c4d-038b4eed3db1" + } + ], + "related-to-property":[ + { + "property-key":"service-instance.service-instance-name", + "property-value":"blablabla" + } + ] + } + ] + } +} + + +COUNT = { + "results":[ + { + "pnf":12 + } + ] +} + + +def test_create_pnf_instance_from_api_response(): + service_instance = mock.MagicMock() + pnf_instance = PnfInstance.create_from_api_response( + PNF_INSTANCE, + service_instance + ) + assert pnf_instance.pnf_name == "blablabla" + assert pnf_instance.pnf_id == "546b282b-2ff7-41a4-9329-55c9a2888477" + assert pnf_instance.equip_type == "pnf" + assert pnf_instance.equip_vendor == "PNF" + assert pnf_instance.equip_model == "Simulated Device" + assert pnf_instance.orchestration_status == "Active" + assert pnf_instance.ipaddress_v4_oam == "172.30.1.6" + assert pnf_instance.sw_version == "2.3.5" + assert pnf_instance.in_maint == False + assert pnf_instance.serial_number == "123" + assert pnf_instance.ipaddress_v6_oam == "0:0:0:0:0:ffff:a0a:011" + assert pnf_instance.resource_version == "1610142659380" + assert pnf_instance.nf_role == "sdn controller" + assert pnf_instance.model_customization_id == "137ce8e8-bee9-465f-b7e1-0c006f10b443" + assert pnf_instance.model_invariant_id == "2ca7ea68-cf61-449c-a733-8122bcac1f9a" + assert pnf_instance.model_version_id == "da467f24-a26d-4620-b185-e1afa1d365ac" + + assert pnf_instance.url.endswith(pnf_instance.pnf_name) + + +@mock.patch.object(PnfInstance, "send_message") +def test_delete_pnf_instance(mock_send_message): + pnf = PnfInstance(mock.MagicMock, "test_pnf", False) + pnf.delete() + method, _, address = mock_send_message.call_args[0] + assert method == "DELETE" + assert address == f"{pnf.url}?resource-version={pnf.resource_version}" + + +def test_pnf_instance_pnf(): + service_instance = mock.MagicMock() + pnf_instance = PnfInstance.create_from_api_response( + PNF_INSTANCE, + service_instance + ) + + assert pnf_instance._pnf is None + service_instance.sdc_service.pnfs = [] + with pytest.raises(ResourceNotFound) as exc: + pnf_instance.pnf + assert exc.type == ResourceNotFound + assert pnf_instance._pnf is None + + pnf = mock.MagicMock() + pnf.model_version_id = "da467f24-a26d-4620-b185-e1afa1d365ac" + service_instance.sdc_service.pnfs = [pnf] + assert pnf == pnf_instance.pnf + assert pnf_instance._pnf is not None + assert pnf_instance.pnf == pnf_instance._pnf + +@mock.patch.object(PnfInstance, "send_message_json") +def test_pnf_count(mock_send_message_json): + mock_send_message_json.return_value = COUNT + assert PnfInstance.count() == 12 diff --git a/tests/test_aai_project.py b/tests/test_aai_project.py new file mode 100644 index 0000000..4186772 --- /dev/null +++ b/tests/test_aai_project.py @@ -0,0 +1,82 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.aai.business.project import Project + + +PROJECTS = { + "project": [ + { + "project-name": "test-name", + "resource-version": "1234" + }, + { + "project-name": "test-name2", + "resource-version": "4321" + } + ] +} + + +COUNT = { + "results":[ + { + "project":1 + } + ] +} + + +@mock.patch("onapsdk.aai.business.project.Project.send_message_json") +def test_project_get_all(mock_send_message_json): + mock_send_message_json.return_value = {} + assert len(list(Project.get_all())) == 0 + + mock_send_message_json.return_value = PROJECTS + projects = list(Project.get_all()) + assert len(projects) == 2 + lob1, lob2 = projects + assert lob1.name == "test-name" + assert lob1.resource_version == "1234" + assert lob2.name == "test-name2" + assert lob2.resource_version == "4321" + + +@mock.patch("onapsdk.aai.business.project.Project.send_message_json") +def test_project_get_by_name(mock_send): + Project.get_by_name(name="test-name") + mock_send.assert_called_once_with("GET", + "Get test-name project", + "https://aai.api.sparky.simpledemo.onap.org:30233/aai/v23/business/projects/project/test-name") + + +@mock.patch("onapsdk.aai.business.project.Project.send_message") +@mock.patch("onapsdk.aai.business.project.Project.get_by_name") +def test_project_create(_, mock_send): + Project.create(name="test-name") + mock_send.assert_called_once_with("PUT", + "Declare A&AI project", + "https://aai.api.sparky.simpledemo.onap.org:30233/aai/v23/business/projects/project/test-name", + data='{\n "project-name": "test-name"\n}') + + +@mock.patch("onapsdk.aai.business.project.Project.send_message_json") +def test_project_count(mock_send_message_json): + mock_send_message_json.return_value = COUNT + assert Project.count() == 1 + +def test_project_url(): + project = Project(name="test-project", resource_version="123") + assert project.name in project.url diff --git a/tests/test_aai_resource.py b/tests/test_aai_resource.py new file mode 100644 index 0000000..c1da0b7 --- /dev/null +++ b/tests/test_aai_resource.py @@ -0,0 +1,60 @@ +"""Test A&AI Element.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 pytest +from unittest import mock + +from onapsdk.aai.aai_element import AaiResource, Relationship +from onapsdk.exceptions import RequestError, ResourceNotFound, RelationshipNotFound +from onapsdk.utils.gui import GuiList + +@mock.patch.object(AaiResource, "send_message_json") +@mock.patch.object(AaiResource, "url") +def test_relationship_not_found(mock_send, mock_url): + + aai_element = AaiResource() + mock_url.return_value = "http://my.url/" + + mock_send.side_effect = ResourceNotFound + + aai_element.send_message_json = mock_send + + with pytest.raises(ResourceNotFound) as exc: + list(aai_element.relationships) + assert exc.type == RelationshipNotFound + + mock_send.assert_called_once() + + +def test_relationship_get_relationship_data(): + r = Relationship( + related_to="test", + related_link="test", + relationship_data=[{ + "relationship-key": "test", + "relationship-value": "test" + }] + ) + assert r.get_relationship_data("invalid key") is None + assert r.get_relationship_data("test") == "test" + +@mock.patch.object(AaiResource, "send_message") +def test_get_guis(send_message_mock): + component = AaiResource() + send_message_mock.return_value.status_code = 200 + send_message_mock.return_value.url = "https://aai.api.sparky.simpledemo.onap.org:30220/services/aai/webapp/index.html#/browse" + gui_results = component.get_guis() + assert type(gui_results) == GuiList + assert gui_results.guilist[0].url == send_message_mock.return_value.url + assert gui_results.guilist[0].status == send_message_mock.return_value.status_code diff --git a/tests/test_aai_service.py b/tests/test_aai_service.py new file mode 100644 index 0000000..aac4c59 --- /dev/null +++ b/tests/test_aai_service.py @@ -0,0 +1,770 @@ +"""Test AaiElement module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +import pytest + +from onapsdk.aai.aai_element import AaiElement, AaiResource, Relationship +from onapsdk.aai.cloud_infrastructure import ( + CloudRegion, + Complex, + EsrSystemInfo, + Tenant +) +from onapsdk.aai.business import Customer +from onapsdk.aai.service_design_and_creation import Service, Model +from onapsdk.onap_service import OnapService + + +# pylint: disable=C0301 +TENANT = { + "tenant": [ + { + "tenant-id": "4bdc6f0f2539430f9428c852ba606808", + "tenant-name": "onap-dublin-daily-vnfs", + "resource-version": "1562591004273", + "relationship-list": { + "relationship": [ + { + "related-to": "service-subscription", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v16/business/customers/customer/generic/service-subscriptions/service-subscription/freeradius", + "relationship-data": [ + { + "relationship-key": "customer.global-customer-id", + "relationship-value": "generic", + }, + { + "relationship-key": "service-subscription.service-type", + "relationship-value": "freeradius", + }, + ], + }, + { + "related-to": "service-subscription", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v16/business/customers/customer/generic/service-subscriptions/service-subscription/ims", + "relationship-data": [ + { + "relationship-key": "customer.global-customer-id", + "relationship-value": "generic", + }, + { + "relationship-key": "service-subscription.service-type", + "relationship-value": "ims", + }, + ], + }, + { + "related-to": "service-subscription", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v16/business/customers/customer/generic/service-subscriptions/service-subscription/ubuntu16", + "relationship-data": [ + { + "relationship-key": "customer.global-customer-id", + "relationship-value": "generic", + }, + { + "relationship-key": "service-subscription.service-type", + "relationship-value": "ubuntu16", + }, + ], + }, + ] + }, + } + ] +} + + +CLOUD_REGIONS = { + "cloud-region": [ + { + "cloud-owner": "OPNFV", + "cloud-region-id": "RegionOne", + "cloud-type": "openstack", + "owner-defined-type": "N/A", + "cloud-region-version": "pike", + "identity-url": "http://msb-iag.onap:80/api/multicloud-pike/v0/OPNFV_RegionOne/identity/v2.0", + "cloud-zone": "OPNFV LaaS", + "complex-name": "Cruguil", + "resource-version": "1561217827955", + "orchestration-disabled": False, + "in-maint": False, + "relationship-list": { + "relationship": [ + { + "related-to": "complex", + "relationship-label": "org.onap.relationships.inventory.LocatedIn", + "related-link": "/aai/v13/cloud-infrastructure/complexes/complex/cruguil", + "relationship-data": [ + { + "relationship-key": "complex.physical-location-id", + "relationship-value": "cruguil", + } + ], + } + ] + }, + } + ] +} + + +CLOUD_REGION = { + "cloud-region": [ + { + "cloud-owner": "OPNFV", + "cloud-region-id": "RegionOne", + "cloud-type": "openstack", + "owner-defined-type": "N/A", + "cloud-region-version": "pike", + "identity-url": "http://msb-iag.onap:80/api/multicloud-pike/v0/OPNFV_RegionOne/identity/v2.0", + "cloud-zone": "OPNFV LaaS", + "complex-name": "Cruguil", + "resource-version": "1561217827955", + "orchestration-disabled": True, + "in-maint": False, + "relationship-list": { + "relationship": [ + { + "related-to": "complex", + "relationship-label": "org.onap.relationships.inventory.LocatedIn", + "related-link": "/aai/v13/cloud-infrastructure/complexes/complex/cruguil", + "relationship-data": [ + { + "relationship-key": "complex.physical-location-id", + "relationship-value": "cruguil", + } + ], + } + ] + }, + } + ] +} + + +COMPLEXES = { + "complex": [ + { + "city": "", + "data-center-code": "1234", + "street1": "", + "street2": "", + "physical-location-id": "integration_test_complex", + "identity-url": "", + "lata": "", + "elevation": "", + "state": "", + "physical-location-type": "", + "longitude": "", + "relationship-list": { + "relationship": [ + { + "related-to-property": [ + { + "property-value": "OwnerType", + "property-key": "cloud-region.owner-defined-type", + } + ], + "relationship-label": "org.onap.relationships.inventory.LocatedIn", + "related-link": "/aai/v16/cloud-infrastructure/cloud-regions/cloud-region/CloudOwner/RegionOne", + "relationship-data": [ + { + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": "CloudOwner", + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": "RegionOne", + }, + ], + "related-to": "cloud-region", + } + ] + }, + "resource-version": "1581510773583", + "latitude": "", + "complex-name": "integration_test_complex", + "postal-code": "", + "country": "", + "region": "", + }, + { + "city": "Beijing", + "data-center-code": "example-data-center-code-val-5556", + "street1": "example-street1-val-34205", + "street2": "example-street2-val-99210", + "physical-location-id": "My_Complex", + "identity-url": "example-identity-url-val-56898", + "lata": "example-lata-val-46073", + "elevation": "example-elevation-val-30253", + "state": "example-state-val-59487", + "physical-location-type": "example-physical-location-type-val-7608", + "longitude": "106.4074", + "resource-version": "1581504768889", + "latitude": "39.9042", + "complex-name": "My_Complex", + "postal-code": "100000", + "country": "example-country-val-94173", + "region": "example-region-val-13893", + }, + ] +} + + +CLOUD_REGION_RELATIONSHIP = { + "relationship": [ + { + "relationship-label": "org.onap.relationships.inventory.LocatedIn", + "related-link": "/aai/v16/cloud-infrastructure/complexes/complex/integration_test_complex", + "relationship-data": [ + { + "relationship-key": "complex.physical-location-id", + "relationship-value": "integration_test_complex", + } + ], + "related-to": "complex", + } + ] +} + + +SERVICE_SUBSCRIPTION = { + "service-subscription": [ + { + "service-type": "freeradius", + "resource-version": "1562591478146", + "relationship-list": { + "relationship": [ + { + "related-to": "tenant", + "relationship-label": "org.onap.relationships.inventory.Uses", + "related-link": "/aai/v16/cloud-infrastructure/cloud-regions/cloud-region/OPNFV/RegionOne/tenants/tenant/4bdc6f0f2539430f9428c852ba606808", + "relationship-data": [ + { + "relationship-key": "cloud-region.cloud-owner", + "relationship-value": "OPNFV", + }, + { + "relationship-key": "cloud-region.cloud-region-id", + "relationship-value": "RegionOne", + }, + { + "relationship-key": "tenant.tenant-id", + "relationship-value": "4bdc6f0f2539430f9428c852ba606808", + }, + ], + "related-to-property": [ + { + "property-key": "tenant.tenant-name", + "property-value": "onap-dublin-daily-vnfs", + } + ], + } + ] + }, + }, + {"service-type": "ims"}, + ] +} + + +SUBSCRIPTION_TYPES_NO_RESOURCES = { + "requestError": { + "serviceException": { + "messageId": "SVC3001", + "text": ("Resource not found for %1 using id " + "%2 (msg=%3) +(ec=%4)"), + "variables": [ + "GET", + "service-design-and-creation/services", + ( + "Node Not Found:No Node of type service found at: " + + "/service-design-and-creation/services" + ), + "ERR.5.4.6114", + ], + } + } +} + + +SUBSCRIPTION_TYPES_LIST = { + "service": [ + { + "service-id": "f4bcf0b0-b44e-423a-8357-5758afc14e88", + "service-description": "ubuntu16", + "resource-version": "1561218639393", + }, + { + "service-id": "2e812e77-e437-46c4-8e8e-908fbc7e176c", + "service-description": "freeradius", + "resource-version": "1561219163076", + }, + { + "service-id": "f208de57-0e02-4505-a0fa-375b13ad24ac", + "service-description": "ims", + "resource-version": "1561219799684", + }, + ] +} + + +CUSTOMERS_NO_RESOURCES = { + "requestError": { + "serviceException": { + "messageId": "SVC3001", + "text": ("Resource not found for %1 using id " + "%2 (msg=%3) +(ec=%4)"), + "variables": [ + "GET", + "business/customers", + ( + "Node Not Found:No Node of type customer found at: " + + "business/customers" + ), + "ERR.5.4.6114", + ], + } + } +} + + +SIMPLE_CUSTOMER = { + "customer": [ + { + "global-customer-id": "generic", + "subscriber-name": "generic", + "subscriber-type": "INFRA", + "resource-version": "1561218640404", + } + ] +} + + +ESR_SYSTEM_INFO = { + 'esr-system-info': [ + { + 'esr-system-info-id': 'c2d5e75d-56fd-47bc-af31-95607b26fa93', + 'service-url': 'http://keystone:5000/v3', + 'user-name': 'test-devel', + 'password': 'test-devel', + 'system-type': 'openstack', + 'cloud-domain': 'Default', + 'resource-version': '1586436352654' + } + ] +} + + +CLOUD_REGIONS_ITERATOR = ( + cloud_region + for cloud_region in [ + CloudRegion( + cloud_owner="OPNFV", + cloud_region_id="RegionOne", + cloud_type="openstack", + owner_defined_type="N/A", + cloud_region_version="pike", + identity_url=None, + cloud_zone="OPNFV LaaS", + complex_name="Cruguil", + sriov_automation=None, + cloud_extra_info=None, + upgrade_cycle=None, + orchestration_disabled=False, + in_maint=False, + resource_version=None, + ) + ] +) +# pylint: enable=C0301 + + +def test_init(): + """Test the initialization.""" + element = AaiElement() + assert isinstance(element, OnapService) + + +def test_class_variables(): + """Test the class variables.""" + assert AaiElement.server == "AAI" + assert AaiElement.base_url == "https://aai.api.sparky.simpledemo.onap.org:30233" + assert AaiElement.headers == { + "Content-Type": "application/json", + "Accept": "application/json", + "x-fromappid": "AAI", + "x-transactionid": "0a3f6713-ba96-4971-a6f8-c2da85a3176e", + "authorization": "Basic QUFJOkFBSQ=="} + +@mock.patch.object(AaiElement, 'send_message_json') +def test_customers(mock_send): + """Test get_customer function of A&AI.""" + mock_send.return_value = SIMPLE_CUSTOMER + assert len(list(Customer.get_all())) == 1 + aai_customer_1 = next(Customer.get_all()) + assert aai_customer_1.global_customer_id == "generic" + assert aai_customer_1.subscriber_name == "generic" + assert aai_customer_1.subscriber_type == "INFRA" + assert aai_customer_1.resource_version == "1561218640404" + mock_send.assert_called_with("GET", 'get customers', mock.ANY) + +@mock.patch.object(AaiElement, 'send_message_json') +def test_customers_no_resources(mock_send): + """Test get_customer function with no customer declared in A&AI.""" + mock_send.return_value = CUSTOMERS_NO_RESOURCES + assert len(list(Customer.get_all())) == 0 + mock_send.assert_called_with("GET", 'get customers', mock.ANY) + +@mock.patch.object(AaiElement, 'send_message_json') +def test_subscription_type_list(mock_send): + """Test the getter of subscription types in A&AI.""" + mock_send.return_value = {} + assert len(list(Service.get_all())) == 0 + assert len(list(Service.get_all())) == 0 + + mock_send.return_value = SUBSCRIPTION_TYPES_LIST + assert len(list(Service.get_all())) == 3 + assert len(list(Service.get_all())) == 3 + subscriptions = Service.get_all() + aai_service_1 = next(subscriptions) + aai_service_2 = next(subscriptions) + aai_service_3 = next(subscriptions) + assert aai_service_1.service_id == "f4bcf0b0-b44e-423a-8357-5758afc14e88" + assert aai_service_1.service_description == "ubuntu16" + assert aai_service_1.resource_version == "1561218639393" + assert aai_service_2.service_id == "2e812e77-e437-46c4-8e8e-908fbc7e176c" + assert aai_service_2.service_description == "freeradius" + assert aai_service_2.resource_version == "1561219163076" + assert aai_service_3.service_id == "f208de57-0e02-4505-a0fa-375b13ad24ac" + assert aai_service_3.service_description == "ims" + assert aai_service_3.resource_version == "1561219799684" + mock_send.assert_called_with("GET", 'get subscriptions', mock.ANY) + +@mock.patch.object(AaiElement, 'send_message_json') +def test_subscription_types_no_resources(mock_send): + """Test get_customer function with no customer declared in A&AI.""" + mock_send.return_value = SUBSCRIPTION_TYPES_NO_RESOURCES + assert len(list(Service.get_all())) == 0 + mock_send.assert_called_with("GET", 'get subscriptions', mock.ANY) + +@mock.patch.object(AaiElement, 'send_message_json') +def test_cloud_regions(mock_send): + """Test get cloud regions from A&AI.""" + mock_send.return_value = CLOUD_REGION + assert len(list(CloudRegion.get_all())) == 1 + cloud_region = next(CloudRegion.get_all()) + assert cloud_region.cloud_owner == "OPNFV" + assert cloud_region.cloud_type == "openstack" + assert cloud_region.complex_name == "Cruguil" + + cloud_region = next(CloudRegion.get_all()) + assert cloud_region.cloud_owner == "OPNFV" + assert cloud_region.cloud_type == "openstack" + assert cloud_region.complex_name == "Cruguil" + + mock_send.return_value = {} + cloud_regions = list(CloudRegion.get_all()) + assert len(cloud_regions) == 0 + + with pytest.raises(StopIteration): + cloud_region = next(CloudRegion.get_all()) + + mock_send.return_value = CLOUD_REGIONS + cloud_regions = list(CloudRegion.get_all()) + assert len(cloud_regions) == 1 + +@mock.patch.object(CloudRegion, "send_message") +def test_cloud_region_creation(mock_send): + """Test cloud region creation""" + cloud_region = CloudRegion.create( + cloud_owner="test_owner", + cloud_region_id="test_cloud_region", + orchestration_disabled=False, + in_maint=True, + owner_defined_type="Test", + cloud_zone="Test zone", + sriov_automation="Test", + upgrade_cycle="Test" + ) + assert cloud_region.cloud_owner == "test_owner" + assert cloud_region.cloud_region_id == "test_cloud_region" + assert cloud_region.orchestration_disabled == False + assert cloud_region.in_maint == True + assert cloud_region.cloud_type == "" + assert cloud_region.owner_defined_type == "Test" + assert cloud_region.cloud_region_version == "" + assert cloud_region.identity_url == "" + assert cloud_region.cloud_zone == "Test zone" + assert cloud_region.complex_name == "" + assert cloud_region.sriov_automation == "Test" + assert cloud_region.cloud_extra_info == "" + assert cloud_region.upgrade_cycle == "Test" + +@mock.patch.object(CloudRegion, 'get_all') +@mock.patch.object(AaiElement, 'send_message_json') +def test_tenants_info(mock_send, mock_cloud_regions): + """Test get Tenant from A&AI.""" + mock_cloud_regions.return_value = CLOUD_REGIONS_ITERATOR + mock_send.return_value = TENANT + cloud_name = "RegionOne" + cloud_region = CloudRegion.get_by_id("DT", cloud_name) + res = list(cloud_region.tenants) + assert len(res) == 1 + assert isinstance(res[0], Tenant) + tenant = res[0] + assert tenant.tenant_id == "4bdc6f0f2539430f9428c852ba606808" + assert tenant.name == "onap-dublin-daily-vnfs" + assert tenant.context is None + assert tenant.resource_version == "1562591004273" + assert tenant.url == ( + f"{tenant.base_url}{tenant.api_version}/cloud-infrastructure/cloud-regions/cloud-region/" + f"OPNFV/RegionOne/tenants/tenant/4bdc6f0f2539430f9428c852ba606808?" + f"resource-version=1562591004273" + ) + +@mock.patch.object(CloudRegion, 'get_all') +@mock.patch.object(AaiElement, 'send_message_json') +def test_tenants_info_wrong_cloud_name(mock_send, mock_cloud_regions): + """Test get Tenant from A&AI.""" + mock_cloud_regions.return_value = CLOUD_REGIONS_ITERATOR + mock_send.return_value = TENANT + cloud_name = "Wrong_cloud_name" + with pytest.raises(Exception) as excinfo: + CloudRegion.get_by_id("DT", cloud_name) + assert "not found" in str(excinfo.value) + + +@mock.patch.object(CloudRegion, "send_message_json") +def test_cloud_regions_relationship(mock_send): + """Test cloud region relationship property.""" + mock_send.return_value = CLOUD_REGION_RELATIONSHIP + cloud_region = CloudRegion(cloud_owner="tester", cloud_region_id="test", + orchestration_disabled=True, in_maint=False) + relationship = next(cloud_region.relationships) + assert isinstance(relationship, Relationship) + assert relationship.relationship_label == "org.onap.relationships.inventory.LocatedIn" + assert relationship.related_link == \ + "/aai/v16/cloud-infrastructure/complexes/complex/integration_test_complex" + assert relationship.related_to == "complex" + assert relationship.relationship_data[0]["relationship-key"] == "complex.physical-location-id" + assert relationship.relationship_data[0]["relationship-value"] == "integration_test_complex" + + +@mock.patch.object(CloudRegion, "send_message_json") +def test_cloud_regions_esr_system_infos(mock_send): + """Test cloud region esr system info""" + mock_send.return_value = ESR_SYSTEM_INFO + cloud_region = CloudRegion(cloud_owner="tester", cloud_region_id="test", + orchestration_disabled=True, in_maint=False) + esr_system_info = next(cloud_region.esr_system_infos) + assert isinstance(esr_system_info, EsrSystemInfo) + assert esr_system_info.esr_system_info_id == "c2d5e75d-56fd-47bc-af31-95607b26fa93" + assert esr_system_info.user_name == "test-devel" + assert esr_system_info.password == "test-devel" + assert esr_system_info.system_type == "openstack" + assert esr_system_info.resource_version == "1586436352654" + assert esr_system_info.system_name is None + assert esr_system_info.esr_type is None + assert esr_system_info.vendor is None + assert esr_system_info.version is None + assert esr_system_info.service_url == "http://keystone:5000/v3" + assert esr_system_info.protocol is None + assert esr_system_info.ssl_cacert is None + assert esr_system_info.ssl_insecure is None + assert esr_system_info.ip_address is None + assert esr_system_info.port is None + assert esr_system_info.cloud_domain == "Default" + assert esr_system_info.default_tenant is None + assert esr_system_info.passive is None + assert esr_system_info.remote_path is None + assert esr_system_info.system_status is None + assert esr_system_info.openstack_region_id is None + +@mock.patch.object(Complex, "send_message") +def test_create_complex(mock_send): + """Test complex creation""" + cmplx = Complex.create( + name="test complex", + physical_location_id="somewhere", + data_center_code="5555", + physical_location_type="test", + city="Test City", + postal_code="55555", + region="Test region", + elevation="TestElevation", + ) + + assert cmplx.name == "test complex" + assert cmplx.physical_location_id == "somewhere" + assert cmplx.identity_url == "" + assert cmplx.physical_location_type == "test" + assert cmplx.street1 == "" + assert cmplx.street2 == "" + assert cmplx.city == "Test City" + assert cmplx.state == "" + assert cmplx.postal_code == "55555" + assert cmplx.country == "" + assert cmplx.region == "Test region" + assert cmplx.latitude == "" + assert cmplx.longitude == "" + assert cmplx.elevation == "TestElevation" + assert cmplx.lata == "" + + +@mock.patch.object(Complex, "send_message_json") +def text_get_all_complexes(mock_send): + """Test get_all Complex class method.""" + mock_send.return_value = {} + assert len(list(Complex.get_all())) == 0 + + mock_send.return_value = COMPLEXES + assert len(list(Complex.get_all())) == 2 + + +def test_filter_none_value(): + """Test method to filter out None value keys from dictionary.""" + ret: dict = AaiResource.filter_none_key_values({"a": None}) + assert not ret + + ret: dict = AaiResource.filter_none_key_values({"a": "b", "c": None}) + assert ret == {"a": "b"} + + ret: dict = AaiResource.filter_none_key_values({"a": "b", "c": "d"}) + assert ret == {"a": "b", "c": "d"} + + +@mock.patch.object(AaiResource, "send_message") +def test_add_relationship(mock_send): + """Test add_relationship method.""" + cloud_region = CloudRegion(cloud_owner="tester", cloud_region_id="test", + orchestration_disabled=True, in_maint=False) + cloud_region.add_relationship(Relationship(related_to="test", + related_link="test", + relationship_data={})) + + +# # ----------------------------------------------------------------------------- +# def test_check_aai_resource_service(): +# """Test that a given service instance is in A&AI.""" +# pass + +# def test_check_aai_resource_service_not_found(): +# """Test that a given service instance is not in A&AI (cleaned).""" +# pass + +# def test_check_aai_resource_vnf(): +# """Test that a given vnf is in A&AI.""" +# pass + +# def test_check_aai_resource_vnf_not_found(): +# """Test that a given vnf is not in A&AI (cleaned).""" +# pass + +# def test_check_aai_resource_module(): +# """Test that a given module is in A&AI.""" +# pass + +# def test_check_aai_resource_module_not_found(): +# """Test that a given module is not in A&AI (cleaned).""" +# pass + +# def test_check_aai_net_module(): +# """Test that a given net is in A&AI.""" +# pass + +# def test_check_aai_resource_net_not_found(): +# """Test that a given net is not in A&AI (cleaned).""" +# pass + + +# pylint: disable=C0301 +SIMPLE_MODEL = { + "model": [ + { + "model-invariant-id": "1234567890", + "model-type": "generic", + "resource-version": "1561218640404", + } + ] +} +# pylint: enable=C0301 + + +def test_service_url(): + """Test service property""" + service = Service("12345", "description", "version1.0") + assert service.url == (f"{service.base_url}{service.api_version}/service-design-and-creation/services/service/" + f"{service.service_id}?resource-version={service.resource_version}") + + +@mock.patch.object(Service, 'send_message') +def test_service_create(mock_send): + """Test service creation""" + Service.create("1234", "description") + mock_send.assert_called_once() + method, description, url = mock_send.call_args[0] + assert method == "PUT" + assert description == "Create A&AI service" + assert url == (f"{Service.base_url}{Service.api_version}/service-design-and-creation/" + f"services/service/1234") + + +def test_model_init(): + """Test model initailization""" + model = Model("12345", "ubuntu", "version16") + assert isinstance(model, Model) + + +def test_model_url(): + """Test Model's url property""" + model = Model("12345", "ubuntu", "version16") + assert model.url == (f"{model.base_url}{model.api_version}/service-design-and-creation/models/" + f"model/{model.invariant_id}?resource-version={model.resource_version}") + + +@mock.patch.object(Model, 'send_message_json') +def test_zero_model_get_all(mock_send_message_json): + """Test get_all Model class method""" + mock_send_message_json.return_value = {} + Model.get_all() + assert len(list(Model.get_all())) == 0 + + +@mock.patch.object(Model, 'send_message_json') +def test_model_get_all(mock_send_message_json): + """Test get_all Model class method""" + mock_send_message_json.return_value = SIMPLE_MODEL + Model.get_all() + assert len(list(Model.get_all())) == 1 + model_1 = next(Model.get_all()) + assert model_1.invariant_id == "1234567890" + assert model_1.model_type == "generic" + assert model_1.resource_version == "1561218640404" + mock_send_message_json.assert_called_with("GET", 'Get A&AI sdc models', mock.ANY) + + +@mock.patch.object(CloudRegion, "send_message_json") +@mock.patch.object(Complex, "get_by_physical_location_id") +def test_cloud_region_complex_property(mock_complex_get_by_physical_location_id, mock_send): + """Test cloud region complex property.""" + mock_send.return_value = {} + cloud_region = CloudRegion(cloud_owner="tester", cloud_region_id="test", + orchestration_disabled=True, in_maint=False) + assert cloud_region.complex is None + mock_send.return_value = CLOUD_REGION_RELATIONSHIP + assert cloud_region.complex is not None + assert mock_complex_get_by_physical_location_id.called_once_with("integration_test_complex") diff --git a/tests/test_aai_service_instance.py b/tests/test_aai_service_instance.py new file mode 100644 index 0000000..9f84dfd --- /dev/null +++ b/tests/test_aai_service_instance.py @@ -0,0 +1,275 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +import pytest + +from onapsdk.aai.aai_element import AaiElement +from onapsdk.aai.business import ServiceInstance +from onapsdk.so.deletion import ServiceDeletionRequest +from onapsdk.so.instantiation import NetworkInstantiation, VnfInstantiation +from onapsdk.exceptions import StatusError + + +RELATIONSHIPS_VNF = { + "relationship": [ + { + "related-to": "generic-vnf", + "relationship_label": "anything", + "related_link": "test_relationship_related_link", + "relationship_data": [] + } + ] +} + + +RELATIONSHIPS_NETWORK = { + "relationship": [ + { + "related-to": "l3-network", + "relationship-label": "anything", + "related-link": "related_link", + "relationship-data": [] + } + ] +} + + +NETWORK_INSTANCE = { + 'network-id': '49dab38b-3a5b-47e5-9cd6-b8d069d6109d', + 'network-name': 'Python_ONAP_SDK_network_instance_0b4308ca-3fe0-4af1-9c4e-ed2c816b9496', + 'is-bound-to-vpn': False, + 'resource-version': '1593162237842', + 'orchestration-status': 'Inventoried', + 'model-invariant-id': 'cdbb2169-e638-4aab-a4e9-b9d2d6d62b04', + 'model-version-id': '51789f7b-5ffc-4c12-ac87-02363fdb40b1', + 'model-customization-id': 'db9c9a6c-2a1c-4cdd-8fbc-e10448d0e4cc', + 'is-provider-network': False, + 'is-shared-network': False, + 'is-external-network': False, + 'relationship-list': { + 'relationship': [ + { + 'related-to': 'service-instance', + 'relationship-label': 'org.onap.relationships.inventory.ComposedOf', + 'related-link': '/aai/v19/business/customers/customer/TestCustomer/service-subscriptions/service-subscription/vFW_with_net/service-instances/service-instance/72fd9ee9-077f-4d3d-8e86-08ed24514802', + 'relationship-data': [ + { + 'relationship-key': 'customer.global-customer-id', + 'relationship-value': 'TestCustomer' + }, + { + 'relationship-key': 'service-subscription.service-type', + 'relationship-value': 'vFW_with_net' + }, + { + 'relationship-key': 'service-instance.service-instance-id', + 'relationship-value': '72fd9ee9-077f-4d3d-8e86-08ed24514802' + } + ], + 'related-to-property': [ + { + 'property-key': 'service-instance.service-instance-name', + 'property-value': 'Python_ONAP_SDK_service_instance_7be66d06-c466-46cf-b84a-cd7af2d633ed' + } + ] + }, + { + 'related-to': 'cloud-region', + 'relationship-label': 'org.onap.relationships.inventory.Uses', + 'related-link': '/aai/v19/cloud-infrastructure/cloud-regions/cloud-region/TestCloudOwner/RegionOne', + 'relationship-data': [ + { + 'relationship-key': 'cloud-region.cloud-owner', + 'relationship-value': 'TestCloudOwner' + }, + { + 'relationship-key': 'cloud-region.cloud-region-id', + 'relationship-value': 'RegionOne' + } + ], + 'related-to-property': [ + { + 'property-key': 'cloud-region.owner-defined-type', + 'property-value': '' + } + ] + }, + { + 'related-to': 'line-of-business', + 'relationship-label': 'org.onap.relationships.inventory.Uses', + 'related-link': '/aai/v19/business/lines-of-business/line-of-business/Test-BusinessLine', + 'relationship-data': [ + { + 'relationship-key': 'line-of-business.line-of-business-name', + 'relationship-value': 'Test-BusinessLine' + } + ] + }, + { + 'related-to': 'tenant', + 'relationship-label': 'org.onap.relationships.inventory.Uses', + 'related-link': '/aai/v19/cloud-infrastructure/cloud-regions/cloud-region/TestCloudOwner/RegionOne/tenants/tenant/89788fdf49514f94963b12a6c0cfdc71', + 'relationship-data': [ + { + 'relationship-key': 'cloud-region.cloud-owner', + 'relationship-value': 'TestCloudOwner' + }, + { + 'relationship-key': 'cloud-region.cloud-region-id', + 'relationship-value': 'RegionOne' + }, + { + 'relationship-key': 'tenant.tenant-id', + 'relationship-value': '89788fdf49514f94963b12a6c0cfdc71' + } + ], + 'related-to-property': [ + { + 'property-key': 'tenant.tenant-name', + 'property-value': 'test-tenant' + } + ] + }, + { + 'related-to': 'platform', + 'relationship-label': 'org.onap.relationships.inventory.Uses', + 'related-link': '/aai/v19/business/platforms/platform/Test-Platform', + 'relationship-data': [ + { + 'relationship-key': 'platform.platform-name', + 'relationship-value': 'Test-Platform' + } + ] + } + ] + } +} + + +COUNT = { + "results":[ + { + "service-instance":29 + } + ] +} + + +def test_service_instance(): + service_subscription = mock.MagicMock() + service_subscription.url = "test_url" + service_instance = ServiceInstance(service_subscription=service_subscription, + instance_id="test_service_instance_id") + assert service_instance.url == (f"{service_instance.service_subscription.url}/service-instances/" + f"service-instance/{service_instance.instance_id}") + + +@mock.patch.object(ServiceInstance, "send_message_json") +def test_service_instance_vnf_instances(mock_relationships_send_message_json): + service_instance = ServiceInstance(service_subscription=mock.MagicMock(), + instance_id="test_service_instance_id") + mock_relationships_send_message_json.return_value = {"relationship": []} + assert len(list(service_instance.vnf_instances)) == 0 + mock_relationships_send_message_json.return_value = RELATIONSHIPS_VNF + assert len(list(service_instance.vnf_instances)) == 1 + + +@mock.patch.object(AaiElement, "send_message_json") +def test_service_instance_network_instances(mock_aai_element_send_message_json): + service_instance = ServiceInstance(service_subscription=mock.MagicMock(), + instance_id="test_service_instance_id") + mock_aai_element_send_message_json.side_effect = [RELATIONSHIPS_NETWORK, NETWORK_INSTANCE] + assert len(list(service_instance.network_instances)) == 1 + + +@mock.patch.object(VnfInstantiation, "instantiate_ala_carte") +@mock.patch.object(ServiceInstance, "sdc_service", new_callable=mock.PropertyMock) +def test_service_instance_add_vnf(mock_sdc_service, mock_vnf_instantiation): + service_instance = ServiceInstance(service_subscription=mock.MagicMock(), + instance_id="test_service_instance_id") + service_instance.orchestration_status = "Inactive" + with pytest.raises(StatusError) as exc: + service_instance.add_vnf(mock.MagicMock(), + mock.MagicMock(), + mock.MagicMock()) + assert exc.type == StatusError + service_instance.orchestration_status = "Active" + service_instance.add_vnf(mock.MagicMock(), + mock.MagicMock(), + mock.MagicMock()) + mock_vnf_instantiation.assert_called_once() + + +@mock.patch.object(VnfInstantiation, "instantiate_macro") +@mock.patch.object(ServiceInstance, "sdc_service", new_callable=mock.PropertyMock) +def test_service_instance_add_vnf_macro(mock_sdc_service, mock_vnf_instantiation): + service_instance = ServiceInstance(service_subscription=mock.MagicMock(), + instance_id="test_service_instance_id") + service_instance.orchestration_status = "Inactive" + with pytest.raises(StatusError) as exc: + service_instance.add_vnf(mock.MagicMock(), + mock.MagicMock(), + mock.MagicMock(), + a_la_carte=False) + assert exc.type == StatusError + service_instance.orchestration_status = "Active" + service_instance.add_vnf(mock.MagicMock(), + mock.MagicMock(), + mock.MagicMock(), + a_la_carte=False) + mock_vnf_instantiation.assert_called_once() + + +@mock.patch.object(NetworkInstantiation, "instantiate_ala_carte") +@mock.patch.object(ServiceInstance, "sdc_service", new_callable=mock.PropertyMock) +def test_service_instance_add_network(mock_sdc_service, mock_network_instantiation): + service_instance = ServiceInstance(service_subscription=mock.MagicMock(), + instance_id="test_service_instance_id") + service_instance.orchestration_status = "Inactive" + with pytest.raises(StatusError) as exc: + service_instance.add_network(mock.MagicMock(), + mock.MagicMock(), + mock.MagicMock()) + assert exc.type == StatusError + service_instance.orchestration_status = "Active" + service_instance.add_network(mock.MagicMock(), + mock.MagicMock(), + mock.MagicMock()) + mock_network_instantiation.assert_called_once() + + +@mock.patch.object(ServiceDeletionRequest, "send_request") +@mock.patch.object(ServiceInstance, "sdc_service", new_callable=mock.PropertyMock) +def test_service_instance_deletion(mock_sdc_service, mock_service_deletion_request): + service_instance = ServiceInstance(service_subscription=mock.MagicMock(), + instance_id="test_service_instance_id") + service_instance.delete() + mock_service_deletion_request.assert_called_once_with(service_instance, True) + + +@mock.patch("onapsdk.aai.business.service.Service.get_by_unique_uuid") +def test_service_instance_sdc_service(mock_service_get_by_unique_uuid): + si = ServiceInstance(service_subscription=mock.MagicMock(), + instance_id="test_service_instance_id", + model_invariant_id="1234") + si.sdc_service + mock_service_get_by_unique_uuid.assert_called_once_with("1234") + si.sdc_service + mock_service_get_by_unique_uuid.assert_called_once_with("1234") + +@mock.patch.object(ServiceInstance, "send_message_json") +def test_service_instance_count(mock_send_message_json): + mock_send_message_json.return_value = COUNT + assert ServiceInstance.count(service_subscription=mock.MagicMock()) diff --git a/tests/test_aai_service_subscription.py b/tests/test_aai_service_subscription.py new file mode 100644 index 0000000..3c7eb91 --- /dev/null +++ b/tests/test_aai_service_subscription.py @@ -0,0 +1,191 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.aai.business import Customer, ServiceSubscription, ServiceInstance +from onapsdk.aai.cloud_infrastructure import CloudRegion + + +SERVICE_INSTANCES = { + "service-instance":[ + { + "service-instance-id":"5410bf79-2aa3-450e-a324-ec5630dc18cf", + "service-instance-name":"test", + "environment-context":"General_Revenue-Bearing", + "workload-context":"Production", + "model-invariant-id":"2a51a89b-6f94-4417-8831-c468fb30ed02", + "model-version-id":"92a82807-b483-4579-86b1-c79b1286aab4", + "resource-version":"1589457727708", + "orchestration-status":"Active", + "relationship-list":{ + "relationship":[ + { + "related-to":"owning-entity", + "relationship-label":"org.onap.relationships.inventory.BelongsTo", + "related-link":"/aai/v16/business/owning-entities/owning-entity/ff6c945f-89ab-4f14-bafd-0cdd6eac791a", + "relationship-data":[ + { + "relationship-key":"owning-entity.owning-entity-id", + "relationship-value":"ff6c945f-89ab-4f14-bafd-0cdd6eac791a" + } + ] + }, + { + "related-to":"project", + "relationship-label":"org.onap.relationships.inventory.Uses", + "related-link":"/aai/v16/business/projects/project/python_onap_sdk_project", + "relationship-data":[ + { + "relationship-key":"project.project-name", + "relationship-value":"python_onap_sdk_project" + } + ] + } + ] + } + } + ] +} + + +MULTIPLE_CLOUD_REGIONS_AND_TENATS_RELATIONSHIP = { + "relationship":[ + { + "related-to":"tenant", + "relationship-label":"org.onap.relationships.inventory.Uses", + "related-link":"/aai/v19/cloud-infrastructure/cloud-regions/cloud-region/DT/RegionOne/tenants/tenant/8fa33ca96caa4172aeeeefd1dbf5c715", + "relationship-data":[ + { + "relationship-key":"cloud-region.cloud-owner", + "relationship-value":"DT" + }, + { + "relationship-key":"cloud-region.cloud-region-id", + "relationship-value":"RegionOne" + }, + { + "relationship-key":"tenant.tenant-id", + "relationship-value":"8fa33ca96caa4172aeeeefd1dbf5c715" + } + ], + "related-to-property":[ + { + "property-key":"tenant.tenant-name", + "property-value":"ci-onap-master-vnfs" + } + ] + }, + { + "related-to":"tenant", + "relationship-label":"org.onap.relationships.inventory.Uses", + "related-link":"/aai/v19/cloud-infrastructure/cloud-regions/cloud-region/test_cloud_owner/test_cloud_region_id/tenants/tenant/1234", + "relationship-data":[ + { + "relationship-key":"cloud-region.cloud-owner", + "relationship-value":"test_cloud_owner" + }, + { + "relationship-key":"cloud-region.cloud-region-id", + "relationship-value":"test_cloud_region_id" + }, + { + "relationship-key":"tenant.tenant-id", + "relationship-value":"1234" + } + ], + "related-to-property":[ + { + "property-key":"tenant.tenant-name", + "property-value":"test_tenant" + } + ] + } + ] +} + + +COUNT = { + "results":[ + { + "service-subscription":1 + } + ] +} + + +@mock.patch.object(ServiceSubscription, "send_message_json") +def test_get_service_instance_by_filter_parameter(mock_send_message_json): + """Test Service Subscription get_service_instance_by_filter_parameter method""" + customer = Customer("generic", "generic", "INFRA") + service_subscription = ServiceSubscription(customer=customer, + service_type="test_service_type", + resource_version="test_resource_version") + mock_send_message_json.return_value = SERVICE_INSTANCES + service_instance = service_subscription._get_service_instance_by_filter_parameter(filter_parameter_name="service-instance-id", filter_parameter_value="5410bf79-2aa3-450e-a324-ec5630dc18cf") + assert service_instance.instance_name == "test" + assert service_instance.instance_id == "5410bf79-2aa3-450e-a324-ec5630dc18cf" + + +@mock.patch.object(ServiceSubscription, "_get_service_instance_by_filter_parameter") +def test_get_service_instance_by_id(mock_get): + """Test Service Subscription get_service_instance_by_id method""" + service_subscription = ServiceSubscription(customer=None, + service_type="test_service_type", + resource_version="test_resource_version") + mock_get.return_value = ServiceInstance(service_subscription="ServiceSubscription", + instance_id="5410bf79-2aa3-450e-a324-ec5630dc18cf") + service_instance = service_subscription.get_service_instance_by_id(service_instance_id="5410bf79-2aa3-450e-a324-ec5630dc18cf") + assert service_instance.instance_id == "5410bf79-2aa3-450e-a324-ec5630dc18cf" + + +@mock.patch.object(ServiceSubscription, "_get_service_instance_by_filter_parameter") +def test_get_service_instance_by_name(mock_get): + """Test Service Subscription get_service_instance_by_name method""" + service_subscription = ServiceSubscription(customer=None, + service_type="test_service_type", + resource_version="test_resource_version") + mock_get.return_value = ServiceInstance(service_subscription="ServiceSubscription", + instance_id="5410bf79-2aa3-450e-a324-ec5630dc18cf", + instance_name="test") + service_instance = service_subscription.get_service_instance_by_name(service_instance_name="test") + assert service_instance.instance_name == "test" + + +@mock.patch.object(ServiceSubscription, "send_message_json") +@mock.patch.object(CloudRegion, "get_by_id") +def test_cloud_regions(mock_cloud_region_get_by_id, mock_send_message_json): + """Test service subscription `cloud_regions` property""" + service_subscription = ServiceSubscription(customer=mock.MagicMock(), + service_type="test_service_type", + resource_version="test_resource_version") + mock_send_message_json.return_value = MULTIPLE_CLOUD_REGIONS_AND_TENATS_RELATIONSHIP + assert len(list(service_subscription.cloud_regions)) == 2 + assert len(mock_cloud_region_get_by_id.mock_calls) == 2 + + +@mock.patch.object(ServiceSubscription, "send_message_json") +@mock.patch.object(CloudRegion, "get_by_id") +@mock.patch.object(CloudRegion, "get_tenant") +def test_tenants(mock_cloud_region_get_tenant, mock_cloud_region_get_by_id, mock_send_message_json): + """Test service subscription `tenants` property""" + service_subscription = ServiceSubscription(customer=mock.MagicMock(), + service_type="test_service_type", + resource_version="test_resource_version") + mock_send_message_json.return_value = MULTIPLE_CLOUD_REGIONS_AND_TENATS_RELATIONSHIP + assert len(list(service_subscription.tenants)) == 2 + +@mock.patch.object(ServiceSubscription, "send_message_json") +def test_service_subscription_count(mock_send_message_json): + mock_send_message_json.return_value = COUNT + assert ServiceSubscription.count(customer=mock.MagicMock()) == 1 diff --git a/tests/test_aai_site_resource.py b/tests/test_aai_site_resource.py new file mode 100644 index 0000000..68dd556 --- /dev/null +++ b/tests/test_aai_site_resource.py @@ -0,0 +1,57 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest.mock import patch + +from onapsdk.aai.network.site_resource import SiteResource + +SITE_RESOURCE = { + "site-resource-id":"123", + "resource-version":"213" +} + +SITE_RESOURCES = { + "site-resource":[ + SITE_RESOURCE, + { + "site-resource-id":"321", + "resource-version":"312" + } + ] +} + +@patch("onapsdk.aai.network.site_resource.SiteResource.send_message_json") +def test_site_resource_get_all(mock_send_message_json): + assert len(list(SiteResource.get_all())) == 0 + mock_send_message_json.return_value = SITE_RESOURCES + site_resources = list(SiteResource.get_all()) + assert len(site_resources) == 2 + sr1, sr2 = site_resources + assert sr1.site_resource_id == "123" + assert sr1.resource_version == "213" + assert sr2.site_resource_id == "321" + assert sr2.resource_version == "312" + +@patch("onapsdk.aai.network.site_resource.SiteResource.send_message_json") +def test_site_resource_get_by_id(mock_send_message_json): + mock_send_message_json.return_value = SITE_RESOURCE + sr = SiteResource.get_by_site_resource_id("123") + assert sr.site_resource_id == "123" + assert sr.resource_version == "213" + +@patch("onapsdk.aai.network.site_resource.SiteResource.send_message") +@patch("onapsdk.aai.network.site_resource.SiteResource.get_by_site_resource_id") +def test_site_resource_create(mock_get_by_site_resource_id, mock_send_message): + SiteResource.create("123") + mock_send_message.assert_called_once() + assert mock_get_by_site_resource_id.called_once_with("123") diff --git a/tests/test_aai_vf_module.py b/tests/test_aai_vf_module.py new file mode 100644 index 0000000..7f3b8a6 --- /dev/null +++ b/tests/test_aai_vf_module.py @@ -0,0 +1,83 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +import pytest + +from onapsdk.aai.business import VfModuleInstance +from onapsdk.so.deletion import VfModuleDeletionRequest +from onapsdk.exceptions import ResourceNotFound + + +COUNT = { + "results":[ + { + "vf-module":1 + } + ] +} + + +def test_vf_module(): + vnf_instance = mock.MagicMock() + vnf_instance.url = "test_url" + vf_module_instance = VfModuleInstance(vnf_instance=vnf_instance, + vf_module_id="test_vf_module_id", + is_base_vf_module=True, + automated_assignment=False) + + assert vf_module_instance.url == (f"{vf_module_instance.vnf_instance.url}/vf-modules/" + f"vf-module/{vf_module_instance.vf_module_id}") + + +@mock.patch.object(VfModuleDeletionRequest, "send_request") +def test_vf_module_deletion(mock_deletion_request): + vf_module_instance = VfModuleInstance(vnf_instance=mock.MagicMock(), + vf_module_id="test_vf_module_id", + is_base_vf_module=True, + automated_assignment=False) + vf_module_instance.delete() + mock_deletion_request.assert_called_once_with(vf_module_instance, True) + + +def test_vnf_vf_module(): + """Test VfModudleInstance's vf_module property""" + vnf_instance = mock.MagicMock() + vnf_instance.vnf = mock.MagicMock() + + vf_module = mock.MagicMock() + vf_module.model_version_id = "test_model_version_id" + + vf_module_instance = VfModuleInstance(vnf_instance=vnf_instance, + model_version_id="test_model_version_id", + vf_module_id="test_vf_module_id", + is_base_vf_module=True, + automated_assignment=False) + + vnf_instance.vnf.vf_modules = [] + with pytest.raises(ResourceNotFound) as exc: + vf_module_instance.vf_module + assert exc.type == ResourceNotFound + assert vf_module_instance._vf_module is None + + vnf_instance.vnf.vf_modules = [vf_module] + + assert vf_module == vf_module_instance.vf_module + assert vf_module_instance._vf_module is not None + assert vf_module_instance.vf_module == vf_module_instance._vf_module + +@mock.patch.object(VfModuleInstance, "send_message_json") +def test_vf_module_instance_count(mock_send_message_json): + mock_send_message_json.return_value = COUNT + assert VfModuleInstance.count(vnf_instance=mock.MagicMock()) == 1 diff --git a/tests/test_aai_vnf.py b/tests/test_aai_vnf.py new file mode 100644 index 0000000..4dea6a8 --- /dev/null +++ b/tests/test_aai_vnf.py @@ -0,0 +1,449 @@ +"""Test A&AI VNF module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +import pytest + +from onapsdk.aai.business import ServiceInstance, VnfInstance, PnfInstance, VfModuleInstance +from onapsdk.so.deletion import VnfDeletionRequest +from onapsdk.so.instantiation import VfModuleInstantiation, VnfInstantiation, SoService +from onapsdk.exceptions import ResourceNotFound, StatusError + + +VNF_INSTANCE = { + "vnf-id":"6d644ab5-254d-4a49-98fe-0f481c099f1a", + "vnf-name":"Python_ONAP_SDK_vnf_instance_14856120-e946-46ce-bf5f-384b20209f9c", + "vnf-type":"testService11/testVF11 0", + "service-id":"1234", + "prov-status":"PREPROV", + "orchestration-status":"Inventoried", + "in-maint":True, + "is-closed-loop-disabled":False, + "resource-version":"1590395148980", + "model-invariant-id":"a3285832-77d5-4ab2-95c5-217070de77c9", + "model-version-id":"0da841b9-f787-4ce0-9227-a23092a4a035", + "model-customization-id":"9426293e-bc5d-4fd3-8236-85190f1142aa", + "selflink":"restconf/config/GENERIC-RESOURCE-API:services/service/5410bf79-2aa3-450e-a324-ec5630dc18cf/service-data/vnfs/vnf/6d644ab5-254d-4a49-98fe-0f481c099f1a/vnf-data/vnf-topology/", + "relationship-list":{ + "relationship":[ + { + "related-to":"tenant", + "relationship-label":"org.onap.relationships.inventory.BelongsTo", + "related-link":"/aai/v19/cloud-infrastructure/cloud-regions/cloud-region/DT/RegionOne/tenants/tenant/89788fdf49514f94963b12a6c0cfdc71", + "relationship-data":[ + { + "relationship-key":"cloud-region.cloud-owner", + "relationship-value":"DT" + }, + { + "relationship-key":"cloud-region.cloud-region-id", + "relationship-value":"RegionOne" + }, + { + "relationship-key":"tenant.tenant-id", + "relationship-value":"89788fdf49514f94963b12a6c0cfdc71" + } + ], + "related-to-property":[ + { + "property-key":"tenant.tenant-name", + "property-value":"onap-devel" + } + ] + }, + { + "related-to":"cloud-region", + "relationship-label":"org.onap.relationships.inventory.LocatedIn", + "related-link":"/aai/v19/cloud-infrastructure/cloud-regions/cloud-region/DT/RegionOne", + "relationship-data":[ + { + "relationship-key":"cloud-region.cloud-owner", + "relationship-value":"DT" + },{ + "relationship-key":"cloud-region.cloud-region-id", + "relationship-value":"RegionOne" + } + ], + "related-to-property":[ + { + "property-key":"cloud-region.owner-defined-type", + "property-value":"" + } + ] + }, + { + "related-to":"service-instance", + "relationship-label":"org.onap.relationships.inventory.ComposedOf", + "related-link":"/aai/v19/business/customers/customer/generic/service-subscriptions/service-subscription/testService11/service-instances/service-instance/5410bf79-2aa3-450e-a324-ec5630dc18cf", + "relationship-data":[ + { + "relationship-key":"customer.global-customer-id", + "relationship-value":"generic" + }, + { + "relationship-key":"service-subscription.service-type", + "relationship-value":"testService11" + }, + { + "relationship-key":"service-instance.service-instance-id", + "relationship-value":"5410bf79-2aa3-450e-a324-ec5630dc18cf" + } + ], + "related-to-property":[ + { + "property-key":"service-instance.service-instance-name", + "property-value":"test22" + } + ] + }, + { + "related-to":"availability-zone", + "relationship-label":"org.onap.relationships.inventory.Uses", + "related-link":"/aai/v19/cloud-infrastructure/cloud-regions/cloud-region/DT/RegionOne/availability-zones/availability-zone/nova", + "relationship-data":[ + { + "relationship-key":"cloud-region.cloud-owner", + "relationship-value":"DT" + }, + { + "relationship-key":"cloud-region.cloud-region-id", + "relationship-value":"RegionOne" + }, + { + "relationship-key":"availability-zone.availability-zone-name", + "relationship-value":"nova" + } + ] + }, + { + "related-to":"availability-zone", + "relationship-label":"org.onap.relationships.inventory.Uses", + "related-link":"/aai/v19/cloud-infrastructure/cloud-regions/cloud-region/DT/RegionOne/availability-zones/availability-zone/brittany", + "relationship-data":[ + { + "relationship-key":"cloud-region.cloud-owner", + "relationship-value":"DT" + }, + { + "relationship-key":"cloud-region.cloud-region-id", + "relationship-value":"RegionOne" + }, + { + "relationship-key":"availability-zone.availability-zone-name", + "relationship-value":"brittany" + } + ] + }, + { + "related-to":"platform", + "relationship-label":"org.onap.relationships.inventory.Uses", + "related-link":"/aai/v19/business/platforms/platform/Python_ONAPSDK_Platform", + "relationship-data":[ + { + "relationship-key":"platform.platform-name", + "relationship-value":"Python_ONAPSDK_Platform" + } + ] + }, + { + "related-to":"line-of-business", + "relationship-label":"org.onap.relationships.inventory.Uses", + "related-link":"/aai/v19/business/lines-of-business/line-of-business/Python_ONAPSDK_LineOfBusiness", + "relationship-data":[ + { + "relationship-key":"line-of-business.line-of-business-name", + "relationship-value":"Python_ONAPSDK_LineOfBusiness" + } + ] + } + ] + } +} + + +VF_MODULE = { + "vf-module": [ + { + "vf-module-id": "test-module-id", + "is-base-vf-module": True, + "automated-assignment": False, + "vf-module-name": "test_vf_module", + "heat-stack-id": "test_heat_stack_id", + "orchestration-status": "test_orchestration_status", + "resource-version": "1590395148980", + "model-invariant-id": "test_model_invariant_id", + "model-version-id": "test_model_version_id" + } + ] +} + + +COUNT = { + "results":[ + { + "generic-vnf":17 + } + ] +} + + +@mock.patch.object(VnfDeletionRequest, "send_request") +def test_vnf_instance(mock_vnf_deletion_request): + service_instance = ServiceInstance(None, + instance_id="test_service_instance_id") + vnf_instance = VnfInstance(service_instance, + vnf_id="test_vnf_id", + vnf_type="test_vnf_type", + in_maint=False, + is_closed_loop_disabled=True) + assert vnf_instance.service_instance == service_instance + assert vnf_instance.vnf_id == "test_vnf_id" + assert vnf_instance.vnf_type == "test_vnf_type" + assert vnf_instance.in_maint is False + assert vnf_instance.is_closed_loop_disabled is True + assert vnf_instance._vnf is None + assert vnf_instance.url == (f"{vnf_instance.base_url}{vnf_instance.api_version}/network/" + f"generic-vnfs/generic-vnf/{vnf_instance.vnf_id}") + vnf_instance.delete() + mock_vnf_deletion_request.assert_called_once_with(vnf_instance, True) + + +@mock.patch.object(VnfInstance, "send_message_json") +def test_vnf_instance_vf_modules(mock_vnf_instance_send_message_json): + service_instance = mock.MagicMock() + vnf_instance = VnfInstance(service_instance, + vnf_id="test_vnf_id", + vnf_type="test_vnf_type", + in_maint=False, + is_closed_loop_disabled=True) + mock_vnf_instance_send_message_json.return_value = {"vf-module": []} + vf_modules = list(vnf_instance.vf_modules) + assert len(vf_modules) == 0 + + mock_vnf_instance_send_message_json.return_value = VF_MODULE + vf_modules = list(vnf_instance.vf_modules) + assert len(vf_modules) == 1 + + +def test_vnf_instance_vnf(): + service_instance = mock.MagicMock() + vnf_instance = VnfInstance(service_instance, + vnf_id="test_vnf_id", + vnf_type="test_vnf_type", + in_maint=False, + is_closed_loop_disabled=True, + model_version_id="test_model_version_id") + assert vnf_instance._vnf is None + service_instance.sdc_service.vnfs = [] + with pytest.raises(ResourceNotFound) as exc: + vnf_instance.vnf + assert exc.type == ResourceNotFound + assert vnf_instance._vnf is None + + vnf = mock.MagicMock() + vnf.model_version_id = "test_model_version_id" + service_instance.sdc_service.vnfs = [vnf] + assert vnf == vnf_instance.vnf + assert vnf_instance._vnf is not None + assert vnf_instance.vnf == vnf_instance._vnf + + +@mock.patch.object(VfModuleInstantiation, "instantiate_ala_carte") +def test_vnf_add_vf_module(mock_vf_module_instantiation): + vnf_instance = VnfInstance(mock.MagicMock(), + vnf_id="test_vnf_id", + vnf_type="test_vnf_type", + in_maint=False, + is_closed_loop_disabled=True, + model_version_id="test_model_version_id") + vnf_instance.add_vf_module(mock.MagicMock()) + mock_vf_module_instantiation.assert_called_once() + + +@mock.patch.object(VnfInstance, "_execute_so_action") +@mock.patch.object(VnfInstance, "vnf") +def test_vnf_update(mock_vnf, mock_vnf_instantiation): + + property_skip_true = mock.MagicMock() + property_skip_true.name = "skip_post_instantiation_configuration" + property_skip_true.value = "false" + + vnf_instance = mock.MagicMock() + vnf_instance.vnf = mock_vnf + vnf_instance.vnf.properties = (item for item in [property_skip_true]) + + vnf_instance = VnfInstance(vnf_instance, + vnf_id="test_vnf_id", + vnf_type="test_vnf_type", + in_maint=False, + is_closed_loop_disabled=True) + + vnf_instance.update([mock.MagicMock()]) + mock_vnf_instantiation.assert_called_once() + + property_skip_false = mock.MagicMock() + property_skip_false.name = "skip_post_instantiation_configuration" + property_skip_false.value = "true" + + vnf_instance2 = mock.MagicMock() + vnf_instance2.vnf = mock_vnf + vnf_instance2.vnf.properties = (item for item in [property_skip_false]) + + vnf_instance2 = VnfInstance(vnf_instance2, + vnf_id="test_vnf_id", + vnf_type="test_vnf_type", + in_maint=False, + is_closed_loop_disabled=True) + + with pytest.raises(StatusError): + vnf_instance2.update([mock.MagicMock()]) + + +@mock.patch.object(VnfInstance, "_execute_so_action") +def test_vnf_healthcheck(mock_vnf_instantiation): + + instance = mock.MagicMock() + vnf_instance = VnfInstance(instance, + vnf_id="test_vnf_id", + vnf_type="test_vnf_type", + in_maint=False, + is_closed_loop_disabled=True) + + vnf_instance.healthcheck() + mock_vnf_instantiation.assert_called_once() + + +@mock.patch.object(VnfInstance, "_build_so_input") +@mock.patch.object(VnfInstantiation, "so_action") +def test_vnf_execute_so_action(mock_build_so_input, mock_so_action): + + instance = mock.MagicMock() + + relation_1 = mock.MagicMock() + relation_1.related_to = "line-of-business" + relation_1.relationship_data = [{"relationship-value": "test"}] + relation_2 = mock.MagicMock() + relation_2.related_to = "platform" + relation_2.relationship_data = [{"relationship-value": "test"}] + + vnf_instance = VnfInstance(instance, + vnf_id="test_vnf_id", + vnf_type="test_vnf_type", + in_maint=False, + is_closed_loop_disabled=True) + + vnf_instance.service_instance = mock.MagicMock() + vnf_instance.service_instance.active = True + + type(vnf_instance).relationships = mock.PropertyMock(return_value=[relation_1, relation_2]) + + vnf_instance._execute_so_action(operation_type="test", + vnf_parameters=[mock.MagicMock()]) + mock_so_action.assert_called_once() + + vnf_instance.service_instance.active = False + with pytest.raises(StatusError): + vnf_instance._execute_so_action(operation_type="test", + vnf_parameters=[mock.MagicMock()]) + + +@mock.patch.object(VnfInstance, "send_message") +def test_build_so_input(mock_send_message): + + pnf = mock.MagicMock() + pnf.model_version_id = "test_pnf_model_version_id" + pnf.model_name = "test_model" + + vnf = mock.MagicMock() + vnf.model_version_id = "test_vnf_model_version_id" + vnf.model_name = "vnf_test_model" + + vf_module = mock.MagicMock() + vf_module.model_version_id = "test_vfm_model_version_id" + vf_module.model_name = "test..vfm_model..name" + + vnf.vf_modules = [vf_module] + + instance = mock.MagicMock() + instance.service_subscription = mock.MagicMock() + instance.service_subscription.service_type = "1234" + + instance.sdc_service.pnfs = [pnf] + instance.sdc_service.vnfs = [vnf] + + pnf_instance = PnfInstance(instance, + pnf_name="test_pnf", + in_maint=False, + model_version_id="test_pnf_model_version_id") + + vnf_instance = VnfInstance(instance, + vnf_name="test_name", + vnf_id="test_vnf_id", + vnf_type="test_vnf_type", + in_maint=False, + is_closed_loop_disabled=True, + model_version_id="test_vnf_model_version_id") + + vf_module_instance = VfModuleInstance(vnf_instance=vnf_instance, + vf_module_name="test_vfm_name", + model_version_id="test_vfm_model_version_id", + vf_module_id="test_vf_module_id", + is_base_vf_module=True, + automated_assignment=False) + + vnf_instance.vnf.vf_modules = [vf_module] + type(vnf_instance).vf_modules = mock.PropertyMock(return_value=[vf_module_instance]) + instance.pnfs = [pnf_instance] + instance.vnf_instances = [vnf_instance] + + test_so_input_no_params = vnf_instance._build_so_input() + + assert isinstance(test_so_input_no_params, SoService) + assert len(test_so_input_no_params.vnfs[0].parameters) == 0 + + vnf_param1 = mock.MagicMock() + vnf_param1.name = "test_name" + vnf_param1.value = "test_value" + + test_so_input = vnf_instance._build_so_input([vnf_param1]) + + assert isinstance(test_so_input, SoService) + assert test_so_input.subscription_service_type == "1234" + assert not test_so_input.instance_name + assert len(test_so_input.vnfs) == 1 + + test_so_input_vnf = test_so_input.vnfs[0] + + assert test_so_input_vnf.model_name == "vnf_test_model" + assert test_so_input_vnf.instance_name == "test_name" + assert len(test_so_input_vnf.parameters) == 1 + assert test_so_input_vnf.parameters["test_name"] == "test_value" + assert len(test_so_input_vnf.vf_modules) == 1 + + test_so_input_vnf_vf_module = test_so_input_vnf.vf_modules[0] + + assert test_so_input_vnf_vf_module.model_name == "vfm_model" + assert test_so_input_vnf_vf_module.instance_name == "test_vfm_name" + assert len(test_so_input_vnf_vf_module.parameters) == 0 + + assert len(test_so_input.pnfs) == 1 + assert test_so_input.pnfs[0].model_name == "test_model" + assert test_so_input.pnfs[0].instance_name == "test_pnf" + +@mock.patch.object(VnfInstance, "send_message_json") +def test_vnf_instance_mock(mock_send_message_json): + mock_send_message_json.return_value = COUNT + assert VnfInstance.count() == 17 diff --git a/tests/test_cds.py b/tests/test_cds.py new file mode 100644 index 0000000..b7859f3 --- /dev/null +++ b/tests/test_cds.py @@ -0,0 +1,435 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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.path +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import MagicMock, patch, PropertyMock, mock_open + +from pytest import raises + +from onapsdk.cds.blueprint import Blueprint, Mapping, MappingSet, ResolvedTemplate, Workflow +from onapsdk.cds.blueprint_processor import Blueprintprocessor +from onapsdk.cds.cds_element import CdsElement +from onapsdk.cds.data_dictionary import DataDictionary, DataDictionarySet +from onapsdk.exceptions import FileError, ParameterError, RequestError, ValidationError +from onapsdk.utils.gui import GuiItem, GuiList + +DD_1 = { + "name": "vf-module-name", + "tags": "vf-module-name", + "data_type": "string", + "description": "vf-module-name", + "entry_schema": "string", + "updatedBy": "Singal, Kapil ", + "definition": { + "tags": "vf-module-name", + "name": "vf-module-name", + "property": { + "description": "vf-module-name", + "type": "string" + }, + "updated-by": "Singal, Kapil ", + "sources": { + "input": { + "type": "source-input" + }, + "default": { + "type": "source-default", + "properties": {} + } + } + } +} + + +RAW_DD = { + "tags": "vf-module-name", + "name": "vf-module-name", + "property": { + "description": "vf-module-name", + "type": "string" + }, + "updated-by": "Singal, Kapil ", + "sources": { + "input": { + "type": "source-input" + }, + "default": { + "type": "source-default", + "properties": {} + } + } +} + + +vLB_CBA_Python_meta_bytes = b'TOSCA-Meta-File-Version: 1.0.0\nCSAR-Version: 1.0\nCreated-By: PLATANIA, MARCO \nEntry-Definitions: Definitions/vLB_CDS.json\nTemplate-Tags: vDNS-CDS-test1\nContent-Type: application/vnd.oasis.bpmn\nTemplate-Name: vDNS-CDS-test1\nTemplate-Version: 1.0' + +vLB_CBA_Python_base_template_mapping_bytes = b'[\n {\n "name": "service-instance-id",\n "property": {\n "description": "",\n "required": false,\n "type": "string",\n "status": "",\n "constraints": [\n {}\n ],\n "entry_schema": {\n "type": ""\n }\n },\n "input-param": false,\n "dictionary-name": "service-instance-id",\n "dictionary-source": "input",\n "dependencies": [],\n "version": 0\n },\n {\n "name": "vnf-id",\n "property": {\n "description": "",\n "required": false,\n "type": "string",\n "status": "",\n "constraints": [\n {}\n ],\n "entry_schema": {\n "type": ""\n }\n },\n "input-param": false,\n "dictionary-name": "vnf-id",\n "dictionary-source": "input",\n "dependencies": [],\n "version": 0\n },\n {\n "name": "vdns_vf_module_id",\n "property": {\n "description": "",\n "required": false,\n "type": "string",\n "status": "",\n "constraints": [\n {}\n ],\n "entry_schema": {\n "type": ""\n }\n },\n "input-param": false,\n "dictionary-name": "vdns_vf_module_id",\n "dictionary-source": "sdnc",\n "dependencies": [\n\t "service-instance-id",\n "vnf-id"\n ],\n "version": 0\n },\n {\n "name": "vdns_int_private_ip_0",\n "property": {\n "description": "",\n "required": false,\n "type": "string",\n "status": "",\n "constraints": [\n {}\n ],\n "entry_schema": {\n "type": ""\n }\n },\n "input-param": false,\n "dictionary-name": "vdns_int_private_ip_0",\n "dictionary-source": "sdnc",\n "dependencies": [\n "service-instance-id",\n "vnf-id",\n "vdns_vf_module_id"\n ],\n "version": 0\n },\n {\n "name": "vdns_onap_private_ip_0",\n "property": {\n "description": "",\n "required": false,\n "type": "string",\n "status": "",\n "constraints": [\n {}\n ],\n "entry_schema": {\n "type": ""\n }\n },\n "input-param": false,\n "dictionary-name": "vdns_onap_private_ip_0",\n "dictionary-source": "sdnc",\n "dependencies": [\n "service-instance-id",\n "vnf-id",\n "vdns_vf_module_id"\n ],\n "version": 0\n }\n]' + + +@patch.object(Blueprint, "send_message") +def test_blueprint_enrichment(send_message_mock): + blueprint = Blueprint(b"test cba - it will never work") + blueprint.enrich() + send_message_mock.assert_called_once() + send_message_mock.reset_mock() + send_message_mock.side_effect = RequestError + with raises(RequestError): + blueprint.enrich() + + +@patch.object(Blueprint, "send_message") +def test_blueprint_publish(send_message_mock): + blueprint = Blueprint(b"test cba - it will never work") + blueprint.publish() + send_message_mock.assert_called_once() + + +@patch.object(Blueprint, "send_message") +def test_blueprint_deploy(send_message_mock): + blueprint = Blueprint(b"test cba - it will never work") + blueprint.deploy() + send_message_mock.assert_called_once() + + +def test_blueprint_load_from_file(): + with TemporaryDirectory() as tmpdirname: + path = os.path.join(tmpdirname, "test.zip") + with open(path, "wb") as f: + f.write(b"test cba - it will never work") + blueprint = Blueprint.load_from_file(path) + assert blueprint.cba_file_bytes == b"test cba - it will never work" + +def test_blueprint_load_from_file_file_error(): + + with TemporaryDirectory() as tmpdirname, \ + patch("__main__.open", new_callable=mock_open) as mo, \ + raises(FileError) as exc: + + path = os.path.join(tmpdirname, "nonexistent_file.zip") + mo.side_effect = FileNotFoundError + + Blueprint.load_from_file(path) + + assert exc.type == FileError + + +def test_blueprint_save(): + blueprint = Blueprint(b"test cba - it will never work") + with TemporaryDirectory() as tmpdirname: + path = os.path.join(tmpdirname, "test.zip") + blueprint.save(path) + with open(path, "rb") as f: + assert f.read() == b"test cba - it will never work" + + +def test_blueprint_read_cba_metadata(): + b = Blueprint(b"test cba - it will never work") + with raises(ValidationError) as exc: + b.get_cba_metadata(b"Invalid") + b.get_cba_metadata(b"123: 456") + assert exc.type is ValidationError + + cba_metadata = b.get_cba_metadata(vLB_CBA_Python_meta_bytes) + assert cba_metadata.tosca_meta_file_version == "1.0.0" + assert cba_metadata.csar_version == 1.0 + assert cba_metadata.created_by == "PLATANIA, MARCO " + assert cba_metadata.entry_definitions == "Definitions/vLB_CDS.json" + assert cba_metadata.template_name == "vDNS-CDS-test1" + assert cba_metadata.template_version == 1.0 + assert cba_metadata.template_tags == "vDNS-CDS-test1" + + with open(Path(Path(__file__).resolve().parent, "data/vLB_CBA_Python.zip"), "rb") as cba_file: + b = Blueprint(cba_file.read()) + assert b.metadata.tosca_meta_file_version == "1.0.0" + assert b.metadata.csar_version == 1.0 + assert b.metadata.created_by == "PLATANIA, MARCO " + assert b.metadata.entry_definitions == "Definitions/vLB_CDS.json" + assert b.metadata.template_name == "vDNS-CDS-test1" + assert b.metadata.template_version == 1.0 + assert b.metadata.template_tags == "vDNS-CDS-test1" + + +def test_blueprint_get_mappings_from_mapping_file(): + b = Blueprint(b"test cba - it will never work") + mappings = list(b.get_mappings_from_mapping_file(vLB_CBA_Python_base_template_mapping_bytes)) + assert len(mappings) == 5 + mapping = mappings[0] + assert mapping.name == "service-instance-id" + assert mapping.mapping_type == "string" + assert mapping.dictionary_name == "service-instance-id" + assert mapping.dictionary_sources == ["input"] + + +def test_blueprint_generate_data_dictionary_set(): + with open(Path(Path(__file__).resolve().parent, "data/vLB_CBA_Python.zip"), "rb") as cba_file: + b = Blueprint(cba_file.read()) + dd_set = b.get_data_dictionaries() + print(dd_set) + + +@patch.object(CdsElement, "_url", new_callable=PropertyMock) +def test_data_dictionary(cds_element_url_property_mock): + cds_element_url_property_mock.return_value = "http://127.0.0.1" + + with raises(ValidationError) as exc: + DataDictionary({}) + assert exc.type is ValidationError + + dd = DataDictionary({}, fix_schema=False) + assert dd.url == "http://127.0.0.1/api/v1/dictionary" + assert dd.data_dictionary_json == {} + + dd = DataDictionary(DD_1) + dd.name == DD_1["name"] + + +@patch.object(DataDictionary, "send_message") +def test_data_dictionary_upload(send_message_mock): + dd = DataDictionary(DD_1) + dd.upload() + send_message_mock.assert_called_once() + + +@patch.object(DataDictionary, "send_message") +def test_data_dictionary_set(send_message_mock): + dd_set = DataDictionarySet() + + dd_set.add(DataDictionary(DD_1)) + assert dd_set.length == 1 + + dd_set.add(DataDictionary(DD_1)) + assert dd_set.length == 1 + + dd_set.add(DataDictionary({"name": "test"}, fix_schema=False)) + assert dd_set.length == 2 + + dd_set.upload() + assert send_message_mock.call_count == 2 + + +def test_data_dictionary_set_save_to_file_load_from_file(): + dd = DataDictionarySet() + dd.add(DataDictionary(DD_1)) + with TemporaryDirectory() as tmpdirname: + path = os.path.join(tmpdirname, "dd.json") + dd.save_to_file(path) + with open(path, "r") as f: + assert f.read() == json.dumps([dd.data_dictionary_json for dd in dd.dd_set], indent=4) + dd_2 = DataDictionarySet.load_from_file(path) + assert dd.dd_set == dd_2.dd_set + +def test_data_dictionary_load_from_file_file_error(): + + with TemporaryDirectory() as tmpdirname, \ + patch("__main__.open", new_callable=mock_open) as mo, \ + raises(FileError) as exc: + + path = os.path.join(tmpdirname, "nonexistent_file.zip") + mo.side_effect = FileNotFoundError + + DataDictionarySet.load_from_file(path) + + assert exc.type == FileError + + +def test_mapping(): + m1 = Mapping(name="test", + mapping_type="string", + dictionary_name="test_dictionary_name", + dictionary_sources=["dictionary_source_1"]) + + m2 = Mapping(name="test", mapping_type="string", dictionary_name="test_dictionary_name", dictionary_sources=["dictionary_source_2"]) + + assert m1 == m2 + m1.merge(m2) + assert sorted(m1.dictionary_sources) == ["dictionary_source_1", "dictionary_source_2"] + m1.merge(m2) + assert sorted(m1.dictionary_sources) == ["dictionary_source_1", "dictionary_source_2"] + + +def test_mapping_set(): + ms = MappingSet() + assert len(ms) == 0 + m1 = Mapping(name="test", + mapping_type="string", + dictionary_name="test_dictionary_name", + dictionary_sources=["dictionary_source_1"]) + + m2 = Mapping(name="test", mapping_type="string", dictionary_name="test_dictionary_name", dictionary_sources=["dictionary_source_2"]) + + ms.add(m1) + assert len(ms) == 1 + ms.add(m2) + assert len(ms) == 1 + assert sorted(ms[0].dictionary_sources) == ["dictionary_source_1", "dictionary_source_2"] + + +def test_blueprint_get_workflows_from_entry_definitions_file(): + with open(Path(Path(__file__).resolve().parent, "data/vLB_CBA_Python.zip"), "rb") as cba_file: + b = Blueprint(cba_file.read()) + assert len(b.workflows) == 3 + workflow = b.workflows[0] + assert len(workflow.steps) == 1 + assert workflow.steps[0].name == "resource-assignment" + assert workflow.steps[0].description == "Resource Assign Workflow" + assert workflow.steps[0].target == "resource-assignment" + assert len(workflow.inputs) == 2 + assert len(workflow.outputs) == 1 + + +def test_blueprint_get_workflow_by_name(): + with open(Path(Path(__file__).resolve().parent, "data/vLB_CBA_Python.zip"), "rb") as cba_file: + b = Blueprint(cba_file.read()) + workflow = b.get_workflow_by_name("resource-assignment") + assert workflow.name == "resource-assignment" + workflow = b.get_workflow_by_name("config-assign") + assert workflow.name == "config-assign" + workflow = b.get_workflow_by_name("config-deploy") + assert workflow.name == "config-deploy" + with raises(ParameterError): + b.get_workflow_by_name("non-existing-workflow") + + +@patch.object(Workflow, "send_message") +def test_workflow_execute(send_message_mock): + metadata = MagicMock(template_name="test", template_version="test") + blueprint = MagicMock(metadata=metadata) + workflow = Workflow("test_workflow", {}, blueprint) + assert len(workflow.steps) == 0 + assert len(workflow.inputs) == 0 + assert len(workflow.outputs) == 0 + workflow.execute({}) + send_message_mock.assert_called_once() + + +def test_data_dictionary_validation(): + assert DataDictionary(DD_1).has_valid_schema() + raw_dd = DataDictionary(RAW_DD, fix_schema=False) + assert not raw_dd.has_valid_schema() + raw_dd = DataDictionary(RAW_DD, fix_schema=True) + assert raw_dd.has_valid_schema() + + +@patch.object(Blueprintprocessor, "send_message") +def test_blueprintprocessor_bootstrap(mock_send_message): + + Blueprintprocessor.bootstrap() + assert mock_send_message.called_once() + assert mock_send_message.call_args[1]["data"] == '{\n "loadModelType" : true,\n "loadResourceDictionary" : true,\n "loadCBA" : true\n}' + mock_send_message.reset_mock() + + Blueprintprocessor.bootstrap(load_cba=False, load_model_type=False, load_resource_dictionary=False) + assert mock_send_message.called_once() + assert mock_send_message.call_args[1]["data"] == '{\n "loadModelType" : false,\n "loadResourceDictionary" : false,\n "loadCBA" : false\n}' + + +@patch.object(DataDictionary, "send_message_json") +def test_data_dictionary_get_by_name(mock_send_message_json): + + DataDictionary.get_by_name("test_name") + mock_send_message_json.assert_called_once() + assert "test_name" in mock_send_message_json.call_args[0][2] + + +@patch.object(CdsElement, "send_message") +def test_get_guis(send_message_mock): + component = CdsElement() + send_message_mock.return_value.status_code = 200 + send_message_mock.return_value.url = "http://portal.api.simpledemo.onap.org:30449/" + gui_results = component.get_guis() + assert type(gui_results) == GuiList + assert gui_results.guilist[0].url == send_message_mock.return_value.url + assert gui_results.guilist[0].status == send_message_mock.return_value.status_code + + +@patch.object(ResolvedTemplate, "send_message_json") +@patch.object(CdsElement, "_url", new_callable=PropertyMock) +def test_blueprint_get_resolved_template(cds_element_url_property_mock, mock_send_message_json): + cds_element_url_property_mock.return_value = "http://127.0.0.1" + + with open(Path(Path(__file__).resolve().parent, "data/vLB_CBA_Python.zip"), "rb") as cba_file: + b = Blueprint(cba_file.read()) + b.get_resolved_template("test_artifact") + assert mock_send_message_json.called_once() + assert mock_send_message_json.call_args[0][2] == 'http://127.0.0.1/api/v1/template?bpName=vDNS-CDS-test1&bpVersion=1.0&artifactName=test_artifact&format=application%2Fjson' + + +@patch.object(ResolvedTemplate, "send_message") +@patch.object(CdsElement, "_url", new_callable=PropertyMock) +def test_blueprint_store_resolved_template(cds_element_url_property_mock, mock_send_message): + cds_element_url_property_mock.return_value = "http://127.0.0.1" + + with open(Path(Path(__file__).resolve().parent, "data/vLB_CBA_Python.zip"), "rb") as cba_file: + b = Blueprint(cba_file.read()) + b.store_resolved_template("test_artifact", resolution_key="resolution_key", data={"a": "b"}) + assert mock_send_message.called_once() + assert mock_send_message.call_args[0][2] == 'http://127.0.0.1/api/v1/template/vDNS-CDS-test1/1.0/test_artifact/resolution_key' + + +@patch.object(ResolvedTemplate, "send_message_json") +@patch.object(CdsElement, "_url", new_callable=PropertyMock) +def test_resolved_template_get_template_url(cds_element_url_property_mock, mock_send_message_json): + cds_element_url_property_mock.return_value = "http://127.0.0.1" + blueprint = MagicMock() + blueprint.metadata.template_name = "test_blueprint" + blueprint.metadata.template_version = "v1.0.0" + rt = ResolvedTemplate(blueprint, "test_artifact") + rt.get_resolved_template() + assert mock_send_message_json.called_once() + assert mock_send_message_json.call_args[0][2] == 'http://127.0.0.1/api/v1/template?bpName=test_blueprint&bpVersion=v1.0.0&artifactName=test_artifact&format=application%2Fjson' + + mock_send_message_json.reset_mock() + blueprint = MagicMock() + blueprint.metadata.template_name = "test_blueprint" + blueprint.metadata.template_version = "v1.0.0" + rt = ResolvedTemplate(blueprint, resolution_key="test_rk") + rt.get_resolved_template() + assert mock_send_message_json.called_once() + assert mock_send_message_json.call_args[0][2] == 'http://127.0.0.1/api/v1/template?bpName=test_blueprint&bpVersion=v1.0.0&resolutionKey=test_rk&format=application%2Fjson' + + mock_send_message_json.reset_mock() + blueprint = MagicMock() + blueprint.metadata.template_name = "test_blueprint" + blueprint.metadata.template_version = "v1.0.0" + rt = ResolvedTemplate(blueprint, resource_id="r_id", resource_type="r_type") + rt.get_resolved_template() + assert mock_send_message_json.called_once() + assert mock_send_message_json.call_args[0][2] == 'http://127.0.0.1/api/v1/template?bpName=test_blueprint&bpVersion=v1.0.0&resourceType=r_type&resourceId=r_id&format=application%2Fjson' + + +@patch.object(ResolvedTemplate, "send_message") +@patch.object(CdsElement, "_url", new_callable=PropertyMock) +def test_resolved_template_store_template_url(cds_element_url_property_mock, mock_send_message): + cds_element_url_property_mock.return_value = "http://127.0.0.1" + + blueprint = MagicMock() + blueprint.metadata.template_name = "test_blueprint" + blueprint.metadata.template_version = "v1.0.0" + rt = ResolvedTemplate(blueprint, "test_artifact", resolution_key="resolution_key") + rt.store_resolved_template({"a": "b"}) + assert mock_send_message.called_once() + assert mock_send_message.call_args[0][2] == 'http://127.0.0.1/api/v1/template/test_blueprint/v1.0.0/test_artifact/resolution_key' + + mock_send_message.reset_mock() + rt = ResolvedTemplate(blueprint, "test_artifact", resource_id="resource_id", resource_type="resource_type") + rt.store_resolved_template({"a": "b"}) + assert mock_send_message.called_once() + assert mock_send_message.call_args[0][2] == 'http://127.0.0.1/api/v1/template/test_blueprint/v1.0.0/test_artifact/resource_type/resource_id' + + mock_send_message.reset_mock() + rt = ResolvedTemplate(blueprint, "test_artifact") + with raises(ParameterError): + rt.store_resolved_template({"a": "b"}) diff --git a/tests/test_cds_blueprint_models.py b/tests/test_cds_blueprint_models.py new file mode 100644 index 0000000..4b1744d --- /dev/null +++ b/tests/test_cds_blueprint_models.py @@ -0,0 +1,201 @@ +"""Test CdsBlueprintModel module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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.path + +from unittest import mock +from tempfile import TemporaryDirectory + +import pytest +import requests +import io + +from onapsdk.exceptions import ResourceNotFound +from onapsdk.cds.blueprint_model import BlueprintModel +from onapsdk.cds.cds_element import CdsElement +from onapsdk.cds.blueprint import Blueprint +from onapsdk.onap_service import OnapService + + +# pylint: disable=C0301 +BLUEPRINT_MODEL = { + "blueprintModel": { + "id": "11111111-2222-3333-4444-555555555555", + "artifactUUId": None, + "artifactType": "SDNC_MODEL", + "artifactVersion": "1.0.0", + "artifactDescription": "", + "internalVersion": None, + "createdDate": "2020-12-14T19:33:57.000Z", + "artifactName": "test_blueprint", + "published": "Y", + "updatedBy": "Carlos Santana ", + "tags": "Carlos Santana, test, blueprint" + } +} + +BLUEPRINT_MODEL_LIST = [ + BLUEPRINT_MODEL +] +# pylint: enable=C0301 + + +def test_init(): + """Test the initialization.""" + element = CdsElement() + assert isinstance(element, OnapService) + + +def test_class_variables(): + """Test the class variables.""" + assert CdsElement._url == "http://portal.api.simpledemo.onap.org:30449" + assert CdsElement.auth == ("ccsdkapps", "ccsdkapps") + assert CdsElement.headers == { + "Content-Type": "application/json", + "Accept": "application/json"} + + +@mock.patch.object(CdsElement, 'send_message_json') +def test_blueprint_model_all(mock_send): + """Test get_all function of BlueprintModel.""" + mock_send.return_value = BLUEPRINT_MODEL_LIST + assert len(list(BlueprintModel.get_all())) == 1 + + blueprint_model_1 = next(BlueprintModel.get_all()) + assert blueprint_model_1.blueprint_model_id == "11111111-2222-3333-4444-555555555555" + assert blueprint_model_1.artifact_uuid is None + assert blueprint_model_1.artifact_type == "SDNC_MODEL" + assert blueprint_model_1.artifact_version == "1.0.0" + assert blueprint_model_1.internal_version is None + assert blueprint_model_1.created_date == "2020-12-14T19:33:57.000Z" + assert blueprint_model_1.artifact_name == "test_blueprint" + assert blueprint_model_1.published == "Y" + assert blueprint_model_1.updated_by == "Carlos Santana " + assert blueprint_model_1.tags == "Carlos Santana, test, blueprint" + + +@mock.patch.object(CdsElement, 'send_message_json') +def test_blueprint_model_all_empty(mock_send): + """Test get_all function of BlueprintModel with no BlueprintModels.""" + mock_send.return_value = "" + assert len(list(BlueprintModel.get_all())) == 0 + + +@mock.patch.object(CdsElement, 'send_message_json') +def test_blueprint_model_by_id(mock_send): + """Test get_by_id function of BlueprintModel.""" + mock_send.return_value = BLUEPRINT_MODEL + + blueprint_model_2 = BlueprintModel.get_by_id( + blueprint_model_id="11111111-2222-3333-4444-555555555555") + assert blueprint_model_2.blueprint_model_id == "11111111-2222-3333-4444-555555555555" + assert blueprint_model_2.artifact_uuid is None + assert blueprint_model_2.artifact_type == "SDNC_MODEL" + assert blueprint_model_2.artifact_version == "1.0.0" + assert blueprint_model_2.internal_version is None + assert blueprint_model_2.created_date == "2020-12-14T19:33:57.000Z" + assert blueprint_model_2.artifact_name == "test_blueprint" + assert blueprint_model_2.published == "Y" + assert blueprint_model_2.updated_by == "Carlos Santana " + assert blueprint_model_2.tags == "Carlos Santana, test, blueprint" + + +@mock.patch.object(CdsElement, 'send_message_json') +def test_blueprint_model_by_id_non_existing(mock_send): + """Test get_by_id exception for non existing BlueprintModel.""" + + mock_send.side_effect = ResourceNotFound + with pytest.raises(ResourceNotFound) as exc: + BlueprintModel.get_by_id( + blueprint_model_id="11111111-2222-3333-4444-555555555555") + + assert exc.type == ResourceNotFound + + +@mock.patch.object(CdsElement, 'send_message_json') +def test_blueprint_model_by_name_and_version(mock_send): + """Test get_by_name_and_version function of BlueprintModel.""" + mock_send.return_value = BLUEPRINT_MODEL + + blueprint_model_3 = BlueprintModel.get_by_name_and_version( + blueprint_name="test_blueprint", + blueprint_version="1.0.0") + assert blueprint_model_3.blueprint_model_id == "11111111-2222-3333-4444-555555555555" + assert blueprint_model_3.artifact_uuid is None + assert blueprint_model_3.artifact_type == "SDNC_MODEL" + assert blueprint_model_3.artifact_version == "1.0.0" + assert blueprint_model_3.internal_version is None + assert blueprint_model_3.created_date == "2020-12-14T19:33:57.000Z" + assert blueprint_model_3.artifact_name == "test_blueprint" + assert blueprint_model_3.published == "Y" + assert blueprint_model_3.updated_by == "Carlos Santana " + assert blueprint_model_3.tags == "Carlos Santana, test, blueprint" + + +@mock.patch.object(CdsElement, 'send_message_json') +def test_blueprint_model_by_name_and_version_non_existing(mock_send): + """Test get_by_name_and_version exception for non existing BlueprintModel.""" + + mock_send.side_effect = ResourceNotFound + with pytest.raises(ResourceNotFound) as exc: + BlueprintModel.get_by_name_and_version( + blueprint_name="test_blueprint_wrong", + blueprint_version="1.0.0") + + assert exc.type == ResourceNotFound + + +@mock.patch.object(CdsElement, 'send_message') +def test_get_blueprint_object(mock_send): + """Test retrieve Blueprint object for selected BlueprintModel.""" + mock_send.return_value.content = b"test cba - it will never work" + + blueprint_model_4 = BlueprintModel( + blueprint_model_id="11111111-2222-3333-4444-555555555555") + + blueprint4_object = blueprint_model_4.get_blueprint() + assert isinstance(blueprint4_object, Blueprint) + + +@mock.patch.object(CdsElement, 'send_message') +def test_save_blueprint(mock_send): + """Test download BlueprintModel from onap cds.""" + r = requests.Response() + r.raw = io.BytesIO(b'test cba - it will never work') + mock_send.return_value = r + + blueprint_model_5 = BlueprintModel( + blueprint_model_id="11111111-2222-3333-4444-555555555555") + + with TemporaryDirectory() as tmpdirname: + path = os.path.join(tmpdirname, "test.zip") + blueprint_model_5.save(dst_file_path=path) + + with open(path, "rb") as f: + assert f.read() == b"test cba - it will never work" + + +@mock.patch.object(CdsElement, 'send_message') +def test_delete_blueprint(mock_send): + """Test delete BlueprintModel in onap cds. """ + + blueprint_model_6 = BlueprintModel( + blueprint_model_id="11111111-2222-3333-4444-555555555555") + blueprint_model_6.delete() + mock_send.assert_called_once() + + method, description, url = mock_send.call_args[0] + assert method == "DELETE" + assert description == f"Delete blueprint" + assert url == f"{CdsElement._url}/api/v1/blueprint-model/{blueprint_model_6.blueprint_model_id}" diff --git a/tests/test_clamp.py b/tests/test_clamp.py new file mode 100644 index 0000000..5768341 --- /dev/null +++ b/tests/test_clamp.py @@ -0,0 +1,532 @@ +"""Test clamp module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock +import pytest + +from onapsdk.clamp.clamp_element import Clamp +from onapsdk.clamp.loop_instance import LoopInstance +from onapsdk.exceptions import ParameterError, ResourceNotFound +from onapsdk.sdc.service import Service + +#examples +TEMPLATES = [ + { + "name" : "test_template", + "modelService" : { + "serviceDetails" : { + "name" : "test" + } + } + } +] + +POLICIES = [ + { + "policyModelType" : "onap.policies.controlloop.Test", + "version" : "1.0.0", + "policyAcronym" : "Test", + "createdDate" : "2020-04-30T09:03:30.362897Z", + "updatedDate" : "2020-04-30T09:03:30.362897Z", + "updatedBy" : "Not found", + "createdBy" : "Not found" + } +] + +LOOP_DETAILS = { + "name" : "LOOP_test", + "globalPropertiesJson": { + "dcaeDeployParameters" : { + "uniqueBlueprintParameters" : { + "policy_id" : "Microservice12345" + } + } + }, + "components" : { + "POLICY" : { + "componentState" : { + "stateName" : "UNKNOWN" + } + }, + "DCAE" : { + "componentState" : { + "stateName" : "BLUEPRINT_DEPLOYED" + } + } + }, + "modelService" : { + "resourceDetails": { + "VFModule" : { + "resourceID" : { + "vfModuleModelName" : "resourceID", + "vfModuleModelInvariantUUID" : "InvariantUUID", + "vfModuleModelUUID" : "UUID", + "vfModuleModelVersion" : "1.0", + "vfModuleModelCustomizationUUID" : "CustomizationUUID" + } + } + } + }, + "operationalPolicies" : [ + { + "name" : "MICROSERVICE_test" + } + ], + "microServicePolicies" : [ + { + "name" : "MICROSERVICE_test" + } + ] +} + +#for policy deploy to policy engine +SUBMITED_POLICY = { + "components" : { + "POLICY" : { + "componentState" : { + "stateName" : "SENT_AND_DEPLOYED" + } + } + } +} + +NOT_SUBMITED_POLICY = { + "components" : { + "POLICY" : { + "componentState" : { + "stateName" : "SENT" + } + } + } +} + +#for the deploy to DCAE +SUBMITED = { + "components" : { + "DCAE" : { + "componentState" : { + "stateName" : "MICROSERVICE_INSTALLED_SUCCESSFULLY" + } + } + } +} + +NOT_SUBMITED = { + "components" : { + "DCAE" : { + "componentState" : { + "stateName" : "MICROSERVICE_INSTALLATION_FAILED" + } + } + } +} +#end of examples + + +def test_initialization(): + """Class initialization test.""" + clamp = Clamp() + assert isinstance(clamp, Clamp) + + +@mock.patch.object(Clamp, 'send_message_json') +def test_check_loop_template(mock_send_message_json): + """Test Clamp's class method.""" + svc = Service(name='test') + mock_send_message_json.return_value = TEMPLATES + template = Clamp.check_loop_template(service=svc) + mock_send_message_json.assert_called_once_with('GET', + 'Get Loop Templates', + (f"{Clamp.base_url()}/templates/")) + assert template == "test_template" + + +@mock.patch.object(Clamp, 'send_message_json') +def test_check_loop_template_none(mock_send_message_json): + """Test Clamp's class method.""" + svc = Service(name='test') + mock_send_message_json.return_value = {} + with pytest.raises(ResourceNotFound) as exc: + template = Clamp.check_loop_template(service=svc) + assert template is None + assert exc.type is ResourceNotFound + + +@mock.patch.object(Clamp, 'send_message_json') +def test_check_policies(mock_send_message_json): + mock_send_message_json.return_value = POLICIES + exists = Clamp.check_policies(policy_name="Test", req_policies=1) + mock_send_message_json.\ + assert_called_once_with('GET', + 'Get stocked policies', + (f"{Clamp.base_url()}/policyToscaModels/")) + assert exists + + +@mock.patch.object(Clamp, 'send_message_json') +def test_check_policies_none(mock_send_message_json): + mock_send_message_json.return_value = POLICIES + exists = Clamp.check_policies(policy_name="Test") + mock_send_message_json.\ + assert_called_once_with('GET', + 'Get stocked policies', + (f"{Clamp.base_url()}/policyToscaModels/")) + assert not exists + + +def test_cl_initialization(): + """Class initialization test.""" + loop = LoopInstance(template="template", name="LOOP_name", details={}) + assert isinstance(loop, LoopInstance) + + +@mock.patch.object(LoopInstance, '_update_loop_details') +def test_details(mock_update): + """Test loop instace details gette.""" + loop = LoopInstance(template="template", name="LOOP_name", details={}) + mock_update.return_value = {"name" : "test"} + details = loop.details + assert details == {} + + +@mock.patch.object(LoopInstance, 'send_message_json') +def test_update_loop_details(mock_send_message_json): + """Test Loop instance methode.""" + loop = LoopInstance(template="template", name="test", details={}) + mock_send_message_json.return_value = LOOP_DETAILS + loop.details = loop._update_loop_details() + mock_send_message_json.assert_called_once_with('GET', 'Get loop details', + (f"{loop.base_url()}/loop/LOOP_test")) + assert loop.details == LOOP_DETAILS + + +@mock.patch('time.sleep', return_value=False) +@mock.patch.object(LoopInstance, 'send_message_json') +def test_refresh_status(mock_send_message_json,mock_timer): + """Test Loop instance methode.""" + loop = LoopInstance(template="template", name="test", details={}) + mock_send_message_json.return_value = LOOP_DETAILS + loop.refresh_status() + mock_send_message_json.assert_called_once_with('GET', 'Get loop status', + (f"{loop.base_url()}/loop/getstatus/LOOP_test")) + assert loop.details == LOOP_DETAILS + + +def test_validate(): + """Test Loop instance details validation.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + valid = loop.validate_details() + assert valid + + +def test_validate_details(): + """Test Loop instance details validation.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + loop.details = {"test":"test"} + valid = loop.validate_details() + assert not valid + + +@mock.patch.object(LoopInstance, 'send_message_json') +def test_create(mock_send_message_json): + """Test Loop instance creation.""" + instance = LoopInstance(template="template", name="test", details={}) + mock_send_message_json.return_value = LOOP_DETAILS + instance.create() + mock_send_message_json.assert_called_once_with('POST', 'Create Loop Instance', + (f"{instance.base_url()}/loop/create/LOOP_test?templateName=template")) + assert instance.name == "LOOP_test" + assert len(instance.details["microServicePolicies"]) > 0 + + +@mock.patch.object(LoopInstance, 'send_message_json') +def test_add_operational_policy(mock_send_message_json): + """Test adding an op policy.""" + loop = LoopInstance(template="template", name="test", details={}) + loop.details = { + "name" : "LOOP_test", + "operationalPolicies" : None, + "microServicePolicies" : [ + { + "name" : "MICROSERVICE_test" + } + ] + } + mock_send_message_json.return_value = LOOP_DETAILS + loop.add_operational_policy(policy_type="FrequencyLimiter", policy_version="1.0.0") + mock_send_message_json.assert_called_once_with('PUT', 'Create Operational Policy', + (f"{loop.base_url()}/loop/addOperationaPolicy/{loop.name}/policyModel/FrequencyLimiter/1.0.0")) + assert loop.name == "LOOP_test" + assert len(loop.details["operationalPolicies"]) > 0 + + +@mock.patch.object(LoopInstance, 'send_message_json') +def test_not_add_operational_policy_parameter_error(mock_send_message_json): + """Test adding an op policy - mistaken policy version.""" + loop = LoopInstance(template="template", name="test", details={}) + loop.details = { + "name" : "LOOP_test", + "operationalPolicies" : [], + "microServicePolicies" : [ + { + "name" : "MICROSERVICE_test" + } + ] + } + with pytest.raises(ParameterError) as exc: + mock_send_message_json.return_value = loop.details + loop.add_operational_policy(policy_type="FrequencyLimiter", policy_version="not_correct") + mock_send_message_json.assert_called_once_with('PUT', 'Create Operational Policy', + (f"{loop.base_url()}/loop/addOperationaPolicy/{loop.name}/policyModel/FrequencyLimiter/not_correct")) + assert len(loop.details["operationalPolicies"]) == 0 + assert exc.type is ParameterError + +@mock.patch.object(LoopInstance, 'send_message_json') +def test_add_operational_policy_key_parameter_error(mock_send_message_json): + """Test adding an op policy - key doesn't exist.""" + loop = LoopInstance(template="template", name="test", details={}) + loop.details = {} + with pytest.raises(ParameterError) as exc: + mock_send_message_json.return_value = loop.details + loop.add_operational_policy(policy_type="FrequencyLimiter", policy_version="not_correct") + mock_send_message_json.assert_called_once_with('PUT', 'Create Operational Policy', + (f"{loop.base_url()}/loop/addOperationaPolicy/{loop.name}/policyModel/FrequencyLimiter/not_correct")) + assert exc.type is ParameterError + +@mock.patch.object(LoopInstance, 'send_message_json') +def test_add_operational_policy_condition_parameter_error(mock_send_message_json): + """Test adding an op policy - response cintains more policies.""" + + key = "operationalPolicies" + + response_policies = ["one"] # N policies + current_policies = ["one", "two"] # N+1 policies + + details = {key: current_policies} + response = {key: response_policies} + + loop = LoopInstance(template="template", name="test", details=details) + + assert len(response_policies) < len(current_policies) # raising condition + with pytest.raises(ParameterError) as exc: + mock_send_message_json.return_value = response + loop.add_operational_policy(policy_type="FrequencyLimiter", policy_version="not_correct") + assert exc.type is ParameterError + + +@mock.patch.object(LoopInstance, 'send_message_json') +def test_remove_operational_policy(mock_send_message_json): + """Test remove an op policy.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + mock_send_message_json.return_value = { + "name" : "LOOP_test", + "operationalPolicies" : [], + "microServicePolicies" : [ + { + "name" : "MICROSERVICE_test" + } + ] + } + loop.remove_operational_policy(policy_type="FrequencyLimiter", policy_version="1.0.0") + mock_send_message_json.assert_called_once_with('PUT', 'Remove Operational Policy', + (f"{loop.base_url()}/loop/removeOperationaPolicy/{loop.name}/policyModel/FrequencyLimiter/1.0.0")) + assert len(loop.details["operationalPolicies"]) == 0 + + +@mock.patch.object(LoopInstance, 'send_message') +def test_update_microservice_policy(mock_send_message): + """Test Loop Instance add TCA configuration.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + mock_send_message.return_value = True + loop.update_microservice_policy() + mock_send_message.assert_called_once() + method, description, url = mock_send_message.call_args[0] + assert method == "POST" + assert description == "ADD TCA config" + assert url == (f"{loop.base_url()}/loop/updateMicroservicePolicy/{loop.name}") + + +@mock.patch.object(LoopInstance, 'send_message') +def test_update_microservice_policy_none(mock_send_message): + """Test Loop Instance add TCA configuration.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + mock_send_message.return_value = False + loop.update_microservice_policy() + mock_send_message.assert_called_once() + + +def test_extract_operational_policy_name(): + """Test Loop Instance extract operational policy name.""" + loop = LoopInstance(template="template", name="test", details={}) + loop.details = {"operationalPolicies":[{"name":"test","policyModel":{"policyAcronym":"Drools"}}]} + policy_name = loop.extract_operational_policy_name(policy_type="Drools") + assert policy_name=='test' + + +def test_extract_none(): + """Test Loop Instance extract operational policy name.""" + loop = LoopInstance(template="template", name="test", details={}) + loop.details = {"operationalPolicies":[]} + with pytest.raises(ParameterError) as exc: + policy_name = loop.extract_operational_policy_name(policy_type="Drools") + assert policy_name == None + assert exc.type is ParameterError + + +@mock.patch.object(LoopInstance, 'extract_operational_policy_name') +@mock.patch.object(LoopInstance, 'send_message') +def test_add_drools_policy_config(mock_send_message, mock_extract): + """Test Loop Instance add op policy configuration.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + mock_send_message.return_value = True + loop.add_op_policy_config(loop.add_drools_conf) + mock_send_message.assert_called_once() + method, description, url = mock_send_message.call_args[0] + assert method == "POST" + assert description == "ADD operational policy config" + assert url == (f"{loop.base_url()}/loop/updateOperationalPolicies/{loop.name}") + + +@mock.patch.object(LoopInstance, 'extract_operational_policy_name') +@mock.patch.object(LoopInstance, 'send_message') +def test_add_minmax_config(mock_send_message, mock_extract): + """Test Loop Instance add op policy configuration.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + mock_send_message.return_value = True + loop.add_op_policy_config(loop.add_minmax_config) + mock_send_message.assert_called_once() + method, description, url = mock_send_message.call_args[0] + assert method == "POST" + assert description == "ADD operational policy config" + assert url == (f"{loop.base_url()}/loop/updateOperationalPolicies/{loop.name}") + + +@mock.patch.object(LoopInstance, 'extract_operational_policy_name') +@mock.patch.object(LoopInstance, 'send_message') +def test_add_frequency_policy_config(mock_send_message, mock_extract): + """Test Loop Instance add op policy configuration.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + mock_send_message.return_value = True + loop.add_op_policy_config(loop.add_frequency_limiter) + mock_send_message.assert_called_once() + method, description, url = mock_send_message.call_args[0] + assert method == "POST" + assert description == "ADD operational policy config" + assert url == (f"{loop.base_url()}/loop/updateOperationalPolicies/{loop.name}") + +@mock.patch.object(LoopInstance, 'send_message') +@mock.patch.object(LoopInstance, 'add_minmax_config') +@mock.patch.object(LoopInstance, 'add_frequency_limiter') +def test_add_two_policies_config(mock_freq, mock_min, mock_send_message): + """Test Loop Instance add op policy configuration.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + mock_min.return_value = '[{"test1":"test1"}]' + mock_freq.return_value = '[{"test2":"test2"}]' + loop.add_op_policy_config(loop.add_minmax_config) + mock_min.assert_called_once() + mock_send_message.assert_called_once() + loop.add_op_policy_config(loop.add_frequency_limiter) + mock_freq.assert_called_once() + assert loop.operational_policies == '[{"test1":"test1"},{"test2":"test2"}]' + + +@mock.patch.object(LoopInstance, 'refresh_status') +@mock.patch.object(LoopInstance, 'send_message') +def test_submit_policy(mock_send_message, mock_refresh): + """Test submit policies to policy engine.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + action = loop.act_on_loop_policy(loop.submit) + mock_send_message.assert_called_once_with('PUT', + 'submit policy', + (f"{loop.base_url()}/loop/submit/LOOP_test")) + mock_refresh.assert_called_once() + loop.details = SUBMITED_POLICY + assert loop.details["components"]["POLICY"]["componentState"]["stateName"] == "SENT_AND_DEPLOYED" + + +@mock.patch.object(LoopInstance, 'refresh_status') +@mock.patch.object(LoopInstance, 'send_message') +def test_stop_policy(mock_send_message, mock_refresh): + """Test submit policies to policy engine.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + action = loop.act_on_loop_policy(loop.stop) + mock_send_message.assert_called_once_with('PUT', + 'stop policy', + (f"{loop.base_url()}/loop/stop/LOOP_test")) + mock_refresh.assert_called_once() + loop.details = {"components":{"POLICY":{"componentState":{"stateName":"SENT"}}}} + assert loop.details["components"]["POLICY"]["componentState"]["stateName"] == "SENT" + + +@mock.patch.object(LoopInstance, 'refresh_status') +@mock.patch.object(LoopInstance, 'send_message') +def test_restart_policy(mock_send_message, mock_refresh): + """Test submit policies to policy engine.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + action = loop.act_on_loop_policy(loop.restart) + mock_send_message.assert_called_once_with('PUT', + 'restart policy', + (f"{loop.base_url()}/loop/restart/LOOP_test")) + mock_refresh.assert_called_once() + loop.details = SUBMITED_POLICY + assert loop.details["components"]["POLICY"]["componentState"]["stateName"] == "SENT_AND_DEPLOYED" + + +@mock.patch.object(LoopInstance, 'refresh_status') +@mock.patch.object(LoopInstance, 'send_message') +def test_not_submited_policy(mock_send_message, mock_refresh): + """Test submit policies to policy engine.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + mock_refresh.return_value = NOT_SUBMITED_POLICY + action = loop.act_on_loop_policy(loop.submit) + mock_send_message.assert_called_once_with('PUT', + 'submit policy', + (f"{loop.base_url()}/loop/submit/LOOP_test")) + mock_refresh.assert_called_once() + loop.details = NOT_SUBMITED_POLICY + assert loop.details["components"]["POLICY"]["componentState"]["stateName"] == "SENT" + + +@mock.patch('time.sleep', return_value=False) +@mock.patch.object(LoopInstance, 'send_message_json') +@mock.patch.object(LoopInstance, 'send_message') +def test_deploy_microservice_to_dcae(mock_send_message, mock_send_message_json, mock_timer): + """Test stop microservice.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + mock_send_message_json.return_value = SUBMITED + state = loop.deploy_microservice_to_dcae() + mock_send_message.assert_called_once_with('PUT', + 'Deploy microservice to DCAE', + (f"{loop.base_url()}/loop/deploy/LOOP_test")) + assert state + + +@mock.patch.object(LoopInstance, 'send_message') +def test_undeploy_microservice_from_dcae(mock_send_message): + """Test stop microservice.""" + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + request = loop.undeploy_microservice_from_dcae() + mock_send_message.assert_called_once_with('PUT', + 'Undeploy microservice from DCAE', + (f"{loop.base_url()}/loop/undeploy/LOOP_test")) + + +@mock.patch.object(LoopInstance, 'send_message') +def test_delete(mock_send_message): + loop = LoopInstance(template="template", name="test", details=LOOP_DETAILS) + request = loop.delete() + mock_send_message.assert_called_once_with('PUT', + 'Delete loop instance', + (f"{loop.base_url()}/loop/delete/{loop.name}")) diff --git a/tests/test_configuration.py b/tests/test_configuration.py new file mode 100644 index 0000000..70ae14a --- /dev/null +++ b/tests/test_configuration.py @@ -0,0 +1,24 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.utils.configuration import tosca_path +from onapsdk.utils.configuration import components_needing_distribution + +def test_tosca_path(): + assert tosca_path() == "/tmp/tosca_files/" + +def test_components_needing_distribution(): + assert "SO" in components_needing_distribution() + assert "sdnc" in components_needing_distribution() + assert "aai" in components_needing_distribution() diff --git a/tests/test_cps.py b/tests/test_cps.py new file mode 100644 index 0000000..8f7bdc4 --- /dev/null +++ b/tests/test_cps.py @@ -0,0 +1,240 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock +from typing import List + +from onapsdk.cps import Anchor, Dataspace, SchemaSet, SchemaSetModuleReference, anchor + +DATASPACE_ANCHOR = { + "name": "anchor1", + "schemaSetName": "schemaSet1" +} + +DATASPACE_ANCHORS = [ + DATASPACE_ANCHOR, + { + "name": "anchor2", + "schemaSetName": "schemaSet2" + } +] + +DATASPACE_SCHEMA_SET = { + "name": "schemaSet1", + "moduleReferences": [ + { + "name": "mr1", + "namespace": "mr1_namespace", + "revision": "mr1_revision", + }, + { + "name": "mr2", + "namespace": "mr2_namespace", + "revision": "mr2_revision", + } + ] +} + +# Dataspace tests +def test_dataspace(): + ds = Dataspace(name="test_ds") + assert ds.name == "test_ds" + assert f"cps/api/v1/dataspaces/{ds.name}" in ds.url + +@mock.patch("onapsdk.cps.Dataspace.send_message") +def test_dataspace_create_anchor(mock_send_message): + ds = Dataspace(name="test_ds") + anchor = ds.create_anchor(mock.MagicMock(), "test_anchor") + mock_send_message.assert_called_once() + assert anchor.name == "test_anchor" + +@mock.patch("onapsdk.cps.Dataspace.send_message_json") +def test_dataspace_get_anchors(mock_send_message_json): + mock_send_message_json.return_value = DATASPACE_ANCHORS + ds = Dataspace(name="test_ds") + anchors = list(ds.get_anchors()) + assert len(anchors) == 2 + anchor_1, anchor_2 = anchors + assert isinstance(anchor_1, Anchor) + assert isinstance(anchor_2, Anchor) + assert anchor_1.name == "anchor1" + assert isinstance(anchor_1.schema_set, SchemaSet) + assert anchor_1.schema_set.name == "schemaSet1" + assert anchor_1.schema_set.dataspace == ds + assert anchor_2.name == "anchor2" + assert isinstance(anchor_2.schema_set, SchemaSet) + assert anchor_2.schema_set.name == "schemaSet2" + assert anchor_2.schema_set.dataspace == ds + +@mock.patch("onapsdk.cps.Dataspace.send_message_json") +def test_dataspace_get_anchor(mock_send_message_json): + mock_send_message_json.return_value = DATASPACE_ANCHOR + ds = Dataspace(name="test_ds") + anchor = ds.get_anchor("anything") + assert anchor.name == "anchor1" + assert anchor.schema_set.name == "schemaSet1" + assert anchor.schema_set.dataspace == ds + +@mock.patch("onapsdk.cps.Dataspace.send_message_json") +def test_dataspace_get_schema_set(mock_send_message_json): + mock_send_message_json.return_value = DATASPACE_SCHEMA_SET + ds = Dataspace(name="test_ds") + schema_set = ds.get_schema_set("anything") + assert isinstance(schema_set, SchemaSet) + assert schema_set.dataspace == ds + assert schema_set.name == "schemaSet1" + assert len(schema_set.module_refences) == 2 + mr_1, mr_2 = schema_set.module_refences + assert mr_1.name == "mr1" + assert mr_1.namespace == "mr1_namespace" + assert mr_1.revision == "mr1_revision" + assert mr_2.name == "mr2" + assert mr_2.namespace == "mr2_namespace" + assert mr_2.revision == "mr2_revision" + +@mock.patch("onapsdk.cps.Dataspace.send_message") +@mock.patch("onapsdk.cps.Dataspace.get_schema_set") +def test_dataspace_create_schema_set(mock_get_chema_set, mock_send_message): + ds = Dataspace(name="test_ds") + _ = ds.create_schema_set("test_schema_set_name", b"fake_file") + mock_send_message.assert_called_once() + mock_get_chema_set.assert_called_once_with("test_schema_set_name") + +@mock.patch("onapsdk.cps.Dataspace.send_message") +def test_dataspace_delete(mock_send_message): + ds = Dataspace(name="test_ds") + ds.delete() + mock_send_message.assert_called_once() + +# Schemaset tests +def test_schema_set(): + schema_set = SchemaSet(name="test", dataspace=mock.MagicMock()) + assert schema_set.name == "test" + assert isinstance(schema_set.module_refences, List) + assert not len(schema_set.module_refences) + + schema_set = SchemaSet(name="test_with_mr", dataspace=mock.MagicMock(), + module_references=[SchemaSetModuleReference(name="mr1", namespace="mr1_n", revision="mr1_rev"), + SchemaSetModuleReference(name="mr2", namespace="mr2_n", revision="mr2_rev")]) + assert schema_set.name == "test_with_mr" + assert isinstance(schema_set.module_refences, List) + assert len(schema_set.module_refences) == 2 + mr_1, mr_2 = schema_set.module_refences + assert isinstance(mr_1, SchemaSetModuleReference) + assert isinstance(mr_2, SchemaSetModuleReference) + assert mr_1.name == "mr1" + assert mr_1.namespace == "mr1_n" + assert mr_1.revision == "mr1_rev" + assert mr_2.name == "mr2" + assert mr_2.namespace == "mr2_n" + assert mr_2.revision == "mr2_rev" + +@mock.patch("onapsdk.cps.SchemaSet.send_message") +def test_schemaset_delete(mock_send_message): + schema_set = SchemaSet(name="test", dataspace=mock.MagicMock()) + schema_set.delete() + mock_send_message.assert_called_once() + +# Anchor tests +def test_anchor(): + anchor = Anchor(name="test_anchor", schema_set=mock.MagicMock()) + assert anchor.name == "test_anchor" + assert "test_anchor" in anchor.url + +@mock.patch("onapsdk.cps.Anchor.send_message") +def test_anchor_delete(mock_send_message): + anchor = Anchor(name="test_anchor", schema_set=mock.MagicMock()) + anchor.delete() + mock_send_message.assert_called_once() + url = mock_send_message.call_args[0][2] + assert anchor.url in url + +@mock.patch("onapsdk.cps.Anchor.send_message") +def test_anchor_create_node(mock_send_message): + anchor = Anchor(name="test_anchor", schema_set=mock.MagicMock()) + anchor.create_node('{"test": "data"}') + mock_send_message.assert_called_once() + data = mock_send_message.call_args[1]["data"] + assert data == '{"test": "data"}' + +@mock.patch("onapsdk.cps.Anchor.send_message_json") +def test_anchor_get_node(mock_send_message_json): + anchor = Anchor(name="test_anchor", schema_set=mock.MagicMock()) + anchor.get_node("test-xpath") + mock_send_message_json.assert_called_once() + url = mock_send_message_json.call_args[0][2] + assert "xpath=test-xpath" in url + assert "include-descendants=False" in url + + mock_send_message_json.reset_mock() + anchor.get_node("test-xpath-2", include_descendants=True) + mock_send_message_json.assert_called_once() + url = mock_send_message_json.call_args[0][2] + assert "xpath=test-xpath-2" in url + assert "include-descendants=True" in url + + mock_send_message_json.reset_mock() + anchor.get_node("test-xpath-3", include_descendants=False) + mock_send_message_json.assert_called_once() + url = mock_send_message_json.call_args[0][2] + assert "xpath=test-xpath-3" in url + assert "include-descendants=False" in url + +@mock.patch("onapsdk.cps.Anchor.send_message") +def test_anchor_update_node(mock_send_message): + anchor = Anchor(name="test_anchor", schema_set=mock.MagicMock()) + anchor.update_node("test-xpath", '{"test": "data"}') + mock_send_message.assert_called_once() + url = mock_send_message.call_args[0][2] + assert "xpath=test-xpath" in url + +@mock.patch("onapsdk.cps.Anchor.send_message") +def test_anchor_replace_node(mock_send_message): + anchor = Anchor(name="test_anchor", schema_set=mock.MagicMock()) + anchor.replace_node("test-xpath", '{"test": "data"}') + mock_send_message.assert_called_once() + url = mock_send_message.call_args[0][2] + assert "xpath=test-xpath" in url + +@mock.patch("onapsdk.cps.Anchor.send_message") +def test_anchor_add_list_node(mock_send_message): + anchor = Anchor(name="test_anchor", schema_set=mock.MagicMock()) + anchor.add_list_node("test-xpath", '{"test": "data"}') + mock_send_message.assert_called_once() + url = mock_send_message.call_args[0][2] + assert "xpath=test-xpath" in url + +@mock.patch("onapsdk.cps.Anchor.send_message_json") +def test_anchor_query_node(mock_send_message_json): + anchor = Anchor(name="test_anchor", schema_set=mock.MagicMock()) + anchor.query_node("/test-query") + mock_send_message_json.assert_called_once() + url = mock_send_message_json.call_args[0][2] + assert "cps-path=/test-query" in url + assert "include-descendants=False" in url + + mock_send_message_json.reset_mock() + anchor.query_node("/test-query1", include_descendants=True) + mock_send_message_json.assert_called_once() + url = mock_send_message_json.call_args[0][2] + assert "cps-path=/test-query1" in url + assert "include-descendants=True" in url + +@mock.patch("onapsdk.cps.Anchor.send_message") +def test_anchor_delete_nodes(mock_send_message): + anchor = Anchor(name="test_anchor", schema_set=mock.MagicMock()) + anchor.delete_nodes("test-xpath") + mock_send_message.assert_called_once() + url = mock_send_message.call_args[0][2] + assert "xpath=test-xpath" in url diff --git a/tests/test_dmaap.py b/tests/test_dmaap.py new file mode 100644 index 0000000..28165bc --- /dev/null +++ b/tests/test_dmaap.py @@ -0,0 +1,47 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest.mock import patch + +from onapsdk.dmaap.dmaap import Dmaap, ACTION, GET_HTTP_METHOD + +TOPIC = "fault" + +DMAAP_EVENTS_URL = "http://dmaap.api.simpledemo.onap.org:3904/events" +DMAAP_EVENTS_FROM_TOPIC_URL = f"http://dmaap.api.simpledemo.onap.org:3904/events/{TOPIC}/CG1/C1" +DMAAP_RESET_EVENTS = "http://dmaap.api.simpledemo.onap.org:3904/reset" +DMAAP_GET_ALL_TOPICS = "http://dmaap.api.simpledemo.onap.org:3904/topics" +BASIC_AUTH = {'username': 'dcae@dcae.onap.org', 'password': 'demo123456!'} + + +@patch.object(Dmaap, "send_message_json") +def test_should_get_all_events(send_message_mock): + Dmaap.get_all_events(BASIC_AUTH) + verify_send_event_to_ves_called(send_message_mock, DMAAP_EVENTS_URL) + +@patch.object(Dmaap, "send_message_json") +def test_should_get_events_from_topic(send_message_mock): + Dmaap.get_events_for_topic(TOPIC, BASIC_AUTH) + verify_send_event_to_ves_called(send_message_mock, DMAAP_EVENTS_FROM_TOPIC_URL) + +@patch.object(Dmaap, "send_message_json") +def test_should_get_all_topics(send_message_mock): + Dmaap.get_all_topics(BASIC_AUTH) + verify_send_event_to_ves_called(send_message_mock, DMAAP_GET_ALL_TOPICS) + +def verify_send_event_to_ves_called(send_message_mock, dmaap_url): + send_message_mock.assert_called_once_with( + GET_HTTP_METHOD, ACTION, dmaap_url, + basic_auth=BASIC_AUTH + ) + diff --git a/tests/test_esr.py b/tests/test_esr.py new file mode 100644 index 0000000..9f3c234 --- /dev/null +++ b/tests/test_esr.py @@ -0,0 +1,39 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.msb.esr import ESR, MSB + + +def test_esr(): + esr = ESR() + assert esr.base_url == f"{MSB.base_url}/api/aai-esr-server/v1/vims" + + +@mock.patch.object(ESR, "send_message") +def test_est_register_vim(mock_esr_send_message): + ESR.register_vim( + "test_cloud_owner", + "test_cloud_region_id", + "test_cloud_type", + "test_cloud_region_version", + "test_auth_info_cloud_domain", + "test_auth_info_username", + "test_auth_info_password", + "test_auth_info_url" + ) + mock_esr_send_message.assert_called_once() + method, _, url = mock_esr_send_message.call_args[0] + assert method == "POST" + assert url == ESR.base_url diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py new file mode 100644 index 0000000..6c564b6 --- /dev/null +++ b/tests/test_exceptions.py @@ -0,0 +1,23 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.exceptions import APIError + + +def test_api_error_response_status_code(): + err = APIError() + assert err.response_status_code == 0 + err.response_status_code = 404 + assert err.response_status_code == 404 + err = APIError(response_status_code=404) + assert err.response_status_code == 404 diff --git a/tests/test_generic_instance.txt b/tests/test_generic_instance.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_gui.py b/tests/test_gui.py new file mode 100644 index 0000000..06b2a28 --- /dev/null +++ b/tests/test_gui.py @@ -0,0 +1,55 @@ +"""Test A&AI Element.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 logging +import unittest + +from onapsdk.nbi.nbi import Nbi +from onapsdk.utils.gui import GuiItem, GuiList +from onapsdk.exceptions import NoGuiError + +class GuiTestingBase(unittest.TestCase): + + """The super class which testing classes could inherit.""" + logging.disable(logging.CRITICAL) + + def test_get_guis_request_error(self): + nbi_element = Nbi() + with self.assertRaises(NoGuiError): + nbi_element.get_guis() + + def test_create_bad_gui_item(self): + with self.assertRaises(TypeError): + gui1 = GuiItem(184) + + def test_create_bad_gui_list(self): + with self.assertRaises(TypeError): + list = GuiList(1, 2, 3) + + def test_add_gui_item(self): + gui1 = GuiItem('url1', 184) + gui2 = GuiItem('url2', 200) + test = GuiList([]) + test.add(gui1) + test.add(gui2) + assert len(test.guilist) == 2 + assert test.guilist[0].status == 184 + assert test.guilist[1].url == 'url2' + + def test_add_bad_gui_item(self): + with self.assertRaises(AttributeError): + test = GuiList([]) + test.add('not a gui item object') + + diff --git a/tests/test_headers_creator.py b/tests/test_headers_creator.py new file mode 100644 index 0000000..98efd8d --- /dev/null +++ b/tests/test_headers_creator.py @@ -0,0 +1,84 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.utils.headers_creator import ( + headers_aai_creator, + headers_sdc_creator, + headers_sdc_tester, + headers_sdc_governor, + headers_sdc_operator, + headers_sdnc_creator, + headers_so_creator, + headers_so_catelog_db_creator, +) + +def test_headers_sdc_creator(): + base_header = {} + sdc_headers_creator = headers_sdc_creator(base_header) + assert base_header != sdc_headers_creator + assert sdc_headers_creator["USER_ID"] == "cs0008" + assert sdc_headers_creator["Authorization"] + +def test_headers_sdc_tester(): + base_header = {} + sdc_headers_tester = headers_sdc_tester(base_header) + assert base_header != sdc_headers_tester + assert sdc_headers_tester["USER_ID"] == "jm0007" + assert sdc_headers_tester["Authorization"] + +def test_headers_sdc_governor(): + base_header = {} + sdc_headers_governor = headers_sdc_governor(base_header) + assert base_header != sdc_headers_governor + assert sdc_headers_governor["USER_ID"] == "gv0001" + assert sdc_headers_governor["Authorization"] + +def test_headers_sdc_operator(): + base_header = {} + sdc_headers_operator = headers_sdc_operator(base_header) + assert base_header != sdc_headers_operator + assert sdc_headers_operator["USER_ID"] == "op0001" + assert sdc_headers_operator["Authorization"] + +def test_headers_aai_creator(): + base_header = {} + aai_headers_creator = headers_aai_creator(base_header) + assert base_header != aai_headers_creator + assert aai_headers_creator["x-fromappid"] == "AAI" + assert aai_headers_creator["authorization"] + assert aai_headers_creator["x-transactionid"] + +def test_headers_so_creator(): + base_header = {} + so_headers_creator = headers_so_creator(base_header) + assert base_header != so_headers_creator + assert so_headers_creator["x-fromappid"] == "AAI" + assert so_headers_creator["authorization"] + assert so_headers_creator["x-transactionid"] + +def test_headers_so_catelog_db_creator(): + base_header = {} + so_headers_creator = headers_so_catelog_db_creator(base_header) + assert base_header != so_headers_creator + assert so_headers_creator["x-fromappid"] == "AAI" + assert so_headers_creator["authorization"] + assert so_headers_creator["x-transactionid"] + +def test_headers_sdnc_creator(): + base_header = {} + so_headers_creator = headers_sdnc_creator(base_header) + assert base_header != so_headers_creator + assert so_headers_creator["x-fromappid"] == "API client" + assert so_headers_creator["authorization"] + assert so_headers_creator["x-transactionid"] diff --git a/tests/test_jinja.py b/tests/test_jinja.py new file mode 100644 index 0000000..8ba6d34 --- /dev/null +++ b/tests/test_jinja.py @@ -0,0 +1,26 @@ +"""Test Jinja module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 jinja2 import Environment + +from onapsdk.utils.jinja import jinja_env + +def test_jinja_env(): + """test jinja_env function.""" + test_jinja_env = jinja_env() + assert isinstance(test_jinja_env, Environment) + assert 'sdc_element_action.json.j2' in test_jinja_env.list_templates() + assert 'vendor_create.json.j2' in test_jinja_env.list_templates() + assert 'vsp_create.json.j2' in test_jinja_env.list_templates() + assert test_jinja_env.autoescape != None diff --git a/tests/test_msb_k8s.py b/tests/test_msb_k8s.py new file mode 100644 index 0000000..37be189 --- /dev/null +++ b/tests/test_msb_k8s.py @@ -0,0 +1,378 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.msb.k8s import Definition, ConnectivityInfo, Instance + + +CONNECTIVITY_INFO = { + "cloud-region": "test_cloud_region", + "cloud-owner": "test_cloud_owner", + "other-connectivity-list": {}, + "kubeconfig": "test_kubeconfig" +} + + +DEFINITION = { + "rb-name": "test_rb_name_0", + "rb-version": "test_rb_version_0" +} + + +DEFINITIONS = [ + DEFINITION, + { + "rb-name": "test_rb_name_1", + "rb-version": "test_rb_version_1", + "chart-name": "test_chart_name_1", + "description": "test_description_1", + "labels": {} + } +] + + +PROFILE = { + "rb-name": "test_rb_name", + "rb-version": "test_rb_version", + "profile-name": "test_profile_name", + "namespace": "test_namespace" +} + + +PROFILES = [ + PROFILE, + { + "rb-name": "test_rb_name_1", + "rb-version": "test_rb_version_1", + "profile-name": "test_profile_name_1", + "namespace": "test_namespace_1" + } +] + + +CONFIGURATION_TEMPLATE = { + "template-name": "test_configuration_template_name", + "description": "test_configuration_template_description" +} + + +CONFIGURATION_TEMPLATES = [ + CONFIGURATION_TEMPLATE, + { + "template-name": "test_configuration_template_name_0" + } +] + + +INSTANCE = { + "id": "ID_GENERATED_BY_K8SPLUGIN", + "namespace": "NAMESPACE_WHERE_INSTANCE_HAS_BEEN_DEPLOYED_AS_DERIVED_FROM_PROFILE", + "release-name": "RELEASE_NAME_AS_COMPUTED_BASED_ON_INSTANTIATION_REQUEST_AND_PROFILE_DEFAULT", + "request": { + "rb-name": "test-rbdef", + "rb-version": "v1", + "profile-name": "p1", + "release-name": "release-x", + "cloud-region": "krd", + "override-values": { + "optionalDictOfParameters": "andTheirValues, like", + "global.name": "dummy-name" + }, + "labels": { + "optionalLabelForInternalK8spluginInstancesMetadata": "dummy-value" + }, + }, + "resources": [ + { + "GVK": { + "Group": "", + "Kind": "ConfigMap", + "Version": "v1" + }, + "Name": "test-cm" + }, + { + "GVK": { + "Group": "", + "Kind": "Service", + "Version": "v1" + }, + "Name": "test-svc" + }, + { + "GVK": { + "Group": "apps", + "Kind": "Deployment", + "Version": "v1" + }, + "Name": "test-dep" + } + ] +} + + +INSTANCES = [ + INSTANCE +] + + +@mock.patch.object(ConnectivityInfo, "send_message_json") +def test_get_connectivity_info_by_region_id(mock_send_message_json): + mock_send_message_json.return_value = CONNECTIVITY_INFO + conn_info: ConnectivityInfo = ConnectivityInfo.get_connectivity_info_by_region_id("test_cloud_region_id") + assert conn_info.cloud_region_id == "test_cloud_region" + assert conn_info.cloud_owner == "test_cloud_owner" + assert conn_info.other_connectivity_list == {} + assert conn_info.kubeconfig == "test_kubeconfig" + + +@mock.patch.object(ConnectivityInfo, "send_message") +@mock.patch.object(ConnectivityInfo, "send_message_json") +def test_connectivity_info_create_delete(mock_send_message_json, mock_send_message): + mock_send_message_json.return_value = CONNECTIVITY_INFO + conn_info: ConnectivityInfo = ConnectivityInfo.create("test_cloud_region", "test_cloud_owner", b"kubeconfig") + assert conn_info.cloud_region_id == "test_cloud_region" + assert conn_info.cloud_owner == "test_cloud_owner" + assert conn_info.other_connectivity_list == {} + assert conn_info.kubeconfig == "test_kubeconfig" + conn_info.delete() + + +@mock.patch.object(Definition, "send_message_json") +def test_definition_get_all(mock_send_message_json): + mock_send_message_json.return_value = [] + assert len(list(Definition.get_all())) == 0 + + mock_send_message_json.return_value = DEFINITIONS + definitions = list(Definition.get_all()) + assert len(definitions) == 2 + + def_0, def_1 = definitions + assert def_0.rb_name == "test_rb_name_0" + assert def_0.rb_version == "test_rb_version_0" + assert def_0.chart_name is None + assert def_0.description is None + assert def_0.labels is None + + assert def_1.rb_name == "test_rb_name_1" + assert def_1.rb_version == "test_rb_version_1" + assert def_1.chart_name == "test_chart_name_1" + assert def_1.description == "test_description_1" + assert def_1.labels == {} + + +@mock.patch.object(Definition, "send_message_json") +def test_get_definition_by_name_version(mock_send_message_json): + mock_send_message_json.return_value = DEFINITION + def_0 = Definition.get_definition_by_name_version("rb_name", "rb_version") + assert def_0.rb_name == "test_rb_name_0" + assert def_0.rb_version == "test_rb_version_0" + assert def_0.chart_name is None + assert def_0.description is None + assert def_0.labels is None + + +@mock.patch.object(Definition, "send_message_json") +@mock.patch.object(Definition, "send_message") +def test_create_definition(mock_send_message, mock_send_message_json): + mock_send_message_json.return_value = DEFINITION + def_0 = Definition.create( + rb_name="test_rb_name_0", + rb_version="test_rb_version_0" + ) + assert def_0.rb_name == "test_rb_name_0" + assert def_0.rb_version == "test_rb_version_0" + assert def_0.chart_name is None + assert def_0.description is None + assert def_0.labels is None + + +@mock.patch.object(Definition, "send_message_json") +@mock.patch.object(Definition, "send_message") +def test_definition_create_profile(mock_send_message, mock_send_message_json): + mock_send_message_json.return_value = PROFILE + deff = Definition( + rb_name="test_rb_name", + rb_version="test_rb_version", + chart_name="test_chart_name", + description="test_description", + labels={} + ) + profile = deff.create_profile( + profile_name="test_profile_name", + namespace="test_namespace", + kubernetes_version="test_k8s_version" + ) + assert profile.rb_name == "test_rb_name" + assert profile.rb_version == "test_rb_version" + assert profile.profile_name == "test_profile_name" + assert profile.namespace == "test_namespace" + assert profile.kubernetes_version is None + assert profile.labels == {} + assert profile.release_name == "test_profile_name" + + +@mock.patch.object(Definition, "send_message_json") +def test_definition_get_profile_by_name(mock_send_message_json): + mock_send_message_json.return_value = PROFILE + deff = Definition( + rb_name="test_rb_name", + rb_version="test_rb_version", + chart_name="test_chart_name", + description="test_description", + labels={} + ) + profile = deff.get_profile_by_name("test_profile_name") + assert profile.rb_name == "test_rb_name" + assert profile.rb_version == "test_rb_version" + assert profile.profile_name == "test_profile_name" + assert profile.namespace == "test_namespace" + assert profile.kubernetes_version is None + assert profile.labels == {} + assert profile.release_name == "test_profile_name" + + +@mock.patch.object(Definition, "send_message_json") +def test_definition_get_all_profiles(mock_send_message_json): + mock_send_message_json.return_value = [] + deff = Definition( + rb_name="test_rb_name", + rb_version="test_rb_version", + chart_name="test_chart_name", + description="test_description", + labels={} + ) + assert len(list(deff.get_all_profiles())) == 0 + + mock_send_message_json.return_value = PROFILES + profiles = list(deff.get_all_profiles()) + assert len(profiles) == 2 + prof_0, prof_1 = profiles + + assert prof_0.rb_name == "test_rb_name" + assert prof_0.rb_version == "test_rb_version" + assert prof_0.profile_name == "test_profile_name" + assert prof_0.namespace == "test_namespace" + assert prof_0.kubernetes_version is None + assert prof_0.labels == {} + assert prof_0.release_name == "test_profile_name" + + assert prof_1.rb_name == "test_rb_name_1" + assert prof_1.rb_version == "test_rb_version_1" + assert prof_1.profile_name == "test_profile_name_1" + assert prof_1.namespace == "test_namespace_1" + assert prof_1.kubernetes_version is None + assert prof_1.labels == {} + assert prof_1.release_name == "test_profile_name_1" + + +@mock.patch.object(Definition, "send_message_json") +def test_definition_get_configuration_template_by_name(mock_send_message_json): + mock_send_message_json.return_value = CONFIGURATION_TEMPLATE + deff = Definition( + rb_name="test_rb_name", + rb_version="test_rb_version", + chart_name="test_chart_name", + description="test_description", + labels={} + ) + configuration_tmpl = deff.get_configuration_template_by_name( + template_name="test_configuration_template_name" + ) + assert configuration_tmpl.rb_name == deff.rb_name + assert configuration_tmpl.rb_version == deff.rb_version + assert configuration_tmpl.template_name == "test_configuration_template_name" + assert configuration_tmpl.description == "test_configuration_template_description" + + +@mock.patch.object(Definition, "send_message_json") +@mock.patch.object(Definition, "send_message") +def test_definition_create_configuration_template(mock_send_message, mock_send_message_json): + mock_send_message_json.return_value = CONFIGURATION_TEMPLATE + deff = Definition( + rb_name="test_rb_name", + rb_version="test_rb_version", + chart_name="test_chart_name", + description="test_description", + labels={} + ) + configuration_tmpl = deff.create_configuration_template( + template_name="test_configuration_template_name", + description="test_configuration_template_description" + ) + assert configuration_tmpl.rb_name == deff.rb_name + assert configuration_tmpl.rb_version == deff.rb_version + assert configuration_tmpl.template_name == "test_configuration_template_name" + assert configuration_tmpl.description == "test_configuration_template_description" + assert configuration_tmpl.url == f"{deff.base_url}/{deff.rb_name}/{deff.rb_version}/config-template/test_configuration_template_name" + + +@mock.patch.object(Definition, "send_message_json") +def test_definition_get_all_configuration_templates(mock_send_message_json): + mock_send_message_json.return_value = [] + deff = Definition( + rb_name="test_rb_name", + rb_version="test_rb_version", + chart_name="test_chart_name", + description="test_description", + labels={} + ) + assert len(list(deff.get_all_configuration_templates())) == 0 + + mock_send_message_json.return_value = CONFIGURATION_TEMPLATES + configuration_tmplts = list(deff.get_all_configuration_templates()) + assert len(configuration_tmplts) == 2 + + tmpl_0, tmpl_1 = configuration_tmplts + assert tmpl_0.rb_name == deff.rb_name + assert tmpl_0.rb_version == deff.rb_version + assert tmpl_0.template_name == "test_configuration_template_name" + assert tmpl_0.description == "test_configuration_template_description" + + assert tmpl_1.rb_name == deff.rb_name + assert tmpl_1.rb_version == deff.rb_version + assert tmpl_1.template_name == "test_configuration_template_name_0" + assert tmpl_1.description is None + + +@mock.patch.object(Instance, "send_message_json") +def test_instance_get_all(mock_send_message_json): + mock_send_message_json.return_value = [] + assert len(list(Instance.get_all())) == 0 + + mock_send_message_json.return_value = INSTANCES + assert len(list(Instance.get_all())) == 1 + + +@mock.patch.object(Instance, "send_message_json") +def test_instance_create(mock_send_message_json): + mock_send_message_json.return_value = INSTANCE + instance = Instance.create( + "test_cloud_region_id", + "test_profile_name", + "test_rb_name", + "test_rb_version" + ) + assert instance.instance_id == "ID_GENERATED_BY_K8SPLUGIN" + assert instance.namespace == "NAMESPACE_WHERE_INSTANCE_HAS_BEEN_DEPLOYED_AS_DERIVED_FROM_PROFILE" + + +@mock.patch.object(Instance, "send_message_json") +@mock.patch.object(Instance, "send_message") +def test_instance_get_by_id(mock_send_message, mock_send_message_json): + mock_send_message_json.return_value = INSTANCE + instance = Instance.get_by_id("ID_GENERATED_BY_K8SPLUGIN") + assert instance.instance_id == "ID_GENERATED_BY_K8SPLUGIN" + assert instance.namespace == "NAMESPACE_WHERE_INSTANCE_HAS_BEEN_DEPLOYED_AS_DERIVED_FROM_PROFILE" + instance.delete() diff --git a/tests/test_multicloud.py b/tests/test_multicloud.py new file mode 100644 index 0000000..8c05b93 --- /dev/null +++ b/tests/test_multicloud.py @@ -0,0 +1,38 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.msb.multicloud import Multicloud + + +@mock.patch.object(Multicloud, "send_message") +def test_multicloud_register(mock_send_message): + Multicloud.register_vim(cloud_owner="test_cloud_owner", + cloud_region_id="test_cloud_region") + mock_send_message.assert_called_once() + method, description, url = mock_send_message.call_args[0] + assert method == "POST" + assert description == "Register VIM instance to ONAP" + assert url == f"{Multicloud.base_url}/test_cloud_owner/test_cloud_region/registry" + + +@mock.patch.object(Multicloud, "send_message") +def test_multicloud_unregister(mock_send_message): + Multicloud.unregister_vim(cloud_owner="test_cloud_owner", + cloud_region_id="test_cloud_region") + mock_send_message.assert_called_once() + method, description, url = mock_send_message.call_args[0] + assert method == "DELETE" + assert description == "Unregister VIM instance from ONAP" + assert url == f"{Multicloud.base_url}/test_cloud_owner/test_cloud_region" diff --git a/tests/test_nbi.py b/tests/test_nbi.py new file mode 100644 index 0000000..725baf2 --- /dev/null +++ b/tests/test_nbi.py @@ -0,0 +1,563 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 collections import namedtuple +from unittest import mock + +from onapsdk.aai.business import Customer +from onapsdk.exceptions import RequestError +from onapsdk.nbi import Nbi, Service, ServiceOrder, ServiceSpecification + + +SERVICE_SPECIFICATION = { + "id":"a80c901c-6593-491f-9465-877e5acffb46", + "name":"testService1", + "invariantUUID":"217deaa7-dfc3-41d8-aa53-bb009029c09f", + "category":"Network Service", + "distributionStatus":"DISTRIBUTED", + "version":"1.0", + "lifecycleStatus":"CERTIFIED", + "relatedParty":{ + "id":"cs0008", + "role":"lastUpdater" + } +} + + +SERVICE_SPECIFICATIONS = [ + { + "id":"a80c901c-6593-491f-9465-877e5acffb46", + "name":"testService1", + "invariantUUID":"217deaa7-dfc3-41d8-aa53-bb009029c09f", + "category":"Network Service", + "distributionStatus":"DISTRIBUTED", + "version":"1.0", + "lifecycleStatus":"CERTIFIED", + "relatedParty":{ + "id":"cs0008", + "role":"lastUpdater" + } + }, + { + "id":"b1cda0ab-d968-41ef-9051-d26b33b120be", + "name":"testService2", + "invariantUUID":"906c3185-9656-4639-8f4d-d51d9ee0695d", + "category":"Network Service", + "distributionStatus":"DISTRIBUTED", + "version":"1.0", + "lifecycleStatus":"CERTIFIED", + "relatedParty":{ + "id":"cs0008" + ,"role":"lastUpdater" + } + } +] + + +SERVICES = [ + { + "id":"5c855390-7c39-4fe4-b164-2029b09de57c", + "name":"test6", + "serviceSpecification":{ + "name":"testService9", + "id":"125727ad-8660-423e-b4a1-99cd4a749f45" + }, + "relatedParty":{ + "role":"ONAPcustomer", + "id":"generic" + }, + "href":"service/5c855390-7c39-4fe4-b164-2029b09de57c" + }, + { + "id":"f948be83-c3e8-4515-a27d-2983eba63911", + "name":"test4", + "serviceSpecification":{ + "name":"testService8", + "id":"0960aedb-3ad8-49e1-ade5-a59414f6fda4" + }, + "relatedParty":{ + "role":"ONAPcustomer", + "id":"generic" + }, + "href":"service/f948be83-c3e8-4515-a27d-2983eba63911" + }, + { + "id":"5066eabd-846c-4ed9-886b-69892a12968d", + "name":"test5", + "serviceSpecification":{ + "name":"testService8", + "id":"0960aedb-3ad8-49e1-ade5-a59414f6fda4" + }, + "relatedParty":{ + "role":"ONAPcustomer", + "id":"generic" + }, + "href":"service/5066eabd-846c-4ed9-886b-69892a12968d" + } +] + + +SERVICES_CUSTOMER = [ + { + "id":"6a855390-7c39-4fe4-b164-2029b09de57d", + "name":"test7", + "serviceSpecification":{ + "name":"testService9", + "id":"125727ad-8660-423e-b4a1-99cd4a749f45" + }, + "relatedParty":{ + "role":"ONAPcustomer", + "id":"test_customer" + }, + "href":"service/6a855390-7c39-4fe4-b164-2029b09de57d" + } +] + + + +SERVICE_ORDERS = [ + { + "id":"5e9d6d98ae76af6b04e4df9a", + "href":"serviceOrder/5e9d6d98ae76af6b04e4df9a", + "externalId":"", + "priority":"1", + "description":"testService order for generic customer via Python ONAP SDK", + "category":"Consumer", + "state":"rejected", + "orderDate":"2020-04-20T09:38:32.286Z", + "completionDateTime":"2020-04-20T09:38:47.866Z", + "expectedCompletionDate":None, + "requestedStartDate":"2020-04-20T09:47:49.919Z", + "requestedCompletionDate":"2020-04-20T09:47:49.919Z", + "startDate":None, + "@baseType":None, + "@type":None, + "@schemaLocation":None, + "relatedParty":[ + { + "id":"generic", + "href":None, + "role":"ONAPcustomer", + "name":"generic", + "@referredType":None + } + ], + "orderRelationship":None, + "orderItem":[ + { + "orderMessage":[], + "id":"1", + "action":"add", + "state":"rejected", + "percentProgress":"0", + "@type":None, + "@schemaLocation":None, + "@baseType":None, + "orderItemRelationship":[], + "service":{ + "id":None, + "serviceType":None, + "href":None, + "name":"08d960ae-c2e1-4d5c-baf0-6420659ea68a", + "serviceState":"active", + "@type":None, + "@schemaLocation":None, + "serviceCharacteristic":None, + "serviceRelationship":None, + "relatedParty":None, + "serviceSpecification":{ + "id":"a80c901c-6593-491f-9465-877e5acffb46", + "href":None, + "name":None, + "version":None, + "targetServiceSchema":None, + "@type":None, + "@schemaLocation":None, + "@baseType":None + } + }, + "orderItemMessage":[] + } + ], + "orderMessage":[ + { + "code":"501", + "field":None, + "messageInformation":"Problem with AAI API", + "severity":"error", + "correctionRequired":True + }, + { + "code":"503", + "field":None, + "messageInformation":"tenantId not found in AAI", + "severity":"error", + "correctionRequired":True + } + ] + } +] + +SERVICE_ORDERS_NO_RELATED_PARTY = [ + { + "id":"5e9d6d98ae76af6b04e4df9a", + "href":"serviceOrder/5e9d6d98ae76af6b04e4df9a", + "externalId":"", + "priority":"1", + "description":"testService order for generic customer via Python ONAP SDK", + "category":"Consumer", + "state":"rejected", + "orderDate":"2020-04-20T09:38:32.286Z", + "completionDateTime":"2020-04-20T09:38:47.866Z", + "expectedCompletionDate":None, + "requestedStartDate":"2020-04-20T09:47:49.919Z", + "requestedCompletionDate":"2020-04-20T09:47:49.919Z", + "startDate":None, + "@baseType":None, + "@type":None, + "@schemaLocation":None, + "relatedParty": None, + "orderRelationship":None, + "orderItem":[ + { + "orderMessage":[], + "id":"1", + "action":"add", + "state":"rejected", + "percentProgress":"0", + "@type":None, + "@schemaLocation":None, + "@baseType":None, + "orderItemRelationship":[], + "service":{ + "id":None, + "serviceType":None, + "href":None, + "name":"08d960ae-c2e1-4d5c-baf0-6420659ea68a", + "serviceState":"active", + "@type":None, + "@schemaLocation":None, + "serviceCharacteristic":None, + "serviceRelationship":None, + "relatedParty":None, + "serviceSpecification":{ + "id":"a80c901c-6593-491f-9465-877e5acffb46", + "href":None, + "name":None, + "version":None, + "targetServiceSchema":None, + "@type":None, + "@schemaLocation":None, + "@baseType":None + } + }, + "orderItemMessage":[] + } + ], + "orderMessage":[ + { + "code":"501", + "field":None, + "messageInformation":"Problem with AAI API", + "severity":"error", + "correctionRequired":True + }, + { + "code":"503", + "field":None, + "messageInformation":"tenantId not found in AAI", + "severity":"error", + "correctionRequired":True + } + ] + } +] + +SERVICE_ORDER_STATE_COMPLETED = { + 'state': 'completed' +} + +SERVICE_ORDER_STATE_FAILED = { + 'state': 'failed' +} + +SERVICE_ORDER_STATE_IN_PROGRESS = { + 'state': 'inProgress' +} + +SERVICE_ORDER_STATE_REJECTED = { + 'state': 'rejected' +} + +SERVICE_ORDER_STATE_UNKNOWN = { + 'state': 'lalala' +} + +@mock.patch.object(Nbi, "send_message") +def test_nbi(mock_send_message): + + assert Nbi.base_url == "https://nbi.api.simpledemo.onap.org:30274" + assert Nbi.api_version == "/nbi/api/v4" + + mock_send_message.side_effect = RequestError + assert Nbi.is_status_ok() == False + mock_send_message.side_effect = None + assert Nbi.is_status_ok() == True + + +@mock.patch.object(ServiceSpecification, "send_message_json") +def test_service_specification_get_all(mock_service_specification_send_message): + mock_service_specification_send_message.return_value = [] + assert len(list(ServiceSpecification.get_all())) == 0 + + mock_service_specification_send_message.return_value = SERVICE_SPECIFICATIONS + service_specifications = list(ServiceSpecification.get_all()) + assert len(service_specifications) == 2 + + assert service_specifications[0].unique_id == "a80c901c-6593-491f-9465-877e5acffb46" + assert service_specifications[0].name == "testService1" + assert service_specifications[0].invariant_uuid == "217deaa7-dfc3-41d8-aa53-bb009029c09f" + assert service_specifications[0].category == "Network Service" + assert service_specifications[0].distribution_status == "DISTRIBUTED" + assert service_specifications[0].version == "1.0" + assert service_specifications[0].lifecycle_status == "CERTIFIED" + + assert service_specifications[1].unique_id == "b1cda0ab-d968-41ef-9051-d26b33b120be" + assert service_specifications[1].name == "testService2" + assert service_specifications[1].invariant_uuid == "906c3185-9656-4639-8f4d-d51d9ee0695d" + assert service_specifications[1].category == "Network Service" + assert service_specifications[1].distribution_status == "DISTRIBUTED" + assert service_specifications[1].version == "1.0" + assert service_specifications[1].lifecycle_status == "CERTIFIED" + + +@mock.patch.object(ServiceSpecification, "send_message_json") +def test_service_specification_get_by_id(mock_service_specification_send_message): + + mock_service_specification_send_message.return_value = SERVICE_SPECIFICATION + service_specification = ServiceSpecification.get_by_id("test") + assert service_specification.unique_id == "a80c901c-6593-491f-9465-877e5acffb46" + assert service_specification.name == "testService1" + assert service_specification.invariant_uuid == "217deaa7-dfc3-41d8-aa53-bb009029c09f" + assert service_specification.category == "Network Service" + assert service_specification.distribution_status == "DISTRIBUTED" + assert service_specification.version == "1.0" + assert service_specification.lifecycle_status == "CERTIFIED" + + +@mock.patch.object(Service, "send_message_json") +@mock.patch.object(Customer, "get_by_global_customer_id") +@mock.patch.object(ServiceSpecification, "get_by_id") +def test_service_get_all(mock_service_specification_get_by_id, + mock_customer_get_by_id, + mock_service_send_message): + mock_service_send_message.return_value = [] + assert len(list(Service.get_all())) == 0 + mock_service_send_message.return_value = SERVICES + services_list = list(Service.get_all()) + assert len(services_list) == 3 + + service = services_list[0] + + assert service.name == "test6" + assert service.service_id == "5c855390-7c39-4fe4-b164-2029b09de57c" + assert service._service_specification_name == "testService9" + assert service._service_specification_id == "125727ad-8660-423e-b4a1-99cd4a749f45" + assert service._customer_id == "generic" + assert service.customer_role == "ONAPcustomer" + assert service.href == "service/5c855390-7c39-4fe4-b164-2029b09de57c" + + assert service.customer is not None + mock_customer_get_by_id.assert_called_once_with(service._customer_id) + + service._customer_id = None + assert service.customer is None + + assert service.service_specification is not None + mock_service_specification_get_by_id.assert_called_once_with(service._service_specification_id) + + service._service_specification_id = None + assert service.service_specification is None + + mock_service_send_message.return_value = SERVICES_CUSTOMER + services_list = list(Service.get_all(customer_id="test_customer")) + assert len(services_list) == 1 + + service = services_list[0] + + assert service.name == "test7" + assert service.service_id == "6a855390-7c39-4fe4-b164-2029b09de57d" + assert service._service_specification_name == "testService9" + assert service._service_specification_id == "125727ad-8660-423e-b4a1-99cd4a749f45" + assert service._customer_id == "test_customer" + assert service.customer_role == "ONAPcustomer" + assert service.href == "service/6a855390-7c39-4fe4-b164-2029b09de57d" + + + +@mock.patch.object(ServiceOrder, "send_message_json") +def test_service_order(mock_service_order_send_message): + mock_service_order_send_message.return_value = [] + assert len(list(ServiceOrder.get_all())) == 0 + + mock_service_order_send_message.return_value = SERVICE_ORDERS + service_orders = list(ServiceOrder.get_all()) + assert len(service_orders) == 1 + service_order = service_orders[0] + assert service_order.unique_id == "5e9d6d98ae76af6b04e4df9a" + assert service_order.href =="serviceOrder/5e9d6d98ae76af6b04e4df9a" + assert service_order.priority == "1" + assert service_order.category == "Consumer" + assert service_order.description == "testService order for generic customer via Python ONAP SDK" + assert service_order.external_id == "" + assert service_order._customer == None + assert service_order._customer_id == "generic" + assert service_order._service_specification == None + assert service_order._service_specification_id == "a80c901c-6593-491f-9465-877e5acffb46" + assert service_order.service_instance_name == "08d960ae-c2e1-4d5c-baf0-6420659ea68a" + assert service_order.state == "rejected" + + +@mock.patch.object(ServiceOrder, "send_message_json") +def test_service_order_status(mock_service_order_send_message): + mock_service_order_send_message.return_value = SERVICE_ORDERS + service_order = next(ServiceOrder.get_all()) + + mock_service_order_send_message.return_value = SERVICE_ORDER_STATE_COMPLETED + assert service_order.status == service_order.StatusEnum.COMPLETED + assert service_order.completed + assert service_order.finished + assert not service_order.rejected + assert not service_order.failed + + mock_service_order_send_message.return_value = SERVICE_ORDER_STATE_FAILED + assert service_order.status == service_order.StatusEnum.FAILED + assert not service_order.completed + assert service_order.finished + assert not service_order.rejected + assert service_order.failed + + mock_service_order_send_message.return_value = SERVICE_ORDER_STATE_IN_PROGRESS + assert service_order.status == service_order.StatusEnum.IN_PROGRESS + assert not service_order.completed + assert not service_order.finished + assert not service_order.rejected + assert not service_order.failed + + mock_service_order_send_message.return_value = SERVICE_ORDER_STATE_REJECTED + assert service_order.status == service_order.StatusEnum.REJECTED + assert not service_order.completed + assert service_order.finished + assert service_order.rejected + assert not service_order.failed + + mock_service_order_send_message.return_value = SERVICE_ORDER_STATE_UNKNOWN + assert service_order.status == service_order.StatusEnum.UNKNOWN + assert not service_order.completed + assert service_order.finished + assert not service_order.rejected + assert not service_order.failed + + mock_service_order_send_message.return_value = {} + assert service_order.status == service_order.StatusEnum.UNKNOWN + assert not service_order.completed + assert service_order.finished + assert not service_order.rejected + assert not service_order.failed + + +@mock.patch.object(ServiceOrder, "send_message_json") +def test_service_order_no_related_party(mock_service_order_send_message): + mock_service_order_send_message.return_value = [] + assert len(list(ServiceOrder.get_all())) == 0 + + mock_service_order_send_message.return_value = SERVICE_ORDERS_NO_RELATED_PARTY + service_orders = list(ServiceOrder.get_all()) + assert len(service_orders) == 1 + service_order = service_orders[0] + assert service_order.unique_id == "5e9d6d98ae76af6b04e4df9a" + assert service_order.href =="serviceOrder/5e9d6d98ae76af6b04e4df9a" + assert service_order.priority == "1" + assert service_order.category == "Consumer" + assert service_order.description == "testService order for generic customer via Python ONAP SDK" + assert service_order.external_id == "" + assert service_order._customer == None + assert service_order._customer_id == None + assert service_order._service_specification == None + assert service_order._service_specification_id == "a80c901c-6593-491f-9465-877e5acffb46" + assert service_order.service_instance_name == "08d960ae-c2e1-4d5c-baf0-6420659ea68a" + assert service_order.state == "rejected" + + +@mock.patch.object(Customer, "get_by_global_customer_id") +def test_service_order_customer(mock_customer_get_by_id): + service_order = ServiceOrder("test_unique_id", + "test_href", + "test_priority", + "test_description", + "test_category", + "test_external_id", + "test_service_instance_name") + assert service_order.customer is None + assert service_order._customer is None + service_order._customer_id = "test_customer_id" + assert service_order.customer is not None + mock_customer_get_by_id.assert_called_once_with("test_customer_id") + assert service_order._customer is not None + + +@mock.patch.object(ServiceSpecification, "get_by_id") +def test_service_order_service_specification(mock_service_spec_get_by_id): + service_order = ServiceOrder("test_unique_id", + "test_href", + "test_priority", + "test_description", + "test_category", + "test_external_id", + "test_service_instance_name") + assert service_order.service_specification is None + assert service_order._service_specification_id is None + service_order._service_specification_id = "test_service_spec_id" + assert service_order.service_specification is not None + mock_service_spec_get_by_id.assert_called_once_with("test_service_spec_id") + assert service_order._service_specification is not None + + +@mock.patch.object(ServiceOrder, "send_message_json") +def test_service_order_create(mock_service_order_send_message): + ServiceOrder.create(customer=mock.MagicMock(), + service_specification=mock.MagicMock()) + mock_service_order_send_message.assert_called_once() + method, _, url = mock_service_order_send_message.call_args[0] + assert method == "POST" + assert url == f"{ServiceOrder.base_url}{ServiceOrder.api_version}/serviceOrder" + + +def test_service_order_wait_for_finish(): + with mock.patch.object(ServiceOrder, "finished", new_callable=mock.PropertyMock) as mock_finished: + with mock.patch.object(ServiceOrder, "completed", new_callable=mock.PropertyMock) as mock_completed: + service_order = ServiceOrder( + unique_id="test", + href="test", + priority="test", + description="test", + category="test", + external_id="test", + service_instance_name="test", + ) + service_order.WAIT_FOR_SLEEP_TIME = 0 + mock_finished.side_effect = [False, False, True] + mock_completed.return_value = True + rv = namedtuple("Value", ["return_value"]) + service_order._wait_for_finish(rv) + assert rv.return_value diff --git a/tests/test_onap_service.py b/tests/test_onap_service.py new file mode 100644 index 0000000..62a9880 --- /dev/null +++ b/tests/test_onap_service.py @@ -0,0 +1,380 @@ +"""Test OnapService module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock +from unittest.mock import ANY + +import pytest +from requests import Response, Session + +from requests import ConnectionError, RequestException + +from onapsdk.exceptions import ( + RequestError, APIError, ResourceNotFound, InvalidResponse, ConnectionFailed +) + +from onapsdk.onap_service import OnapService +from onapsdk.sdc.vendor import Vendor + +def http_codes(): + return [ + 400, # Bad Request + 401, # Unauthorized + 403, # Forbidden + 405, # Method Not Allowed + 408, # Request Timeout + 415, # Unsupported Media Type + 429, # Too Many Requests + 500, # Internal Server Error + 501, # Not Implemented + 502, # Bad Gateway + 503, # Service Unavailable + 504 # Gateway Timeout + ] + +class TestException(Exception): + """Test exception.""" + +def test_init(): + """Test initialization.""" + svc = OnapService() + +def test_class_variables(): + """Test class variables.""" + assert OnapService.server == None + assert OnapService.headers == { + "Content-Type": "application/json", + "Accept": "application/json", + } + assert OnapService.proxy == None + +def test_set_proxy(): + """Test set_proxy().""" + assert OnapService.proxy == None + Vendor.set_proxy({'the', 'proxy'}) + assert OnapService.proxy == {'the', 'proxy'} + Vendor.set_proxy(None) + assert OnapService.proxy == None + +# ------------------ + +@mock.patch.object(Session, 'request') +def test_send_message_OK(mock_request): + """Returns response if OK.""" + svc = OnapService() + mocked_response = Response() + mocked_response.status_code = 200 + mock_request.return_value = mocked_response + expect_headers = { + "Content-Type": "application/json", + "Accept": "application/json", + } + response = svc.send_message("GET", 'test get', 'http://my.url/') + mock_request.assert_called_once_with('GET', 'http://my.url/', + headers=expect_headers, verify=False, + proxies=None) + assert response == mocked_response + +@mock.patch.object(Session, 'request') +def test_send_message_custom_header_OK(mock_request): + """Returns response if returns OK with a custom header.""" + svc = OnapService() + mocked_response = Response() + mocked_response.status_code = 200 + mock_request.return_value = mocked_response + expect_headers = { + "Content-Type": "application/json", + "Accept": "application/json", + "Custom": "Header" + } + response = svc.send_message("GET", 'test get', 'http://my.url/', + headers=expect_headers) + mock_request.assert_called_once_with('GET', 'http://my.url/', + headers=expect_headers, verify=False, + proxies=None) + assert response == mocked_response + +@mock.patch.object(OnapService, '_set_basic_auth_if_needed') +@mock.patch.object(Session, 'request') +def test_send_message_with_basic_auth(mock_request, mock_set_basic_auth_if_needed): + """Should give response of request if OK.""" + svc = OnapService() + mocked_response = Response() + mocked_response.status_code = 200 + basic_auth = {'username': 'user1', "password": "password1"} + mock_request.return_value = mocked_response + expect_headers = { + "Content-Type": "application/json", + "Accept": "application/json", + "Once": "Upon a time" + } + response = svc.send_message("GET", 'test get', 'http://my.url/', + headers=expect_headers, basic_auth=basic_auth) + mock_set_basic_auth_if_needed.assert_called_once_with(basic_auth, ANY) + mock_request.assert_called_once_with('GET', 'http://my.url/', + headers=expect_headers, verify=False, + proxies=None) + assert response == mocked_response + +@mock.patch.object(Session, 'request') +def test_send_message_resource_not_found(mock_request): + """Should raise ResourceNotFound if status code 404.""" + svc = OnapService() + + mocked_response = Response() + mocked_response.status_code = 404 + + mock_request.return_value = mocked_response + + with pytest.raises(ResourceNotFound) as exc: + svc.send_message("GET", 'test get', 'http://my.url/') + assert exc.type is ResourceNotFound + + mock_request.assert_called_once() + +@mock.patch.object(Session, 'request') +@pytest.mark.parametrize("code", http_codes()) +def test_send_message_api_error(mock_request, code): + """Raise APIError if status code is between 400 and 599, and not 404.""" + svc = OnapService() + mocked_response = Response() + mocked_response.status_code = code + mock_request.return_value = mocked_response + + with pytest.raises(APIError) as exc: + svc.send_message("GET", 'test get', 'http://my.url/') + assert exc.type is APIError + + mock_request.assert_called_once() + +@mock.patch.object(Session, 'request') +def test_send_message_connection_failed(mock_request): + """Should raise ResourceNotFound if status code 404.""" + svc = OnapService() + + mock_request.side_effect = ConnectionError + + with pytest.raises(ConnectionFailed) as exc: + svc.send_message("GET", 'test get', 'http://my.url/') + assert exc.type is ConnectionFailed + + mock_request.assert_called_once() + +@mock.patch.object(Session, 'request') +def test_send_message_request_error(mock_request): + """Should raise RequestError for an amiguous request exception.""" + svc = OnapService() + + mock_request.side_effect = RequestException + + with pytest.raises(RequestError) as exc: + svc.send_message("GET", 'test get', 'http://my.url/') + assert exc.type is RequestError + + mock_request.assert_called_once() + + +@mock.patch.object(Session, 'request') +def test_send_message_custom_error(mock_request): + """Should raise RequestError for an amiguous request exception.""" + svc = OnapService() + + mock_request.side_effect = RequestException + + with pytest.raises(TestException) as exc: + svc.send_message("GET", 'test get', 'http://my.url/', + exception=TestException) + assert exc.type is TestException + + mock_request.assert_called_once() + +@mock.patch.object(OnapService, 'send_message') +def test_send_message_json_OK(mock_send): + """JSON is received and successfully decoded.""" + svc = OnapService() + + mocked_response = Response() + mocked_response._content = b'{"yolo": "yala"}' + mocked_response.encoding = "UTF-8" + mocked_response.status_code = 200 + + mock_send.return_value = mocked_response + + response = svc.send_message_json("GET", 'test get', 'http://my.url/') + + mock_send.assert_called_once_with("GET", 'test get', 'http://my.url/') + assert response['yolo'] == 'yala' + +@mock.patch.object(OnapService, 'send_message') +def test_send_message_json_invalid_response(mock_send): + """Raises InvalidResponse if response is not JSON.""" + svc = OnapService() + + mocked_response = Response() + mocked_response._content = b'{yolo}' + mocked_response.encoding = "UTF-8" + mocked_response.status_code = 200 + + mock_send.return_value = mocked_response + + with pytest.raises(InvalidResponse) as exc: + svc.send_message_json("GET", 'test get', 'http://my.url/') + assert exc.type is InvalidResponse + + mock_send.assert_called_once() + +@mock.patch.object(OnapService, 'send_message') +def test_send_message_json_connection_failed(mock_send): + """ConnectionFailed from send_message is handled.""" + svc = OnapService() + + mock_send.side_effect = ConnectionFailed + + with pytest.raises(ConnectionFailed) as exc: + svc.send_message_json("GET", 'test get', 'http://my.url/') + assert exc.type is ConnectionFailed + + mock_send.assert_called_once() + +@mock.patch.object(OnapService, 'send_message') +def test_send_message_json_api_error(mock_send): + """APIError (error codes) from send_message is handled.""" + svc = OnapService() + + mock_send.side_effect = APIError + + with pytest.raises(APIError) as exc: + svc.send_message_json("GET", 'test get', 'http://my.url/') + assert exc.type is APIError + + mock_send.assert_called_once() + +@mock.patch.object(OnapService, 'send_message') +def test_send_message_json_resource_not_found(mock_send): + """ResourceNotFound exception from send_message is handled.""" + svc = OnapService() + + mock_send.side_effect = ResourceNotFound + + with pytest.raises(ResourceNotFound) as exc: + svc.send_message_json("GET", 'test get', 'http://my.url/') + assert exc.type is ResourceNotFound + + mock_send.assert_called_once() + +@mock.patch.object(OnapService, 'send_message') +def test_send_message_json_request_error(mock_send): + """RequestError exception from send_message is handled.""" + svc = OnapService() + + mock_send.side_effect = RequestError + + with pytest.raises(RequestError) as exc: + svc.send_message_json("GET", 'test get', 'http://my.url/') + assert exc.type is RequestError + + mock_send.assert_called_once() + + +@mock.patch.object(OnapService, 'send_message') +def test_send_message_json_custom_error(mock_send): + """RequestError exception from send_message is handled.""" + svc = OnapService() + + mock_send.side_effect = RequestError + + with pytest.raises(TestException) as exc: + svc.send_message_json("GET", 'test get', 'http://my.url/', + exception=TestException) + assert exc.type is TestException + + mock_send.assert_called_once() + +@mock.patch("onapsdk.onap_service.requests.Session") +def test_set_header(mock_session): + + OnapService.send_message("GET", 'test get', 'http://my.url/') + _, _, kwargs = mock_session.return_value.request.mock_calls[0] + headers = kwargs["headers"] + assert "test-header-key" not in headers + + mock_session.reset_mock() + OnapService.set_header({"test-header-key": "test-header-value"}) + OnapService.send_message("GET", 'test get', 'http://my.url/') + _, _, kwargs = mock_session.return_value.request.mock_calls[0] + headers = kwargs["headers"] + assert "test-header-key" in headers + assert headers["test-header-key"] == "test-header-value" + + mock_session.reset_mock() + OnapService.send_message("GET", 'test get', 'http://my.url/', headers={}) + _, _, kwargs = mock_session.return_value.request.mock_calls[0] + headers = kwargs["headers"] + assert "test-header-key" in headers + assert headers["test-header-key"] == "test-header-value" + + mock_session.reset_mock() + OnapService.send_message("GET", 'test get', 'http://my.url/', headers={"test-header-key": "test-header-another-value"}) + _, _, kwargs = mock_session.return_value.request.mock_calls[0] + headers = kwargs["headers"] + assert "test-header-key" in headers + assert headers["test-header-key"] == "test-header-value" + + mock_session.reset_mock() + OnapService.set_header(None) + OnapService.send_message("GET", 'test get', 'http://my.url/') + _, _, kwargs = mock_session.return_value.request.mock_calls[0] + headers = kwargs["headers"] + assert "test-header-key" not in headers + + def test_header_method(): + return {"test-header-callable-key": "test-header-callable-value"} + + mock_session.reset_mock() + OnapService.set_header(test_header_method) + OnapService.send_message("GET", 'test get', 'http://my.url/', headers={}) + _, _, kwargs = mock_session.return_value.request.mock_calls[0] + headers = kwargs["headers"] + assert "test-header-callable-key" in headers + assert headers["test-header-callable-key"] == "test-header-callable-value" + + mock_session.reset_mock() + OnapService.send_message("GET", 'test get', 'http://my.url/', headers={"test-header-key": "test-header-value"}) + _, _, kwargs = mock_session.return_value.request.mock_calls[0] + headers = kwargs["headers"] + assert "test-header-callable-key" in headers + assert headers["test-header-callable-key"] == "test-header-callable-value" + assert "test-header-key" in headers + assert headers["test-header-key"] == "test-header-value" + + mock_session.reset_mock() + OnapService.set_header({"test-header-dict-key": "test-header-dict-value"}) + OnapService.send_message("GET", 'test get', 'http://my.url/', headers={}) + _, _, kwargs = mock_session.return_value.request.mock_calls[0] + headers = kwargs["headers"] + assert "test-header-callable-key" in headers + assert headers["test-header-callable-key"] == "test-header-callable-value" + assert "test-header-dict-key" in headers + assert headers["test-header-dict-key"] == "test-header-dict-value" + + mock_session.reset_mock() + OnapService.send_message("GET", 'test get', 'http://my.url/', headers={"test-header-common-key": "test-header-common-value"}) + _, _, kwargs = mock_session.return_value.request.mock_calls[0] + headers = kwargs["headers"] + assert "test-header-callable-key" in headers + assert headers["test-header-callable-key"] == "test-header-callable-value" + assert "test-header-dict-key" in headers + assert headers["test-header-dict-key"] == "test-header-dict-value" + assert "test-header-common-key" in headers + assert headers["test-header-common-key"] == "test-header-common-value" diff --git a/tests/test_pnf.py b/tests/test_pnf.py new file mode 100644 index 0000000..0f606a0 --- /dev/null +++ b/tests/test_pnf.py @@ -0,0 +1,485 @@ +"""Test pnf module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock +from unittest.mock import MagicMock +from pathlib import Path + +import pytest + +import onapsdk.constants as const +from onapsdk.exceptions import ParameterError, RequestError, StatusError +from onapsdk.sdc.category_management import ResourceCategory +from onapsdk.sdc.properties import Property +from onapsdk.sdc.sdc_resource import SdcResource +from onapsdk.sdc.pnf import Pnf +from onapsdk.sdc.vsp import Vsp +from onapsdk.sdc.vsp import Vendor + + +@mock.patch.object(Pnf, 'send_message_json') +def test_get_all_no_pnf(mock_send): + """Returns empty array if no pnfs.""" + mock_send.return_value = {} + assert Pnf.get_all() == [] + mock_send.assert_called_once_with("GET", 'get Pnfs', 'https://sdc.api.be.simpledemo.onap.org:30204/sdc/v1/catalog/resources?resourceType=PNF') + + +@mock.patch.object(Pnf, 'send_message_json') +def test_get_all_some_pnfs(mock_send): + """Returns a list of pnfs.""" + mock_send.return_value = [ + {'resourceType': 'PNF', 'name': 'one', 'uuid': '1234', 'invariantUUID': '5678', 'version': '1.0', 'lifecycleState': 'CERTIFIED', 'category': 'Generic', "subCategory": "Abstract"}, + {'resourceType': 'PNF', 'name': 'two', 'uuid': '1235', 'invariantUUID': '5679', 'version': '1.0', 'lifecycleState': 'NOT_CERTIFIED_CHECKOUT', 'category': 'Generic', "subCategory": "Abstract"}] + all_pnfs = Pnf.get_all() + assert len(all_pnfs) == 2 + pnf_1 = all_pnfs[0] + assert pnf_1.name == "one" + assert pnf_1.identifier == "1234" + assert pnf_1.unique_uuid == "5678" + assert pnf_1.version == "1.0" + assert pnf_1.status == const.CERTIFIED + assert pnf_1.created() + pnf_2 = all_pnfs[1] + assert pnf_2.name == "two" + assert pnf_2.identifier == "1235" + assert pnf_2.unique_uuid == "5679" + assert pnf_2.status == const.DRAFT + assert pnf_2.version == "1.0" + assert pnf_2.created() + mock_send.assert_called_once_with("GET", 'get Pnfs', 'https://sdc.api.be.simpledemo.onap.org:30204/sdc/v1/catalog/resources?resourceType=PNF') + + +def test_init_no_name(): + """Check init with no names.""" + pnf = Pnf() + assert isinstance(pnf, SdcResource) + assert pnf._identifier is None + assert pnf._version is None + assert pnf.name == "ONAP-test-PNF" + assert pnf.headers["USER_ID"] == "cs0008" + assert pnf.vsp is None + assert pnf.vendor is None + assert isinstance(pnf._base_url(), str) + +@mock.patch.object(Pnf, 'exists') +def test_init_with_name(mock_exists): + """Check init with names.""" + mock_exists.return_value = False + pnf = Pnf(name="YOLO") + assert pnf._identifier == None + assert pnf._version == None + assert pnf.name == "YOLO" + assert pnf.created() == False + assert pnf.headers["USER_ID"] == "cs0008" + assert pnf.vsp == None + assert isinstance(pnf._base_url(), str) + + +def test_equality_really_equals(): + """Check two pnfs are equals if name is the same.""" + pnf_1 = Pnf(name="equal") + pnf_1.identifier = "1234" + pnf_2 = Pnf(name="equal") + pnf_2.identifier = "1235" + assert pnf_1 == pnf_2 + + +def test_equality_not_equals(): + """Check two pnfs are not equals if name is not the same.""" + pnf_1 = Pnf(name="equal") + pnf_1.identifier = "1234" + pnf_2 = Pnf(name="not_equal") + pnf_2.identifier = "1234" + assert pnf_1 != pnf_2 + + +def test_equality_not_equals_not_same_object(): + """Check a pnf and something different are not equals.""" + pnf_1 = Pnf(name="equal") + pnf_1.identifier = "1234" + pnf_2 = SdcResource() + pnf_2.name = "equal" + assert pnf_1 != pnf_2 + + +@mock.patch.object(Pnf, 'get_all') +def test_exists_not_exists(mock_get_all): + """Return False if pnf doesn't exist in SDC.""" + pnf_1 = Pnf(name="one") + pnf_1.identifier = "1234" + mock_get_all.return_value = [pnf_1] + pnf = Pnf(name="two") + assert not pnf.exists() + + +@mock.patch.object(Pnf, 'get_all') +def test_exists(mock_get_all): + """Return True if pnf exists in SDC.""" + pnf_1 = Pnf(name="one") + pnf_1.identifier = "1234" + pnf_1.unique_uuid = "5689" + pnf_1.unique_identifier = "71011" + pnf_1.status = const.DRAFT + pnf_1.version = "1.1" + mock_get_all.return_value = [pnf_1] + pnf = Pnf(name="one") + assert pnf.exists() + assert pnf.identifier == "1234" + assert pnf.unique_uuid == "5689" + assert pnf.unique_identifier == "71011" + assert pnf.status == const.DRAFT + assert pnf.version == "1.1" + + +@mock.patch.object(Pnf, 'exists') +def test_load_created(mock_exists): + """Load is a wrapper around exists().""" + pnf = Pnf(name="one") + pnf.load() + mock_exists.assert_called_once() + + +@mock.patch.object(Pnf, 'exists') +def test_create_no_vsp_no_vendor(mock_exists): + """Do nothing if no vsp and no vendor""" + pnf = Pnf() + mock_exists.return_value = False + with pytest.raises(ParameterError) as err: + pnf.create() + assert err.type == ParameterError + assert str(err.value) == "Neither Vsp nor Vendor provided." + + +@mock.patch.object(Pnf, 'exists') +@mock.patch.object(Pnf, 'send_message_json') +@mock.patch.object(Pnf, "category") +def test_create_already_exists(mock_category, mock_send, mock_exists): + """Do nothing if already created in SDC.""" + pnf = Pnf() + vsp = Vsp() + vsp._identifier = "1232" + pnf.vsp = vsp + mock_exists.return_value = True + pnf.create() + mock_send.assert_not_called() + + +@mock.patch.object(Pnf, 'exists') +@mock.patch.object(Pnf, 'send_message_json') +@mock.patch.object(Pnf, "category", new_callable=mock.PropertyMock) +def test_create_issue_in_creation(mock_category, mock_send, mock_exists): +# def test_create_issue_in_creation(mock_send, mock_exists): + """Do nothing if not created but issue during creation.""" + pnf = Pnf() + vsp = Vsp() + vendor = Vendor() + vsp._identifier = "1232" + vsp.create_csar = MagicMock(return_value=True) + vsp.vendor = vendor + pnf.vsp = vsp + expected_data = '{\n "artifacts": {},\n "attributes": [],\n "capabilities": {},\n "categories": [\n {\n "normalizedName": "generic",\n "name": "Generic",\n "uniqueId": "resourceNewCategory.generic",\n "subcategories": [{"empty": false, "groupings": null, "icons": ["objectStorage", "compute"], "name": "Abstract", "normalizedName": "abstract", "ownerId": null, "type": null, "uniqueId": "resourceNewCategory.generic.abstract", "version": null}],\n "version": null,\n "ownerId": null,\n "empty": false,\n "type": null,\n "icons": null\n }\n ],\n "componentInstances": [],\n "componentInstancesAttributes": {},\n "componentInstancesProperties": {},\n "componentType": "RESOURCE",\n "contactId": "cs0008",\n \n "csarUUID": "None",\n "csarVersion": "1.0",\n "vendorName": "Generic-Vendor",\n \n "deploymentArtifacts": {},\n "description": "PNF",\n "icon": "defaulticon",\n "name": "ONAP-test-PNF",\n "properties": [],\n "groups": [],\n "requirements": {},\n "resourceType": "PNF",\n "tags": ["ONAP-test-PNF"],\n "toscaArtifacts": {},\n "vendorRelease": "1.0"\n}' + mock_exists.return_value = False + mock_send.side_effect = RequestError + rc = ResourceCategory( + name="Generic" + ) + rc.normalized_name="generic" + rc.unique_id="resourceNewCategory.generic" + rc.subcategories=[{"empty": False, "groupings": None, "icons": ["objectStorage", "compute"], "name": "Abstract", "normalizedName": "abstract", "ownerId": None, "type": None, "uniqueId": "resourceNewCategory.generic.abstract", "version": None}] + rc.version=None + rc.owner_id=None + rc.empty=False + rc.type=None + rc.icons=None + mock_category.return_value = rc + with pytest.raises(RequestError) as exc: + pnf.create() + mock_send.assert_called_once_with("POST", "create Pnf", 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/resources', data=expected_data) + assert not pnf.created() + + +@mock.patch.object(Pnf, 'exists') +@mock.patch.object(Pnf, 'send_message_json') +@mock.patch.object(Pnf, "category", new_callable=mock.PropertyMock) +def test_create_OK(mock_category, mock_send, mock_exists): + """Create and update object.""" + pnf = Pnf() + vsp = Vsp() + vendor = Vendor() + vsp._identifier = "1232" + pnf.vsp = vsp + vsp.vendor = vendor + vsp._csar_uuid = "1234" + expected_data = '{\n "artifacts": {},\n "attributes": [],\n "capabilities": {},\n "categories": [\n {\n "normalizedName": "generic",\n "name": "Generic",\n "uniqueId": "resourceNewCategory.generic",\n "subcategories": [{"empty": false, "groupings": null, "icons": ["objectStorage", "compute"], "name": "Abstract", "normalizedName": "abstract", "ownerId": null, "type": null, "uniqueId": "resourceNewCategory.generic.abstract", "version": null}],\n "version": null,\n "ownerId": null,\n "empty": false,\n "type": null,\n "icons": null\n }\n ],\n "componentInstances": [],\n "componentInstancesAttributes": {},\n "componentInstancesProperties": {},\n "componentType": "RESOURCE",\n "contactId": "cs0008",\n \n "csarUUID": "1234",\n "csarVersion": "1.0",\n "vendorName": "Generic-Vendor",\n \n "deploymentArtifacts": {},\n "description": "PNF",\n "icon": "defaulticon",\n "name": "ONAP-test-PNF",\n "properties": [],\n "groups": [],\n "requirements": {},\n "resourceType": "PNF",\n "tags": ["ONAP-test-PNF"],\n "toscaArtifacts": {},\n "vendorRelease": "1.0"\n}' + mock_exists.return_value = False + mock_send.return_value = {'resourceType': 'PNF', 'name': 'one', 'uuid': '1234', 'invariantUUID': '5678', 'version': '1.0', 'uniqueId': '91011', 'lifecycleState': 'NOT_CERTIFIED_CHECKOUT'} + rc = ResourceCategory( + name="Generic" + ) + rc.normalized_name="generic" + rc.unique_id="resourceNewCategory.generic" + rc.subcategories=[{"empty": False, "groupings": None, "icons": ["objectStorage", "compute"], "name": "Abstract", "normalizedName": "abstract", "ownerId": None, "type": None, "uniqueId": "resourceNewCategory.generic.abstract", "version": None}] + rc.version=None + rc.owner_id=None + rc.empty=False + rc.type=None + rc.icons=None + mock_category.return_value = rc + pnf.create() + mock_send.assert_called_once_with("POST", "create Pnf", 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/resources', data=expected_data) + assert pnf.created() + assert pnf._status == const.DRAFT + assert pnf.identifier == "1234" + assert pnf.unique_uuid == "5678" + assert pnf.version == "1.0" + +@mock.patch.object(Pnf, 'exists') +@mock.patch.object(Pnf, 'load') +def test_version_no_load_no_created(mock_load, mock_exists): + """Test versions when not created.""" + mock_exists.return_value = False + pnf = Pnf() + assert pnf.version is None + mock_load.assert_not_called() + +@mock.patch.object(Pnf, 'load') +def test_version_no_load_created(mock_load): + """Test versions when created.""" + pnf = Pnf() + pnf.identifier = "1234" + pnf._version = "64" + assert pnf.version == "64" + mock_load.assert_not_called() + + +@mock.patch.object(Pnf, 'load') +def test_version_with_load(mock_load): + """Test versions when not created but with identifier.""" + pnf = Pnf() + pnf.identifier = "1234" + assert pnf.version is None + mock_load.assert_called_once() + +@mock.patch.object(Pnf, 'exists') +@mock.patch.object(Pnf, 'load') +def test_status_no_load_no_created(mock_load, mock_exists): + """Test status when not created.""" + mock_exists.return_value = False + pnf = Pnf() + assert pnf.status is None + + +@pytest.mark.parametrize("status", [const.COMMITED, const.CERTIFIED, const.UPLOADED, const.VALIDATED]) +@mock.patch.object(Pnf, 'exists') +@mock.patch.object(Pnf, 'load') +@mock.patch.object(Pnf, 'send_message') +def test_submit_not_Commited(mock_send, mock_load, mock_exists, status): + """Do nothing if not created.""" + mock_exists.return_value = False + pnf = Pnf() + pnf._status = status + pnf.submit() + mock_send.assert_not_called() + +@mock.patch.object(Pnf, 'exists') +@mock.patch.object(Pnf, 'load') +@mock.patch.object(Pnf, 'send_message') +def test_submit_OK(mock_send, mock_load, mock_exists): + """Don't update status if submission NOK.""" + mock_exists.return_value = True + pnf = Pnf() + pnf._status = const.COMMITED + expected_data = '{\n "userRemarks": "certify"\n}' + pnf._version = "1234" + pnf._unique_identifier = "12345" + pnf.submit() + mock_send.assert_called_once_with( + "POST", "Certify Pnf", + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/resources/12345/lifecycleState/Certify', + data=expected_data) + + +@mock.patch.object(Pnf, 'load') +@mock.patch.object(Pnf, 'certify') +@mock.patch.object(Pnf, 'submit') +@mock.patch.object(Pnf, 'create') +def test_onboard_new_pnf(mock_create, mock_submit, mock_certify, mock_load): + getter_mock = mock.Mock(wraps=Pnf.status.fget) + mock_status = Pnf.status.getter(getter_mock) + with mock.patch.object(Pnf, 'status', mock_status): + getter_mock.side_effect = [None, const.APPROVED, const.APPROVED, + const.APPROVED, const.APPROVED] + vsp = Vsp() + pnf = Pnf(vsp=vsp) + pnf._time_wait = 0 + pnf.onboard() + mock_create.assert_called_once() + mock_submit.assert_not_called() + mock_certify.assert_not_called() + mock_load.assert_not_called() + +@mock.patch.object(Pnf, 'load') +@mock.patch.object(Pnf, 'certify') +@mock.patch.object(Pnf, 'submit') +@mock.patch.object(Pnf, 'create') +def test_onboard_pnf_submit(mock_create, mock_submit, mock_certify, mock_load): + getter_mock = mock.Mock(wraps=Pnf.status.fget) + mock_status = Pnf.status.getter(getter_mock) + with mock.patch.object(Pnf, 'status', mock_status): + getter_mock.side_effect = [const.DRAFT, const.DRAFT, const.APPROVED, + const.APPROVED, const.APPROVED, const.APPROVED] + pnf = Pnf() + pnf._time_wait = 0 + pnf.onboard() + mock_create.assert_not_called() + mock_submit.assert_called_once() + mock_certify.assert_not_called() + mock_load.assert_not_called() + +@mock.patch.object(Pnf, 'load') +@mock.patch.object(Pnf, 'certify') +@mock.patch.object(Pnf, 'submit') +@mock.patch.object(Pnf, 'create') +def test_onboard_pnf_certify(mock_create, mock_submit, mock_certify, mock_load): + getter_mock = mock.Mock(wraps=Pnf.status.fget) + mock_status = Pnf.status.getter(getter_mock) + with mock.patch.object(Pnf, 'status', mock_status): + getter_mock.side_effect = [const.CHECKED_IN, const.CHECKED_IN, const.CHECKED_IN, + const.APPROVED, const.APPROVED, const.APPROVED, + const.APPROVED] + pnf = Pnf() + pnf._time_wait = 0 + pnf.onboard() + mock_create.assert_not_called() + mock_submit.assert_not_called() + mock_certify.assert_called_once() + mock_load.assert_not_called() + +@mock.patch.object(Pnf, 'load') +@mock.patch.object(Pnf, 'certify') +@mock.patch.object(Pnf, 'submit') +@mock.patch.object(Pnf, 'create') +def test_onboard_pnf_load(mock_create, mock_submit, mock_certify, mock_load): + getter_mock = mock.Mock(wraps=Pnf.status.fget) + mock_status = Pnf.status.getter(getter_mock) + with mock.patch.object(Pnf, 'status', mock_status): + getter_mock.side_effect = [const.CERTIFIED, const.CERTIFIED, + const.CERTIFIED, const.CERTIFIED, + const.APPROVED, const.APPROVED, + const.APPROVED] + pnf = Pnf() + pnf._time_wait = 0 + pnf.onboard() + mock_create.assert_not_called() + mock_submit.assert_not_called() + mock_certify.assert_not_called() + mock_load.assert_called_once() + +@mock.patch.object(Pnf, 'load') +@mock.patch.object(Pnf, 'submit') +@mock.patch.object(Pnf, 'create') +@mock.patch.object(Pnf, 'certify') +def test_onboard_whole_pnf_vsp(mock_certify, mock_create, mock_submit, mock_load): + """Test onboarding with vsp""" + getter_mock = mock.Mock(wraps=Pnf.status.fget) + mock_status = Pnf.status.getter(getter_mock) + with mock.patch.object(Pnf, 'status', mock_status): + getter_mock.side_effect = [None, const.DRAFT, const.DRAFT, + const.CHECKED_IN, const.CHECKED_IN, const.CHECKED_IN, + const.CERTIFIED, const.CERTIFIED, const.CERTIFIED, + const.CERTIFIED, const.APPROVED, const.APPROVED, + const.APPROVED, const.APPROVED] + vsp = Vsp() + pnf = Pnf(vsp=vsp) + pnf._time_wait = 0 + pnf.onboard() + mock_create.assert_called_once() + mock_submit.assert_called_once() + mock_load.assert_called_once() + mock_certify.assert_called_once() + +@mock.patch.object(Pnf, 'load') +@mock.patch.object(Pnf, 'submit') +@mock.patch.object(Pnf, 'create') +@mock.patch.object(Pnf, 'certify') +def test_onboard_whole_pnf_vendor(mock_certify, mock_create, mock_submit, mock_load): + """Test onboarding with vendor""" + getter_mock = mock.Mock(wraps=Pnf.status.fget) + mock_status = Pnf.status.getter(getter_mock) + with mock.patch.object(Pnf, 'status', mock_status): + getter_mock.side_effect = [None, const.DRAFT, const.DRAFT, + const.CHECKED_IN, const.CHECKED_IN, const.CHECKED_IN, + const.CERTIFIED, const.CERTIFIED, const.CERTIFIED, + const.CERTIFIED, const.APPROVED, const.APPROVED, + const.APPROVED, const.APPROVED] + vendor = Vendor() + pnf = Pnf(vendor=vendor) + pnf._time_wait = 0 + pnf.onboard() + mock_create.assert_called_once() + mock_submit.assert_called_once() + mock_load.assert_called_once() + mock_certify.assert_called_once() + +@mock.patch.object(Pnf, "send_message_json") +def test_add_properties(mock_send_message_json): + pnf = Pnf(name="test") + pnf._identifier = "toto" + pnf._unique_identifier = "toto" + pnf._status = const.CERTIFIED + with pytest.raises(StatusError) as err: + pnf.add_property(Property(name="test", property_type="string")) + pnf._status = const.DRAFT + pnf.add_property(Property(name="test", property_type="string")) + mock_send_message_json.assert_called_once() + +@mock.patch.object(Pnf, 'load') +@mock.patch.object(Pnf, 'send_message') +def test_add_artifact_to_pnf(mock_send_message, mock_load): + """Test Pnf add artifact""" + pnf = Pnf(name="test") + pnf.status = const.DRAFT + mycbapath = Path(Path(__file__).resolve().parent, "data/vLB_CBA_Python.zip") + + result = pnf.add_deployment_artifact(artifact_label="cba", + artifact_type="CONTROLLER_BLUEPRINT_ARCHIVE", + artifact_name="vLB_CBA_Python.zip", + artifact=mycbapath) + mock_send_message.assert_called() + method, description, url = mock_send_message.call_args[0] + assert method == "POST" + assert description == "Add deployment artifact for test sdc resource" + assert url == ("https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/resources/" + f"{pnf.unique_identifier}/artifacts") + + +@mock.patch.object(Pnf, "created") +@mock.patch.object(ResourceCategory, "get") +def test_pnf_category(mock_resource_category, mock_created): + mock_created.return_value = False + pnf = Pnf(name="test") + _ = pnf.category + mock_resource_category.assert_called_once_with(name="Generic", subcategory="Abstract") + mock_resource_category.reset_mock() + + pnf = Pnf(name="test", category="test", subcategory="test") + _ = pnf.category + mock_resource_category.assert_called_once_with(name="test", subcategory="test") + mock_resource_category.reset_mock() + + mock_created.return_value = True + _ = pnf.category + mock_resource_category.assert_called_once_with(name="test", subcategory="test") diff --git a/tests/test_preload.py b/tests/test_preload.py new file mode 100644 index 0000000..0a97043 --- /dev/null +++ b/tests/test_preload.py @@ -0,0 +1,150 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 +from collections.abc import Iterable +from unittest import mock + +from onapsdk.sdnc.preload import NetworkPreload, PreloadInformation, VfModulePreload +from onapsdk.so.instantiation import Subnet + + +PRELOAD_INFORMATIONS = { + 'preload-information': { + 'preload-list': [ + { + 'preload-id': 'Python_ONAP_SDK_network_instance_338d5238-22fe-44d1-857a-223e2f6edd9b', + 'preload-type': 'network', + 'preload-data': { + 'preload-network-topology-information': { + 'physical-network-name': 'Not Aplicable', + 'is-provider-network': False, + 'is-external-network': False, + 'network-topology-identifier-structure': { + 'network-technology': 'neutron', + 'network-type': 'Generic NeutronNet', + 'network-name': 'Python_ONAP_SDK_network_instance_338d5238-22fe-44d1-857a-223e2f6edd9b', + 'network-role': 'integration_test_net' + }, + 'is-shared-network': False + }, + 'preload-oper-status': { + 'create-timestamp': '2020-06-26T09:12:03.708Z', + 'order-status': 'PendingAssignment' + } + } + }, + { + 'preload-id': 'Python_ONAP_SDK_network_instance_5d61bcf6-ec37-4cea-9d1b-744d0c2b75b9', + 'preload-type': 'network', + 'preload-data': { + 'preload-network-topology-information': { + 'is-provider-network': False, + 'is-external-network': False, + 'network-topology-identifier-structure': { + 'network-technology': 'neutron', + 'network-type': 'Generic NeutronNet', + 'network-name': 'Python_ONAP_SDK_network_instance_5d61bcf6-ec37-4cea-9d1b-744d0c2b75b9', + 'network-id': '1234', + 'network-role': 'integration_test_net' + }, + 'is-shared-network': False + }, + 'preload-oper-status': { + 'create-timestamp': '2020-06-25T12:22:35.939Z', + 'order-status': 'PendingAssignment' + } + } + } + ] + } +} + + +@mock.patch.object(VfModulePreload, "send_message_json") +def test_vf_module_preload_gr_api(mock_send_message_json): + VfModulePreload.upload_vf_module_preload(vnf_instance=mock.MagicMock(), + vf_module_instance_name="test", + vf_module=mock.MagicMock()) + mock_send_message_json.assert_called_once() + method, description, url = mock_send_message_json.call_args[0] + assert method == "POST" + assert description == "Upload VF module preload using GENERIC-RESOURCE-API" + assert url == (f"{VfModulePreload.base_url}/restconf/operations/" + "GENERIC-RESOURCE-API:preload-vf-module-topology-operation") + + +@mock.patch.object(PreloadInformation, "send_message_json") +def test_preload_information(mock_send_message_json): + mock_send_message_json.return_value = PRELOAD_INFORMATIONS + preload_informations = PreloadInformation.get_all() + assert isinstance(preload_informations, Iterable) + preload_informations_list = list(preload_informations) + assert len(preload_informations_list) == 2 + preload_information = preload_informations_list[0] + assert isinstance(preload_information, PreloadInformation) + assert preload_information.preload_id == "Python_ONAP_SDK_network_instance_338d5238-22fe-44d1-857a-223e2f6edd9b" + assert preload_information.preload_type == "network" + + +@mock.patch.object(NetworkPreload, "send_message_json") +def test_network_preload(mock_send_message_json): + NetworkPreload.upload_network_preload( + mock.MagicMock(), + network_instance_name="test_instance", + ) + mock_send_message_json.assert_called_once() + _, _, kwargs = mock_send_message_json.mock_calls[0] + assert "data" in kwargs + data = json.loads(kwargs["data"]) + assert not len(data["input"]["preload-network-topology-information"]["subnets"]) + + mock_send_message_json.reset_mock() + NetworkPreload.upload_network_preload( + mock.MagicMock(), + network_instance_name="test_instance", + subnets=[Subnet( + name="test_subnet", + start_address="127.0.0.0", + gateway_address="127.0.0.1" + )] + ) + mock_send_message_json.assert_called_once() + _, _, kwargs = mock_send_message_json.mock_calls[0] + assert "data" in kwargs + data = json.loads(kwargs["data"]) + assert len(data["input"]["preload-network-topology-information"]["subnets"]) + assert data["input"]["preload-network-topology-information"]["subnets"][0]["subnet-name"] == "test_subnet" + assert data["input"]["preload-network-topology-information"]["subnets"][0]["dhcp-enabled"] == "N" + + mock_send_message_json.reset_mock() + NetworkPreload.upload_network_preload( + mock.MagicMock(), + network_instance_name="test_instance", + subnets=[Subnet( + name="test_subnet", + start_address="127.0.0.0", + gateway_address="127.0.0.1", + dhcp_enabled=True, + dhcp_start_address="192.168.0.0", + dhcp_end_address="192.168.0.1" + )] + ) + mock_send_message_json.assert_called_once() + _, _, kwargs = mock_send_message_json.mock_calls[0] + assert "data" in kwargs + data = json.loads(kwargs["data"]) + assert len(data["input"]["preload-network-topology-information"]["subnets"]) + assert data["input"]["preload-network-topology-information"]["subnets"][0]["subnet-name"] == "test_subnet" + assert data["input"]["preload-network-topology-information"]["subnets"][0]["dhcp-start-address"] == "192.168.0.0" + assert data["input"]["preload-network-topology-information"]["subnets"][0]["dhcp-end-address"] == "192.168.0.1" diff --git a/tests/test_sdc_category_management.py b/tests/test_sdc_category_management.py new file mode 100644 index 0000000..59ca5f5 --- /dev/null +++ b/tests/test_sdc_category_management.py @@ -0,0 +1,349 @@ +"""Test SdcElement module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +import pytest +from onapsdk.exceptions import APIError, ResourceNotFound + +from onapsdk.sdc.category_management import ResourceCategory, ServiceCategory + + +CATEGORIES = { + "categories": { + 'resourceCategories': [ + { + 'name': 'Network L4+', + 'normalizedName': 'network l4+', + 'uniqueId': 'resourceNewCategory.network l4+', + 'icons': None, + 'subcategories': [ + { + 'name': 'Common Network Resources', + 'normalizedName': 'common network resources', + 'uniqueId': 'resourceNewCategory.network l4+.common network resources', + 'icons': ['network'], + 'groupings': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + } + ], + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'Network L2-3', + 'normalizedName': 'network l2-3', + 'uniqueId': 'resourceNewCategory.network l2-3', + 'icons': None, + 'subcategories': [ + { + 'name': 'Router', + 'normalizedName': 'router', + 'uniqueId': 'resourceNewCategory.network l2-3.router', + 'icons': ['router', 'vRouter'], + 'groupings': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'LAN Connectors', + 'normalizedName': 'lan connectors', + 'uniqueId': 'resourceNewCategory.network l2-3.lan connectors', + 'icons': ['network', 'connector', 'port'], + 'groupings': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'Infrastructure', + 'normalizedName': 'infrastructure', + 'uniqueId': 'resourceNewCategory.network l2-3.infrastructure', + 'icons': ['ucpe'], + 'groupings': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'Gateway', + 'normalizedName': 'gateway', + 'uniqueId': 'resourceNewCategory.network l2-3.gateway', + 'icons': ['gateway'], + 'groupings': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'WAN Connectors', + 'normalizedName': 'wan connectors', + 'uniqueId': 'resourceNewCategory.network l2-3.wan connectors', + 'icons': ['network', 'connector', 'port'], + 'groupings': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + } + ], + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'Network Connectivity', + 'normalizedName': 'network connectivity', + 'uniqueId': 'resourceNewCategory.network connectivity', + 'icons': None, + 'subcategories': [ + { + 'name': 'Connection Points', + 'normalizedName': 'connection points', + 'uniqueId': 'resourceNewCategory.network connectivity.connection points', + 'icons': ['cp'], + 'groupings': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'Virtual Links', + 'normalizedName': 'virtual links', + 'uniqueId': 'resourceNewCategory.network connectivity.virtual links', + 'icons': ['vl'], + 'groupings': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + } + ], + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'Configuration', + 'normalizedName': 'configuration', + 'uniqueId': 'resourceNewCategory.configuration', + 'icons': None, + 'subcategories': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + ], + 'serviceCategories': [ + { + 'name': 'Partner Domain Service', + 'normalizedName': 'partner domain service', + 'uniqueId': 'serviceNewCategory.partner domain service', + 'icons': ['partner_domain_service'], + 'subcategories': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'Mobility', + 'normalizedName': 'mobility', + 'uniqueId': 'serviceNewCategory.mobility', + 'icons': ['mobility'], + 'subcategories': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'VoIP Call Control', + 'normalizedName': 'voip call control', + 'uniqueId': 'serviceNewCategory.voip call control', + 'icons': ['call_controll'], + 'subcategories': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'E2E Service', + 'normalizedName': 'e2e service', + 'uniqueId': 'serviceNewCategory.e2e service', + 'icons': ['network_l_1-3'], + 'subcategories': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'Network L4+', + 'normalizedName': 'network l4+', + 'uniqueId': 'serviceNewCategory.network l4+', + 'icons': ['network_l_4'], + 'subcategories': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'Network L1-3', + 'normalizedName': 'network l1-3', + 'uniqueId': 'serviceNewCategory.network l1-3', + 'icons': ['network_l_1-3'], + 'subcategories': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + { + 'name': 'Network Service', + 'normalizedName': 'network service', + 'uniqueId': 'serviceNewCategory.network service', + 'icons': ['network_l_1-3'], + 'subcategories': None, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + ], + 'productCategories': [] + } +} + + +@mock.patch.object(ResourceCategory, "send_message_json") +def test_resource_category_exists(mock_send_message_json): + + rc = ResourceCategory(name="test_name") + mock_send_message_json.return_value = {} + assert not rc.exists() + mock_send_message_json.return_value = CATEGORIES + assert not rc.exists() + rc = ResourceCategory(name="Network Connectivity") + assert rc.exists() + +@mock.patch.object(ResourceCategory, "send_message_json") +def test_resource_category_get(mock_send_message_json): + + mock_send_message_json.return_value = CATEGORIES + rc = ResourceCategory.get(name="Network Connectivity") + assert rc.name == "Network Connectivity" + assert rc.normalized_name == "network connectivity" + assert rc.unique_id == "resourceNewCategory.network connectivity" + assert not rc.icons + assert not rc.version + assert not rc.owner_id + assert not rc.empty + assert not rc.type + assert len(rc.subcategories) == 2 + + with pytest.raises(ResourceNotFound): + ResourceCategory.get(name="Network Connectivity", subcategory="Toto") + rc = ResourceCategory.get(name="Network Connectivity", subcategory="Connection Points") + assert rc.name == "Network Connectivity" + assert rc.normalized_name == "network connectivity" + assert rc.unique_id == "resourceNewCategory.network connectivity" + assert not rc.icons + assert not rc.version + assert not rc.owner_id + assert not rc.empty + assert not rc.type + assert len(rc.subcategories) == 1 + + mock_send_message_json.side_effect = APIError + with pytest.raises(ResourceNotFound): + ResourceCategory.get(name="Network Connectivity") + + mock_send_message_json.side_effect = KeyError + with pytest.raises(ResourceNotFound): + ResourceCategory.get(name="Network Connectivity") + +@mock.patch.object(ResourceCategory, "send_message_json") +def test_resource_category_create(mock_send_message_json): + + mock_send_message_json.return_value = CATEGORIES + rc = ResourceCategory.create(name="Network Connectivity") + assert rc.name == "Network Connectivity" + assert rc.normalized_name == "network connectivity" + assert rc.unique_id == "resourceNewCategory.network connectivity" + assert not rc.icons + assert not rc.version + assert not rc.owner_id + assert not rc.empty + assert not rc.type + ResourceCategory.create(name="New category") + +@mock.patch.object(ServiceCategory, "send_message_json") +def test_service_category_exists(mock_send_message_json): + + sc = ServiceCategory(name="test_name") + mock_send_message_json.return_value = CATEGORIES + assert not sc.exists() + sc = ServiceCategory(name="Partner Domain Service") + assert sc.exists() + mock_send_message_json.side_effect = APIError + assert not sc.exists() + mock_send_message_json.side_effect = KeyError + assert not sc.exists() + +@mock.patch.object(ServiceCategory, "send_message_json") +def test_service_category_get(mock_send_message_json): + + mock_send_message_json.return_value = {} + with pytest.raises(ResourceNotFound): + ServiceCategory.get(name="Partner Domain Service") + mock_send_message_json.return_value = CATEGORIES + sc = ServiceCategory.get(name="Partner Domain Service") + assert sc.name == "Partner Domain Service" + assert sc.normalized_name == "partner domain service" + assert sc.unique_id == "serviceNewCategory.partner domain service" + assert not sc.version + assert not sc.owner_id + assert not sc.empty + assert not sc.type + +@mock.patch.object(ServiceCategory, "send_message_json") +def test_service_category_create(mock_send_message_json): + + mock_send_message_json.return_value = CATEGORIES + sc = ServiceCategory.create(name="Partner Domain Service") + assert sc.name == "Partner Domain Service" + assert sc.normalized_name == "partner domain service" + assert sc.unique_id == "serviceNewCategory.partner domain service" + assert not sc.version + assert not sc.owner_id + assert not sc.empty + assert not sc.type + ServiceCategory.create(name="New category") \ No newline at end of file diff --git a/tests/test_sdc_component.py b/tests/test_sdc_component.py new file mode 100644 index 0000000..ee77de7 --- /dev/null +++ b/tests/test_sdc_component.py @@ -0,0 +1,44 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.sdc.component import Component + + +def test_sdc_component_delete(): + mock_sdc_resource = mock.MagicMock() + mock_parent_sdc_resource = mock.MagicMock() + mock_parent_sdc_resource.resource_inputs_url = "http://test.onap.org" + component = Component( + created_from_csar=False, + actual_component_uid="123", + unique_id="456", + normalized_name="789", + name="test_component", + origin_type="test-origin-type", + customization_uuid="098", + component_uid="765", + component_version="432", + tosca_component_name="test-tosca-component-name", + component_name="test-component-name", + sdc_resource=mock_sdc_resource, + parent_sdc_resource=mock_parent_sdc_resource, + group_instances=None + ) + component.delete() + mock_sdc_resource.send_message_json.assert_called_once_with( + "DELETE", + "Delete test_component component", + f"http://test.onap.org/resourceInstance/{component.unique_id}" + ) diff --git a/tests/test_sdc_element.py b/tests/test_sdc_element.py new file mode 100644 index 0000000..88bcd8f --- /dev/null +++ b/tests/test_sdc_element.py @@ -0,0 +1,109 @@ +"""Test SdcElement module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.onap_service import OnapService +from onapsdk.sdc.sdc_element import SdcElement +from onapsdk.sdc.vendor import Vendor +from onapsdk.sdc.vsp import Vsp +from onapsdk.sdc import SDC +from onapsdk.utils.gui import GuiList + +def test_init(): + """Test the initialization.""" + element = Vendor() + assert isinstance(element, OnapService) + +def test_class_variables(): + """Test the class variables.""" + assert SdcElement.server == "SDC" + assert SdcElement.base_front_url == "https://sdc.api.fe.simpledemo.onap.org:30207" + assert SdcElement.base_back_url == "https://sdc.api.be.simpledemo.onap.org:30204" + assert SdcElement.headers == { + "Content-Type": "application/json", + "Accept": "application/json" + } + +@mock.patch.object(Vendor, 'created') +@mock.patch.object(Vendor, 'send_message_json') +def test__get_item_details_not_created(mock_send, mock_created): + vendor = Vendor() + mock_created.return_value = False + assert vendor._get_item_details() == {} + mock_send.assert_not_called() + +@mock.patch.object(Vsp, 'send_message_json') +def test__get_item_details_created(mock_send): + vsp = Vsp() + vsp.identifier = "1234" + mock_send.return_value = {'results': [{"creationTime": "2"}, {"creationTime": "3"}], "listCount": 2} + assert vsp._get_item_details() == {"creationTime": "3"} + mock_send.assert_called_once_with('GET', 'get item', "{}/items/1234/versions".format(vsp._base_url())) + +@mock.patch.object(Vsp, 'created') +@mock.patch.object(Vsp, 'send_message_json') +def test__get_items_version_details_not_created(mock_send, mock_created): + vsp = Vsp() + mock_created.return_value = False + assert vsp._get_item_version_details() == {} + mock_send.assert_not_called() + +@mock.patch.object(Vsp, 'load') +@mock.patch.object(Vsp, 'send_message_json') +def test__get_items_version_details_no_version(mock_send, mock_load): + vsp = Vsp() + vsp.identifier = "1234" + assert vsp._get_item_version_details() == {} + mock_send.assert_not_called() + +@mock.patch.object(Vsp, 'send_message_json') +def test__get_items_version_details(mock_send): + vsp = Vsp() + vsp.identifier = "1234" + vsp._version = "4567" + mock_send.return_value = {'return': 'value'} + assert vsp._get_item_version_details() == {'return': 'value'} + mock_send.assert_called_once_with('GET', 'get item version', "{}/items/1234/versions/4567".format(vsp._base_url())) + +@mock.patch.object(SDC, "send_message") +def test_get_guis(send_message_mock): + send_message_mock.return_value.status_code = 200 + send_message_mock.return_value.url = "https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/portal" + gui_results = SDC.get_guis() + assert type(gui_results) == GuiList + assert gui_results.guilist[0].url == send_message_mock.return_value.url + assert gui_results.guilist[0].status == send_message_mock.return_value.status_code + +@mock.patch.object(SDC, "get_all") +@mock.patch.object(Vsp, "created") +def test_exists_versions(mock_vsp_created, mock_get_all): + mock_vsp_created.return_value = True + sdc_el1 = Vsp(name="test1") + sdc_el1._version = "1.0" + sdc_el1._identifier = "123" + sdc_el2 = Vsp(name="test2") + sdc_el2._version = "2.0" + sdc_el2._identifier = "123" + mock_get_all.return_value = [sdc_el1, sdc_el2] + assert sdc_el1.exists() + + sdc_el1 = Vsp(name="test1") + sdc_el1._version = "anything" + sdc_el1._identifier = "123" + sdc_el2 = Vsp(name="test2") + sdc_el2._version = "what_is_not_a_float" + sdc_el2._identifier = "123" + mock_get_all.return_value = [sdc_el1, sdc_el2] + assert sdc_el1.exists() diff --git a/tests/test_sdc_resource.py b/tests/test_sdc_resource.py new file mode 100644 index 0000000..1bf562a --- /dev/null +++ b/tests/test_sdc_resource.py @@ -0,0 +1,487 @@ +"""Test SdcResource module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock +import logging + +import pytest + +import onapsdk.constants as const +from onapsdk.exceptions import ParameterError, RequestError, ResourceNotFound +from onapsdk.onap_service import OnapService +from onapsdk.sdc.component import Component +from onapsdk.sdc.properties import ComponentProperty, Input, NestedInput, Property +from onapsdk.sdc.sdc_resource import SdcResource +from onapsdk.sdc.service import Service +from onapsdk.sdc.vf import Vf +from onapsdk.utils.headers_creator import headers_sdc_tester +from onapsdk.utils.headers_creator import headers_sdc_creator + + +COMPONENT_PROPERTIES = [ + { + "uniqueId":"3d9a184f-4268-4a0e-9ddd-252e49670013.vf_module_id", + "type":"string", + "required":False, + "definition":False, + "description":"The vFirewall Module ID is provided by ECOMP", + "password":False, + "name":"vf_module_id", + "label":"vFirewall module ID", + "hidden":False, + "immutable":False, + "isDeclaredListInput":False, + "getInputProperty":False, + "empty":False + },{ + "uniqueId":"74f79006-ae56-4d58-947e-6a5089000774.skip_post_instantiation_configuration", + "type":"boolean", + "required":False, + "definition":False, + "password":False, + "name":"skip_post_instantiation_configuration", + "value":"true", + "hidden":False, + "immutable":False, + "parentUniqueId":"74f79006-ae56-4d58-947e-6a5089000774", + "isDeclaredListInput":False, + "getInputProperty":False, + "ownerId":"74f79006-ae56-4d58-947e-6a5089000774", + "empty":False + } +] + + + +def test_init(): + """Test the initialization.""" + element = SdcResource() + assert isinstance(element, OnapService) + +def test_class_variables(): + """Test the class variables.""" + assert SdcResource.server == "SDC" + assert SdcResource.base_front_url == "https://sdc.api.fe.simpledemo.onap.org:30207" + assert SdcResource.base_back_url == "https://sdc.api.be.simpledemo.onap.org:30204" + assert SdcResource.headers == { + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Basic YWFpOktwOGJKNFNYc3pNMFdYbGhhazNlSGxjc2UyZ0F3ODR2YW9HR21KdlV5MlU=", + "USER_ID": "cs0008", + "X-ECOMP-InstanceID": "onapsdk" + } + +@mock.patch.object(Vf, 'load') +def test__unique_uuid_no_load(mock_load): + vf = Vf() + vf.identifier = "1234" + vf._unique_uuid = "4567" + assert vf.unique_uuid == "4567" + mock_load.assert_not_called() + +@mock.patch.object(Vf, 'load') +def test__unique_uuid_load(mock_load): + vf = Vf() + vf.identifier = "1234" + assert vf.unique_uuid == None + mock_load.assert_called_once() + +def test__unique_uuid_setter(): + vf = Vf() + vf.identifier = "1234" + vf.unique_uuid = "4567" + assert vf._unique_uuid == "4567" + +@mock.patch.object(Vf, 'deep_load') +def test__unique_identifier_load(mock_load): + vf = Vf() + vf.identifier = "1234" + assert vf.unique_identifier == None + mock_load.assert_called_once() + +@mock.patch.object(Vf, 'deep_load') +def test__unique_identifier_no_load(mock_load): + vf = Vf() + vf.identifier = "1234" + vf._unique_identifier= "toto" + assert vf.unique_identifier == "toto" + mock_load.assert_not_called() + +def test__status_setter(): + vf = Vf() + vf.identifier = "1234" + vf.status = "4567" + assert vf._status == "4567" + +@mock.patch.object(Vf, 'created') +@mock.patch.object(Vf, 'send_message_json') +def test__deep_load_request_error(mock_send, mock_created): + mock_created.return_value = True + vf = Vf() + vf.identifier = "1234" + vf._version = "4567" + vf._status = const.CHECKED_IN + mock_send.side_effect = RequestError + with pytest.raises(RequestError) as err: + vf.deep_load() + assert err.type == RequestError + assert vf._unique_identifier is None + mock_send.assert_called_once_with('GET', 'Deep Load Vf', + "{}/sdc1/feProxy/rest/v1/screen?excludeTypes=VFCMT&excludeTypes=Configuration".format(vf.base_front_url), + headers=headers_sdc_creator(vf.headers)) + +@mock.patch.object(Vf, 'created') +@mock.patch.object(Vf, 'send_message_json') +def test__deep_load_response_OK(mock_send, mock_created): + mock_created.return_value = True + vf = Vf() + vf.identifier = "5689" + vf.unique_uuid = "1234" + vf._version = "4567" + vf._status = const.CHECKED_IN + mock_send.return_value = {'resources': [{'uuid': '5689', 'name': 'test', 'uniqueId': '71011', 'invariantUUID': '1234', 'categories': [{'name': 'test', 'subcategories': [{'name': 'test_subcategory'}]}]}]} + vf.deep_load() + assert vf.unique_identifier == "71011" + assert vf._category_name == "test" + assert vf._subcategory_name == "test_subcategory" + mock_send.assert_called_once_with('GET', 'Deep Load Vf', + "{}/sdc1/feProxy/rest/v1/screen?excludeTypes=VFCMT&excludeTypes=Configuration".format(vf.base_front_url), + headers=headers_sdc_creator(vf.headers)) + +@mock.patch.object(Service, 'created') +@mock.patch.object(Service, 'send_message_json') +def test__deep_load_response_OK_dependency(mock_send, mock_created): + mock_created.return_value = True + vf = Service() + vf.identifier = "4321" + vf.unique_uuid = "1234" + vf._version = "4567" + vf._status = const.CHECKED_IN + mock_send.side_effect = [{'services': [{'uuid': '5689', 'name': 'test', 'uniqueId': '71011', 'invariantUUID': '1234', 'categories': [{'name': 'test', 'subcategories': [{'name': 'test_subcategory'}]}]}]}, [{'version': '4567', 'uniqueId': '71011'}]] + vf.deep_load() + assert vf.unique_identifier == "71011" + +@mock.patch.object(Vf, 'created') +@mock.patch.object(Vf, 'send_message_json') +def test__deep_load_response_NOK(mock_send, mock_created): + mock_created.return_value = True + vf = Vf() + vf.identifier = "5678" + vf.unique_uuid = "1234" + vf._version = "4567" + vf._status = const.CHECKED_IN + mock_send.return_value = {'resources': [{'uuid': '5689', 'name': 'test', 'uniqueId': '71011', 'invariantUUID': '1234', }]} + vf.deep_load() + assert vf._unique_identifier is None + mock_send.assert_called_once_with('GET', 'Deep Load Vf', + "{}/sdc1/feProxy/rest/v1/screen?excludeTypes=VFCMT&excludeTypes=Configuration".format(vf.base_front_url), + headers=headers_sdc_creator(vf.headers)) + +@mock.patch.object(Vf, 'created') +@mock.patch.object(Vf, 'send_message_json') +def test__deep_load_response_OK_under_cert(mock_send, mock_created): + mock_created.return_value = True + vf = Vf() + vf.identifier = "5689" + vf.unique_uuid = "1234" + vf._version = "4567" + vf._status = const.UNDER_CERTIFICATION + mock_send.return_value = {'resources': [{'uuid': '5689', 'name': 'test', 'uniqueId': '71011', 'invariantUUID': '1234', 'categories': [{'name': 'test', 'subcategories': [{'name': 'test_subcategory'}]}]}]} + vf.deep_load() + assert vf.unique_identifier == "71011" + assert vf._category_name == "test" + assert vf._subcategory_name == "test_subcategory" + mock_send.assert_called_once_with('GET', 'Deep Load Vf', + "{}/sdc1/feProxy/rest/v1/screen?excludeTypes=VFCMT&excludeTypes=Configuration".format(vf.base_front_url), + headers=headers_sdc_tester(vf.headers)) + +@mock.patch.object(Vf, 'created') +@mock.patch.object(Vf, 'send_message_json') +def test__deep_load_response_NOK_under_cert(mock_send, mock_created): + mock_created.return_value = True + vf = Vf() + vf.identifier = "5678" + vf.unique_uuid = "1234" + vf._version = "4567" + vf._status = const.UNDER_CERTIFICATION + mock_send.return_value = {'resources': [{'uuid': '5689', 'name': 'test', 'invariantUUID': '1234', 'uniqueId': '71011'}]} + vf.deep_load() + assert vf._unique_identifier is None + mock_send.assert_called_once_with('GET', 'Deep Load Vf', + "{}/sdc1/feProxy/rest/v1/screen?excludeTypes=VFCMT&excludeTypes=Configuration".format(vf.base_front_url), + headers=headers_sdc_tester(vf.headers)) + +def test__parse_sdc_status_certified(): + assert SdcResource._parse_sdc_status("CERTIFIED", None, logging.getLogger()) == const.CERTIFIED + +def test__parse_sdc_status_certified_not_approved(): + assert SdcResource._parse_sdc_status("CERTIFIED", + const.DISTRIBUTION_NOT_APPROVED, + logging.getLogger()) == const.CERTIFIED + +def test__parse_sdc_status_certified_approved(): + assert SdcResource._parse_sdc_status("CERTIFIED", + const.DISTRIBUTION_APPROVED, + logging.getLogger()) == const.CERTIFIED + +def test__parse_sdc_status_distributed(): + assert SdcResource._parse_sdc_status("CERTIFIED", const.SDC_DISTRIBUTED, + logging.getLogger()) == const.DISTRIBUTED + +def test__parse_sdc_status_draft(): + assert SdcResource._parse_sdc_status(const.NOT_CERTIFIED_CHECKOUT, None, + logging.getLogger() ) == const.DRAFT + +def test__parse_sdc_status_draft(): + assert SdcResource._parse_sdc_status(const.NOT_CERTIFIED_CHECKIN, None, + logging.getLogger() ) == const.CHECKED_IN + +def test__parse_sdc_status_submitted(): + assert SdcResource._parse_sdc_status(const.READY_FOR_CERTIFICATION, None, + logging.getLogger() ) == const.SUBMITTED + +def test__parse_sdc_status_under_certification(): + assert SdcResource._parse_sdc_status(const.CERTIFICATION_IN_PROGRESS, None, + logging.getLogger() ) == const.UNDER_CERTIFICATION + +def test__parse_sdc_status_unknown(): + assert SdcResource._parse_sdc_status("UNKNOWN", None, logging.getLogger() ) == 'UNKNOWN' + +def test__parse_sdc_status_empty(): + assert SdcResource._parse_sdc_status("", None, logging.getLogger() ) is None + +def test__really_submit(): + sdcResource = SdcResource() + with pytest.raises(NotImplementedError): + sdcResource._really_submit() + +def test__action_url_no_action_type(): + sdcResource = SdcResource() + url = sdcResource._action_url("base", "subpath", "version_path") + assert url == "base/resources/version_path/lifecycleState/subpath" + +def test__action_url_action_type(): + sdcResource = SdcResource() + url = sdcResource._action_url("base", "subpath", "version_path", + action_type="distribution") + assert url == "base/resources/version_path/distribution/subpath" + +@mock.patch.object(SdcResource, '_parse_sdc_status') +def test_update_informations_from_sdc_creation_no_distribitution_state(mock_parse): + mock_parse.return_value = "12" + sdcResource = SdcResource() + details = {'invariantUUID': '1234', + 'lifecycleState': 'state', + 'version': 'v12', + 'uniqueId': '5678'} + + sdcResource.update_informations_from_sdc_creation(details) + assert sdcResource.unique_uuid == "1234" + assert sdcResource.status == "12" + assert sdcResource.version == "v12" + assert sdcResource.unique_identifier == "5678" + mock_parse.assert_called_once_with("state", None, mock.ANY) + +@mock.patch.object(SdcResource, '_parse_sdc_status') +def test_update_informations_from_sdc_creation_distribitution_state(mock_parse): + mock_parse.return_value = "bgt" + sdcResource = SdcResource() + details = {'invariantUUID': '1234', + 'lifecycleState': 'state', + 'distributionStatus': 'trez', + 'version': 'v12', + 'uniqueId': '5678'} + + sdcResource.update_informations_from_sdc_creation(details) + assert sdcResource.unique_uuid == "1234" + assert sdcResource.status == "bgt" + assert sdcResource.version == "v12" + assert sdcResource.unique_identifier == "5678" + mock_parse.assert_called_once_with("state", 'trez', mock.ANY) + +@mock.patch.object(SdcResource, "is_own_property") +@mock.patch.object(SdcResource, "declare_input_for_own_property") +@mock.patch.object(SdcResource, "declare_nested_input") +def test_declare_input(mock_nested, mock_own, mock_is_own): + sdc_resource = SdcResource() + prop = Property(name="test", property_type="test") + mock_is_own.return_value = False + with pytest.raises(ParameterError): + sdc_resource.declare_input(prop) + mock_is_own.return_value = True + sdc_resource.declare_input(prop) + mock_own.assert_called_once() + mock_nested.assert_not_called() + + mock_nested.reset_mock() + mock_own.reset_mock() + sdc_resource.declare_input(NestedInput(sdc_resource=mock.MagicMock(), input_obj=mock.MagicMock())) + mock_own.assert_not_called() + mock_nested.assert_called_once() + +@mock.patch.object(SdcResource, "send_message_json") +@mock.patch.object(SdcResource, "get_component") +@mock.patch.object(SdcResource, "resource_inputs_url", new_callable=mock.PropertyMock) +def test_declare_nested_input(mock_resource_inputs, mock_get_component, mock_send_json): + sdc_resource = SdcResource() + sdc_resource.unique_identifier = "toto" + mock_resource_inputs.return_value = "test" + sdc_resource.declare_input(NestedInput(sdc_resource=mock.MagicMock(), input_obj=mock.MagicMock())) + mock_get_component.assert_called_once() + mock_send_json.assert_called_once() + +@mock.patch.object(SdcResource, "inputs", new_callable=mock.PropertyMock) +def test_get_input(mock_inputs): + sdc_resource = SdcResource() + + mock_inputs.return_value = [ + Input(unique_id="123", + input_type="integer", + name="test", + sdc_resource=sdc_resource), + Input(unique_id="321", + input_type="string", + name="test2", + sdc_resource=sdc_resource) + ] + assert sdc_resource.get_input("test") + assert sdc_resource.get_input("test2") + with pytest.raises(ResourceNotFound): + sdc_resource.get_input("test3") + +@mock.patch.object(SdcResource, "components", new_callable=mock.PropertyMock) +def test_get_component(mock_components): + sdc_resource = SdcResource() + + mock_components.return_value = [ + Component( + created_from_csar=False, + actual_component_uid="123", + unique_id="123", + normalized_name="123", + name="123", + origin_type="123", + customization_uuid="123", + tosca_component_name="123", + component_name="123", + component_uid="123", + component_version="123", + group_instances=None, + sdc_resource=SdcResource(name="test"), + parent_sdc_resource=sdc_resource + ) + ] + assert sdc_resource.get_component(SdcResource(name="test")) + with pytest.raises(ResourceNotFound): + sdc_resource.get_component(SdcResource(name="test2")) + assert sdc_resource.get_component_by_name("test") + with pytest.raises(ResourceNotFound): + sdc_resource.get_component_by_name("invalid_name") + +def test_component_properties(): + sdc_resource = mock.MagicMock() + parent_sdc_resource = SdcResource() + + component = Component( + created_from_csar=False, + actual_component_uid="123", + unique_id="123", + normalized_name="123", + name="123", + origin_type="123", + customization_uuid="123", + tosca_component_name="123", + component_name="123", + component_uid="123", + component_version="123", + group_instances=None, + sdc_resource=sdc_resource, + parent_sdc_resource=mock.MagicMock() + ) + sdc_resource.send_message_json.return_value = {} + assert not len(list(component.properties)) + + sdc_resource.send_message_json.return_value = COMPONENT_PROPERTIES + properties = list(component.properties) + assert len(properties) == 2 + prop1, prop2 = properties + + assert prop1.unique_id == "3d9a184f-4268-4a0e-9ddd-252e49670013.vf_module_id" + assert prop1.property_type == "string" + assert prop1.name == "vf_module_id" + assert prop1.value is None + + assert prop2.unique_id == "74f79006-ae56-4d58-947e-6a5089000774.skip_post_instantiation_configuration" + assert prop2.property_type == "boolean" + assert prop2.name == "skip_post_instantiation_configuration" + assert prop2.value == "true" + +@mock.patch.object(Component, "properties", new_callable=mock.PropertyMock) +def test_component_property_set_value(mock_component_properties): + mock_sdc_resource = mock.MagicMock() + component = Component( + created_from_csar=False, + actual_component_uid="123", + unique_id="123", + normalized_name="123", + name="123", + origin_type="123", + customization_uuid="123", + tosca_component_name="123", + component_name="123", + component_uid="123", + component_version="123", + group_instances=None, + sdc_resource=mock_sdc_resource, + parent_sdc_resource=mock.MagicMock() + ) + mock_component_properties.return_value = [ + ComponentProperty( + unique_id="123", + property_type="string", + name="test_property", + component=component + ) + ] + with pytest.raises(ParameterError): + component.get_property(property_name="non_exists") + prop1 = component.get_property(property_name="test_property") + assert prop1.name == "test_property" + assert prop1.unique_id == "123" + assert prop1.property_type == "string" + assert not prop1.value + + prop1.value = "123" + mock_sdc_resource.send_message_json.assert_called_once() + +@mock.patch.object(SdcResource, "_action_to_sdc") +def test_sdc_resource_checkout(mock_action_to_sdc): + mock_action_to_sdc.return_value = None + sdc_resource = SdcResource() + sdc_resource.checkout() + mock_action_to_sdc.assert_called_once_with(const.CHECKOUT, "lifecycleState") + +@mock.patch.object(SdcResource, "_action_to_sdc") +def test_sdc_resource_undo_checkout(mock_action_to_sdc): + mock_action_to_sdc.return_value = None + sdc_resource = SdcResource() + sdc_resource.undo_checkout() + mock_action_to_sdc.assert_called_once_with(const.UNDOCHECKOUT, "lifecycleState") + +@mock.patch.object(SdcResource, "_action_to_sdc") +def test_sdc_resource_certify(mock_action_to_sdc): + mock_action_to_sdc.return_value = None + sdc_resource = SdcResource() + sdc_resource.certify() + mock_action_to_sdc.assert_called_once_with(const.CERTIFY, "lifecycleState") diff --git a/tests/test_sdc_resource_properties.py b/tests/test_sdc_resource_properties.py new file mode 100644 index 0000000..4502868 --- /dev/null +++ b/tests/test_sdc_resource_properties.py @@ -0,0 +1,1080 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +import pytest + +from onapsdk.exceptions import ParameterError +from onapsdk.sdc.properties import Input, Property +from onapsdk.sdc.sdc_resource import SdcResource +from onapsdk.sdc.service import Service +from onapsdk.sdc.vf import Vf +from onapsdk.sdc.vl import Vl + +INPUTS = { + 'inputs': [ + { + 'uniqueId': '9ee5fb23-4c4a-46bd-8682-68698559ee9c.skip_post_instantiation_configuration', + 'type': 'boolean', + 'required': False, + 'definition': False, + 'defaultValue': 'true', + 'description': None, + 'schema': None, + 'password': False, + 'name': 'skip_post_instantiation_configuration', + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': '9ee5fb23-4c4a-46bd-8682-68698559ee9c', + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'constraints': None, + 'inputs': None, + 'properties': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': '9ee5fb23-4c4a-46bd-8682-68698559ee9c', + 'empty': False + }, + { + 'uniqueId': '4a84415b-4580-4a78-aa33-501f0cd3d079.test', + 'type': 'string', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': { + 'derivedFrom': None, + 'constraints': None, + 'properties': None, + 'property': { + 'uniqueId': None, + 'type': '', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': None, + 'password': False, + 'name': None, + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': None, + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': None, + 'empty': False + }, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + 'password': False, + 'name': 'test', + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': '4a84415b-4580-4a78-aa33-501f0cd3d079', + 'propertyId': '4a84415b-4580-4a78-aa33-501f0cd3d079.sraka', + 'parentPropertyType': 'string', + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': 'cs0008', + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'constraints': None, + 'inputs': None, + 'properties': None, + 'schemaType': '', + 'schemaProperty': { + 'uniqueId': None, + 'type': '', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': None, + 'password': False, + 'name': None, + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': None, + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': None, + 'empty': False + }, + 'getInputProperty': False, + 'version': None, + 'ownerId': 'cs0008', + 'empty': False + }, + { + 'uniqueId': '9ee5fb23-4c4a-46bd-8682-68698559ee9c.controller_actor', + 'type': 'string', + 'required': False, + 'definition': False, + 'defaultValue': 'SO-REF-DATA', + 'description': None, + 'schema': None, + 'password': False, + 'name': 'controller_actor', + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': '9ee5fb23-4c4a-46bd-8682-68698559ee9c', + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'constraints': None, + 'inputs': None, + 'properties': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': '9ee5fb23-4c4a-46bd-8682-68698559ee9c', + 'empty': False + }, + { + 'uniqueId': '4a84415b-4580-4a78-aa33-501f0cd3d079.lililili', + 'type': 'list', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': { + 'derivedFrom': None, + 'constraints': None, + 'properties': None, + 'property': { + 'uniqueId': None, + 'type': 'abc', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': None, + 'password': False, + 'name': None, + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': None, + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': None, + 'empty': False + }, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + 'password': False, + 'name': 'lililili', + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': '4a84415b-4580-4a78-aa33-501f0cd3d079', + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': None, + 'getInputValues': None, + 'isDeclaredListInput': True, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'constraints': None, + 'inputs': None, + 'properties': None, + 'schemaType': 'abc', + 'schemaProperty': { + 'uniqueId': None, + 'type': 'abc', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': None, + 'password': False, + 'name': None, + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': None, + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': None, + 'empty': False + }, + 'getInputProperty': False, + 'version': None, + 'ownerId': None, + 'empty': False + } + ] +} + + +PROPERTIES = { + "properties": [{ + 'uniqueId': '4a84415b-4580-4a78-aa33-501f0cd3d079.llllll', + 'type': 'integer', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': { + 'derivedFrom': None, + 'constraints': None, + 'properties': None, + 'property': { + 'uniqueId': None, + 'type': '', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': None, + 'password': False, + 'name': None, + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': None, + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': None, + 'empty': False + }, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + 'password': False, + 'name': 'llllll', + 'value': '{"get_input":["lililili","INDEX","llllll"]}', + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': '4a84415b-4580-4a78-aa33-501f0cd3d079', + 'getInputValues': [ + { + 'propName': None, + 'inputName': 'lililili', + 'inputId': '4a84415b-4580-4a78-aa33-501f0cd3d079.lililili', + 'indexValue': None, + 'getInputIndex': None, + 'list': False, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + } + ], + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'constraints': None, + 'schemaType': '', + 'schemaProperty': { + 'uniqueId': None, + 'type': '', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': None, + 'password': False, + 'name': None, + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': None, + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': None, + 'empty': False + }, + 'getInputProperty': True, + 'version': None, + 'ownerId': '4a84415b-4580-4a78-aa33-501f0cd3d079', + 'empty': False + }, + { + 'uniqueId': '4a84415b-4580-4a78-aa33-501f0cd3d079.test', + 'type': 'string', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': { + 'derivedFrom': None, + 'constraints': None, + 'properties': None, + 'property': { + 'uniqueId': None, + 'type': '', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': None, + 'password': False, + 'name': None, + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': None, + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': None, + 'empty': False + }, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + 'password': False, + 'name': 'test', + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': '4a84415b-4580-4a78-aa33-501f0cd3d079', + 'getInputValues': [], + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'constraints': None, + 'schemaType': '', + 'schemaProperty': { + 'uniqueId': None, + 'type': '', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': None, + 'password': False, + 'name': None, + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': None, + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': None, + 'empty': False + }, + 'getInputProperty': True, + 'version': None, + 'ownerId': '4a84415b-4580-4a78-aa33-501f0cd3d079', + 'empty': False + }, + { + 'uniqueId': '4a84415b-4580-4a78-aa33-501f0cd3d079.yyy', + 'type': 'string', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': { + 'derivedFrom': None, + 'constraints': None, + 'properties': None, + 'property': { + 'uniqueId': None, + 'type': '', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': None, + 'password': False, + 'name': None, + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': None, + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': None, + 'empty': False + }, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + 'password': False, + 'name': 'yyy', + 'value': 'lalala', + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': '4a84415b-4580-4a78-aa33-501f0cd3d079', + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'constraints': None, + 'schemaType': '', + 'schemaProperty': { + 'uniqueId': None, + 'type': '', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': None, + 'password': False, + 'name': None, + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': None, + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': None, + 'empty': False + }, + 'getInputProperty': False, + 'version': None, + 'ownerId': '4a84415b-4580-4a78-aa33-501f0cd3d079', + 'empty': False + }, + { + 'uniqueId': '4a84415b-4580-4a78-aa33-501f0cd3d079.test2', + 'type': 'boolean', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': 'test2', + 'schema': { + 'derivedFrom': None, + 'constraints': None, + 'properties': None, + 'property': { + 'uniqueId': None, + 'type': '', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': None, + 'password': False, + 'name': None, + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': None, + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': None, + 'empty': False + }, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + }, + 'password': False, + 'name': 'test2', + 'value': '{"get_input":"test2"}', + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': '4a84415b-4580-4a78-aa33-501f0cd3d079', + 'getInputValues': [ + { + 'propName': None, + 'inputName': 'test2', + 'inputId': '4a84415b-4580-4a78-aa33-501f0cd3d079.test2', + 'indexValue': None, + 'getInputIndex': None, + 'list': False, + 'version': None, + 'ownerId': None, + 'empty': False, + 'type': None + } + ], + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'constraints': None, + 'schemaType': '', + 'schemaProperty': { + 'uniqueId': None, + 'type': '', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': None, + 'schema': None, + 'password': False, + 'name': None, + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': None, + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': None, + 'empty': False + }, + 'getInputProperty': True, + 'version': None, + 'ownerId': '4a84415b-4580-4a78-aa33-501f0cd3d079', + 'empty': False + }] +} + + +VL_PROPERTIES = { + "properties": [{ + 'uniqueId': 'd37cd65e-9842-4490-9343-a1a874e6b52a.network_role', + 'type': 'string', + 'required': False, + 'definition': False, + 'defaultValue': None, + 'description': 'Unique label that defines the role that this network performs. example: vce oam network, vnat sr-iov1 network\n', + 'schema': None, + 'password': False, + 'name': 'network_role', + 'value': None, + 'label': None, + 'hidden': False, + 'immutable': False, + 'inputPath': None, + 'status': None, + 'inputId': None, + 'instanceUniqueId': None, + 'propertyId': None, + 'parentPropertyType': None, + 'subPropertyInputPath': None, + 'annotations': None, + 'parentUniqueId': '1af9771b-0f79-4e98-8747-30fd06da85cb', + 'getInputValues': None, + 'isDeclaredListInput': False, + 'getPolicyValues': None, + 'propertyConstraints': None, + 'constraints': None, + 'schemaType': None, + 'schemaProperty': None, + 'getInputProperty': False, + 'version': None, + 'ownerId': '1af9771b-0f79-4e98-8747-30fd06da85cb', + 'empty': False + }] +} + + +@mock.patch.object(Service, "send_message_json") +@mock.patch.object(Service, "send_message") +def test_service_properties(mock_send, mock_send_json): + + service = Service(name="test") + service.unique_identifier = "toto" + + mock_send_json.return_value = {} + assert len(list(service.properties)) == 0 + + mock_send_json.return_value = PROPERTIES + properties_list = list(service.properties) + assert len(properties_list) == 4 + prop1, prop2, prop3, prop4 = properties_list + + mock_send_json.return_value = INPUTS + + assert prop1.sdc_resource == service + assert prop1.unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079.llllll" + assert prop1.name == "llllll" + assert prop1.property_type == "integer" + assert prop1.parent_unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079" + assert prop1.value == '{"get_input":["lililili","INDEX","llllll"]}' + assert prop1.description is None + assert prop1.get_input_values + prop1_input = prop1.input + assert prop1_input.unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079.lililili" + assert prop1_input.input_type == "list" + assert prop1_input.name == "lililili" + assert prop1_input.default_value is None + + assert prop2.sdc_resource == service + assert prop2.unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079.test" + assert prop2.name == "test" + assert prop2.property_type == "string" + assert prop2.parent_unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079" + assert prop2.value is None + assert prop2.description is None + assert prop2.get_input_values == [] + assert prop2.input is None + + assert prop3.sdc_resource == service + assert prop3.unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079.yyy" + assert prop3.name == "yyy" + assert prop3.property_type == "string" + assert prop3.parent_unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079" + assert prop3.value == "lalala" + assert prop3.description is None + assert prop3.get_input_values is None + assert prop3.input is None + + assert prop4.sdc_resource == service + assert prop4.unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079.test2" + assert prop4.name == "test2" + assert prop4.property_type == "boolean" + assert prop4.parent_unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079" + assert prop4.value == '{"get_input":"test2"}' + assert prop4.description == "test2" + assert prop4.get_input_values + with pytest.raises(ParameterError): + prop4.input + + +@mock.patch.object(Service, "send_message_json") +def test_service_inputs(mock_send_json): + service = Service(name="test") + service.unique_identifier = "toto" + + mock_send_json.return_value = {} + assert len(list(service.inputs)) == 0 + + mock_send_json.return_value = INPUTS + inputs_list = list(service.inputs) + assert len(inputs_list) == 4 + + input1, input2, input3, input4 = inputs_list + assert input1.unique_id == "9ee5fb23-4c4a-46bd-8682-68698559ee9c.skip_post_instantiation_configuration" + assert input1.input_type == "boolean" + assert input1.name == "skip_post_instantiation_configuration" + assert input1.default_value == "true" + + assert input2.unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079.test" + assert input2.input_type == "string" + assert input2.name == "test" + assert input2.default_value is None + + assert input3.unique_id == "9ee5fb23-4c4a-46bd-8682-68698559ee9c.controller_actor" + assert input3.input_type == "string" + assert input3.name == "controller_actor" + assert input3.default_value == "SO-REF-DATA" + + assert input4.unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079.lililili" + assert input4.input_type == "list" + assert input4.name == "lililili" + assert input4.default_value is None + + +@mock.patch.object(Vf, "send_message_json") +def test_vf_properties(mock_send_json): + vf = Vf(name="test") + vf.unique_identifier = "toto" + + mock_send_json.return_value = {} + assert len(list(vf.properties)) == 0 + + mock_send_json.return_value = PROPERTIES + properties_list = list(vf.properties) + assert len(properties_list) == 4 + prop1, prop2, prop3, prop4 = properties_list + + mock_send_json.return_value = INPUTS + + assert prop1.sdc_resource == vf + assert prop1.unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079.llllll" + assert prop1.name == "llllll" + assert prop1.property_type == "integer" + assert prop1.parent_unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079" + assert prop1.value == '{"get_input":["lililili","INDEX","llllll"]}' + assert prop1.description is None + assert prop1.get_input_values + prop1_input = prop1.input + assert prop1_input.unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079.lililili" + assert prop1_input.input_type == "list" + assert prop1_input.name == "lililili" + assert prop1_input.default_value is None + + assert prop2.sdc_resource == vf + assert prop2.unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079.test" + assert prop2.name == "test" + assert prop2.property_type == "string" + assert prop2.parent_unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079" + assert prop2.value is None + assert prop2.description is None + assert prop2.get_input_values == [] + assert prop2.input is None + + assert prop3.sdc_resource == vf + assert prop3.unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079.yyy" + assert prop3.name == "yyy" + assert prop3.property_type == "string" + assert prop3.parent_unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079" + assert prop3.value == "lalala" + assert prop3.description is None + assert prop3.get_input_values is None + assert prop3.input is None + + assert prop4.sdc_resource == vf + assert prop4.unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079.test2" + assert prop4.name == "test2" + assert prop4.property_type == "boolean" + assert prop4.parent_unique_id == "4a84415b-4580-4a78-aa33-501f0cd3d079" + assert prop4.value == '{"get_input":"test2"}' + assert prop4.description == "test2" + assert prop4.get_input_values + with pytest.raises(ParameterError): + prop4.input + + +@mock.patch.object(Vl, "send_message_json") +@mock.patch.object(Vl, "exists") +def test_vl_properties(mock_exists, mock_send_json): + mock_exists.return_value = True + vl = Vl(name="test") + vl.unique_identifier = "toto" + + mock_send_json.return_value = {} + assert len(list(vl.properties)) == 0 + + mock_send_json.return_value = VL_PROPERTIES + properties_list = list(vl.properties) + assert len(properties_list) == 1 + prop = properties_list[0] + + assert prop.sdc_resource == vl + assert prop.unique_id == "d37cd65e-9842-4490-9343-a1a874e6b52a.network_role" + assert prop.name == "network_role" + assert prop.property_type == "string" + assert prop.parent_unique_id == "1af9771b-0f79-4e98-8747-30fd06da85cb" + assert prop.value is None + assert prop.description == "Unique label that defines the role that this network performs. example: vce oam network, vnat sr-iov1 network\n" + assert prop.get_input_values is None + assert prop.input is None + + +@mock.patch.object(SdcResource, "send_message_json") +def test_sdc_resource_is_own_property(mock_send_json): + sdc_resource = SdcResource(name="test") + sdc_resource.unique_identifier = "toto" + mock_send_json.return_value = PROPERTIES + prop1 = Property( + name="llllll", + property_type="integer" + ) + prop2 = Property( + name="test2", + property_type="string" + ) + assert sdc_resource.is_own_property(prop1) + assert not sdc_resource.is_own_property(prop2) + +@mock.patch.object(SdcResource, "properties", new_callable=mock.PropertyMock) +@mock.patch.object(SdcResource, "send_message_json") +def test_sdc_resource_set_property_value(mock_send_message_json, mock_sdc_resource_properties): + sdc_resource = SdcResource(name="test") + sdc_resource.unique_identifier = "toto" + + mock_sdc_resource_properties.return_value = [ + Property(name="test", + property_type="string", + sdc_resource=sdc_resource) + ] + with pytest.raises(ParameterError): + sdc_resource.set_property_value(Property(name="test2", + property_type="integer", + sdc_resource=sdc_resource), + value="lalala") + prop = sdc_resource.get_property(property_name="test") + assert prop.name == "test" + assert prop.property_type == "string" + assert not prop.value + + prop.value = "test" + mock_send_message_json.assert_called_once() + assert prop.value == "test" + +@mock.patch.object(SdcResource, "inputs", new_callable=mock.PropertyMock) +@mock.patch.object(SdcResource, "send_message_json") +def test_sdc_resource_input_default_value(mock_send_message_json, mock_inputs): + sdc_resource = SdcResource(name="test") + sdc_resource.unique_identifier = "toto" + + mock_inputs.return_value = [ + Input(unique_id="123", + input_type="integer", + name="test", + sdc_resource=sdc_resource) + ] + assert sdc_resource.get_input("test") + input_obj = sdc_resource.get_input("test") + assert not input_obj.default_value + input_obj.default_value = "123" + mock_send_message_json.assert_called_once() + assert input_obj.default_value == "123" diff --git a/tests/test_sdc_vfc.py b/tests/test_sdc_vfc.py new file mode 100644 index 0000000..7322e0a --- /dev/null +++ b/tests/test_sdc_vfc.py @@ -0,0 +1,82 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +import pytest + +import onapsdk.constants as const +from onapsdk.exceptions import ResourceNotFound +from onapsdk.sdc.vfc import Vfc + + +VFCS = [ + { + "uuid":"18167a36-5f7d-4e10-809f-b73ce7268b00", + "invariantUUID":"a5852d77-c364-4eec-97c6-8630b8f138ac", + "name":"Allotted Resource", + "version":"1.0", + "toscaModelURL":"/sdc/v1/catalog/resources/18167a36-5f7d-4e10-809f-b73ce7268b00/toscaModel", + "category":"Allotted Resource", + "subCategory":"Allotted Resource", + "resourceType":"VFC", + "lifecycleState":"CERTIFIED", + "lastUpdaterUserId":"jh0003" + }, + { + "uuid":"3b9f3a0d-f9d1-4d95-80ce-7f7812a2b7b5", + "invariantUUID":"c4aa9ad7-1c68-4fde-884e-b9d693b5f725", + "name":"Controller", + "version":"1.0", + "toscaModelURL":"/sdc/v1/catalog/resources/3b9f3a0d-f9d1-4d95-80ce-7f7812a2b7b5/toscaModel", + "category":"Generic", + "subCategory":"Infrastructure", + "resourceType":"VFC", + "lifecycleState":"CERTIFIED", + "lastUpdaterUserId":"jh0003" + } +] + + +@mock.patch.object(Vfc, 'send_message_json') +def test_get_all_no_vfc(mock_send): + """Returns empty array if no vfcs.""" + mock_send.return_value = {} + assert Vfc.get_all() == [] + mock_send.assert_called_once_with("GET", 'get Vfcs', 'https://sdc.api.be.simpledemo.onap.org:30204/sdc/v1/catalog/resources?resourceType=VFC') + + +@mock.patch.object(Vfc, 'send_message_json') +def test_get_all_vfc(mock_send): + mock_send.return_value = VFCS + vfcs = Vfc.get_all() + assert len(vfcs) == 2 + vfc = vfcs[0] + assert vfc.name == "Allotted Resource" + assert vfc.identifier == "18167a36-5f7d-4e10-809f-b73ce7268b00" + assert vfc.unique_uuid == "a5852d77-c364-4eec-97c6-8630b8f138ac" + assert vfc.version == "1.0" + assert vfc.status == const.CERTIFIED + vfc = vfcs[1] + assert vfc.name == "Controller" + assert vfc.identifier == "3b9f3a0d-f9d1-4d95-80ce-7f7812a2b7b5" + assert vfc.unique_uuid == "c4aa9ad7-1c68-4fde-884e-b9d693b5f725" + assert vfc.version == "1.0" + assert vfc.status == const.CERTIFIED + + +@mock.patch.object(Vfc, 'send_message_json') +def test_create_vfc_not_exists(mock_send): + mock_send.return_value = VFCS + with pytest.raises(ResourceNotFound): + Vfc("not_exists") diff --git a/tests/test_sdc_vl.py b/tests/test_sdc_vl.py new file mode 100644 index 0000000..8eb2a20 --- /dev/null +++ b/tests/test_sdc_vl.py @@ -0,0 +1,82 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +import pytest + +import onapsdk.constants as const +from onapsdk.exceptions import ResourceNotFound +from onapsdk.sdc.vl import Vl + + +VLS = [ + { + "uuid":"e12cedf4-fd3f-4d76-ae2a-0368eaee40dc", + "invariantUUID":"4084c513-5149-456d-9be0-efc503058799", + "name":"NeutronNet", + "version":"1.0", + "toscaModelURL":"/sdc/v1/catalog/resources/e12cedf4-fd3f-4d76-ae2a-0368eaee40dc/toscaModel", + "category":"Generic", + "subCategory":"Network Elements", + "resourceType":"VL", + "lifecycleState":"CERTIFIED", + "lastUpdaterUserId":"jh0003" + }, + { + "uuid":"3b9f3a0d-f9d1-4d95-80ce-7f7812a2b7b5", + "invariantUUID":"c4aa9ad7-1c68-4fde-884e-b9d693b5f725", + "name":"Network", + "version":"1.0", + "toscaModelURL":"/sdc/v1/catalog/resources/3b9f3a0d-f9d1-4d95-80ce-7f7812a2b7b5/toscaModel", + "category":"Generic", + "subCategory":"Infrastructure", + "resourceType":"VL", + "lifecycleState":"CERTIFIED", + "lastUpdaterUserId":"jh0003" + } +] + + +@mock.patch.object(Vl, 'send_message_json') +def test_get_all_no_vl(mock_send): + """Returns empty array if no vls.""" + mock_send.return_value = {} + assert Vl.get_all() == [] + mock_send.assert_called_once_with("GET", 'get Vls', 'https://sdc.api.be.simpledemo.onap.org:30204/sdc/v1/catalog/resources?resourceType=VL') + + +@mock.patch.object(Vl, 'send_message_json') +def test_get_all_vl(mock_send): + mock_send.return_value = VLS + vls = Vl.get_all() + assert len(vls) == 2 + vl = vls[0] + assert vl.name == "NeutronNet" + assert vl.identifier == "e12cedf4-fd3f-4d76-ae2a-0368eaee40dc" + assert vl.unique_uuid == "4084c513-5149-456d-9be0-efc503058799" + assert vl.version == "1.0" + assert vl.status == const.CERTIFIED + vl = vls[1] + assert vl.name == "Network" + assert vl.identifier == "3b9f3a0d-f9d1-4d95-80ce-7f7812a2b7b5" + assert vl.unique_uuid == "c4aa9ad7-1c68-4fde-884e-b9d693b5f725" + assert vl.version == "1.0" + assert vl.status == const.CERTIFIED + + +@mock.patch.object(Vl, 'send_message_json') +def test_create_vl_not_exists(mock_send): + mock_send.return_value = VLS + with pytest.raises(ResourceNotFound): + Vl("not_exists") diff --git a/tests/test_sdnc_element.py b/tests/test_sdnc_element.py new file mode 100644 index 0000000..6c8fe2d --- /dev/null +++ b/tests/test_sdnc_element.py @@ -0,0 +1,26 @@ +"""Test A&AI Element.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.sdnc.sdnc_element import SdncElement +from onapsdk.utils.gui import GuiList + +@mock.patch.object(SdncElement, "send_message") +def test_get_guis(send_message_mock): + component = SdncElement() + gui_results = component.get_guis() + assert type(gui_results) == GuiList + assert len(gui_results.guilist) == 2 + # assert gui_results.guilist[0].status == send_message_mock.return_value.status_code diff --git a/tests/test_service.py b/tests/test_service.py new file mode 100755 index 0000000..e486833 --- /dev/null +++ b/tests/test_service.py @@ -0,0 +1,1498 @@ +"""Test Service module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 os import path +from pathlib import Path +from unittest import mock +from unittest.mock import MagicMock, PropertyMock +import shutil + +import oyaml as yaml +import pytest + +import onapsdk.constants as const +from onapsdk.exceptions import ParameterError, RequestError, ResourceNotFound, StatusError, ValidationError +from onapsdk.sdc.category_management import ServiceCategory +from onapsdk.sdc.component import Component +from onapsdk.sdc.properties import ComponentProperty, Property +from onapsdk.sdc.service import Service, ServiceInstantiationType +from onapsdk.sdc.sdc_resource import SdcResource +from onapsdk.utils.headers_creator import headers_sdc_operator +from onapsdk.utils.headers_creator import headers_sdc_creator + + +ARTIFACTS = { + "componentInstances" : [ + { + "uniqueId" : "test_unique_id", + "componentName" : "ubuntu16test_VF 0" + } + ] +} + + +COMPONENTS = { + "componentInstances":[ + { + "actualComponentUid":"374f0a98-a280-43f1-9e6c-00b436782ce7", + "createdFromCsar":True, + "uniqueId":"bcfa7544-6e3d-4666-93b1-c5973356d069.374f0a98-a280-43f1-9e6c-00b436782ce7.abstract_vsn", + "normalizedName":"abstract_vsn", + "name":"abstract_vsn", + "originType":"CVFC", + "customizationUUID":"971043e1-495b-4b75-901e-3d09baed7521", + "componentUid":"374f0a98-a280-43f1-9e6c-00b436782ce7", + "componentVersion":"1.0", + "toscaComponentName":"org.openecomp.resource.vfc.11111cvfc.abstract.abstract.nodes.vsn", + "componentName":"11111-nodes.vsnCvfc", + "groupInstances":None + } + ] +} + + +COMPONENT = { + "metadata":{ + "uniqueId":"374f0a98-a280-43f1-9e6c-00b436782ce7", + "name":"11111-nodes.vsnCvfc", + "version":"1.0", + "isHighestVersion":True, + "creationDate":1594898496259, + "lastUpdateDate":1594898496325, + "description":"Complex node type that is used as nested type in VF", + "lifecycleState":"CERTIFIED", + "tags":[ + "11111-nodes.vsnCvfc" + ], + "icon":"defaulticon", + "normalizedName":"11111nodesvsncvfc", + "systemName":"11111NodesVsncvfc", + "contactId":"cs0008", + "allVersions":{ + "1.0":"374f0a98-a280-43f1-9e6c-00b436782ce7" + }, + "isDeleted":None, + "projectCode":None, + "csarUUID":None, + "csarVersion":None, + "importedToscaChecksum":None, + "invariantUUID":"3c027ba1-8d3a-4b59-9394-d748fec5e42c", + "componentType":"RESOURCE", + "name":"Generic", + "normalizedName":"generic", + "uniqueId":"resourceNewCategory.generic", + "icons":None, + "creatorUserId":"cs0008", + "creatorFullName":"Carlos Santana", + "lastUpdaterUserId":"cs0008", + "lastUpdaterFullName":"Carlos Santana", + "archiveTime":0, + "vendorName":"mj", + "vendorRelease":"1.0", + "resourceVendorModelNumber":"", + "resourceType":"CVFC", + "isAbstract":None, + "cost":None, + "licenseType":None, + "toscaResourceName":"org.openecomp.resource.vfc.11111cvfc.abstract.abstract.nodes.vsn", + "derivedFrom":None, + "uuid":"59f05bfb-ccea-4857-8799-6acff59e6344", + "archived":False, + "vspArchived":False, + "groupInstances":None + } +} + + +COMPONENT_PROPERTIES = [ + { + "uniqueId":"3d9a184f-4268-4a0e-9ddd-252e49670013.vf_module_id", + "type":"string", + "required":False, + "definition":False, + "description":"The vFirewall Module ID is provided by ECOMP", + "password":False, + "name":"vf_module_id", + "label":"vFirewall module ID", + "hidden":False, + "immutable":False, + "isDeclaredListInput":False, + "getInputProperty":False, + "empty":False + },{ + "uniqueId":"74f79006-ae56-4d58-947e-6a5089000774.skip_post_instantiation_configuration", + "type":"boolean", + "required":False, + "definition":False, + "password":False, + "name":"skip_post_instantiation_configuration", + "value":"true", + "hidden":False, + "immutable":False, + "parentUniqueId":"74f79006-ae56-4d58-947e-6a5089000774", + "isDeclaredListInput":False, + "getInputProperty":False, + "ownerId":"74f79006-ae56-4d58-947e-6a5089000774", + "empty":False + } +] + + +COMPONENTS_WITH_ALL_ORIGIN_TYPES = { + "componentInstances":[ + { + "actualComponentUid":"374f0a98-a280-43f1-9e6c-00b436782ce7", + "createdFromCsar":True, + "uniqueId":"bcfa7544-6e3d-4666-93b1-c5973356d069.374f0a98-a280-43f1-9e6c-00b436782ce7.abstract_vsn", + "normalizedName":"abstract_vsn", + "name":"abstract_vsn", + "originType":"VF", + "customizationUUID":"971043e1-495b-4b75-901e-3d09baed7521", + "componentUid":"374f0a98-a280-43f1-9e6c-00b436782ce7", + "componentVersion":"1.0", + "toscaComponentName":"org.openecomp.resource.vfc.11111cvfc.abstract.abstract.nodes.vsn", + "componentName":"11111-nodes.vsnCvfc", + "groupInstances":None + }, + { + "actualComponentUid":"374f0a98-a280-43f1-9e6c-00b436782ce7", + "createdFromCsar":True, + "uniqueId":"bcfa7544-6e3d-4666-93b1-c5973356d069.374f0a98-a280-43f1-9e6c-00b436782ce7.abstract_vsn", + "normalizedName":"abstract_vsn", + "name":"abstract_vsn", + "originType":"PNF", + "customizationUUID":"971043e1-495b-4b75-901e-3d09baed7521", + "componentUid":"374f0a98-a280-43f1-9e6c-00b436782ce7", + "componentVersion":"1.0", + "toscaComponentName":"org.openecomp.resource.vfc.11111cvfc.abstract.abstract.nodes.vsn", + "componentName":"11111-nodes.vsnCvfc", + "groupInstances":None + }, + { + "actualComponentUid":"374f0a98-a280-43f1-9e6c-00b436782ce7", + "createdFromCsar":True, + "uniqueId":"bcfa7544-6e3d-4666-93b1-c5973356d069.374f0a98-a280-43f1-9e6c-00b436782ce7.abstract_vsn", + "normalizedName":"abstract_vsn", + "name":"abstract_vsn", + "originType":"VL", + "customizationUUID":"971043e1-495b-4b75-901e-3d09baed7521", + "componentUid":"374f0a98-a280-43f1-9e6c-00b436782ce7", + "componentVersion":"1.0", + "toscaComponentName":"org.openecomp.resource.vfc.11111cvfc.abstract.abstract.nodes.vsn", + "componentName":"11111-nodes.vsnCvfc", + "groupInstances":None + } + ] +} + + +def test_init_no_name(): + """Check init with no names.""" + svc = Service() + assert isinstance(svc, SdcResource) + assert svc._identifier is None + assert svc._version is None + assert svc.name == "ONAP-test-Service" + assert svc.headers["USER_ID"] == "cs0008" + assert svc.distribution_status is None + assert svc._distribution_id is None + assert isinstance(svc._base_url(), str) + +@mock.patch.object(Service, 'exists') +def test_init_with_name(mock_exists): + """Check init with no names.""" + mock_exists.return_value = False + svc = Service(name="YOLO") + assert svc._identifier == None + assert svc._version == None + assert svc.name == "YOLO" + assert svc.created() == False + assert svc.headers["USER_ID"] == "cs0008" + assert svc.distribution_status is None + assert svc._distribution_id is None + assert isinstance(svc._base_url(), str) + +@mock.patch.object(Service, 'exists') +def test_init_with_sdc_values(mock_exists): + """Check init with no names.""" + sdc_values = {'uuid': '12', 'version': '14', 'invariantUUID': '56', + 'distributionStatus': 'yes', 'lifecycleState': 'state', + 'category': 'Network Service'} + svc = Service(sdc_values=sdc_values) + mock_exists.return_value = True + assert svc._identifier == "12" + assert svc._version == "14" + assert svc.name == "ONAP-test-Service" + assert svc.created() + assert svc.headers["USER_ID"] == "cs0008" + assert svc.distribution_status == "yes" + assert svc._distribution_id is None + assert svc.category_name == "Network Service" + assert isinstance(svc._base_url(), str) + +@mock.patch.object(Service, 'get_all') +def test_version_filter(mock_get_all): + """Check version filter""" + svc_1 = Service(name="test_version_filter") + svc_1.identifier = "1111" + svc_1.unique_uuid = "2222" + svc_1.unique_identifier = "3333" + svc_1.status = const.CERTIFIED + svc_1.version = "1.0" + + svc_2 = Service(name="test_version_filter") + svc_2.identifier = "1111" + svc_2.unique_uuid = "2222" + svc_2.unique_identifier = "3333" + svc_2.status = const.DRAFT + svc_2.version = "1.1" + + mock_get_all.return_value = [svc_1, svc_2] + + svc = Service(name='test_version_filter') + assert svc.exists() + assert svc.version == "1.1" + + svc = Service(name='test_version_filter', version='1.0') + assert svc.exists() + assert svc.version == "1.0" + + svc = Service(name='test_version_filter', version='-111') + assert not svc.exists() + assert not svc.version + +@mock.patch.object(Service, 'get_all') +def test_get_the_latest_version(mock_get_all): + svc_1 = Service(name="test_get_max_version") + svc_1.identifier = "1111" + svc_1.unique_uuid = "2222" + svc_1.unique_identifier = "3333" + svc_1.status = const.CERTIFIED + svc_1.version = "9.0" + + svc_2 = Service(name="test_get_max_version") + svc_2.identifier = "1111" + svc_2.unique_uuid = "2222" + svc_2.unique_identifier = "3333" + svc_2.status = const.DRAFT + svc_2.version = "10.0" + + mock_get_all.return_value = [svc_1, svc_2] + svc = Service(name='test_get_max_version') + assert svc.version == "10.0" + + svc_3 = Service(name="test_get_max_version") + svc_3.identifier = "1111" + svc_3.unique_uuid = "2222" + svc_3.unique_identifier = "3333" + svc_3.status = const.DRAFT + svc_3.version = "10.1" + mock_get_all.return_value = [svc_1, svc_2, svc_3] + svc = Service(name='test_get_max_version') + assert svc.version == "10.1" + + svc_4 = Service(name="test_get_max_version") + svc_4.identifier = "1111" + svc_4.unique_uuid = "2222" + svc_4.unique_identifier = "3333" + svc_4.status = const.DRAFT + svc_4.version = "20.0" + mock_get_all.return_value = [svc_1, svc_2, svc_3, svc_4] + svc = Service(name='test_get_max_version') + assert svc.version == "20.0" + + svc_5 = Service(name="test_get_max_version") + svc_5.identifier = "1111" + svc_5.unique_uuid = "2222" + svc_5.unique_identifier = "3333" + svc_5.status = const.DRAFT + svc_5.version = "99.0" + + svc_6 = Service(name="test_get_max_version") + svc_6.identifier = "1111" + svc_6.unique_uuid = "2222" + svc_6.unique_identifier = "3333" + svc_6.status = const.DRAFT + svc_6.version = "100.0" + mock_get_all.return_value = [svc_1, svc_2, svc_3, svc_4, svc_5, svc_6] + svc = Service(name='test_get_max_version') + assert svc.version == "100.0" + + +def test_equality_really_equals(): + """Check two vfs are equals if name is the same.""" + svc_1 = Service(name="equal") + svc_1.identifier = "1234" + svc_2 = Service(name="equal") + svc_2.identifier = "1235" + assert svc_1 == svc_2 + + +def test_equality_not_equals(): + """Check two vfs are not equals if name is not the same.""" + svc_1 = Service(name="equal") + svc_1.identifier = "1234" + svc_2 = Service(name="not_equal") + svc_2.identifier = "1234" + assert svc_1 != svc_2 + + +def test_equality_not_equals_not_same_object(): + """Check a vf and something different are not equals.""" + svc_1 = Service(name="equal") + svc_1.identifier = "1234" + svc_2 = SdcResource() + svc_2.name = "equal" + assert svc_1 != svc_2 + +@mock.patch.object(Service, 'load_metadata') +def test_distribution_id_no_load(mock_load): + svc = Service() + svc.identifier = "1234" + svc._distribution_id = "4567" + assert svc.distribution_id == "4567" + mock_load.assert_not_called() + +@mock.patch.object(Service, 'load_metadata') +def test_distribution_id_load(mock_load): + svc = Service() + svc.identifier = "1234" + assert svc.distribution_id is None + mock_load.assert_called_once() + +@mock.patch.object(Service, '_check_distributed') +def test_distributed_no_load(mock_check_distributed): + svc = Service() + svc.identifier = "1234" + svc._distributed = True + assert svc.distributed + mock_check_distributed.assert_not_called() + +@mock.patch.object(Service, '_check_distributed') +def test_distributed_load(mock_check_distributed): + svc = Service() + svc.identifier = "1234" + assert not svc.distributed + mock_check_distributed.assert_called_once() + +def test_distribution_id_setter(): + svc = Service() + svc.identifier = "1234" + svc.distribution_id = "4567" + assert svc._distribution_id == "4567" + +@mock.patch.object(Service, '_create') +@mock.patch.object(Service, "category", new_callable=mock.PropertyMock) +@mock.patch.object(Service, "exists") +def test_create(mock_exists, mock_category, mock_create): + mock_exists.return_value = False + + svc = Service() + svc.create() + mock_create.assert_called_once_with("service_create.json.j2", + name="ONAP-test-Service", + instantiation_type="A-la-carte", + category=svc.category, + role="", + function="", + service_type="" + ) + mock_create.reset_mock() + svc = Service(instantiation_type=ServiceInstantiationType.MACRO) + svc.create() + mock_create.assert_called_once_with("service_create.json.j2", + name="ONAP-test-Service", + instantiation_type="Macro", + category=svc.category, + role="", + function="", + service_type="" + ) + +@mock.patch.object(Service, 'exists') +@mock.patch.object(Service, 'send_message') +def test_add_resource_not_draft(mock_send, mock_exists): + mock_exists.return_value = False + svc = Service() + resource = SdcResource() + with pytest.raises(StatusError): + svc.add_resource(resource) + mock_send.assert_not_called() + +@mock.patch.object(Service, 'load') +@mock.patch.object(Service, 'send_message') +def test_add_resource_bad_result(mock_send, mock_load): + svc = Service() + svc.unique_identifier = "45" + svc.identifier = "93" + svc.status = const.DRAFT + mock_send.return_value = {} + resource = SdcResource() + resource.unique_identifier = "12" + resource.created = MagicMock(return_value=True) + resource.version = "40" + resource.name = "test" + assert svc.add_resource(resource) is None + mock_send.assert_called_once_with( + 'POST', 'Add SDCRESOURCE to ServiceProxy', + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/45/resourceInstance', + data='{\n "name": "test",\n "componentVersion": "40",\n "posY": 100,\n "posX": 200,\n "uniqueId": "12",\n "originType": "SDCRESOURCE",\n "componentUid": "12",\n "icon": "defaulticon"\n}') + +@mock.patch.object(Service, 'load') +@mock.patch.object(Service, 'send_message') +def test_add_resource_OK(mock_send, mock_load): + svc = Service() + svc.unique_identifier = "45" + svc.identifier = "93" + svc.status = const.DRAFT + mock_send.return_value = {'yes': 'indeed'} + resource = SdcResource() + resource.unique_identifier = "12" + resource.created = MagicMock(return_value=True) + resource.version = "40" + resource.name = "test" + result = svc.add_resource(resource) + assert result['yes'] == "indeed" + mock_send.assert_called_once_with( + 'POST', 'Add SDCRESOURCE to ServiceProxy', + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/45/resourceInstance', + data='{\n "name": "test",\n "componentVersion": "40",\n "posY": 100,\n "posX": 200,\n "uniqueId": "12",\n "originType": "SDCRESOURCE",\n "componentUid": "12",\n "icon": "defaulticon"\n}') + +@mock.patch.object(Service, '_verify_action_to_sdc') +def test_checkin(mock_verify): + svc = Service() + svc.checkin() + mock_verify.assert_called_once_with(const.DRAFT, const.CHECKIN, 'lifecycleState') + +@mock.patch.object(Service, '_verify_action_to_sdc') +def test_submit(mock_verify): + svc = Service() + svc.submit() + mock_verify.assert_called_once_with(const.CHECKED_IN, const.SUBMIT_FOR_TESTING, 'lifecycleState') + +@mock.patch.object(Service, '_verify_action_to_sdc') +def test_certify(mock_verify): + svc = Service() + svc.certify() + mock_verify.assert_called_once_with( + const.CHECKED_IN, const.CERTIFY, 'lifecycleState', + headers=headers_sdc_creator(svc.headers)) + +@mock.patch.object(Service, '_verify_action_to_sdc') +def test_distribute(mock_verify): + svc = Service() + svc.distribute() + mock_verify.assert_called_once_with( + const.CERTIFIED, const.DISTRIBUTE, 'distribution', + headers=headers_sdc_creator(svc.headers)) + +@mock.patch.object(Service, '_verify_action_to_sdc') +def test_redistribute(mock_verify): + svc = Service() + svc.redistribute() + mock_verify.assert_called_once_with( + const.DISTRIBUTED, const.DISTRIBUTE, 'distribution', + headers=headers_sdc_creator(svc.headers)) + +@mock.patch.object(Service, 'send_message') +def test_get_tosca_no_result(mock_send): + if path.exists('/tmp/tosca_files'): + shutil.rmtree('/tmp/tosca_files') + mock_send.return_value = {} + svc = Service() + svc.identifier = "12" + svc.get_tosca() + headers = headers_sdc_creator(svc.headers) + headers['Accept'] = 'application/octet-stream' + mock_send.assert_called_once_with( + 'GET', 'Download Tosca Model for ONAP-test-Service', + 'https://sdc.api.be.simpledemo.onap.org:30204/sdc/v1/catalog/services/12/toscaModel', + headers=headers) + assert not path.exists('/tmp/tosca_files') + + +def test_get_tosca_bad_csart(requests_mock): + if path.exists('/tmp/tosca_files'): + shutil.rmtree('/tmp/tosca_files') + svc = Service() + svc.identifier = "12" + with open('tests/data/bad.csar', mode='rb') as file: + file_content = file.read() + requests_mock.get( + 'https://sdc.api.be.simpledemo.onap.org:30204/sdc/v1/catalog/services/12/toscaModel', + content=file_content) + svc.get_tosca() + + +def test_get_tosca_result(requests_mock): + if path.exists('/tmp/tosca_files'): + shutil.rmtree('/tmp/tosca_files') + with open('tests/data/test.csar', mode='rb') as file: + file_content = file.read() + requests_mock.get( + 'https://sdc.api.be.simpledemo.onap.org:30204/sdc/v1/catalog/services/12/toscaModel', + content=file_content) + svc = Service() + svc.identifier = "12" + svc.get_tosca() + +def test_get_tosca_result_no_service_in_csar(requests_mock): + if path.exists('/tmp/tosca_files'): + shutil.rmtree('/tmp/tosca_files') + with open('tests/data/bad_no_service.csar', mode='rb') as file: + file_content = file.read() + requests_mock.get( + 'https://sdc.api.be.simpledemo.onap.org:30204/sdc/v1/catalog/services/12/toscaModel', + content=file_content) + svc = Service() + svc.identifier = "12" + with pytest.raises(ValidationError): + svc.get_tosca() + +@mock.patch.object(Service, 'send_message_json') +def test_distributed_api_error(mock_send): + mock_send.side_effect = ResourceNotFound + svc = Service() + svc.distribution_id = "12" + assert not svc.distributed + +@mock.patch.object(Service, 'send_message_json') +def test_distributed_not_distributed(mock_send): + mock_send.return_value = { + 'distributionStatusList':[ + {'omfComponentID': "SO", 'status': "DOWNLOAD_OK"}, + {'omfComponentID': "sdnc", 'status': "DOWNLOAD_NOK"}, + {'omfComponentID': "aai", 'status': "DOWNLOAD_OK"}]} + svc = Service() + svc.distribution_id = "12" + assert not svc.distributed + mock_send.assert_called_once_with( + 'GET', 'Check distribution for ONAP-test-Service', + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/distribution/12', + headers=headers_sdc_operator(svc.headers)) + +@mock.patch.object(Service, 'send_message_json') +def test_distributed_not_distributed(mock_send): + mock_send.return_value = { + 'distributionStatusList':[ + {'omfComponentID': "SO", 'status': "DOWNLOAD_OK"}, + {'omfComponentID': "aai", 'status': "DOWNLOAD_OK"}]} + svc = Service() + svc.distribution_id = "12" + assert not svc.distributed + mock_send.assert_called_once_with( + 'GET', 'Check distribution for ONAP-test-Service', + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/distribution/12', + headers=headers_sdc_creator(svc.headers)) + +@mock.patch.object(Service, 'send_message_json') +def test_distributed_distributed(mock_send): + mock_send.return_value = { + 'distributionStatusList':[ + {'omfComponentID': "SO", 'status': "DOWNLOAD_OK"}, + {'omfComponentID': "sdnc", 'status': "DOWNLOAD_OK"}, + {'omfComponentID': "aai", 'status': "DOWNLOAD_OK"}]} + svc = Service() + svc.distribution_id = "12" + assert svc.distributed + mock_send.assert_called_once_with( + 'GET', 'Check distribution for ONAP-test-Service', + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/distribution/12', + headers=headers_sdc_creator(svc.headers)) + +@mock.patch.object(Service, 'send_message_json') +def test_load_metadata_no_result(mock_send): + mock_send.return_value = {} + svc = Service() + svc.identifier = "1" + svc.load_metadata() + assert svc._distribution_id is None + mock_send.assert_called_once_with( + 'GET', 'Get Metadata for ONAP-test-Service', + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/1/distribution', + headers=headers_sdc_creator(svc.headers)) + +@mock.patch.object(Service, 'send_message_json') +def test_load_metadata_bad_json(mock_send): + mock_send.return_value = {'yolo': 'in the wood'} + svc = Service() + svc.identifier = "1" + svc.load_metadata() + assert svc._distribution_id is None + mock_send.assert_called_once_with( + 'GET', 'Get Metadata for ONAP-test-Service', + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/1/distribution', + headers=headers_sdc_creator(svc.headers)) + +@mock.patch.object(Service, 'send_message_json') +def test_load_metadata_OK(mock_send): + mock_send.return_value = {'distributionStatusOfServiceList': [ + {'distributionID': "11"}, {'distributionID': "12"}]} + svc = Service() + svc.identifier = "1" + svc.load_metadata() + assert svc._distribution_id == "11" + mock_send.assert_called_once_with( + 'GET', 'Get Metadata for ONAP-test-Service', + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/1/distribution', + headers=headers_sdc_creator(svc.headers)) + +def test_get_all_url(): + assert Service._get_all_url() == "https://sdc.api.be.simpledemo.onap.org:30204/sdc/v1/catalog/services" + +@mock.patch.object(Service, '_action_to_sdc') +@mock.patch.object(Service, 'load') +def test_really_submit_request_failed(mock_load, mock_action): + mock_action.side_effect = RequestError + svc = Service() + with pytest.raises(RequestError) as err: + svc._really_submit() + assert err.type == RequestError + mock_load.assert_not_called() + mock_action.assert_called_once_with('Certify', action_type='lifecycleState') + +@mock.patch.object(Service, '_action_to_sdc') +@mock.patch.object(Service, 'load') +def test_really_submit_OK(mock_load, mock_action): + mock_action.return_value = "yes" + svc = Service() + svc._really_submit() + mock_load.assert_called_once() + mock_action.assert_called_once_with('Certify', action_type='lifecycleState') + +@mock.patch.object(Service, 'load') +@mock.patch.object(Service, '_action_to_sdc') +@mock.patch.object(Service, 'created') +def test_verify_action_to_sdc_not_created(mock_created, mock_action, mock_load): + mock_created.return_value = False + svc = Service() + svc._status = "no_yes" + svc._verify_action_to_sdc("yes", "action", action_type='lifecycleState') + mock_created.assert_called() + mock_action.assert_not_called() + mock_load.assert_not_called() + +@mock.patch.object(Service, 'load') +@mock.patch.object(Service, '_action_to_sdc') +@mock.patch.object(Service, 'created') +def test_verify_action_to_sdc_bad_status(mock_created, mock_action, mock_load): + mock_created.return_value = True + svc = Service() + svc._status = "no_yes" + with pytest.raises(StatusError) as err: + svc._verify_action_to_sdc("yes", "action", action_type='lifecycleState') + assert err.type == StatusError + mock_created.assert_called() + mock_action.assert_not_called() + mock_load.assert_not_called() + +@mock.patch.object(Service, 'load') +@mock.patch.object(Service, '_action_to_sdc') +@mock.patch.object(Service, 'created') +def test_verify_action_to_sdc_OK(mock_created, mock_action, mock_load): + mock_created.return_value = True + mock_action.return_value = "good" + svc = Service() + svc._status = "yes" + svc._verify_action_to_sdc("yes", "action", action_type='lifecycleState') + mock_created.assert_called() + mock_action.assert_called_once() + mock_load.assert_called_once() + +@mock.patch.object(Service, 'distribute') +@mock.patch.object(Service, 'approve') +@mock.patch.object(Service, 'certify') +@mock.patch.object(Service, 'start_certification') +@mock.patch.object(Service, 'submit') +@mock.patch.object(Service, 'checkin') +@mock.patch.object(Service, 'add_resource') +@mock.patch.object(Service, 'create') +def test_onboard_new_service(mock_create, mock_add_resource, + mock_checkin, mock_submit, + mock_start_certification, mock_certify, + mock_approve, mock_distribute): + getter_mock = mock.Mock(wraps=Service.status.fget) + mock_status = Service.status.getter(getter_mock) + with mock.patch.object(Service, 'status', mock_status): + getter_mock.side_effect = [None, const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, None] + service = Service() + service._time_wait = 0 + service.onboard() + mock_create.assert_called_once() + mock_add_resource.assert_not_called() + mock_checkin.assert_not_called() + mock_submit.assert_not_called() + mock_start_certification.assert_not_called() + mock_certify.assert_not_called() + mock_approve.assert_not_called() + mock_distribute.assert_not_called() + +@mock.patch.object(Service, 'status') +def test_onboard_invalid_status(mock_status): + mock_status.return_value = False + service = Service() + service._time_wait = 0 + with pytest.raises(StatusError) as err: + service.onboard() + assert err.type == StatusError + +@mock.patch.object(Service, 'distribute') +@mock.patch.object(Service, 'approve') +@mock.patch.object(Service, 'certify') +@mock.patch.object(Service, 'start_certification') +@mock.patch.object(Service, 'submit') +@mock.patch.object(Service, 'checkin') +@mock.patch.object(Service, 'add_resource') +@mock.patch.object(Service, 'create') +def test_onboard_service_no_resources(mock_create, + mock_add_resource, mock_checkin, + mock_submit, mock_start_certification, + mock_certify, mock_approve, + mock_distribute): + getter_mock = mock.Mock(wraps=Service.status.fget) + mock_status = Service.status.getter(getter_mock) + with mock.patch.object(Service, 'status', mock_status): + getter_mock.side_effect = [const.DRAFT, const.DRAFT, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, None] + service = Service() + service._time_wait = 0 + with pytest.raises(ParameterError): + service.onboard() + mock_create.assert_not_called() + mock_add_resource.assert_not_called() + mock_checkin.assert_not_called() + mock_submit.assert_not_called() + mock_start_certification.assert_not_called() + mock_certify.assert_not_called() + mock_approve.assert_not_called() + mock_distribute.assert_not_called() + +@mock.patch.object(Service, 'distribute') +@mock.patch.object(Service, 'approve') +@mock.patch.object(Service, 'certify') +@mock.patch.object(Service, 'start_certification') +@mock.patch.object(Service, 'submit') +@mock.patch.object(Service, 'checkin') +@mock.patch.object(Service, 'add_resource') +@mock.patch.object(Service, 'create') +def test_onboard_service_resources(mock_create, mock_add_resource, + mock_checkin, mock_submit, + mock_start_certification, mock_certify, + mock_approve, mock_distribute): + getter_mock = mock.Mock(wraps=Service.status.fget) + mock_status = Service.status.getter(getter_mock) + with mock.patch.object(Service, 'status', mock_status): + getter_mock.side_effect = [const.DRAFT, const.DRAFT, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, None] + resource = SdcResource() + service = Service(resources=[resource]) + service._time_wait = 0 + service.onboard() + mock_create.assert_not_called() + mock_add_resource.assert_called_once_with(resource) + mock_checkin.assert_called_once() + mock_submit.assert_not_called() + mock_start_certification.assert_not_called() + mock_certify.assert_not_called() + mock_approve.assert_not_called() + mock_distribute.assert_not_called() + +@mock.patch.object(Service, 'distribute') +@mock.patch.object(Service, 'approve') +@mock.patch.object(Service, 'certify') +@mock.patch.object(Service, 'start_certification') +@mock.patch.object(Service, 'submit') +@mock.patch.object(Service, 'checkin') +@mock.patch.object(Service, 'add_resource') +@mock.patch.object(Service, 'create') +def test_onboard_service_several_resources(mock_create, + mock_add_resource, mock_checkin, + mock_submit, + mock_start_certification, + mock_certify, mock_approve, + mock_distribute): + getter_mock = mock.Mock(wraps=Service.status.fget) + mock_status = Service.status.getter(getter_mock) + with mock.patch.object(Service, 'status', mock_status): + getter_mock.side_effect = [const.DRAFT, const.DRAFT, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, None] + resource1 = SdcResource() + resource2 = SdcResource() + service = Service(resources=[resource1, resource2]) + service._time_wait = 0 + service.onboard() + mock_create.assert_not_called() + calls = [mock.call(resource1), mock.call(resource2)] + mock_add_resource.assert_has_calls(calls, any_order=True) + assert mock_add_resource.call_count == 2 + mock_checkin.assert_called_once() + mock_submit.assert_not_called() + mock_start_certification.assert_not_called() + mock_certify.assert_not_called() + mock_approve.assert_not_called() + mock_distribute.assert_not_called() + +@mock.patch.object(Service, 'distribute') +@mock.patch.object(Service, 'approve') +@mock.patch.object(Service, 'certify') +@mock.patch.object(Service, 'start_certification') +@mock.patch.object(Service, 'submit') +@mock.patch.object(Service, 'checkin') +@mock.patch.object(Service, 'add_resource') +@mock.patch.object(Service, 'create') +def test_onboard_service_certifi(mock_create, + mock_add_resource, mock_checkin, + mock_submit, mock_start_certification, + mock_certify, mock_approve, + mock_distribute): + getter_mock = mock.Mock(wraps=Service.status.fget) + mock_status = Service.status.getter(getter_mock) + with mock.patch.object(Service, 'status', mock_status): + getter_mock.side_effect = [const.CHECKED_IN, + const.CHECKED_IN, + const.CHECKED_IN, + const.CHECKED_IN, + const.CHECKED_IN, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, None] + service = Service() + service._time_wait = 0 + service.onboard() + mock_create.assert_not_called() + mock_add_resource.assert_not_called() + mock_checkin.assert_not_called() + mock_submit.assert_not_called() + mock_start_certification.assert_not_called() + mock_certify.assert_called_once() + mock_approve.assert_not_called() + mock_distribute.assert_not_called() + +@mock.patch.object(Service, 'distribute') +@mock.patch.object(Service, 'certify') +@mock.patch.object(Service, 'checkin') +@mock.patch.object(Service, 'add_resource') +@mock.patch.object(Service, 'create') +def test_onboard_service_distribute(mock_create, + mock_add_resource, + mock_checkin, + mock_certify, + mock_distribute): + getter_mock = mock.Mock(wraps=Service.status.fget) + mock_status = Service.status.getter(getter_mock) + with mock.patch.object(Service, 'status', mock_status): + getter_mock.side_effect = [const.CERTIFIED, const.CERTIFIED, const.CERTIFIED, + const.CERTIFIED, const.CERTIFIED, const.CERTIFIED, + const.CERTIFIED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, None] + service = Service() + service._time_wait = 0 + service.onboard() + mock_create.assert_not_called() + mock_add_resource.assert_not_called() + mock_checkin.assert_not_called() + mock_certify.assert_not_called() + mock_distribute.assert_called_once() + +@mock.patch.object(Service, 'distribute') +@mock.patch.object(Service, 'certify') +@mock.patch.object(Service, 'checkin') +@mock.patch.object(Service, 'add_resource') +@mock.patch.object(Service, 'create') +def test_onboard_whole_service(mock_create, + mock_add_resource, + mock_checkin, + mock_certify, + mock_distribute): + getter_mock = mock.Mock(wraps=Service.status.fget) + mock_status = Service.status.getter(getter_mock) + with mock.patch.object(Service, 'status', mock_status): + getter_mock.side_effect = [None, const.DRAFT, const.DRAFT,const.CHECKED_IN, + const.CHECKED_IN, const.CHECKED_IN, + const.CERTIFIED, const.CERTIFIED, + const.CERTIFIED, const.CERTIFIED, + const.CERTIFIED, const.CERTIFIED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, None] + resource = SdcResource() + service = Service(resources=[resource]) + service._time_wait = 0 + service.onboard() + mock_create.assert_called_once() + mock_add_resource.assert_called_once_with(resource) + mock_checkin.assert_called_once() + mock_certify.assert_called_once() + mock_distribute.assert_called_once() + +@mock.patch("onapsdk.sdc.service.Service.send_message_json") +@mock.patch("onapsdk.sdc.service.SdcResource.import_from_sdc") +@mock.patch("onapsdk.sdc.service.Service.resource_inputs_url", new_callable=mock.PropertyMock) +def test_vnf_vf_modules_one(mock_service_resource_inputs_url, mock_import_from_sdc, mock_send_message_json): + """Test parsing TOSCA file with one VNF which has associated one VFmodule""" + service = Service(name="test") + mock_send_message_json.side_effect = [{ + "componentInstances": [{ + "actualComponentUid": "123", + "originType": "VF", + "name": "ubuntu16_VF 0", + "toscaComponentName": "org.openecomp.resource.vf.Ubuntu16Vf", + "createdFromCsar": False, + "uniqueId": "123", + "normalizedName": "123", + "customizationUUID": "123", + "componentUid": "123", + "componentVersion": "123", + "componentName": "123", + "groupInstances": [ + { + "name": "ubuntu16_vf0..Ubuntu16Vf..base_ubuntu16..module-0", + "type": "org.openecomp.groups.VfModule", + "groupName": "Ubuntu16Vf..base_ubuntu16..module-0", + "groupUUID": "ed041b38-63fc-486d-9d4d-4e2531bc7e54", + "invariantUUID": "f47c3a9b-6a5f-4d1a-8a0b-b7f56ebb9a90", + "version": "1", + "customizationUUID": "d946ea06-ec4b-4ed2-921a-117e1379b913", + "properties": [ + { + "name": "123", + "type": "test type", + "value": "val", + "description": "12234", + }, + { + "name": "123", + "type": "test type", + "value": None, + "description": "12234", + } + ] + } + ] + }] + }, MagicMock() + ] + vnfs = list(service.vnfs) + assert len(vnfs) == 1 + vnf = vnfs[0] + assert vnf.name == "ubuntu16_VF 0" + assert vnf.node_template_type == "org.openecomp.resource.vf.Ubuntu16Vf" + assert vnf.vf_modules + assert vnf.vf_modules[0].name == "ubuntu16_vf0..Ubuntu16Vf..base_ubuntu16..module-0" + assert len(list(vnf.vf_modules[0].properties)) == 1 + +@mock.patch("onapsdk.sdc.service.Service.send_message_json") +@mock.patch("onapsdk.sdc.service.SdcResource.import_from_sdc") +@mock.patch("onapsdk.sdc.service.Service.resource_inputs_url", new_callable=mock.PropertyMock) +def test_pnf_modules_one(mock_service_resource_inputs_url, mock_import_from_sdc, mock_send_message_json): + """Test parsing TOSCA file with one PNF which has associated one PNFmodule""" + service = Service(name="test") + mock_send_message_json.side_effect = [{ + "componentInstances": [{ + "actualComponentUid": "123", + "originType": "PNF", + "name": "test_pnf_vsp 0", + "toscaComponentName": "org.openecomp.resource.pnf.TestPnfVsp", + "createdFromCsar": False, + "uniqueId": "123", + "normalizedName": "123", + "customizationUUID": "123", + "componentUid": "123", + "componentVersion": "123", + "componentName": "123", + "groupInstances": None + }] + }, MagicMock() + ] + pnfs = list(service.pnfs) + assert len(pnfs) == 1 + pnf = pnfs[0] + assert pnf.name == "test_pnf_vsp 0" + assert pnf.node_template_type == "org.openecomp.resource.pnf.TestPnfVsp" + +@mock.patch("onapsdk.sdc.service.Service.send_message_json") +@mock.patch("onapsdk.sdc.service.SdcResource.import_from_sdc") +@mock.patch("onapsdk.sdc.service.Service.resource_inputs_url", new_callable=mock.PropertyMock) +def test_vnf_vf_modules_two(mock_service_resource_inputs_url, mock_import_from_sdc, mock_send_message_json): + """Test parsing TOSCA file with two VNF which has associated one VFmodule""" + service = Service(name="test") + mock_send_message_json.side_effect = [{ + "componentInstances": [{ + "actualComponentUid": "123", + "originType": "VF", + "name": "vFWCL_vPKG-vf 0", + "toscaComponentName": "org.openecomp.resource.vf.VfwclVpkgVf", + "createdFromCsar": False, + "uniqueId": "123", + "normalizedName": "123", + "customizationUUID": "123", + "componentUid": "123", + "componentVersion": "123", + "componentName": "123", + "groupInstances": [ + { + "name": "vfwcl_vpkgvf0..VfwclVpkgVf..base_vpkg..module-0", + "type": "org.openecomp.groups.VfModule", + "groupName": "Ubuntu16Vf..base_ubuntu16..module-0", + "groupUUID": "ed041b38-63fc-486d-9d4d-4e2531bc7e54", + "invariantUUID": "f47c3a9b-6a5f-4d1a-8a0b-b7f56ebb9a90", + "version": "1", + "customizationUUID": "d946ea06-ec4b-4ed2-921a-117e1379b913", + "properties": [ + { + "name": "123", + "type": "test type", + "value": "val", + "description": "12234", + }, + { + "name": "333", + "type": "test type", + "value": "val", + "description": "12234", + }, + { + "name": "123", + "type": "test type", + "value": None, + "description": "12234", + } + ] + }, + { + "name": "vfwcl_vpkgvf0..base_template_dummy_ignore..base_vpkg..module-0", + "type": "org.openecomp.groups.VfModule", + "groupName": "Ubuntu16Vf..base_ubuntu16..module-0", + "groupUUID": "ed041b38-63fc-486d-9d4d-4e2531bc7e54", + "invariantUUID": "f47c3a9b-6a5f-4d1a-8a0b-b7f56ebb9a90", + "version": "1", + "customizationUUID": "d946ea06-ec4b-4ed2-921a-117e1379b913", + "properties": [ + { + "name": "123", + "type": "test type", + "value": "val", + "description": "12234", + }, + { + "name": "333", + "type": "test type", + "value": "val", + "description": "12234", + }, + { + "name": "vf_module_label", + "type": "test type", + "value": "base_template_dummy_ignore", + "description": "12234", + } + ] + } + ] + }, + { + "actualComponentUid": "123", + "originType": "VF", + "name": "vFWCL_vFWSNK-vf 0", + "toscaComponentName": "org.openecomp.resource.vf.VfwclVfwsnkVf", + "createdFromCsar": False, + "uniqueId": "123", + "normalizedName": "123", + "customizationUUID": "123", + "componentUid": "123", + "componentVersion": "123", + "componentName": "123", + "groupInstances": [ + { + "name": "vfwcl_vfwsnkvf0..VfwclVfwsnkVf..base_vfw..module-0", + "type": "org.openecomp.groups.VfModule", + "groupName": "Ubuntu16Vf..base_ubuntu16..module-0", + "groupUUID": "ed041b38-63fc-486d-9d4d-4e2531bc7e54", + "invariantUUID": "f47c3a9b-6a5f-4d1a-8a0b-b7f56ebb9a90", + "version": "1", + "customizationUUID": "d946ea06-ec4b-4ed2-921a-117e1379b913", + "properties": [ + { + "name": "123", + "type": "test type", + "value": "val", + "description": "12234", + }, + { + "name": "123", + "type": "test type", + "value": None, + "description": "12234", + } + ] + } + ] + }] + }, MagicMock(), MagicMock() + ] + vnfs = list(service.vnfs) + assert len(vnfs) == 2 + vnf = vnfs[0] + assert vnf.name == "vFWCL_vPKG-vf 0" + assert vnf.node_template_type == "org.openecomp.resource.vf.VfwclVpkgVf" + assert vnf.vf_modules + assert len(vnf.vf_modules) == 1 + assert vnf.vf_modules[0].name == "vfwcl_vpkgvf0..VfwclVpkgVf..base_vpkg..module-0" + assert len(list(vnf.vf_modules[0].properties)) == 2 + + vnf = vnfs[1] + assert vnf.name == "vFWCL_vFWSNK-vf 0" + assert vnf.node_template_type == "org.openecomp.resource.vf.VfwclVfwsnkVf" + assert vnf.vf_modules + assert vnf.vf_modules[0].name == "vfwcl_vfwsnkvf0..VfwclVfwsnkVf..base_vfw..module-0" + assert len(list(vnf.vf_modules[0].properties)) == 1 + + +@mock.patch.object(Service, 'send_message_json') +def test_get_vnf_unique_id(mock_send): + """Test Service get nf uid with One Vf""" + svc = Service() + svc.unique_identifier = "service_unique_identifier" + mock_send.return_value = ARTIFACTS + unique_id = svc.get_nf_unique_id(nf_name="ubuntu16test_VF 0") + mock_send.assert_called_once_with( + 'GET', 'Get nf unique ID', + f"https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/{svc.unique_identifier}") + assert unique_id == 'test_unique_id' + +@mock.patch.object(Service, 'send_message_json') +def test_get_vnf_unique_id_not_found(mock_send): + """Test Service get nf uid with One Vf""" + svc = Service() + svc.unique_identifier = "service_unique_identifier" + artifacts = {"componentInstances": []} + mock_send.return_value = artifacts + with pytest.raises(ResourceNotFound) as err: + svc.get_nf_unique_id(nf_name="ubuntu16test_VF 0") + assert err.type == ResourceNotFound + mock_send.assert_called_once_with( + 'GET', 'Get nf unique ID', + f"https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/{svc.unique_identifier}") + +@mock.patch.object(Service, 'get_nf_unique_id') +@mock.patch.object(Service, 'load') +@mock.patch.object(Service, 'send_message') +def test_add_artifact_to_vf(mock_send_message, mock_load, mock_add): + """Test Service add artifact""" + svc = Service() + mock_add.return_value = "54321" + result = svc.add_artifact_to_vf(vnf_name="ubuntu16test_VF 0", + artifact_type="DCAE_INVENTORY_BLUEPRINT", + artifact_name="clampnode.yaml", + artifact="data".encode('utf-8')) + mock_send_message.assert_called() + method, description, url = mock_send_message.call_args[0] + assert method == "POST" + assert description == "Add artifact to vf" + assert url == ("https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/" + f"{svc.unique_identifier}/resourceInstance/54321/artifacts") + +@mock.patch.object(Service, 'load') +@mock.patch.object(Service, 'send_message') +def test_add_artifact_to_service(mock_send_message, mock_load): + """Test Service add artifact""" + svc = Service() + svc.status = const.DRAFT + mycbapath = Path(Path(__file__).resolve().parent, "data/vLB_CBA_Python.zip") + + result = svc.add_deployment_artifact(artifact_label="cba", + artifact_type="CONTROLLER_BLUEPRINT_ARCHIVE", + artifact_name="vLB_CBA_Python.zip", + artifact=mycbapath) + mock_send_message.assert_called() + method, description, url = mock_send_message.call_args[0] + assert method == "POST" + assert description == "Add deployment artifact for ONAP-test-Service sdc resource" + assert url == ("https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/services/" + f"{svc.unique_identifier}/artifacts") + +@mock.patch("onapsdk.sdc.service.Service.send_message_json") +@mock.patch("onapsdk.sdc.service.SdcResource.import_from_sdc") +@mock.patch("onapsdk.sdc.service.Service.resource_inputs_url", new_callable=mock.PropertyMock) +def test_service_networks(mock_service_resource_inputs_url, mock_import_from_sdc, mock_send_message_json): + mock_send_message_json.side_effect = [{ + "componentInstances": [{ + "actualComponentUid": "123", + "originType": "VL", + "name": "NeutronNet 0", + "toscaComponentName": "org.openecomp.resource.vl.nodes.heat.network.neutron.Net", + "createdFromCsar": False, + "uniqueId": "123", + "normalizedName": "123", + "customizationUUID": "123", + "componentUid": "123", + "componentVersion": "123", + "componentName": "123", + "groupInstances": None + }] + }, MagicMock() + ] + + service = Service(name="test") + networks = list(service.networks) + assert len(networks) == 1 + network = networks[0] + assert network.name == "NeutronNet 0" + assert network.node_template_type == "org.openecomp.resource.vl.nodes.heat.network.neutron.Net" + +@mock.patch.object(Service, '_unzip_csar_file') +def test_tosca_template_no_tosca_model(mock_unzip): + service = Service(name="test") + getter_mock = mock.Mock(wraps=Service.tosca_model.fget) + getter_mock.return_value = False + mock_tosca_model = Service.tosca_model.getter(getter_mock) + with mock.patch.object(Service, 'tosca_model', mock_tosca_model): + service.tosca_template + mock_unzip.assert_not_called() + +@mock.patch.object(Service, '_unzip_csar_file') +def test_tosca_template_tosca_model(mock_unzip): + service = Service(name="test") + service._tosca_model = str.encode("test") + service.tosca_template + mock_unzip.assert_called_once_with(mock.ANY, mock.ANY) + +@mock.patch.object(Service, '_unzip_csar_file') +def test_tosca_template_present(mock_unzip): + service = Service(name="test") + service._tosca_template = "test" + assert service.tosca_template == "test" + mock_unzip.assert_not_called() + +@mock.patch.object(Service, 'send_message') +def test_tosca_model(mock_send): + service = Service(name="test") + service.identifier = "toto" + service.tosca_model + mock_send.assert_called_once_with("GET", "Download Tosca Model for test", + "https://sdc.api.be.simpledemo.onap.org:30204/sdc/v1/catalog/services/toto/toscaModel", + headers={'Content-Type': 'application/json', 'Accept': 'application/octet-stream', 'USER_ID': 'cs0008', 'Authorization': 'Basic YWFpOktwOGJKNFNYc3pNMFdYbGhhazNlSGxjc2UyZ0F3ODR2YW9HR21KdlV5MlU=', 'X-ECOMP-InstanceID': 'onapsdk'}) + +@mock.patch.object(Service, "send_message_json") +def test_add_properties(mock_send_message_json): + service = Service(name="test") + service._identifier = "toto" + service._unique_identifier = "toto" + service._status = const.CERTIFIED + with pytest.raises(StatusError): + service.add_property(Property(name="test", property_type="string")) + service._status = const.DRAFT + service.add_property(Property(name="test", property_type="string")) + mock_send_message_json.assert_called_once() + +@mock.patch.object(Service, "send_message_json") +def test_service_components(mock_send_message_json): + service = Service(name="test") + service.unique_identifier = "toto" + + mock_send_message_json.return_value = {} + assert len(list(service.components)) == 0 + + mock_send_message_json.reset_mock() + mock_send_message_json.side_effect = [COMPONENTS, COMPONENT] + components = list(service.components) + assert len(components) == 1 + assert mock_send_message_json.call_count == 2 + component = components[0] + assert component.actual_component_uid == "374f0a98-a280-43f1-9e6c-00b436782ce7" + assert component.sdc_resource.unique_uuid == "3c027ba1-8d3a-4b59-9394-d748fec5e42c" + +def test_component_properties(): + sdc_resource = mock.MagicMock() + service = Service(name="test") + service.unique_identifier = "toto" + + component = Component( + created_from_csar=False, + actual_component_uid="123", + unique_id="123", + normalized_name="123", + name="123", + origin_type="123", + customization_uuid="123", + tosca_component_name="123", + component_name="123", + component_uid="123", + component_version="123", + sdc_resource=sdc_resource, + parent_sdc_resource=service, + group_instances=None + ) + sdc_resource.send_message_json.return_value = {} + assert not len(list(component.properties)) + + sdc_resource.send_message_json.return_value = COMPONENT_PROPERTIES + properties = list(component.properties) + assert len(properties) == 2 + prop1, prop2 = properties + + assert prop1.unique_id == "3d9a184f-4268-4a0e-9ddd-252e49670013.vf_module_id" + assert prop1.property_type == "string" + assert prop1.name == "vf_module_id" + assert prop1.value is None + + assert prop2.unique_id == "74f79006-ae56-4d58-947e-6a5089000774.skip_post_instantiation_configuration" + assert prop2.property_type == "boolean" + assert prop2.name == "skip_post_instantiation_configuration" + assert prop2.value == "true" + +@mock.patch.object(Component, "properties", new_callable=mock.PropertyMock) +def test_component_property_set_value(mock_component_properties): + mock_sdc_resource = mock.MagicMock() + service = Service(name="test") + service.unique_identifier = "toto" + component = Component( + created_from_csar=False, + actual_component_uid="123", + unique_id="123", + normalized_name="123", + name="123", + origin_type="123", + customization_uuid="123", + tosca_component_name="123", + component_name="123", + component_uid="123", + component_version="123", + sdc_resource=mock_sdc_resource, + parent_sdc_resource=service, + group_instances=None + ) + mock_component_properties.return_value = [ + ComponentProperty( + unique_id="123", + property_type="string", + name="test_property", + component=component + ) + ] + with pytest.raises(ParameterError): + component.get_property(property_name="non_exists") + prop1 = component.get_property(property_name="test_property") + assert prop1.name == "test_property" + assert prop1.unique_id == "123" + assert prop1.property_type == "string" + assert not prop1.value + + prop1.value = "123" + mock_sdc_resource.send_message_json.assert_called_once() + +@mock.patch.object(Service, "add_resource") +@mock.patch.object(Service, "add_property") +@mock.patch.object(Service, "declare_input") +def test_declare_resources_and_properties(mock_declare_input, mock_add_property, mock_add_resource): + + service = Service(name="test", + resources=[SdcResource()], + properties=[Property(name="test", property_type="string")], + inputs=[Property(name="test", property_type="string")]) + service.declare_resources_and_properties() + mock_add_resource.assert_called_once() + mock_add_property.assert_called_once() + mock_declare_input.assert_called_once() + +@mock.patch.object(Service, "created") +@mock.patch.object(ServiceCategory, "get") +def test_service_category(mock_resource_category, mock_created): + mock_created.return_value = False + service = Service(name="test") + _ = service.category + mock_resource_category.assert_called_once_with(name="Network Service") + mock_resource_category.reset_mock() + + service = Service(name="test", category="test") + _ = service.category + mock_resource_category.assert_called_once_with(name="test") + mock_resource_category.reset_mock() + + mock_created.return_value = True + _ = service.category + mock_resource_category.assert_called_once_with(name="test") + +def test_service_origin_type(): + service = Service(name="test") + assert service.origin_type == "ServiceProxy" + +@mock.patch.object(Service, "unique_identifier", new_callable=PropertyMock) +def test_service_metadata_url(mock_uniquie_identifier): + mock_uniquie_identifier.return_value = "1233" + service = Service(name="test") + assert service.metadata_url == f"{service._base_create_url()}/services/1233/filteredDataByParams?include=metadata" + + +@mock.patch.object(Service, "created") +@mock.patch.object(Service, "send_message_json") +@mock.patch.object(Service, "metadata_url", new_callable=PropertyMock) +def test_service_instantiation_type(mock_metadata_url, mock_send_message_json, mock_created): + mock_created.return_value = False + service = Service(name="test") + assert service.instantiation_type == ServiceInstantiationType.A_LA_CARTE + + service = Service(name="test", instantiation_type=ServiceInstantiationType.MACRO) + assert service.instantiation_type == ServiceInstantiationType.MACRO + + mock_created.return_value = True + mock_send_message_json.return_value = {"metadata": {"instantiationType": "A-la-carte"}} + service = Service(name="test") + assert service.instantiation_type == ServiceInstantiationType.A_LA_CARTE + + mock_send_message_json.return_value = {"metadata": {"instantiationType": "Macro"}} + service = Service(name="test") + assert service.instantiation_type == ServiceInstantiationType.MACRO + + +@mock.patch.object(Service, "get_all") +def test_service_get_by_unique_uuid(mock_get_all): + mock_get_all.return_value = [] + with pytest.raises(ResourceNotFound): + Service.get_by_unique_uuid("test") + mock_service = MagicMock() + mock_service.unique_uuid = "test" + mock_get_all.return_value = [mock_service] + Service.get_by_unique_uuid("test") + +@mock.patch.object(Service, "send_message_json") +def test_service_components(mock_send_message_json): + service = Service(name="test") + service.unique_identifier = "toto" + + mock_send_message_json.side_effect = [COMPONENTS, COMPONENT, COMPONENTS, COMPONENT, COMPONENTS, COMPONENT] + assert not service.has_vnfs + assert not service.has_pnfs + assert not service.has_vls + + mock_send_message_json.side_effect = [COMPONENTS_WITH_ALL_ORIGIN_TYPES, COMPONENT, + COMPONENTS_WITH_ALL_ORIGIN_TYPES, COMPONENT, COMPONENT, + COMPONENTS_WITH_ALL_ORIGIN_TYPES, COMPONENT, COMPONENT, COMPONENT] + assert service.has_vnfs + assert service.has_pnfs + assert service.has_vls diff --git a/tests/test_settings.py b/tests/test_settings.py new file mode 100644 index 0000000..7e62da6 --- /dev/null +++ b/tests/test_settings.py @@ -0,0 +1,79 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 sys +from pathlib import PurePath + +import pytest + +from onapsdk.configuration import settings, SETTINGS_ENV +from onapsdk.configuration.loader import SettingsLoader +from onapsdk.exceptions import ModuleError + + +def test_global_settings(): + """Test global settings.""" + assert len(settings._settings) == 43 + assert settings.AAI_URL == "https://aai.api.sparky.simpledemo.onap.org:30233" + assert settings.CDS_URL == "http://portal.api.simpledemo.onap.org:30449" + assert settings.SDNC_URL == "https://sdnc.api.simpledemo.onap.org:30267" + assert settings.SO_URL == "http://so.api.simpledemo.onap.org:30277" + assert settings.MSB_URL == "https://msb.api.simpledemo.onap.org:30283" + assert settings.SDC_FE_URL == "https://sdc.api.fe.simpledemo.onap.org:30207" + assert settings.SDC_BE_URL == "https://sdc.api.be.simpledemo.onap.org:30204" + assert settings.VID_URL == "https://vid.api.simpledemo.onap.org:30200" + assert settings.CLAMP_URL == "https://clamp.api.simpledemo.onap.org:30258" + assert settings.VES_URL == "http://ves.api.simpledemo.onap.org:30417" + assert settings.DMAAP_URL == "http://dmaap.api.simpledemo.onap.org:3904" + assert settings.NBI_URL == "https://nbi.api.simpledemo.onap.org:30274" + assert settings.DCAEMOD_URL == "" + assert settings.HOLMES_URL == "https://aai.api.sparky.simpledemo.onap.org:30293" + assert settings.POLICY_URL == "" + assert settings.AAI_GUI_URL == "https://aai.api.sparky.simpledemo.onap.org:30220" + assert settings.AAI_GUI_SERVICE == "https://aai.api.sparky.simpledemo.onap.org:30220/services/aai/webapp/index.html#/browse" + assert settings.CDS_GUI_SERVICE == "http://portal.api.simpledemo.onap.org:30449/" + assert settings.SO_MONITOR_GUI_SERVICE == "http://so.api.simpledemo.onap.org:30277/" + assert settings.SDC_GUI_SERVICE == "https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/portal" + assert settings.SDNC_DG_GUI_SERVICE == "https://sdnc.api.simpledemo.onap.org:30267/nifi/" + assert settings.SDNC_ODL_GUI_SERVICE == "https://sdnc.api.simpledemo.onap.org:30267/odlux/index.html" + assert settings.DCAEMOD_GUI_SERVICE == "/" + assert settings.HOLMES_GUI_SERVICE == "https://aai.api.sparky.simpledemo.onap.org:30293/iui/holmes/default.html" + assert settings.POLICY_GUI_SERVICE == "/onap/login.html" + assert settings.POLICY_CLAMP_GUI_SERVICE == "https://clamp.api.simpledemo.onap.org:30258/" + assert settings.PROJECT == "Onapsdk_project" + assert settings.LOB == "Onapsdk_lob" + assert settings.PLATFORM == "Onapsdk_platform" + assert hasattr(settings, "AAI_AUTH") + assert hasattr(settings, "CDS_AUTH") + assert hasattr(settings, "SDC_AUTH") + assert hasattr(settings, "SDNC_AUTH") + assert hasattr(settings, "CLAMP_AUTH") + assert hasattr(settings, "SO_AUTH") + assert hasattr(settings, "SO_CAT_DB_AUTH") + + +def test_settings_load_custom(): + """Test if custom settings is loaded correctly.""" + sys.path.append(str(PurePath(__file__).parent)) + os.environ[SETTINGS_ENV] = "data.tests_settings" + custom_settings = SettingsLoader() + assert custom_settings.AAI_URL == "http://tests.settings.py:1234" + assert custom_settings.TEST_VALUE == "test" + + +def test_invalid_custom_settings(): + """Test if loading invalid custom settings raises ModuleError.""" + os.environ[SETTINGS_ENV] = "non.existings.package" + with pytest.raises(ModuleError): + SettingsLoader() diff --git a/tests/test_so_db_adapter.py b/tests/test_so_db_adapter.py new file mode 100644 index 0000000..50f26d6 --- /dev/null +++ b/tests/test_so_db_adapter.py @@ -0,0 +1,136 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.so.so_db_adapter import SoDbAdapter, IdentityService + +ADD_CLOUD_SITE_RESPONSE = { + '_links': { + 'cloudSite': { + 'href': 'http://so.api.simpledemo.onap.org:30277/cloudSite/mc_test_cloud_site_3' + }, + 'self': { + 'href': 'http://so.api.simpledemo.onap.org:30277/cloudSite/mc_test_cloud_site_3' + } + }, + 'aic_version': '2.5', + 'clli': 'test_clli_0', + 'cloud_owner': None, + 'cloudify_id': None, + 'creation_timestamp': '2021-05-12T08:52:48.134+0000', + 'identityService': { + 'admin_tenant': 'service', + 'creation_timestamp': '2021-05-12T08:52:48.134+0000', + 'identityServerTypeAsString': 'KEYSTONE', + 'identity_authentication_type': 'USERNAME_PASSWORD', + 'identity_server_type': 'KEYSTONE', + 'identity_url': 'http://1.2.3.4:5000/v2.0', + 'last_updated_by': None, + 'member_role': 'admin', + 'mso_id': 'onapsdk_user', + 'mso_pass': 'mso_pass_onapsdk', + 'project_domain_name': 'NULL', + 'tenant_metadata': True, + 'update_timestamp': '2021-05-12T08:52:48.134+0000', + 'user_domain_name': 'NULL' + }, + 'identity_service_id': 'test_identity_0', + 'last_updated_by': None, + 'orchestrator': 'multicloud', + 'platform': None, + 'region_id': 'test_region_0', + 'support_fabric': True, + 'update_timestamp': '2021-05-12T08:52:48.134+0000', + 'uri': None +} + +SERVICE_VNF_RESPONSE = { + 'serviceVnfs': [ + { + 'modelInfo': { + 'modelName': 'test_vnf_01', + 'modelUuid': 'd2779cc5-fb01-449f-a355-7e5d911dca93', + 'modelInvariantUuid': '027cb696-f68f-47db-9b0e-585ea3eaa512', + 'modelVersion': '1.0', + 'modelCustomizationUuid': 'b8740912-e0fc-426f-af97-7657caf57847', + 'modelInstanceName': 'test_vnf_01 0' + }, + 'toscaNodeType': 'org.openecomp.resource.vf.Mvnr5gCucpVfT003', + 'nfFunction': None, + 'nfType': None, + 'nfRole': None, + 'nfNamingCode': None, + 'multiStageDesign': 'false', + 'vnfcInstGroupOrder': None, + 'resourceInput': None, + 'vfModules': [{'modelInfo': + { + 'modelName': 'test_vf_01', + 'modelUuid': '153464b8-4f47-4140-8b92-9614c4578d91', + 'modelInvariantUuid': '753deff5-99a2-4154-8c1d-3e956cb96f32', + 'modelVersion': '1', + 'modelCustomizationUuid': '7ca564f3-b908-499c-b086-ae77ad270d8c' + }, + 'isBase': False, + 'vfModuleLabel': 'vf_mod_label', + 'initialCount': 0, + 'hasVolumeGroup': False + } + ], + 'groups': [] + } + ] +} + + +def test_identity_service(): + identity_service = IdentityService(identity_id="identity_123") + assert identity_service.identity_id == "identity_123" + assert identity_service.url == "http://1.2.3.4:5000/v2.0" + assert identity_service.mso_id == "onapsdk_user" + assert identity_service.mso_pass == "mso_pass_onapsdk" + assert identity_service.project_domain_name == "NULL" + assert identity_service.user_domain_name == "NULL" + assert identity_service.admin_tenant == "service" + assert identity_service.member_role == "admin" + assert identity_service.identity_server_type == "KEYSTONE" + assert identity_service.identity_authentication_type == "USERNAME_PASSWORD" + assert identity_service.hibernate_lazy_initializer == {} + assert identity_service.server_type_as_string == "KEYSTONE" + assert identity_service.tenant_metadata is True + +@mock.patch.object(SoDbAdapter, "send_message_json") +def test_add_cloud_site(mock_send_message_json): + identity_service = IdentityService(identity_id="test_identity_0") + mock_send_message_json.return_value = ADD_CLOUD_SITE_RESPONSE + + response = SoDbAdapter.add_cloud_site(cloud_region_id="test_region_0", + complex_id="test_clli_0", + identity_service=identity_service) + assert response['region_id'] == "test_region_0" + assert response['aic_version'] == "2.5" + assert response['clli'] == "test_clli_0" + assert response['orchestrator'] == "multicloud" + assert response['identity_service_id'] == "test_identity_0" + +@mock.patch.object(SoDbAdapter, "send_message_json") +def test_get_service_vnf_info(mock_send_message_json): + mock_send_message_json.return_value = ADD_CLOUD_SITE_RESPONSE + + response = SoDbAdapter.get_service_vnf_info(identifier="test_id_0") + assert response['region_id'] == "test_region_0" + assert response['aic_version'] == "2.5" + assert response['clli'] == "test_clli_0" + assert response['orchestrator'] == "multicloud" + assert response['identity_service_id'] == "test_identity_0" diff --git a/tests/test_so_deletion.py b/tests/test_so_deletion.py new file mode 100644 index 0000000..ff10474 --- /dev/null +++ b/tests/test_so_deletion.py @@ -0,0 +1,74 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.so.deletion import ( + ServiceDeletionRequest, + VfModuleDeletionRequest, + VnfDeletionRequest +) + + +@mock.patch.object(ServiceDeletionRequest, "send_message") +def test_service_deletion_request(mock_send_message): + mock_instance = mock.MagicMock() + mock_instance.instance_id = "test_instance_id" + ServiceDeletionRequest.send_request(instance=mock_instance) + mock_send_message.assert_called_once() + method, _, url = mock_send_message.call_args[0] + assert method == "DELETE" + assert url == (f"{ServiceDeletionRequest.base_url}/onap/so/infra/" + f"serviceInstantiation/{ServiceDeletionRequest.api_version}/" + "serviceInstances/test_instance_id") + + +@mock.patch.object(VfModuleDeletionRequest, "send_message") +def test_vf_module_deletion_request(mock_send_message): + mock_vf_module_instance = mock.MagicMock() + mock_vf_module_instance.vf_module_id = "test_vf_module_id" + + mock_vnf_instance = mock.MagicMock() + mock_vnf_instance.vnf_id = "test_vnf_id" + mock_vf_module_instance.vnf_instance = mock_vnf_instance + + mock_service_instance = mock.MagicMock() + mock_service_instance.instance_id = "test_service_instance_id" + mock_vnf_instance.service_instance = mock_service_instance + + VfModuleDeletionRequest.send_request(instance=mock_vf_module_instance) + mock_send_message.assert_called_once() + method, _, url = mock_send_message.call_args[0] + assert method == "DELETE" + assert url == (f"{VfModuleDeletionRequest.base_url}/onap/so/infra/" + f"serviceInstantiation/{VfModuleDeletionRequest.api_version}/" + "serviceInstances/test_service_instance_id/" + "vnfs/test_vnf_id/vfModules/test_vf_module_id") + + +@mock.patch.object(VnfDeletionRequest, "send_message") +def test_vnf_deletion_request(mock_send_message): + mock_vnf_instance = mock.MagicMock() + mock_vnf_instance.vnf_id = "test_vnf_id" + + mock_service_instance = mock.MagicMock() + mock_service_instance.instance_id = "test_service_instance" + mock_vnf_instance.service_instance = mock_service_instance + VnfDeletionRequest.send_request(instance=mock_vnf_instance) + mock_send_message.assert_called_once() + method, _, url = mock_send_message.call_args[0] + assert method == "DELETE" + assert url == (f"{VnfDeletionRequest.base_url}/onap/so/infra/" + f"serviceInstantiation/{VnfDeletionRequest.api_version}/" + "serviceInstances/test_service_instance/" + "vnfs/test_vnf_id") diff --git a/tests/test_so_element.py b/tests/test_so_element.py new file mode 100644 index 0000000..cc866d9 --- /dev/null +++ b/tests/test_so_element.py @@ -0,0 +1,28 @@ +"""Test A&AI Element.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.so.so_element import SoElement +from onapsdk.utils.gui import GuiList + +@mock.patch.object(SoElement, "send_message") +def test_get_guis(send_message_mock): + component = SoElement() + send_message_mock.return_value.status_code = 200 + send_message_mock.return_value.url = "http://so.api.simpledemo.onap.org:30277/" + gui_results = component.get_guis() + assert type(gui_results) == GuiList + assert gui_results.guilist[0].url == send_message_mock.return_value.url + assert gui_results.guilist[0].status == send_message_mock.return_value.status_code diff --git a/tests/test_so_instantiation.py b/tests/test_so_instantiation.py new file mode 100644 index 0000000..5c8f41a --- /dev/null +++ b/tests/test_so_instantiation.py @@ -0,0 +1,1002 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 oyaml as yaml +from collections import namedtuple +from pathlib import Path +from unittest import mock + +import pytest + +from onapsdk.exceptions import APIError, InvalidResponse, ResourceNotFound, StatusError +from onapsdk.sdc.service import Service +from onapsdk.sdnc import NetworkPreload, VfModulePreload +from onapsdk.so.instantiation import ( + NetworkInstantiation, + ServiceInstantiation, + SoService, + SoServicePnf, + SoServiceVfModule, + SoServiceVnf, + VfModuleInstantiation, + VnfInstantiation, + VnfOperation +) +from onapsdk.vid import Vid +from onapsdk.aai.business.owning_entity import OwningEntity + + +@mock.patch.object(ServiceInstantiation, "send_message_json") +def test_service_ala_carte_instantiation(mock_service_instantiation_send_message): + mock_sdc_service = mock.MagicMock() + mock_sdc_service.distributed = False + with pytest.raises(StatusError): + ServiceInstantiation.\ + instantiate_ala_carte(sdc_service=mock_sdc_service, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + project=mock.MagicMock(), + service_instance_name="test", + service_subscription=mock.MagicMock()) + mock_sdc_service.distributed = True + service_instance = ServiceInstantiation.\ + instantiate_ala_carte(sdc_service=mock_sdc_service, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + project=mock.MagicMock(), + service_instance_name="test", + service_subscription=mock.MagicMock()) + assert service_instance.name == "test" + + service_instance = ServiceInstantiation.\ + instantiate_ala_carte(sdc_service=mock_sdc_service, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + project=mock.MagicMock(), + service_subscription=mock.MagicMock()) + assert service_instance.name.startswith("Python_ONAP_SDK_service_instance_") + mock_service_instantiation_send_message.assert_called() + method, _, url = mock_service_instantiation_send_message.call_args[0] + assert method == "POST" + assert url == (f"{ServiceInstantiation.base_url}/onap/so/infra/" + f"serviceInstantiation/{ServiceInstantiation.api_version}/serviceInstances") + + +@mock.patch.object(ServiceInstantiation, "send_message_json") +def test_service_macro_instantiation(mock_service_instantiation_send_message): + mock_sdc_service = mock.MagicMock() + mock_sdc_service.distributed = False + with pytest.raises(StatusError): + ServiceInstantiation.\ + instantiate_macro(sdc_service=mock_sdc_service, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + project=mock.MagicMock(), + line_of_business=mock.MagicMock(), + platform=mock.MagicMock(), + service_instance_name="test", + service_subscription=mock.MagicMock()) + mock_sdc_service.distributed = True + service_instance = ServiceInstantiation.\ + instantiate_macro(sdc_service=mock_sdc_service, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + project=mock.MagicMock(), + line_of_business=mock.MagicMock(), + platform=mock.MagicMock(), + service_instance_name="test", + service_subscription=mock.MagicMock()) + assert service_instance.name == "test" + + service_instance = ServiceInstantiation.\ + instantiate_macro(sdc_service=mock_sdc_service, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + line_of_business=mock.MagicMock(), + platform=mock.MagicMock(), + project=mock.MagicMock(), + so_service=mock.MagicMock()) + assert service_instance.name.startswith("Python_ONAP_SDK_service_instance_") + mock_service_instantiation_send_message.assert_called() + method, _, url = mock_service_instantiation_send_message.call_args[0] + assert method == "POST" + assert url == (f"{ServiceInstantiation.base_url}/onap/so/infra/" + f"serviceInstantiation/{ServiceInstantiation.api_version}/serviceInstances") + + so_service_mock = mock.MagicMock() + so_service_mock.instance_name = "SoServiceInstanceName" + service_instance = ServiceInstantiation.\ + instantiate_macro(sdc_service=mock_sdc_service, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + line_of_business=mock.MagicMock(), + platform=mock.MagicMock(), + project=mock.MagicMock(), + so_service=so_service_mock) + assert service_instance.name == "SoServiceInstanceName" + mock_service_instantiation_send_message.assert_called() + method, _, url = mock_service_instantiation_send_message.call_args[0] + assert method == "POST" + assert url == (f"{ServiceInstantiation.base_url}/onap/so/infra/" + f"serviceInstantiation/{ServiceInstantiation.api_version}/serviceInstances") + + +def test_service_instance_aai_service_instance(): + customer_mock = mock.MagicMock() + service_instantiation = ServiceInstantiation(name="test", + request_id="test_request_id", + instance_id="test_instance_id", + sdc_service=mock.MagicMock(), + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=customer_mock, + owning_entity=mock.MagicMock(), + project=mock.MagicMock()) + status_mock = mock.PropertyMock(return_value=ServiceInstantiation.StatusEnum.IN_PROGRESS) + type(service_instantiation).status = status_mock + with pytest.raises(StatusError): + service_instantiation.aai_service_instance + + status_mock.return_value = return_value=ServiceInstantiation.StatusEnum.COMPLETED + assert service_instantiation.aai_service_instance is not None + + customer_mock.get_service_subscription_by_service_type.side_effect = APIError + with pytest.raises(APIError) as err: + service_instantiation.aai_service_instance + assert err.type == APIError + + +@mock.patch.object(VnfInstantiation, "send_message_json") +def test_vnf_instantiation(mock_vnf_instantiation_send_message): + aai_service_instance_mock = mock.MagicMock() + aai_service_instance_mock.instance_id = "test_instance_id" + vnf_instantiation = VnfInstantiation.\ + instantiate_ala_carte(aai_service_instance=aai_service_instance_mock, + vnf_object=mock.MagicMock(), + line_of_business="test_lob", + platform="test_platform", + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + sdc_service=mock.MagicMock()) + assert vnf_instantiation.name.startswith("Python_ONAP_SDK_vnf_instance_") + mock_vnf_instantiation_send_message.assert_called_once() + method, _, url = mock_vnf_instantiation_send_message.call_args[0] + assert method == "POST" + assert url == (f"{VnfInstantiation.base_url}/onap/so/infra/serviceInstantiation/" + f"{VnfInstantiation.api_version}/serviceInstances/" + f"{aai_service_instance_mock.instance_id}/vnfs") + + vnf_instantiation = VnfInstantiation.\ + instantiate_ala_carte(aai_service_instance=aai_service_instance_mock, + vnf_object=mock.MagicMock(), + line_of_business="test_lob", + platform="test_platform", + vnf_instance_name="test", + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + sdc_service=mock.MagicMock()) + assert vnf_instantiation.name == "test" + + +@mock.patch.object(VnfInstantiation, "send_message_json") +def test_vnf_instantiation_with_cr_and_tenant(mock_vnf_instantiation_send_message): + aai_service_instance_mock = mock.MagicMock() + aai_service_instance_mock.instance_id = "test_instance_id" + vnf_instantiation = VnfInstantiation.\ + instantiate_ala_carte(aai_service_instance=aai_service_instance_mock, + vnf_object=mock.MagicMock(), + line_of_business="test_lob", + platform="test_platform", + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + sdc_service=mock.MagicMock()) + assert vnf_instantiation.name.startswith("Python_ONAP_SDK_vnf_instance_") + mock_vnf_instantiation_send_message.assert_called_once() + method, _, url = mock_vnf_instantiation_send_message.call_args[0] + assert method == "POST" + assert url == (f"{VnfInstantiation.base_url}/onap/so/infra/serviceInstantiation/" + f"{VnfInstantiation.api_version}/serviceInstances/" + f"{aai_service_instance_mock.instance_id}/vnfs") + + vnf_instantiation = VnfInstantiation.\ + instantiate_ala_carte(aai_service_instance=aai_service_instance_mock, + vnf_object=mock.MagicMock(), + line_of_business="test_lob", + platform="test_platform", + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + vnf_instance_name="test", + sdc_service=mock.MagicMock()) + assert vnf_instantiation.name == "test" + + +@mock.patch.object(VnfInstantiation, "send_message_json") +@mock.patch.object(OwningEntity, "get_by_owning_entity_id") +def test_vnf_instantiation_macro(mock_owning_entity_get, mock_vnf_instantiation_send_message): + aai_service_instance_mock = mock.MagicMock() + aai_service_instance_mock.instance_id = "test_instance_id" + + relation_1 = mock.MagicMock() + relation_1.related_to = "owning-entity" + relation_1.relationship_data = [{"relationship-value": "test"}] + relation_2 = mock.MagicMock() + relation_2.related_to = "project" + relation_2.relationship_data = [{"relationship-value": "test"}] + + aai_service_instance_mock.relationships = (item for item in [relation_1, relation_2]) + + vnf_instantiation = VnfInstantiation.\ + instantiate_macro(aai_service_instance=aai_service_instance_mock, + vnf_object=mock.MagicMock(), + line_of_business="test_lob", + platform="test_platform", + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + sdc_service=mock.MagicMock()) + assert vnf_instantiation.name.startswith("Python_ONAP_SDK_vnf_instance_") + mock_vnf_instantiation_send_message.assert_called_once() + method, _, url = mock_vnf_instantiation_send_message.call_args[0] + assert method == "POST" + assert url == (f"{VnfInstantiation.base_url}/onap/so/infra/serviceInstantiation/" + f"{VnfInstantiation.api_version}/serviceInstances/" + f"{aai_service_instance_mock.instance_id}/vnfs") + + vnf_instantiation = VnfInstantiation. \ + instantiate_macro(aai_service_instance=aai_service_instance_mock, + vnf_object=mock.MagicMock(), + line_of_business="test_lob", + platform="test_platform", + vnf_instance_name="test", + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + sdc_service=mock.MagicMock()) + assert vnf_instantiation.name == "test" + + vnf_instantiation = VnfInstantiation. \ + instantiate_macro(aai_service_instance=aai_service_instance_mock, + vnf_object=mock.MagicMock(), + line_of_business="test_lob", + platform="test_platform", + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + sdc_service=mock.MagicMock(), + so_vnf=mock.MagicMock()) + assert vnf_instantiation.name.startswith("Python_ONAP_SDK_service_instance_") + + so_vnf_mock = mock.MagicMock() + so_vnf_mock.instance_name = "SoVnfInstanceName" + vnf_instantiation = VnfInstantiation. \ + instantiate_macro(aai_service_instance=aai_service_instance_mock, + vnf_object=mock.MagicMock(), + line_of_business="test_lob", + platform="test_platform", + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + sdc_service=mock.MagicMock(), + so_vnf=so_vnf_mock) + assert vnf_instantiation.name == "SoVnfInstanceName" + + +@mock.patch.object(VnfInstantiation, "send_message_json") +@mock.patch.object(OwningEntity, "get_by_owning_entity_id") +def test_vnf_macro_so_action(mock_owning_entity_get, mock_vnf_instantiation_send_message): + + mock_sdc_service = mock.MagicMock() + with pytest.raises(StatusError): + VnfInstantiation.\ + so_action(vnf_instance=mock.MagicMock(), + operation_type=mock.MagicMock(), + aai_service_instance=mock.MagicMock(), + line_of_business=mock.MagicMock(), + platform=mock.MagicMock(), + sdc_service=mock_sdc_service, + so_service=mock.MagicMock()) + + relation_1 = mock.MagicMock() + relation_1.related_to = "owning-entity" + relation_1.relationship_data = [{"relationship-value": "test"}] + relation_2 = mock.MagicMock() + relation_2.related_to = "project" + relation_2.relationship_data = [{"relationship-value": "test"}] + + mock_aai_service_instance = mock.MagicMock() + mock_aai_service_instance.instance_id = mock.MagicMock() + mock_aai_service_instance.relationships = (item for item in [relation_1, relation_2]) + mock_aai_service_instance.service_subscription = mock.MagicMock() + mock_vnf_instance = mock.MagicMock() + mock_vnf_instance.vnf_name = "test_name_update" + mock_vnf_instance.vnf_id = "1234" + + vnf_instance_update = VnfInstantiation.\ + so_action(vnf_instance=mock_vnf_instance, + operation_type=VnfOperation.UPDATE, + aai_service_instance=mock_aai_service_instance, + line_of_business=mock.MagicMock(), + platform=mock.MagicMock(), + sdc_service=mock_sdc_service, + so_service=mock.MagicMock()) + assert vnf_instance_update.name == "test_name_update" + mock_vnf_instantiation_send_message.assert_called() + method, _, url = mock_vnf_instantiation_send_message.call_args[0] + assert method == "PUT" + assert url == (f"{ServiceInstantiation.base_url}/onap/so/infra/" + f"serviceInstantiation/{ServiceInstantiation.api_version}/serviceInstances/" + f"{mock_aai_service_instance.instance_id}/vnfs/{mock_vnf_instance.vnf_id}") + + mock_vnf_instance = mock.MagicMock() + mock_vnf_instance.vnf_name = "test_name_healthcheck" + + vnf_instance_healthcheck = VnfInstantiation. \ + so_action(vnf_instance=mock_vnf_instance, + operation_type=VnfOperation.HEALTHCHECK, + aai_service_instance=mock_aai_service_instance, + line_of_business=mock.MagicMock(), + platform=mock.MagicMock(), + sdc_service=mock_sdc_service, + so_service=mock.MagicMock()) + assert vnf_instance_healthcheck.name == "test_name_healthcheck" + mock_vnf_instantiation_send_message.assert_called() + method, _, url = mock_vnf_instantiation_send_message.call_args[0] + assert method == "POST" + assert url == (f"{ServiceInstantiation.base_url}/onap/so/infra/" + f"serviceInstantiation/{ServiceInstantiation.api_version}/serviceInstances/" + f"{mock_aai_service_instance.instance_id}/vnfs/{mock_vnf_instance.vnf_id}/healthcheck") + + +@mock.patch.object(NetworkInstantiation, "send_message_json") +@mock.patch.object(NetworkPreload, "send_message_json") +def test_network_instantiation(mock_network_preload, mock_network_instantiation_send_message): + aai_service_instance_mock = mock.MagicMock() + aai_service_instance_mock.instance_id = "test_instance_id" + vnf_instantiation = NetworkInstantiation.\ + instantiate_ala_carte(aai_service_instance=aai_service_instance_mock, + network_object=mock.MagicMock(), + line_of_business="test_lob", + platform="test_platform", + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock()) + mock_network_preload.assert_called_once() + assert vnf_instantiation.name.startswith("Python_ONAP_SDK_network_instance_") + mock_network_instantiation_send_message.assert_called_once() + method, _, url = mock_network_instantiation_send_message.call_args[0] + assert method == "POST" + assert url == (f"{NetworkInstantiation.base_url}/onap/so/infra/serviceInstantiation/" + f"{NetworkInstantiation.api_version}/serviceInstances/" + f"{aai_service_instance_mock.instance_id}/networks") + + network_instantiation = NetworkInstantiation.\ + instantiate_ala_carte(aai_service_instance=aai_service_instance_mock, + network_object=mock.MagicMock(), + line_of_business="test_lob", + platform="test_platform", + network_instance_name="test", + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock()) + assert mock_network_preload.call_count == 2 + assert network_instantiation.name == "test" + + +@mock.patch.object(NetworkInstantiation, "send_message_json") +@mock.patch.object(NetworkPreload, "send_message_json") +def test_network_instantiation_with_cr_and_tenant(mock_network_preload, mock_network_instantiation_send_message): + aai_service_instance_mock = mock.MagicMock() + aai_service_instance_mock.instance_id = "test_instance_id" + vnf_instantiation = NetworkInstantiation.\ + instantiate_ala_carte(aai_service_instance=aai_service_instance_mock, + network_object=mock.MagicMock(), + line_of_business=mock.MagicMock(), + platform=mock.MagicMock(), + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock()) + mock_network_preload.assert_called_once() + assert vnf_instantiation.name.startswith("Python_ONAP_SDK_network_instance_") + mock_network_instantiation_send_message.assert_called_once() + method, _, url = mock_network_instantiation_send_message.call_args[0] + assert method == "POST" + assert url == (f"{NetworkInstantiation.base_url}/onap/so/infra/serviceInstantiation/" + f"{NetworkInstantiation.api_version}/serviceInstances/" + f"{aai_service_instance_mock.instance_id}/networks") + + network_instantiation = NetworkInstantiation.\ + instantiate_ala_carte(aai_service_instance=aai_service_instance_mock, + network_object=mock.MagicMock(), + line_of_business="test_lob", + platform="test_platform", + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + network_instance_name="test") + assert mock_network_preload.call_count == 2 + assert network_instantiation.name == "test" + +@mock.patch.object(Vid, "send_message") +@mock.patch.object(VnfInstantiation, "send_message_json") +@mock.patch("onapsdk.so.instantiation.SdcService") +def test_vnf_instantiation_get_by_vnf_instance_name(mock_sdc_service, mock_send_message_json, mock_send): + mock_sdc_service.return_value.vnfs = [] + mock_send_message_json.return_value = {} + with pytest.raises(InvalidResponse): + VnfInstantiation.get_by_vnf_instance_name("test_vnf_instance_name") + mock_send_message_json.return_value = { + "requestList": [ + { + "request": { + "requestScope": "not_vnf" + } + } + ] + } + with pytest.raises(InvalidResponse): + VnfInstantiation.get_by_vnf_instance_name("test_vnf_instance_name") + mock_send_message_json.return_value = { + "requestList": [ + { + "request": { + "requestScope": "vnf", + "requestType": "updateInstance" + } + } + ] + } + with pytest.raises(InvalidResponse): + VnfInstantiation.get_by_vnf_instance_name("test_vnf_instance_name") + mock_send_message_json.return_value = { + "requestList": [ + { + "request": { + "requestScope": "vnf", + "requestType": "createInstance" + } + } + ] + } + with pytest.raises(ResourceNotFound): + VnfInstantiation.get_by_vnf_instance_name("test_vnf_instance_name") + mock_send_message_json.return_value = { + "requestList": [ + { + "request": { + "requestScope": "vnf", + "requestType": "createInstance", + "requestDetails": { + "relatedInstanceList": [ + { + "relatedInstance": { + "modelInfo": { + "modelType": "service", + "modelName": "test_service" + } + } + } + ] + } + } + } + ] + } + with pytest.raises(ResourceNotFound): + VnfInstantiation.get_by_vnf_instance_name("test_vnf_instance_name") + mock_vnf = mock.MagicMock() + mock_vnf.name = "test_vnf_name" + mock_sdc_service.return_value.vnfs = [mock_vnf] + mock_send_message_json.return_value = { + "requestList": [ + { + "request": { + "requestScope": "vnf", + "requestType": "createInstance", + "requestDetails": { + "modelInfo": { + "modelCustomizationName": "test_fail_vnf_name" + }, + "relatedInstanceList": [ + { + "relatedInstance": { + "modelInfo": { + "modelType": "service", + "modelName": "test_service", + } + } + } + ] + } + } + } + ] + } + with pytest.raises(ResourceNotFound): + VnfInstantiation.get_by_vnf_instance_name("test_vnf_instance_name") + mock_sdc_service.return_value.vnfs = [mock_vnf] + mock_send_message_json.return_value = { + "requestList": [ + { + "request": { + "requestScope": "vnf", + "requestType": "createInstance", + "requestDetails": { + "modelInfo": { + "modelCustomizationName": "test_vnf_name" + }, + "relatedInstanceList": [ + { + "relatedInstance": { + "modelInfo": { + "modelType": "service", + "modelName": "test_service" + } + } + } + ] + } + } + } + ] + } + assert VnfInstantiation.get_by_vnf_instance_name("test_vnf_instance_name") is not None + + +@mock.patch.object(VfModuleInstantiation, "send_message_json") +@mock.patch.object(VfModulePreload, "upload_vf_module_preload") +def test_vf_module_instantiation(mock_vf_module_preload, mock_send_message_json): + mock_service_instance = mock.MagicMock() + mock_service_instance.instance_id = "1234" + mock_vnf_instance = mock.MagicMock() + mock_vnf_instance.service_instance = mock_service_instance + mock_vnf_instance.vnf_id = "4321" + instantiation = VfModuleInstantiation.\ + instantiate_ala_carte(vf_module=mock.MagicMock(), + vnf_instance=mock_vnf_instance, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock()) + assert instantiation.name.startswith("Python_ONAP_SDK_vf_module_instance_") + mock_send_message_json.assert_called_once() + method, _, url = mock_send_message_json.call_args[0] + assert method == "POST" + assert url == (f"{VfModuleInstantiation.base_url}/onap/so/infra/serviceInstantiation/" + f"{VfModuleInstantiation.api_version}/serviceInstances/1234/vnfs/" + f"4321/vfModules") + + instantiation = VfModuleInstantiation.\ + instantiate_ala_carte(vf_module=mock.MagicMock(), + vnf_instance=mock_vnf_instance, + vf_module_instance_name="test", + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock()) + assert instantiation.name == "test" + + +@mock.patch.object(VfModuleInstantiation, "send_message_json") +@mock.patch.object(VfModulePreload, "upload_vf_module_preload") +def test_vf_module_instantiation_with_cr_and_tenant(mock_vf_module_preload, mock_send_message_json): + mock_service_instance = mock.MagicMock() + mock_service_instance.instance_id = "1234" + mock_vnf_instance = mock.MagicMock() + mock_vnf_instance.service_instance = mock_service_instance + mock_vnf_instance.vnf_id = "4321" + instantiation = VfModuleInstantiation.\ + instantiate_ala_carte(vf_module=mock.MagicMock(), + vnf_instance=mock_vnf_instance, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock()) + assert instantiation.name.startswith("Python_ONAP_SDK_vf_module_instance_") + mock_send_message_json.assert_called_once() + method, _, url = mock_send_message_json.call_args[0] + assert method == "POST" + assert url == (f"{VfModuleInstantiation.base_url}/onap/so/infra/serviceInstantiation/" + f"{VfModuleInstantiation.api_version}/serviceInstances/1234/vnfs/" + f"4321/vfModules") + + instantiation = VfModuleInstantiation.\ + instantiate_ala_carte(vf_module=mock.MagicMock(), + vnf_instance=mock_vnf_instance, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + vf_module_instance_name="test") + assert instantiation.name == "test" + + +def test_instantiation_wait_for_finish(): + with mock.patch.object(ServiceInstantiation, "finished", new_callable=mock.PropertyMock) as mock_finished: + with mock.patch.object(ServiceInstantiation, "completed", new_callable=mock.PropertyMock) as mock_completed: + instantiation = ServiceInstantiation( + name="test", + request_id="test", + instance_id="test", + sdc_service=mock.MagicMock(), + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + project=mock.MagicMock() + ) + instantiation.WAIT_FOR_SLEEP_TIME = 0 + mock_finished.side_effect = [False, False, True] + mock_completed.return_value = True + rv = namedtuple("Value", ["return_value"]) + instantiation._wait_for_finish(rv) + assert rv.return_value + +@mock.patch.object(ServiceInstantiation, "send_message_json") +def test_service_instantiation_multicloud(mock_send_message_json): + + mock_sdc_service = mock.MagicMock() + mock_sdc_service.distributed = True + _ = ServiceInstantiation.\ + instantiate_ala_carte(sdc_service=mock_sdc_service, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + project=mock.MagicMock(), + service_subscription=mock.MagicMock()) + _, kwargs = mock_send_message_json.call_args + data = json.loads(kwargs["data"]) + assert data["requestDetails"]["requestParameters"]["userParams"] == [] + mock_send_message_json.reset_mock() + + _ = ServiceInstantiation.\ + instantiate_ala_carte(sdc_service=mock_sdc_service, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + project=mock.MagicMock(), + enable_multicloud=True, + service_subscription=mock.MagicMock()) + _, kwargs = mock_send_message_json.call_args + data = json.loads(kwargs["data"]) + assert data["requestDetails"]["requestParameters"]["userParams"] == [{"name": "orchestrator", "value": "multicloud"}] + mock_send_message_json.reset_mock() + + _ = ServiceInstantiation.\ + instantiate_macro(sdc_service=mock_sdc_service, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + project=mock.MagicMock(), + line_of_business=mock.MagicMock(), + platform=mock.MagicMock(), + service_instance_name="test", + service_subscription=mock.MagicMock()) + _, kwargs = mock_send_message_json.call_args + data = json.loads(kwargs["data"]) + assert not any(filter(lambda x: x == {"name": "orchestrator", "value": "multicloud"}, data["requestDetails"]["requestParameters"]["userParams"])) + mock_send_message_json.reset_mock() + + _ = ServiceInstantiation.\ + instantiate_macro(sdc_service=mock_sdc_service, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + project=mock.MagicMock(), + line_of_business=mock.MagicMock(), + platform=mock.MagicMock(), + service_instance_name="test", + enable_multicloud=True, + service_subscription=mock.MagicMock()) + _, kwargs = mock_send_message_json.call_args + data = json.loads(kwargs["data"]) + assert any(filter(lambda x: x == {"name": "orchestrator", "value": "multicloud"}, data["requestDetails"]["requestParameters"]["userParams"])) + + +@mock.patch.object(ServiceInstantiation, "send_message_json") +def test_service_instantiation_so_service(mock_send_message_json): + mock_sdc_service = mock.MagicMock() + mock_sdc_service.distributed = True + + so_service = SoService( + subscription_service_type="test_so_service", + vnfs=[ + SoServiceVnf( + model_name="test_so_service_vnf_model_name_1", + instance_name="test_so_service_vnf_instance_name_1", + parameters={ + "param_1": "param_1_value", + "param_2": "param_2_value" + } + ), + SoServiceVnf( + model_name="test_so_service_vnf_model_name_2", + instance_name="test_so_service_vnf_instance_name_2", + vf_modules=[ + SoServiceVfModule( + model_name="test_so_service_vf_module_model_name_1", + instance_name="test_so_service_vf_module_instance_name_1", + parameters={ + "vf_module_param_1": "vf_module_param_1_value", + "vf_module_param_2": "vf_module_param_2_value" + } + ), + SoServiceVfModule( + model_name="test_so_service_vf_module_model_name_2", + instance_name="test_so_service_vf_module_instance_name_2", + parameters={ + "vf_module_param_1": "vf_module_param_1_value", + "vf_module_param_2": "vf_module_param_2_value" + } + ), + ] + ) + ], + pnfs=[ + SoServicePnf( + model_name="test_so_service_pnf_model_name_1", + instance_name="test_so_service_pnf_instance_name_1" + ), + SoServicePnf( + model_name="test_so_service_pnf_model_name_2", + instance_name="test_so_service_pnf_instance_name_2" + ) + ] + ) + + _ = ServiceInstantiation.\ + instantiate_macro(sdc_service=mock_sdc_service, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + project=mock.MagicMock(), + line_of_business=mock.MagicMock(), + platform=mock.MagicMock(), + service_instance_name="test", + so_service=so_service) + _, kwargs = mock_send_message_json.call_args + data = json.loads(kwargs["data"]) + assert data["requestDetails"]["requestParameters"]["subscriptionServiceType"] == "test_so_service" + assert len(data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["resources"]["vnfs"]) == 2 + assert len(data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["resources"]["pnfs"]) == 2 + vnf_1_data = data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["resources"]["vnfs"][0] + vnf_2_data = data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["resources"]["vnfs"][1] + pnf_1_data = data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["resources"]["pnfs"][0] + pnf_2_data = data["requestDetails"]["requestParameters"]["userParams"][1]["service"]["resources"]["pnfs"][1] + + assert vnf_1_data["instanceName"] == "test_so_service_vnf_instance_name_1" + assert len(vnf_1_data["instanceParams"][0]) == 2 + assert vnf_1_data["instanceParams"][0]["param_1"] == "param_1_value" + assert vnf_1_data["instanceParams"][0]["param_2"] == "param_2_value" + assert len(vnf_1_data["vfModules"]) == 0 + + assert vnf_2_data["instanceName"] == "test_so_service_vnf_instance_name_2" + assert len(vnf_2_data["instanceParams"][0]) == 0 + assert len(vnf_2_data["vfModules"]) == 2 + vf_module_1_data = vnf_2_data["vfModules"][0] + vf_module_2_data = vnf_2_data["vfModules"][1] + + assert vf_module_1_data["instanceName"] == "test_so_service_vf_module_instance_name_1" + assert len(vf_module_1_data["instanceParams"][0]) == 2 + assert vf_module_1_data["instanceParams"][0]["vf_module_param_1"] == "vf_module_param_1_value" + assert vf_module_1_data["instanceParams"][0]["vf_module_param_2"] == "vf_module_param_2_value" + + assert vf_module_2_data["instanceName"] == "test_so_service_vf_module_instance_name_2" + assert len(vf_module_2_data["instanceParams"][0]) == 2 + assert vf_module_2_data["instanceParams"][0]["vf_module_param_1"] == "vf_module_param_1_value" + assert vf_module_2_data["instanceParams"][0]["vf_module_param_2"] == "vf_module_param_2_value" + + assert pnf_1_data["instanceName"] == "test_so_service_pnf_instance_name_1" + + assert pnf_2_data["instanceName"] == "test_so_service_pnf_instance_name_2" + + +def test_so_service_load_from_yaml(): + + so_service_yaml = """ + subscription_service_type: myservice + vnfs: + - model_name: myvfmodel + instance_name: myfirstvnf + parameters: + param1: value1 + processing_priority: 1 + vf_modules: + - instance_name: mysecondvfm + model_name: base + processing_priority: 2 + parameters: + param-vfm1: value-vfm1 + - instance_name: myfirstvfm + model_name: base + processing_priority: 1 + parameters: + param-vfm1: value-vfm1 + - model_name: myvfmodel + instance_name: mysecondvnf + parameters: + param1: value1 + processing_priority: 2 + vf_modules: + - instance_name: myfirstvfm + model_name: base + processing_priority: 1 + parameters: + param-vfm1: value-vfm1 + - instance_name: mysecondvfm + model_name: base + processing_priority: 2 + parameters: + param-vfm1: value-vfm1 + pnfs: + - model_name: mypnfmodel + instance_name: myfirstpnf + """ + so_service = SoService.load(yaml.safe_load(so_service_yaml)) + assert so_service.subscription_service_type == "myservice" + assert not so_service.instance_name + assert len(so_service.vnfs) == 2 + assert len(so_service.pnfs) == 1 + + so_service_vnf_1 = so_service.vnfs[0] + so_service_vnf_2 = so_service.vnfs[1] + so_service_pnf = so_service.pnfs[0] + + assert so_service_vnf_1.model_name == "myvfmodel" + assert so_service_vnf_1.instance_name == "myfirstvnf" + assert so_service_vnf_1.processing_priority == 1 + assert len(so_service_vnf_1.parameters) == 1 + assert so_service_vnf_1.parameters["param1"] == "value1" + assert len(so_service_vnf_1.vf_modules) == 2 + + so_service_vnf_1_vf_module_1 = so_service_vnf_1.vf_modules[0] + so_service_vnf_1_vf_module_2 = so_service_vnf_1.vf_modules[1] + + assert so_service_vnf_1_vf_module_1.model_name == "base" + assert so_service_vnf_1_vf_module_1.instance_name == "mysecondvfm" + assert so_service_vnf_1_vf_module_1.processing_priority == 2 + assert len(so_service_vnf_1_vf_module_1.parameters) == 1 + assert so_service_vnf_1_vf_module_1.parameters["param-vfm1"] == "value-vfm1" + assert so_service_vnf_1_vf_module_2.model_name == "base" + assert so_service_vnf_1_vf_module_2.instance_name == "myfirstvfm" + assert so_service_vnf_1_vf_module_2.processing_priority == 1 + assert len(so_service_vnf_1_vf_module_2.parameters) == 1 + assert so_service_vnf_1_vf_module_2.parameters["param-vfm1"] == "value-vfm1" + + assert so_service_vnf_2.model_name == "myvfmodel" + assert so_service_vnf_2.instance_name == "mysecondvnf" + assert so_service_vnf_2.processing_priority == 2 + assert len(so_service_vnf_2.parameters) == 1 + assert so_service_vnf_2.parameters["param1"] == "value1" + assert len(so_service_vnf_2.vf_modules) == 2 + + so_service_vnf_1_vf_module_1 = so_service_vnf_2.vf_modules[0] + so_service_vnf_1_vf_module_2 = so_service_vnf_2.vf_modules[1] + + assert so_service_vnf_1_vf_module_1.model_name == "base" + assert so_service_vnf_1_vf_module_1.instance_name == "myfirstvfm" + assert so_service_vnf_1_vf_module_1.processing_priority == 1 + assert len(so_service_vnf_1_vf_module_1.parameters) == 1 + assert so_service_vnf_1_vf_module_1.parameters["param-vfm1"] == "value-vfm1" + assert so_service_vnf_1_vf_module_2.model_name == "base" + assert so_service_vnf_1_vf_module_2.instance_name == "mysecondvfm" + assert so_service_vnf_1_vf_module_2.processing_priority == 2 + assert len(so_service_vnf_1_vf_module_2.parameters) == 1 + assert so_service_vnf_1_vf_module_2.parameters["param-vfm1"] == "value-vfm1" + + assert so_service_pnf.model_name == "mypnfmodel" + assert so_service_pnf.instance_name == "myfirstpnf" + + +def test_so_service_load_from_file(): + with Path(Path(__file__).parent, "data/test_so_service_data.yaml").open() as yaml_template: + so_service_data = yaml.safe_load(yaml_template) + service = Service(next(iter(so_service_data.keys()))) + so_service = SoService.load(so_service_data[service.name]) + assert so_service.subscription_service_type == "myservice" + assert not so_service.instance_name + assert len(so_service.vnfs) == 2 + + so_service_vnf_1 = so_service.vnfs[0] + so_service_vnf_2 = so_service.vnfs[1] + + assert so_service_vnf_1.model_name == "myvfmodel" + assert so_service_vnf_1.instance_name == "myfirstvnf" + assert so_service_vnf_1.processing_priority == 1 + assert len(so_service_vnf_1.parameters) == 1 + assert so_service_vnf_1.parameters["param1"] == "value1" + assert len(so_service_vnf_1.vf_modules) == 2 + + so_service_vnf_1_vf_module_1 = so_service_vnf_1.vf_modules[0] + so_service_vnf_1_vf_module_2 = so_service_vnf_1.vf_modules[1] + + assert so_service_vnf_1_vf_module_1.model_name == "base" + assert so_service_vnf_1_vf_module_1.instance_name == "mysecondvfm" + assert so_service_vnf_1_vf_module_1.processing_priority == 2 + assert len(so_service_vnf_1_vf_module_1.parameters) == 1 + assert so_service_vnf_1_vf_module_1.parameters["param-vfm1"] == "value-vfm1" + assert so_service_vnf_1_vf_module_2.model_name == "base" + assert so_service_vnf_1_vf_module_2.instance_name == "myfirstvfm" + assert so_service_vnf_1_vf_module_2.processing_priority == 1 + assert len(so_service_vnf_1_vf_module_2.parameters) == 1 + assert so_service_vnf_1_vf_module_2.parameters["param-vfm1"] == "value-vfm1" + + assert so_service_vnf_2.model_name == "myvfmodel" + assert so_service_vnf_2.instance_name == "mysecondvnf" + assert so_service_vnf_2.processing_priority == 2 + assert len(so_service_vnf_2.parameters) == 1 + assert so_service_vnf_2.parameters["param1"] == "value1" + assert len(so_service_vnf_2.vf_modules) == 2 + + so_service_vnf_1_vf_module_1 = so_service_vnf_2.vf_modules[0] + so_service_vnf_1_vf_module_2 = so_service_vnf_2.vf_modules[1] + + assert so_service_vnf_1_vf_module_1.model_name == "base" + assert so_service_vnf_1_vf_module_1.instance_name == "myfirstvfm" + assert so_service_vnf_1_vf_module_1.processing_priority == 1 + assert len(so_service_vnf_1_vf_module_1.parameters) == 1 + assert so_service_vnf_1_vf_module_1.parameters["param-vfm1"] == "value-vfm1" + assert so_service_vnf_1_vf_module_2.model_name == "base" + assert so_service_vnf_1_vf_module_2.instance_name == "mysecondvfm" + assert so_service_vnf_1_vf_module_2.processing_priority == 2 + assert len(so_service_vnf_1_vf_module_2.parameters) == 1 + assert so_service_vnf_1_vf_module_2.parameters["param-vfm1"] == "value-vfm1" + + +def test_so_service_vnf_load_from_yaml(): + + so_vnf_yaml = """ + model_name: myvnfmodel + instance_name: mynewvnf + parameters: + param1: value1 + vf_modules: + - instance_name: myfirstvfm + model_name: base + processing_priority: 1 + parameters: + param-vfm1: value-vfm1 + - instance_name: mysecondvfm + model_name: second_base + processing_priority: 2 + parameters: + param-vfm2: value-vfm2 + param-vfm3: value-vfm3 + """ + + so_vnf = SoServiceVnf.load(yaml.safe_load(so_vnf_yaml)) + assert so_vnf.model_name == "myvnfmodel" + assert so_vnf.instance_name == "mynewvnf" + + assert len(so_vnf.parameters) == 1 + assert so_vnf.parameters["param1"] == "value1" + + assert len(so_vnf.vf_modules) == 2 + so_vnf_vf_module_1 = so_vnf.vf_modules[0] + so_vnf_vf_module_2 = so_vnf.vf_modules[1] + + assert so_vnf_vf_module_1.model_name == "base" + assert so_vnf_vf_module_1.instance_name == "myfirstvfm" + assert so_vnf_vf_module_1.processing_priority == 1 + assert len(so_vnf_vf_module_1.parameters) == 1 + assert so_vnf_vf_module_1.parameters["param-vfm1"] == "value-vfm1" + + assert so_vnf_vf_module_2.model_name == "second_base" + assert so_vnf_vf_module_2.instance_name == "mysecondvfm" + assert so_vnf_vf_module_2.processing_priority == 2 + assert len(so_vnf_vf_module_2.parameters) == 2 + assert so_vnf_vf_module_2.parameters["param-vfm2"] == "value-vfm2" + assert so_vnf_vf_module_2.parameters["param-vfm3"] == "value-vfm3" diff --git a/tests/test_so_orchestration_request.py b/tests/test_so_orchestration_request.py new file mode 100644 index 0000000..c93189b --- /dev/null +++ b/tests/test_so_orchestration_request.py @@ -0,0 +1,103 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.so.so_element import OrchestrationRequest, SoElement +from onapsdk.utils.headers_creator import headers_so_creator +from onapsdk.onap_service import OnapService + + +IN_PROGRESS = { + "request": { + "requestStatus": { + "requestState": "IN_PROGRESS" + } + } +} +FAILED = { + "request": { + "requestStatus": { + "requestState": "FAILED" + } + } +} +COMPLETE = { + "request": { + "requestStatus": { + "requestState": "COMPLETE" + } + } +} +UNKNOWN = { + "request": { + "requestStatus": { + "requestState": "INVALID" + } + } +} +BAD_RESPONSE = {} + + +@mock.patch.object(OrchestrationRequest, "send_message_json") +def test_orchestration_request_status(mock_send_message): + orchestration_req = OrchestrationRequest(request_id="test") + + mock_send_message.return_value = BAD_RESPONSE + assert orchestration_req.status == OrchestrationRequest.StatusEnum.UNKNOWN + + mock_send_message.return_value = UNKNOWN + assert orchestration_req.status == OrchestrationRequest.StatusEnum.UNKNOWN + + mock_send_message.return_value = FAILED + assert orchestration_req.status == OrchestrationRequest.StatusEnum.FAILED + + mock_send_message.return_value = COMPLETE + assert orchestration_req.status == OrchestrationRequest.StatusEnum.COMPLETED + + mock_send_message.return_value = IN_PROGRESS + assert orchestration_req.status == OrchestrationRequest.StatusEnum.IN_PROGRESS + assert not orchestration_req.finished + assert not orchestration_req.completed + assert not orchestration_req.failed + + mock_send_message.return_value = COMPLETE + assert orchestration_req.finished + assert orchestration_req.completed + assert not orchestration_req.failed + + mock_send_message.return_value = FAILED + assert orchestration_req.finished + assert not orchestration_req.completed + assert orchestration_req.failed + + +#Test the Class SoElement +def test_SoElement_headers(): + """Test the header property""" + element = SoElement() + assert element.headers != headers_so_creator(OnapService.headers) + #check x-transactionid for headers + + +def test_get_subscription_service_type(): + """Test SO Element class method""" + vf_object_name = SoElement.get_subscription_service_type("vf_name") + assert vf_object_name == "vf_name" + + +def test_base_create_url(): + """Test base create url class method""" + assert SoElement._base_create_url() == "{}/onap/so/infra/serviceInstantiation/{}/serviceInstances".\ + format(SoElement.base_url, SoElement.api_version) + diff --git a/tests/test_sp_partner.py b/tests/test_sp_partner.py new file mode 100644 index 0000000..73a4839 --- /dev/null +++ b/tests/test_sp_partner.py @@ -0,0 +1,84 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +from onapsdk.aai.business import SpPartner +from onapsdk.exceptions import ResourceNotFound + + +SP_PARTNERS = { + "sp-partner":[ + { + "sp-partner-id":"ff6c945f-89ab-4f14-bafd-0cdd6eac791a", + "url":"http://127.0.0.1", + "resource-version":"1588244348931", + }, + { + "sp-partner-id":"OE-generic", + "callsource":"test-callsource", + "resource-version":"1587388597761" + }, + { + "sp-partner-id":"b3dcdbb0-edae-4384-b91e-2f114472520c", + "url":"http://127.0.0.1", + "callsource":"test-callsource", + "operational-status":"test-operational-status", + "model-customization-id":"test-model-customization-id", + "model-invariant-id":"test-model-invariant-id", + "model-version-id":"test-model-version-id", + "resource-version":"1588145971158" + } + ] +} + + +SP_PARTNER = { + "sp-partner-id":"blablabla", + "url":"http://127.0.0.1", + "callsource":"test-callsource", + "resource-version":"1587388597761" +} + + +@mock.patch.object(SpPartner, "send_message_json") +def test_sp_partner_get_all(mock_send): + mock_send.return_value = SP_PARTNERS + owning_entities = list(SpPartner.get_all()) + assert len(owning_entities) == 3 + sp_partner = owning_entities[0] + assert sp_partner.sp_partner_id == "ff6c945f-89ab-4f14-bafd-0cdd6eac791a" + assert sp_partner.sp_partner_url == "http://127.0.0.1" + assert sp_partner.url == (f"{sp_partner.base_url}{sp_partner.api_version}/" + "business/sp-partners/sp-partner/" + f"{sp_partner.sp_partner_id}") + + +@mock.patch.object(SpPartner, "send_message_json") +def test_sp_partner_get_by_sp_partner_id(mock_send): + mock_send.return_value = SP_PARTNER + sp_partner = SpPartner.get_by_sp_partner_id("blablabla") + assert sp_partner.sp_partner_id == "blablabla" + + +@mock.patch.object(SpPartner, "send_message") +@mock.patch.object(SpPartner, "get_by_sp_partner_id") +def test_sp_partner_create(_, mock_send): + + SpPartner.create( + sp_partner_id="123" + ) + mock_send.assert_called_once_with("PUT", + "Declare A&AI sp partner", + "https://aai.api.sparky.simpledemo.onap.org:30233/aai/v23/business/sp-partners/sp-partner/123", + data='{\n "sp-partner-id": "123"\n \n \n \n \n \n \n}') diff --git a/tests/test_subnet.py b/tests/test_subnet.py new file mode 100644 index 0000000..cc7eced --- /dev/null +++ b/tests/test_subnet.py @@ -0,0 +1,49 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 pytest +from onapsdk.exceptions import ParameterError + +from onapsdk.so.instantiation import Subnet + + +def test_dhcp_subnet(): + with pytest.raises(ParameterError): + Subnet(name="test", + role="test", + start_address="192.168.8.0", + gateway_address="192.168.8.1", + dhcp_enabled="sss" + ) + with pytest.raises(ParameterError): + Subnet(name="test", + role="test", + start_address="192.168.8.0", + gateway_address="192.168.8.1", + dhcp_enabled="Y" + ) + subnet = Subnet(name="test", + role="test", + start_address="192.168.8.0", + gateway_address="192.168.8.1", + dhcp_enabled="Y", + dhcp_start_address="10.8.1.0", + dhcp_end_address="10.8.1.1" + ) + assert subnet.name == "test" + assert subnet.role == "test" + assert subnet.start_address == "192.168.8.0" + assert subnet.gateway_address == "192.168.8.1" + assert subnet.dhcp_enabled == "Y" + assert subnet.dhcp_start_address == "10.8.1.0" + assert subnet.dhcp_end_address == "10.8.1.1" diff --git a/tests/test_tosca_file_handler.py b/tests/test_tosca_file_handler.py new file mode 100644 index 0000000..e288693 --- /dev/null +++ b/tests/test_tosca_file_handler.py @@ -0,0 +1,88 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 logging +import json +import oyaml as yaml +import os +import os.path +import unittest + +from onapsdk.exceptions import ValidationError +import onapsdk.utils.tosca_file_handler as tosca_file_handler + + +__author__ = "Morgan Richomme " + + +class ToscaFileHandlerTestingBase(unittest.TestCase): + + """The super class which testing classes could inherit.""" + + logging.disable(logging.CRITICAL) + + _root_path = os.getcwd().rsplit('/onapsdk')[0] + _foo_path = _root_path +"/tests/data/service-Ubuntu16-template.yml" + + + def setUp(self): + pass + + def test_get_parameter_from_yaml(self): + with open(self._foo_path) as f: + model = json.dumps(yaml.safe_load(f)) + param = tosca_file_handler.get_parameter_from_yaml( + "metadata", model) + self.assertEqual(param['name'], "ubuntu16") + + def test_get_wrong_parameter_from_yaml(self): + with open(self._foo_path) as f: + model = json.dumps(yaml.safe_load(f)) + with self.assertRaises(ValidationError): + tosca_file_handler.get_parameter_from_yaml( + "wrong_parameter", model) + + def test_get_parameter_from_wrong_yaml(self): + with self.assertRaises(FileNotFoundError): + with open("wrong_path") as f: + model = json.dumps(yaml.safe_load(f)) + tosca_file_handler.get_parameter_from_yaml( + "metadata", model) + + def test_get_random_string_generator(self): + self.assertEqual( + len(tosca_file_handler.random_string_generator()), 6) + + def test_get_vf_list_from_tosca_file(self): + with open(self._foo_path) as f: + model = json.dumps(yaml.safe_load(f)) + vf_list = tosca_file_handler.get_vf_list_from_tosca_file(model) + self.assertEqual(vf_list[0], 'ubuntu16_VF') + + def test_get_modules_list_from_tosca_file(self): + with open(self._foo_path) as f: + model = json.dumps(yaml.safe_load(f)) + vf_modules = tosca_file_handler.get_modules_list_from_tosca_file(model) + self.assertEqual(len(vf_modules), 1) + + # def get_vf_list_from_tosca_file_wrong_model(self): + # with self.assertRaises(FileNotFoundError): + # tosca_file_handler.get_vf_list_from_tosca_file( + # self._root_path + "wrong_path") + +if __name__ == "__main__": + # logging must be disabled else it calls time.time() + # what will break these unit tests. + logging.disable(logging.CRITICAL) + unittest.main(verbosity=2) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..62bffde --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,46 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 pytest +import time + +from onapsdk.onap_service import OnapService +from onapsdk.utils.mixins import WaitForFinishMixin +from onapsdk.utils import load_json_file + + +class TestWaitForFinish(WaitForFinishMixin, OnapService): + + @property + def completed(self): + return True + + @property + def finished(self): + time.sleep(0.1) + return True + + +def test_wait_for_finish_timeout(): + t = TestWaitForFinish() + with pytest.raises(TimeoutError): + t.wait_for_finish(timeout=0.01) + t.wait_for_finish() + + +def test_load_json_file(): + path_to_event: str = os.path.join(os.getcwd(), "tests/data/utils_load_json_file_test.json") + test_json: str = load_json_file(path_to_event) + assert test_json == '{"event": {"test1": "val1"}}' diff --git a/tests/test_vendor.py b/tests/test_vendor.py new file mode 100644 index 0000000..78c4bad --- /dev/null +++ b/tests/test_vendor.py @@ -0,0 +1,316 @@ +"""Test vendor module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock + +import pytest +from onapsdk.exceptions import RequestError + +from onapsdk.sdc.vendor import Vendor +import onapsdk.constants as const +from onapsdk.sdc.sdc_element import SdcElement + +@mock.patch.object(Vendor, 'send_message_json') +def test_get_all_no_vendors(mock_send): + """Returns empty array if no vendors.""" + mock_send.return_value = {} + assert Vendor.get_all() == [] + mock_send.assert_called_once_with("GET", 'get Vendors', 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-license-models') + +@mock.patch.object(Vendor, 'send_message_json') +def test_get_all_some_vendors(mock_send): + """Returns a list of vendors.""" + mock_send.return_value = {'results':[ + {'name': 'one', 'id': '1234'}, + {'name': 'two', 'id': '1235'}]} + assert len(Vendor.get_all()) == 2 + vendor_1 = Vendor.get_all()[0] + assert vendor_1.name == "one" + assert vendor_1.identifier == "1234" + assert vendor_1.created() == True + vendor_2 = Vendor.get_all()[1] + assert vendor_2.name == "two" + assert vendor_2.identifier == "1235" + assert vendor_2.created() + mock_send.assert_called_with("GET", 'get Vendors', 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-license-models') + +@mock.patch.object(Vendor, 'exists') +def test_init_no_name(mock_exists): + """Check init with no names.""" + mock_exists.return_value = False + vendor = Vendor() + assert isinstance(vendor, SdcElement) + assert vendor._identifier == None + assert vendor._version == None + assert vendor.name == "Generic-Vendor" + assert vendor.created() == False + assert vendor.headers["USER_ID"] == "cs0008" + assert isinstance(vendor._base_url(), str) + assert "sdc1/feProxy/onboarding-api/v1.0" in vendor._base_url() + +def test_init_with_name(): + """Check init with no names.""" + vendor = Vendor(name="YOLO") + assert vendor._identifier == None + assert vendor._version == None + assert vendor.name == "YOLO" + assert vendor.headers["USER_ID"] == "cs0008" + assert isinstance(vendor._base_url(), str) + assert "sdc1/feProxy/onboarding-api/v1.0" in vendor._base_url() + +def test_equality_really_equals(): + """Check two Vendors are equals if name is the same.""" + vendor_1 = Vendor(name="equal") + vendor_1.identifier = "1234" + vendor_2 = Vendor(name="equal") + vendor_2.identifier = "1235" + assert vendor_1 == vendor_2 + +def test_equality_not_equals(): + """Check two Vendors are not equals if name is not the same.""" + vendor_1 = Vendor(name="equal") + vendor_1.identifier = "1234" + vendor_2 = Vendor(name="not_equal") + vendor_2.identifier = "1234" + assert vendor_1 != vendor_2 + +def test_equality_not_equals_not_same_object(): + """Check a Vendor and something different are not equals.""" + vendor_1 = Vendor(name="equal") + vendor_1.identifier = "1234" + vendor_2 = "equal" + assert vendor_1 != vendor_2 + +@mock.patch.object(Vendor, 'get_all') +def test_exists_not_exists(mock_get_all): + """Return False if vendor doesn't exist in SDC.""" + vendor_1 = Vendor(name="one") + vendor_1.identifier = "1234" + mock_get_all.return_value = [vendor_1] + vendor = Vendor(name="two") + assert not vendor.exists() + +@mock.patch.object(Vendor, 'get_all') +def test_exists_exists(mock_get_all): + """Return True if vendor exists in SDC.""" + vendor_1 = Vendor(name="one") + vendor_1.identifier = "1234" + vendor_1.version = "1.1" + mock_get_all.return_value = [vendor_1] + vendor = Vendor(name="one") + assert vendor.exists() + +@mock.patch.object(Vendor, 'get_all') +@mock.patch.object(Vendor, 'send_message_json') +def test_load_created(mock_send, mock_get_all): + mock_send.return_value = {'results': + [{'status': 'state_one', 'id': "5678", "name": "1.0"}], "listCount": 1} + vendor = Vendor(name="one") + vendor.identifier = "1234" + vendor.load() + mock_get_all.assert_not_called() + mock_send.assert_called_once_with('GET', 'get item', 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/items/1234/versions') + assert vendor.status == "state_one" + assert vendor.version == "5678" + +@mock.patch.object(Vendor, 'get_all') +@mock.patch.object(Vendor, 'send_message_json') +def test_load_not_created(mock_send, mock_get_all): + mock_send.return_value = {'results': + [{'status': 'state_one', 'id': "5678", "name": "1.0"}], "listCount": 1} + vendor = Vendor(name="one") + vendor.load() + mock_get_all.return_value = [] + mock_send.assert_not_called() + assert vendor._status == None + assert vendor.version == None + assert vendor._identifier == None + +@mock.patch.object(Vendor, 'exists') +@mock.patch.object(Vendor, 'send_message_json') +def test_create_already_exists(mock_send, mock_exists): + """Do nothing if already created in SDC.""" + vendor = Vendor() + mock_exists.return_value = True + vendor.create() + mock_send.assert_not_called() + +@mock.patch.object(Vendor, 'exists') +@mock.patch.object(Vendor, 'send_message_json') +def test_create_issue_in_creation(mock_send, mock_exists): + """Do nothing if not created but issue during creation.""" + vendor = Vendor() + expected_data = '{\n "iconRef": "icon",\n "vendorName": "Generic-Vendor",\n "description": "vendor"\n}' + mock_exists.return_value = False + mock_send.side_effect = RequestError + with pytest.raises(RequestError) as exc: + vendor.create() + mock_send.assert_called_once_with("POST", "create Vendor", mock.ANY, data=expected_data) + assert vendor.created() == False + +@mock.patch.object(Vendor, 'exists') +@mock.patch.object(Vendor, 'send_message_json') +def test_create_OK(mock_send, mock_exists): + """Create and update object.""" + vendor = Vendor() + expected_data = '{\n "iconRef": "icon",\n "vendorName": "Generic-Vendor",\n "description": "vendor"\n}' + mock_exists.return_value = False + mock_send.return_value = { + 'itemId': "1234", + 'version': {'id': "5678", 'status': 'state_created'}} + vendor.create() + mock_send.assert_called_once_with("POST", "create Vendor", mock.ANY, data=expected_data) + assert vendor.status == const.DRAFT + assert vendor.identifier == "1234" + assert vendor.version == "5678" + +@mock.patch.object(Vendor, 'exists') +@mock.patch.object(Vendor, 'load') +@mock.patch.object(Vendor, 'send_message') +def test_submit_already_certified(mock_send, mock_load, mock_exists): + """Do nothing if already certified.""" + mock_exists.return_value = True + vendor = Vendor() + vendor._status = const.CERTIFIED + vendor.submit() + mock_send.assert_not_called() + +@mock.patch.object(Vendor, 'exists') +@mock.patch.object(Vendor, 'load') +@mock.patch.object(Vendor, 'send_message') +def test_submit_not_created(mock_send, mock_load, mock_exists): + """Do nothing if not created.""" + mock_exists.return_value = False + vendor = Vendor() + vendor.submit() + mock_send.assert_not_called() + +@mock.patch.object(Vendor, 'exists') +@mock.patch.object(Vendor, 'load') +@mock.patch.object(Vendor, 'send_message') +def test_submit_certified_NOK(mock_send, mock_load, mock_exists): + """Don't update status if submission NOK.""" + mock_exists.return_value = True + vendor = Vendor() + vendor._identifier = "12345" + mock_send.side_effect = RequestError + expected_data = '{\n\n "action": "Submit"\n}' + vendor._status = "Draft" + vendor._version = "1234" + with pytest.raises(RequestError) as err: + vendor.submit() + assert err.type == RequestError + mock_send.assert_called_once_with("PUT", "Submit Vendor", 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-license-models/12345/versions/1234/actions', data=expected_data) + assert vendor._status != const.CERTIFIED + +@mock.patch.object(Vendor, 'exists') +@mock.patch.object(Vendor, 'load') +@mock.patch.object(Vendor, 'send_message') +def test_submit_certified_OK(mock_send, mock_load, mock_exists): + """Set status to CERTIFIED if submission OK.""" + mock_exists.return_value = True + vendor = Vendor() + vendor._status = "Draft" + vendor._version = "1234" + vendor.identifier = "12345" + mock_send.return_value = mock.Mock() + expected_data = '{\n\n "action": "Submit"\n}' + vendor.submit() + mock_send.assert_called_once_with("PUT", "Submit Vendor", 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-license-models/12345/versions/1234/actions', data=expected_data) + assert vendor.status == const.CERTIFIED + +@mock.patch.object(Vendor, 'created') +@mock.patch.object(Vendor, 'load') +def test_version_no_load_no_created(mock_load, mock_created): + mock_created.return_value = False + vendor = Vendor() + assert vendor.version == None + mock_load.assert_not_called() + +@mock.patch.object(Vendor, 'created') +@mock.patch.object(Vendor, 'load') +def test_version_no_load_created(mock_load, mock_created): + mock_created.return_value = True + vendor = Vendor() + vendor._version = "64" + assert vendor.version == "64" + mock_load.assert_not_called() + +@mock.patch.object(Vendor, 'load') +def test_version_with_load(mock_load): + vendor = Vendor() + vendor.identifier = "12345" + assert vendor.version == None + mock_load.assert_called_once() + +@mock.patch.object(Vendor, 'created') +@mock.patch.object(Vendor, 'load') +def test_status_no_load_no_created(mock_load, mock_created): + mock_created.return_value = False + vendor = Vendor() + assert vendor.status == None + mock_load.assert_not_called() + +@mock.patch.object(Vendor, 'created') +@mock.patch.object(Vendor, 'load') +def test_status_no_load_created(mock_load, mock_created): + mock_created.return_value = True + vendor = Vendor() + vendor.identifier = "12345" + vendor._status = "Draft" + assert vendor.status == "Draft" + mock_load.assert_not_called() + +@mock.patch.object(Vendor, 'load') +def test_status_with_load(mock_load): + vendor = Vendor() + vendor.identifier = "12345" + assert vendor.status == None + mock_load.assert_called_once() + +@mock.patch.object(Vendor, 'submit') +@mock.patch.object(Vendor, 'create') +def test_onboard_new_vendor(mock_create, mock_submit): + getter_mock = mock.Mock(wraps=Vendor.status.fget) + mock_status = Vendor.status.getter(getter_mock) + with mock.patch.object(Vendor, 'status', mock_status): + getter_mock.side_effect = [None, const.CERTIFIED, const.CERTIFIED] + vendor = Vendor() + vendor.onboard() + mock_create.assert_called_once() + mock_submit.assert_not_called() + +@mock.patch.object(Vendor, 'submit') +@mock.patch.object(Vendor, 'create') +def test_onboard_created_vendor(mock_create, mock_submit): + getter_mock = mock.Mock(wraps=Vendor.status.fget) + mock_status = Vendor.status.getter(getter_mock) + with mock.patch.object(Vendor, 'status', mock_status): + getter_mock.side_effect = [const.DRAFT, const.DRAFT, None] + vendor = Vendor() + vendor.onboard() + mock_submit.assert_called_once() + mock_create.assert_not_called() + +@mock.patch.object(Vendor, 'submit') +@mock.patch.object(Vendor, 'create') +def test_onboard_whole_vendor(mock_create, mock_submit): + getter_mock = mock.Mock(wraps=Vendor.status.fget) + mock_status = Vendor.status.getter(getter_mock) + with mock.patch.object(Vendor, 'status', mock_status): + getter_mock.side_effect = [None, const.DRAFT, const.DRAFT, None] + vendor = Vendor() + vendor.onboard() + mock_submit.assert_called_once() + mock_create.assert_called_once() diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 0000000..589f949 --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,20 @@ +"""Test version module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 onapsdk.version as version + +def test_version(): + """Check version is the right one.""" + assert version.__version__ == '10.2.0' diff --git a/tests/test_ves.py b/tests/test_ves.py new file mode 100644 index 0000000..17d71d5 --- /dev/null +++ b/tests/test_ves.py @@ -0,0 +1,56 @@ +# Copyright 2022 Orange, Deutsche Telekom AG, Nokia +# +# 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 unittest.mock import patch +import json + +from onapsdk.ves.ves import Ves, ACTION, POST_HTTP_METHOD + +VERSION = "v7" + +VES_URL = f"http://ves.api.simpledemo.onap.org:30417/eventListener/{VERSION}" +VES_BATCH_URL = f"http://ves.api.simpledemo.onap.org:30417/eventListener/{VERSION}/eventBatch" + +TEST_EVENT = '{"event": {"test": "val"}}' +BASIC_AUTH = {'username': 'dcae@dcae.onap.org', 'password': 'demo123456!'} + + +@patch.object(Ves, "send_message") +def test_should_send_event_to_ves_service(send_message_mock): + # given + + # when + Ves.send_event(VERSION, TEST_EVENT, BASIC_AUTH) + + # then + verify_that_event_was_send_to_ves(TEST_EVENT, send_message_mock, VES_URL) + + +@patch.object(Ves, "send_message") +def test_should_send_event_batch_to_ves_service(send_message_mock): + # given + + # when + Ves.send_batch_event(VERSION, TEST_EVENT, BASIC_AUTH) + + # then + verify_that_event_was_send_to_ves(TEST_EVENT, send_message_mock, VES_BATCH_URL) + + +def verify_that_event_was_send_to_ves(expected_event, send_message_mock, ves_url): + send_message_mock.assert_called_once_with( + POST_HTTP_METHOD, ACTION, ves_url, + basic_auth=BASIC_AUTH, + json=json.loads(expected_event) + ) + send_message_mock.return_value = None diff --git a/tests/test_vf.py b/tests/test_vf.py new file mode 100644 index 0000000..540a377 --- /dev/null +++ b/tests/test_vf.py @@ -0,0 +1,564 @@ +"""Test vf module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 time +from unittest import mock +from unittest.mock import MagicMock +from pathlib import Path + +import pytest + +import onapsdk.constants as const +from onapsdk.exceptions import ParameterError, StatusError, RequestError, ValidationError +from onapsdk.sdc.category_management import ResourceCategory +from onapsdk.sdc.properties import ComponentProperty, NestedInput, Property +from onapsdk.sdc.sdc_resource import SdcResource +from onapsdk.sdc.vf import Vf +from onapsdk.sdc.vsp import Vsp +from onapsdk.sdc.vsp import Vendor + + +@mock.patch.object(Vf, 'send_message_json') +def test_get_all_no_vf(mock_send): + """Returns empty array if no vfs.""" + mock_send.return_value = {} + assert Vf.get_all() == [] + mock_send.assert_called_once_with("GET", 'get Vfs', 'https://sdc.api.be.simpledemo.onap.org:30204/sdc/v1/catalog/resources?resourceType=VF') + + +@mock.patch.object(Vf, 'send_message_json') +def test_get_all_some_vfs(mock_send): + """Returns a list of vf.""" + mock_send.return_value = [ + {'resourceType': 'VF', 'name': 'one', 'uuid': '1234', 'invariantUUID': '5678', 'version': '1.0', 'lifecycleState': 'CERTIFIED', 'category': 'Generic', "subCategory": "Abstract"}, + {'resourceType': 'VF', 'name': 'two', 'uuid': '1235', 'invariantUUID': '5679', 'version': '1.0', 'lifecycleState': 'NOT_CERTIFIED_CHECKOUT', 'category': 'Generic', "subCategory": "Abstract"}] + all_vfs = Vf.get_all() + assert len(all_vfs) == 2 + vf_1 = all_vfs[0] + assert vf_1.name == "one" + assert vf_1.identifier == "1234" + assert vf_1.unique_uuid == "5678" + assert vf_1.version == "1.0" + assert vf_1.status == const.CERTIFIED + assert vf_1.created() + vf_2 = all_vfs[1] + assert vf_2.name == "two" + assert vf_2.identifier == "1235" + assert vf_2.unique_uuid == "5679" + assert vf_2.status == const.DRAFT + assert vf_2.version == "1.0" + assert vf_2.created() + mock_send.assert_called_once_with("GET", 'get Vfs', 'https://sdc.api.be.simpledemo.onap.org:30204/sdc/v1/catalog/resources?resourceType=VF') + + +def test_init_no_name(): + """Check init with no names.""" + vf = Vf() + assert isinstance(vf, SdcResource) + assert vf._identifier is None + assert vf._version is None + assert vf.name == "ONAP-test-VF" + assert vf.headers["USER_ID"] == "cs0008" + assert vf.vsp is None + assert isinstance(vf._base_url(), str) + +@mock.patch.object(Vf, 'exists') +def test_init_with_name(mock_exists): + """Check init with no names.""" + mock_exists.return_value = False + vf = Vf(name="YOLO") + assert vf._identifier == None + assert vf._version == None + assert vf.name == "YOLO" + assert vf.created() == False + assert vf.headers["USER_ID"] == "cs0008" + assert vf.vsp == None + assert isinstance(vf._base_url(), str) + + +def test_equality_really_equals(): + """Check two vfs are equals if name is the same.""" + vf_1 = Vf(name="equal") + vf_1.identifier = "1234" + vf_2 = Vf(name="equal") + vf_2.identifier = "1235" + assert vf_1 == vf_2 + + +def test_equality_not_equals(): + """Check two vfs are not equals if name is not the same.""" + vf_1 = Vf(name="equal") + vf_1.identifier = "1234" + vf_2 = Vf(name="not_equal") + vf_2.identifier = "1234" + assert vf_1 != vf_2 + + +def test_equality_not_equals_not_same_object(): + """Check a vf and something different are not equals.""" + vf_1 = Vf(name="equal") + vf_1.identifier = "1234" + vf_2 = SdcResource() + vf_2.name = "equal" + assert vf_1 != vf_2 + + +@mock.patch.object(Vf, 'get_all') +def test_exists_not_exists(mock_get_all): + """Return False if vf doesn't exist in SDC.""" + vf_1 = Vf(name="one") + vf_1.identifier = "1234" + mock_get_all.return_value = [vf_1] + vf = Vf(name="two") + assert not vf.exists() + + +@mock.patch.object(Vf, 'get_all') +def test_exists_exists(mock_get_all): + """Return True if vf exists in SDC.""" + vf_1 = Vf(name="one") + vf_1.identifier = "1234" + vf_1.unique_uuid = "5689" + vf_1.unique_identifier = "71011" + vf_1.status = const.DRAFT + vf_1.version = "1.1" + mock_get_all.return_value = [vf_1] + vf = Vf(name="one") + assert vf.exists() + assert vf.identifier == "1234" + assert vf.unique_uuid == "5689" + assert vf.unique_identifier == "71011" + assert vf.status == const.DRAFT + assert vf.version == "1.1" + + +@mock.patch.object(Vf, 'exists') +def test_load_created(mock_exists): + """Load is a wrapper around exists().""" + vf = Vf(name="one") + vf.load() + mock_exists.assert_called_once() + + +@mock.patch.object(Vf, 'exists') +@mock.patch.object(Vf, 'send_message_json') +def test_create_no_vsp(mock_send, mock_exists): + """Do nothing if no vsp.""" + vf = Vf() + mock_exists.return_value = False + with pytest.raises(ParameterError) as err: + vf.create() + assert err.type == ParameterError + assert str(err.value) == "At least vsp or vendor needs to be given" + + +@mock.patch.object(Vf, 'exists') +@mock.patch.object(Vf, 'send_message_json') +@mock.patch.object(Vf, "category", new_callable=mock.PropertyMock) +def test_create_already_exists(mock_category, mock_send, mock_exists): + """Do nothing if already created in SDC.""" + vf = Vf(vendor=MagicMock()) + vsp = Vsp() + vsp._identifier = "1232" + vf.vsp = vsp + mock_exists.return_value = True + vf.create() + mock_send.assert_not_called() + +@mock.patch.object(Vf, 'exists') +@mock.patch.object(Vf, 'send_message_json') +@mock.patch.object(Vf, "category", new_callable=mock.PropertyMock) +def test_create_issue_in_creation(mock_category, mock_send, mock_exists): + """Do nothing if not created but issue during creation.""" + vf = Vf() + vsp = Vsp() + vendor = Vendor() + vsp._identifier = "1232" + vsp.create_csar = MagicMock(return_value=True) + vsp.vendor = vendor + vf.vsp = vsp + expected_data = '{\n "artifacts": {},\n "attributes": [],\n "capabilities": {},\n "categories": [\n {\n "normalizedName": "generic",\n "name": "Generic",\n "uniqueId": "resourceNewCategory.generic",\n "subcategories": [{"empty": false, "groupings": null, "icons": ["objectStorage", "compute"], "name": "Abstract", "normalizedName": "abstract", "ownerId": null, "type": null, "uniqueId": "resourceNewCategory.generic.abstract", "version": null}],\n "version": null,\n "ownerId": null,\n "empty": false,\n "type": null,\n "icons": null\n }\n ],\n "componentInstances": [],\n "componentInstancesAttributes": {},\n "componentInstancesProperties": {},\n "componentType": "RESOURCE",\n "contactId": "cs0008",\n \n "csarUUID": "None",\n "csarVersion": "1.0",\n \n "deploymentArtifacts": {},\n "description": "VF",\n "icon": "defaulticon",\n "name": "ONAP-test-VF",\n "properties": [],\n "groups": [],\n "requirements": {},\n "resourceType": "VF",\n "tags": ["ONAP-test-VF"],\n "toscaArtifacts": {},\n "vendorName": "Generic-Vendor",\n "vendorRelease": "1.0"\n}' + mock_exists.return_value = False + mock_send.side_effect = RequestError + rc = ResourceCategory( + name="Generic" + ) + rc.normalized_name="generic" + rc.unique_id="resourceNewCategory.generic" + rc.subcategories=[{"empty": False, "groupings": None, "icons": ["objectStorage", "compute"], "name": "Abstract", "normalizedName": "abstract", "ownerId": None, "type": None, "uniqueId": "resourceNewCategory.generic.abstract", "version": None}] + rc.version=None + rc.owner_id=None + rc.empty=False + rc.type=None + rc.icons=None + mock_category.return_value = rc + with pytest.raises(RequestError) as exc: + vf.create() + mock_send.assert_called_once_with("POST", "create Vf", 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/resources', data=expected_data) + assert not vf.created() + + +@mock.patch.object(Vf, 'exists') +@mock.patch.object(Vf, 'send_message_json') +@mock.patch.object(Vf, "category", new_callable=mock.PropertyMock) +def test_create_OK(mock_category, mock_send, mock_exists): + """Create and update object.""" + vf = Vf() + vsp = Vsp() + vendor = Vendor() + vsp._identifier = "1232" + vf.vsp = vsp + vsp.vendor = vendor + vsp._csar_uuid = "1234" + expected_data = '{\n "artifacts": {},\n "attributes": [],\n "capabilities": {},\n "categories": [\n {\n "normalizedName": "generic",\n "name": "Generic",\n "uniqueId": "resourceNewCategory.generic",\n "subcategories": [{"empty": false, "groupings": null, "icons": ["objectStorage", "compute"], "name": "Abstract", "normalizedName": "abstract", "ownerId": null, "type": null, "uniqueId": "resourceNewCategory.generic.abstract", "version": null}],\n "version": null,\n "ownerId": null,\n "empty": false,\n "type": null,\n "icons": null\n }\n ],\n "componentInstances": [],\n "componentInstancesAttributes": {},\n "componentInstancesProperties": {},\n "componentType": "RESOURCE",\n "contactId": "cs0008",\n \n "csarUUID": "1234",\n "csarVersion": "1.0",\n \n "deploymentArtifacts": {},\n "description": "VF",\n "icon": "defaulticon",\n "name": "ONAP-test-VF",\n "properties": [],\n "groups": [],\n "requirements": {},\n "resourceType": "VF",\n "tags": ["ONAP-test-VF"],\n "toscaArtifacts": {},\n "vendorName": "Generic-Vendor",\n "vendorRelease": "1.0"\n}' + mock_exists.return_value = False + mock_send.return_value = {'resourceType': 'VF', 'name': 'one', 'uuid': '1234', 'invariantUUID': '5678', 'version': '1.0', 'uniqueId': '91011', 'lifecycleState': 'NOT_CERTIFIED_CHECKOUT'} + rc = ResourceCategory( + name="Generic" + ) + rc.normalized_name="generic" + rc.unique_id="resourceNewCategory.generic" + rc.subcategories=[{"empty": False, "groupings": None, "icons": ["objectStorage", "compute"], "name": "Abstract", "normalizedName": "abstract", "ownerId": None, "type": None, "uniqueId": "resourceNewCategory.generic.abstract", "version": None}] + rc.version=None + rc.owner_id=None + rc.empty=False + rc.type=None + rc.icons=None + mock_category.return_value = rc + vf.create() + mock_send.assert_called_once_with("POST", "create Vf", 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/resources', data=expected_data) + assert vf.created() + assert vf._status == const.DRAFT + assert vf.identifier == "1234" + assert vf.unique_uuid == "5678" + assert vf.version == "1.0" + +@mock.patch.object(Vf, 'exists') +@mock.patch.object(Vf, 'load') +def test_version_no_load_no_created(mock_load, mock_exists): + """Test versions when not created.""" + mock_exists.return_value = False + vf = Vf() + assert vf.version is None + mock_load.assert_not_called() + +@mock.patch.object(Vf, 'load') +def test_version_no_load_created(mock_load): + """Test versions when created.""" + vf = Vf() + vf.identifier = "1234" + vf._version = "64" + assert vf.version == "64" + mock_load.assert_not_called() + + +@mock.patch.object(Vf, 'load') +def test_version_with_load(mock_load): + """Test versions when not created but with identifier.""" + vf = Vf() + vf.identifier = "1234" + assert vf.version is None + mock_load.assert_called_once() + +@mock.patch.object(Vf, 'exists') +@mock.patch.object(Vf, 'load') +def test_status_no_load_no_created(mock_load, mock_exists): + """Test status when not created.""" + mock_exists.return_value = False + vf = Vf() + assert vf.status is None + + +@pytest.mark.parametrize("status", [const.COMMITED, const.CERTIFIED, const.UPLOADED, const.VALIDATED]) +@mock.patch.object(Vf, 'exists') +@mock.patch.object(Vf, 'load') +@mock.patch.object(Vf, 'send_message') +def test_submit_not_Commited(mock_send, mock_load, mock_exists, status): + """Do nothing if not created.""" + mock_exists.return_value = False + vf = Vf() + vf._status = status + vf.submit() + mock_send.assert_not_called() + +@mock.patch.object(Vf, 'exists') +@mock.patch.object(Vf, 'load') +@mock.patch.object(Vf, 'send_message') +def test_submit_OK(mock_send, mock_load, mock_exists): + """Don't update status if submission NOK.""" + mock_exists.return_value = True + vf = Vf() + vf._status = const.COMMITED + expected_data = '{\n "userRemarks": "certify"\n}' + vf._version = "1234" + vf._unique_identifier = "12345" + vf.submit() + mock_send.assert_called_once_with( + "POST", "Certify Vf", + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/resources/12345/lifecycleState/Certify', + data=expected_data) + +@mock.patch.object(Vf, 'load') +@mock.patch.object(Vf, 'certify') +@mock.patch.object(Vf, 'submit') +@mock.patch.object(Vf, 'create') +@mock.patch.object(Vf, 'add_resource') +def test_onboard_new_vf(mock_add_resource, mock_create, mock_submit, mock_certify, mock_load): + getter_mock = mock.Mock(wraps=Vf.status.fget) + mock_status = Vf.status.getter(getter_mock) + with mock.patch.object(Vf, 'status', mock_status): + getter_mock.side_effect = [None, const.APPROVED, const.APPROVED, + const.APPROVED, const.APPROVED] + vsp = Vsp() + vf = Vf(vsp=vsp) + vf._time_wait = 0 + vf.onboard() + mock_create.assert_called_once() + mock_add_resource.assert_not_called() + mock_submit.assert_not_called() + mock_certify.assert_not_called() + mock_load.assert_not_called() + +@mock.patch.object(Vf, 'load') +@mock.patch.object(Vf, 'submit') +@mock.patch.object(Vf, 'create') +@mock.patch.object(Vf, 'add_resource') +@mock.patch.object(Vf, "certify") +def test_onboard_vf_submit(mock_certify, mock_add_resource, mock_create, mock_submit, mock_load): + getter_mock = mock.Mock(wraps=Vf.status.fget) + mock_status = Vf.status.getter(getter_mock) + with mock.patch.object(Vf, 'status', mock_status): + getter_mock.side_effect = [const.DRAFT, const.DRAFT, + const.CHECKED_IN, const.CHECKED_IN, const.CHECKED_IN, + const.APPROVED, const.APPROVED, const.APPROVED, const.APPROVED] + vf = Vf() + vf._time_wait = 0 + vf.onboard() + mock_create.assert_not_called() + mock_add_resource.assert_not_called() + mock_submit.assert_called_once() + mock_load.assert_not_called() + mock_certify.assert_called_once() + +@mock.patch.object(Vf, 'load') +@mock.patch.object(Vf, 'submit') +@mock.patch.object(Vf, 'create') +@mock.patch.object(Vf, 'add_resource') +@mock.patch.object(Vf, "certify") +def test_onboard_vf_load(mock_certify, mock_add_resource, mock_create, mock_submit, mock_load): + getter_mock = mock.Mock(wraps=Vf.status.fget) + mock_status = Vf.status.getter(getter_mock) + with mock.patch.object(Vf, 'status', mock_status): + getter_mock.side_effect = [const.CERTIFIED, const.CERTIFIED, + const.CERTIFIED, const.CERTIFIED, + const.APPROVED, const.APPROVED, + const.APPROVED, const.APPROVED] + vf = Vf() + vf._time_wait = 0 + vf.onboard() + mock_create.assert_not_called() + mock_add_resource.assert_not_called() + mock_submit.assert_not_called() + mock_load.assert_called_once() + mock_certify.assert_not_called() + +@mock.patch.object(Vf, 'load') +@mock.patch.object(Vf, 'submit') +@mock.patch.object(Vf, 'create') +@mock.patch.object(Vf, 'add_resource') +@mock.patch.object(Vf, "certify") +def test_onboard_whole_vf(mock_certify, mock_add_resource, mock_create, mock_submit, mock_load): + getter_mock = mock.Mock(wraps=Vf.status.fget) + mock_status = Vf.status.getter(getter_mock) + with mock.patch.object(Vf, 'status', mock_status): + getter_mock.side_effect = [None, const.DRAFT, const.DRAFT, + const.CHECKED_IN, const.CHECKED_IN, const.CHECKED_IN, + const.CERTIFIED, const.CERTIFIED, const.CERTIFIED, const.CERTIFIED, + const.APPROVED, const.APPROVED, const.APPROVED, const.APPROVED] + vsp = Vsp() + vf = Vf(vsp=vsp) + vf._time_wait = 0 + vf.onboard() + mock_create.assert_called_once() + mock_add_resource.assert_not_called() + mock_submit.assert_called_once() + mock_load.assert_called_once() + mock_certify.assert_called_once() + + +@mock.patch.object(Vf, "send_message_json") +def test_add_properties(mock_send_message_json): + vf = Vf(name="test") + vf._identifier = "toto" + vf._unique_identifier = "toto" + vf._status = const.CERTIFIED + with pytest.raises(StatusError): + vf.add_property(Property(name="test", property_type="string")) + vf._status = const.DRAFT + vf.add_property(Property(name="test", property_type="string")) + mock_send_message_json.assert_called_once() + +@mock.patch.object(Vf, 'load') +@mock.patch.object(Vf, 'send_message') +def test_add_artifact_to_vf(mock_send_message, mock_load): + """Test VF add artifact""" + vf = Vf(name="test") + vf.status = const.DRAFT + mycbapath = Path(Path(__file__).resolve().parent, "data/vLB_CBA_Python.zip") + + result = vf.add_deployment_artifact(artifact_label="cba", + artifact_type="CONTROLLER_BLUEPRINT_ARCHIVE", + artifact_name="vLB_CBA_Python.zip", + artifact=mycbapath) + mock_send_message.assert_called() + method, description, url = mock_send_message.call_args[0] + assert method == "POST" + assert description == "Add deployment artifact for test sdc resource" + assert url == ("https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/resources/" + f"{vf.unique_identifier}/artifacts") + + +@mock.patch.object(Vf, "created") +@mock.patch.object(ResourceCategory, "get") +def test_vf_category(mock_resource_category, mock_created): + mock_created.return_value = False + vf = Vf(name="test") + _ = vf.category + mock_resource_category.assert_called_once_with(name="Generic", subcategory="Abstract") + mock_resource_category.reset_mock() + + vf = Vf(name="test", category="Allotted Resource", subcategory="Allotted Resource") + _ = vf.category + mock_resource_category.assert_called_once_with(name="Allotted Resource", subcategory="Allotted Resource") + mock_resource_category.reset_mock() + + vf = Vf(name="test", category="test", subcategory="test") + _ = vf.category + mock_resource_category.assert_called_once_with(name="test", subcategory="test") + mock_resource_category.reset_mock() + + mock_created.return_value = True + _ = vf.category + mock_resource_category.assert_called_once_with(name="test", subcategory="test") + +@mock.patch.object(Vf, "send_message_json") +def test_update_vsp(mock_send): + + vf = Vf(name="test") + vf._unique_identifier = "123" + vsp = MagicMock() + vsp.csar_uuid = "122333" + vsp.human_readable_version = "1.0" + mock_send.return_value = { + "csarUUID": "322111", + "csarVersion": "0.1", + "tags": [], + "categories": [], + "allVersions": [], + "archived": False, + "creationDate": int(time.time()), + "lastUpdateDate": int(time.time()), + } + vf.update_vsp(vsp) + assert mock_send.call_count == 2 + mock_call_kwargs_data = json.loads(mock_send.mock_calls[-1][2]["data"]) # Get kward from `unittest.mock.call` tuple + assert mock_call_kwargs_data["csarUUID"] == "122333" + assert mock_call_kwargs_data["csarVersion"] == "1.0" + +@mock.patch.object(Vf, 'exists') +@mock.patch.object(Vf, 'send_message') +def test_add_resource_not_draft(mock_send, mock_exists): + mock_exists.return_value = False + vf = Vf() + resource = SdcResource() + with pytest.raises(StatusError): + vf.add_resource(resource) + mock_send.assert_not_called() + +@mock.patch.object(Vf, 'load') +@mock.patch.object(Vf, 'send_message') +def test_add_resource_bad_result(mock_send, mock_load): + vf = Vf() + vf.unique_identifier = "45" + vf.identifier = "93" + vf.status = const.DRAFT + mock_send.return_value = {} + resource = SdcResource() + resource.unique_identifier = "12" + resource.created = MagicMock(return_value=True) + resource.version = "40" + resource.name = "test" + assert vf.add_resource(resource) is None + mock_send.assert_called_once_with( + 'POST', 'Add SDCRESOURCE to VF', + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/resources/45/resourceInstance', + data='{\n "name": "test",\n "componentVersion": "40",\n "posY": 100,\n "posX": 200,\n "uniqueId": "12",\n "originType": "SDCRESOURCE",\n "componentUid": "12",\n "icon": "defaulticon"\n}') + +@mock.patch.object(Vf, 'load') +@mock.patch.object(Vf, 'send_message') +def test_add_resource_OK(mock_send, mock_load): + vf = Vf() + vf.unique_identifier = "45" + vf.identifier = "93" + vf.status = const.DRAFT + mock_send.return_value = {'yes': 'indeed'} + resource = SdcResource() + resource.unique_identifier = "12" + resource.created = MagicMock(return_value=True) + resource.version = "40" + resource.name = "test" + result = vf.add_resource(resource) + assert result['yes'] == "indeed" + mock_send.assert_called_once_with( + 'POST', 'Add SDCRESOURCE to VF', + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/rest/v1/catalog/resources/45/resourceInstance', + data='{\n "name": "test",\n "componentVersion": "40",\n "posY": 100,\n "posX": 200,\n "uniqueId": "12",\n "originType": "SDCRESOURCE",\n "componentUid": "12",\n "icon": "defaulticon"\n}') + +@mock.patch.object(Vf, 'created') +@mock.patch.object(Vf, "send_message_json") +@mock.patch.object(Vf, "resource_inputs_url") +def test_vf_vendor_property(mock_resource_inputs_url, mock_send_message_json, mock_created): + mock_created.return_value = False + vf = Vf() + assert vf.vendor is None + + vsp_mock = MagicMock() + vsp_mock.vendor = MagicMock() + vf.vsp = vsp_mock + assert vf.vendor == vsp_mock.vendor + + vf._vendor = None + mock_created.return_value = True + mock_send_message_json.return_value = {"vendorName": "123"} + assert vf.vendor.name == "123" + +@mock.patch.object(SdcResource, "declare_input") +@mock.patch.object(Vf, "send_message") +def test_vf_declare_input(mock_send_message, mock_sdc_resource_declare_input): + vf = Vf() + prop = Property(name="test_prop", property_type="string") + nested_input = NestedInput(MagicMock(), MagicMock()) + vf.declare_input(prop) + mock_sdc_resource_declare_input.assert_called_once() + mock_send_message.assert_not_called() + mock_sdc_resource_declare_input.reset_mock() + vf.declare_input(nested_input) + mock_sdc_resource_declare_input.assert_called_once() + mock_send_message.assert_not_called() + mock_sdc_resource_declare_input.reset_mock() + vf.declare_input(ComponentProperty("test_unique_id", "test_property_type", "test_name", MagicMock())) + mock_send_message.assert_called() + mock_sdc_resource_declare_input.assert_not_called() diff --git a/tests/test_vid.py b/tests/test_vid.py new file mode 100644 index 0000000..47281ba --- /dev/null +++ b/tests/test_vid.py @@ -0,0 +1,54 @@ +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest.mock import patch + +from onapsdk.vid import ( + OwningEntity, + Project, + LineOfBusiness, + Platform +) + + +@patch.object(LineOfBusiness, "send_message") +def test_line_of_business(send_message_mock): + assert LineOfBusiness.get_create_url() == "https://vid.api.simpledemo.onap.org:30200/vid/maintenance/category_parameter/lineOfBusiness" + + line_of_businnes = LineOfBusiness.create("test") + assert line_of_businnes.name == "test" + + +@patch.object(OwningEntity, "send_message") +def test_owning_entity(send_message_mock): + assert OwningEntity.get_create_url() == "https://vid.api.simpledemo.onap.org:30200/vid/maintenance/category_parameter/owningEntity" + + owning_entity = OwningEntity.create("test") + assert owning_entity.name == "test" + + +@patch.object(Project, "send_message") +def test_project(send_message_mock): + assert Project.get_create_url() == "https://vid.api.simpledemo.onap.org:30200/vid/maintenance/category_parameter/project" + + project = Project.create("test") + assert project.name == "test" + + +@patch.object(Platform, "send_message") +def test_platform(send_message_mock): + assert Platform.get_create_url() == "https://vid.api.simpledemo.onap.org:30200/vid/maintenance/category_parameter/platform" + + platform = Platform.create("test") + assert platform.name == "test" diff --git a/tests/test_vsp.py b/tests/test_vsp.py new file mode 100644 index 0000000..a1aa8a7 --- /dev/null +++ b/tests/test_vsp.py @@ -0,0 +1,799 @@ +"""Test vsp module.""" +# Copyright 2022 Orange, Deutsche Telekom AG +# +# 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 unittest import mock +import json + +import pytest +import requests +from onapsdk.exceptions import APIError, ParameterError, RequestError + +from onapsdk.sdc.vsp import Vsp +from onapsdk.sdc.vendor import Vendor +import onapsdk.constants as const +from onapsdk.sdc.sdc_element import SdcElement + +@mock.patch.object(Vsp, 'send_message_json') +def test_get_all_no_vsp(mock_send): + """Returns empty array if no vsps.""" + mock_send.return_value = {} + assert Vsp.get_all() == [] + mock_send.assert_called_once_with( + "GET", 'get Vsps', + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-software-products') + +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message_json') +def test_get_all_some_vsps(mock_send, mock_load_status): + """Returns a list of vsp.""" + mock_send.return_value = {'results':[ + {'name': 'one', 'id': '1234', 'vendorName': 'vspOne'}, + {'name': 'two', 'id': '1235', 'vendorName': 'vspOne'}]} + assert len(Vsp.get_all()) == 2 + vsp_1 = Vsp.get_all()[0] + assert vsp_1.name == "one" + assert vsp_1.identifier == "1234" + assert vsp_1.created() + vsp_2 = Vsp.get_all()[1] + assert vsp_2.name == "two" + assert vsp_2.identifier == "1235" + assert vsp_2.vendor == vsp_1.vendor + assert vsp_2.created() + mock_send.assert_called_with( + "GET", 'get Vsps', + 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-software-products') + +@mock.patch.object(Vsp, 'created') +def test_init_no_name(mock_created): + """Check init with no names.""" + mock_created.return_value = False + vsp = Vsp() + assert isinstance(vsp, SdcElement) + assert vsp._identifier == None + assert vsp._version == None + assert vsp.name == "ONAP-test-VSP" + assert vsp.headers["USER_ID"] == "cs0008" + assert vsp.vendor == None + assert isinstance(vsp._base_url(), str) + assert "sdc1/feProxy/onboarding-api/v1.0" in vsp._base_url() + +@mock.patch.object(Vsp, 'exists') +def test_init_with_name(mock_exists): + """Check init with no names.""" + vsp = Vsp(name="YOLO") + mock_exists.return_value = False + assert vsp._identifier == None + assert vsp._version == None + assert vsp.name == "YOLO" + assert vsp.created() == False + assert vsp.headers["USER_ID"] == "cs0008" + assert vsp.vendor == None + assert isinstance(vsp._base_url(), str) + assert "sdc1/feProxy/onboarding-api/v1.0" in vsp._base_url() + +def test_equality_really_equals(): + """Check two vsps are equals if name is the same.""" + vsp_1 = Vsp(name="equal") + vsp_1.identifier = "1234" + vsp_2 = Vsp(name="equal") + vsp_2.identifier = "1235" + assert vsp_1 == vsp_2 + +def test_equality_not_equals(): + """Check two vsps are not equals if name is not the same.""" + vsp_1 = Vsp(name="equal") + vsp_1.identifier = "1234" + vsp_2 = Vsp(name="not_equal") + vsp_2.identifier = "1234" + assert vsp_1 != vsp_2 + +def test_equality_not_equals_not_same_object(): + """Check a vsp and something different are not equals.""" + vsp_1 = Vsp(name="equal") + vsp_1.identifier = "1234" + vsp_2 = Vendor(name="equal") + assert vsp_1 != vsp_2 + +@mock.patch.object(Vsp, 'get_all') +def test_exists_not_exists(mock_get_all): + """Return False if vsp doesn't exist in SDC.""" + vsp_1 = Vsp(name="one") + vsp_1.identifier = "1234" + mock_get_all.return_value = [vsp_1] + vsp = Vsp(name="two") + assert not vsp.exists() + +@mock.patch.object(Vsp, 'get_all') +def test_exists_exists(mock_get_all): + """Return True if vsp exists in SDC.""" + vsp_1 = Vsp(name="one") + vsp_1.identifier = "1234" + vsp_1.version = "1.1" + mock_get_all.return_value = [vsp_1] + vsp = Vsp(name="one") + assert vsp.exists() + +@mock.patch.object(Vsp, 'get_all') +@mock.patch.object(Vsp, 'send_message_json') +def test_load_created(mock_send, mock_get_all): + mock_send.return_value = {'results': + [{'status': 'state_one', 'id': "5678", 'vendorName': 'vspOne', 'name': "1.0"}], "listCount": 1} + vsp = Vsp(name="one") + vsp.identifier = "1234" + vsp.load() + mock_get_all.assert_not_called() + mock_send.assert_called_once_with('GET', 'get item', 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/items/1234/versions') + assert vsp._status == None + assert vsp.version == "5678" + +@mock.patch.object(Vsp, 'get_all') +@mock.patch.object(Vsp, 'send_message_json') +def test_load_not_created(mock_send, mock_get_all): + mock_send.return_value = {'results': + [{'status': 'state_one', 'id': "5678", 'vendorName': 'vspOne', 'name': "1.0"}], "listCount": 1} + vsp = Vsp(name="one") + vsp.load() + mock_get_all.return_value = [] + mock_send.assert_not_called() + assert vsp._status == None + assert vsp.version == None + assert vsp.identifier == None + +@mock.patch.object(Vsp, 'exists') +@mock.patch.object(Vsp, 'send_message_json') +def test_create_no_vendor(mock_send, mock_exists): + """Do nothing if no vendor.""" + vsp = Vsp() + mock_exists.return_value = False + vsp.create() + mock_send.assert_not_called() + +@mock.patch.object(Vsp, 'exists') +@mock.patch.object(Vsp, 'send_message_json') +def test_create_already_exists(mock_send, mock_exists): + """Do nothing if already created in SDC.""" + vsp = Vsp() + vendor = Vendor() + vendor._identifier = "1232" + vsp.vendor = vendor + mock_exists.return_value = True + vsp.create() + mock_send.assert_not_called() + +@mock.patch.object(Vsp, 'exists') +@mock.patch.object(Vsp, 'send_message_json') +def test_create_issue_in_creation(mock_send, mock_exists): + """Do nothing if not created but issue during creation.""" + vsp = Vsp() + vendor = Vendor() + vendor._identifier = "1232" + vsp.vendor = vendor + expected_data = '{\n "name": "ONAP-test-VSP",\n "description": "vendor software product",\n "icon": "icon",\n "category": "resourceNewCategory.generic",\n "subCategory": "resourceNewCategory.generic.abstract",\n "vendorName": "Generic-Vendor",\n "vendorId": "1232",\n "licensingData": {},\n "onboardingMethod": "NetworkPackage"\n}' + mock_exists.return_value = False + mock_send.side_effect = RequestError + with pytest.raises(RequestError) as exc: + vsp.create() + mock_send.assert_called_once_with("POST", "create Vsp", 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-software-products', data=expected_data) + assert vsp.created() == False + +@mock.patch.object(Vsp, 'exists') +@mock.patch.object(Vsp, 'send_message_json') +def test_create_OK(mock_send, mock_exists): + """Create and update object.""" + vsp = Vsp() + vendor = Vendor() + vendor._identifier = "1232" + vsp.vendor = vendor + expected_data = '{\n "name": "ONAP-test-VSP",\n "description": "vendor software product",\n "icon": "icon",\n "category": "resourceNewCategory.generic",\n "subCategory": "resourceNewCategory.generic.abstract",\n "vendorName": "Generic-Vendor",\n "vendorId": "1232",\n "licensingData": {},\n "onboardingMethod": "NetworkPackage"\n}' + mock_exists.return_value = False + mock_send.return_value = { + 'itemId': "1234", + 'version': {'id': "5678", 'status': 'state_created'}} + vsp.create() + mock_send.assert_called_once_with("POST", "create Vsp", 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-software-products', data=expected_data) + assert vsp.created() == True + assert vsp._status == const.DRAFT + assert vsp.identifier == "1234" + assert vsp.version == "5678" + +@mock.patch.object(Vsp, 'exists') +@mock.patch.object(Vsp, 'load') +def test_version_no_load_no_created(mock_load, mock_exists): + mock_exists.return_value = False + vsp = Vsp() + assert vsp.version == None + mock_load.assert_not_called() + +@mock.patch.object(Vsp, 'load') +def test_version_no_load_created(mock_load): + vsp = Vsp() + vsp.identifier = "1234" + vsp._version = "64" + assert vsp.version == "64" + mock_load.assert_not_called() + +@mock.patch.object(Vsp, 'load') +def test_version_with_load(mock_load): + vsp = Vsp() + vsp.identifier = "1234" + assert vsp.version == None + mock_load.assert_called_once() + +@mock.patch.object(Vsp, '_get_vsp_details') +@mock.patch.object(Vsp, 'created') +def test_vendor_not_created_not_vendor(mock_created, mock_details): + mock_created.return_value = False + vsp = Vsp() + assert vsp.vendor == None + mock_details.assert_not_called() + +@mock.patch.object(Vsp, '_get_vsp_details') +@mock.patch.object(Vsp, 'created') +def test_vendor_not_created_vendor(mock_created, mock_details): + mock_created.return_value = False + vsp = Vsp() + vendor = Vendor() + vsp.vendor = vendor + assert vsp.vendor == vendor + mock_details.assert_not_called() + +@mock.patch.object(Vsp, '_get_vsp_details') +@mock.patch.object(Vsp, 'created') +def test_vendor_created_not_details(mock_created, mock_details): + mock_created.return_value = True + mock_details.return_value = {} + vsp = Vsp() + assert vsp.vendor == None + mock_details.assert_called_once() + +@mock.patch.object(Vsp, '_get_vsp_details') +@mock.patch.object(Vsp, 'created') +def test_vendor_created_details(mock_created, mock_details): + mock_created.return_value = True + mock_details.return_value = {'vendorName': 'test'} + vsp = Vsp() + assert vsp.vendor.name == 'test' + mock_details.assert_called_once() + +@mock.patch.object(Vsp, '_get_vsp_details') +@mock.patch.object(Vsp, 'created') +def test_vendor_created_but_already_vendor(mock_created, mock_details): + mock_created.return_value = True + vsp = Vsp() + vendor = Vendor() + vsp.vendor = vendor + assert vsp.vendor == vendor + mock_details.assert_not_called() + +@mock.patch.object(Vsp, 'exists') +def test_status_no_load_no_created(mock_exists): + mock_exists.return_value = False + vsp = Vsp() + assert vsp.status == None + +@mock.patch.object(Vsp, '_get_item_details') +def test_status_status_is_certified_in_SDC(mock_vsp_items): + vsp = Vsp() + vsp.identifier = "1234" + mock_vsp_items.return_value={'status': const.CERTIFIED} + vsp._status = "Draft" + assert vsp.status == const.CERTIFIED + +@mock.patch.object(Vsp, '_get_vsp_details') +@mock.patch.object(Vsp, '_get_item_version_details') +@mock.patch.object(Vsp, '_get_item_details') +def test_status_version_is_not_dirty(mock_vsp_items, mock_vsp_items_version, mock_vsp_details): + vsp = Vsp() + vsp.identifier = "1234" + mock_vsp_items.return_value={'status': const.DRAFT} + mock_vsp_items_version.return_value={"state": {'dirty': False}} + mock_vsp_details.return_value={'validationData': "true"} + assert vsp.status == const.COMMITED + +@mock.patch.object(Vsp, '_get_vsp_details') +@mock.patch.object(Vsp, '_get_item_version_details') +@mock.patch.object(Vsp, '_get_item_details') +def test_status_version_is_dirty_has_validation_data(mock_vsp_items, mock_vsp_items_version, + mock_vsp_details): + vsp = Vsp() + vsp.identifier = "1234" + mock_vsp_items.return_value={'status': const.DRAFT} + mock_vsp_items_version.return_value={"state": {'dirty': True}} + mock_vsp_details.return_value={'validationData': {'some': 'thing'}} + assert vsp.status == const.VALIDATED + +@mock.patch.object(Vsp, '_get_vsp_details') +@mock.patch.object(Vsp, '_get_item_version_details') +@mock.patch.object(Vsp, '_get_item_details') +def test_status_version_is_dirty_no_validation_data_no_state(mock_vsp_items, mock_vsp_items_version, + mock_vsp_details): + vsp = Vsp() + vsp.identifier = "1234" + mock_vsp_items.return_value={'status': const.DRAFT} + mock_vsp_items_version.return_value={"status": {'dirty': False}} + mock_vsp_details.return_value={'no_validationData': {'some': 'thing'}} + assert vsp.status == const.DRAFT + +@mock.patch.object(Vsp, '_get_vsp_details') +@mock.patch.object(Vsp, '_get_item_version_details') +@mock.patch.object(Vsp, '_get_item_details') +def test_status_version_is_dirty_no_validation_data_but_state(mock_vsp_items, mock_vsp_items_version, + mock_vsp_details): + vsp = Vsp() + vsp.identifier = "1234" + mock_vsp_items.return_value={'status': const.DRAFT} + mock_vsp_items_version.return_value={"state": {'dirty': True}} + mock_vsp_details.return_value={'no_validationData': {'some': 'thing'}} + assert vsp.status == const.DRAFT + +@mock.patch.object(Vsp, '_get_vsp_details') +@mock.patch.object(Vsp, '_get_item_version_details') +@mock.patch.object(Vsp, '_get_item_details') +def test_status_version_is_dirty_no_validation_data_but_networkPackageName(mock_vsp_items, mock_vsp_items_version, + mock_vsp_details): + vsp = Vsp() + vsp.identifier = "1234" + mock_vsp_items.return_value={'status': const.DRAFT} + mock_vsp_items_version.return_value={"state": {'dirty': True}} + mock_vsp_details.return_value={'no_validationData': {'some': 'thing'}, 'networkPackageName': 'ubuntu16'} + assert vsp.status == const.UPLOADED + + +@mock.patch.object(Vsp, 'exists') +@mock.patch.object(Vsp, 'send_message_json') +def test__get_vsp_details_not_created(mock_send, mock_exists): + mock_exists.return_value = False + vsp = Vsp() + assert vsp._get_vsp_details() == {} + mock_send.assert_not_called() + +@mock.patch.object(Vsp, 'load') +@mock.patch.object(Vsp, 'send_message_json') +def test__get_vsp_details_no_version(mock_send, mock_load): + vsp = Vsp() + vsp.identifier = "1234" + mock_send.assert_not_called() + assert vsp._get_vsp_details() == {} + +@mock.patch.object(Vsp, 'send_message_json') +def test__get_vsp_details(mock_send): + vsp = Vsp() + vsp.identifier = "1234" + vsp._version = "4567" + mock_send.return_value = {'return': 'value'} + assert vsp._get_vsp_details() == {'return': 'value'} + mock_send.assert_called_once_with('GET', 'get vsp version', "{}/vendor-software-products/1234/versions/4567".format(vsp._base_url())) + +@pytest.mark.parametrize("status", [const.DRAFT, const.CERTIFIED, const.UPLOADED, const.VALIDATED]) +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message') +def test_submit_not_Commited(mock_send, mock_status, status): + """Do nothing if not created.""" + vsp = Vsp() + vsp._status = status + vsp.submit() + mock_send.assert_not_called() + +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message') +def test_submit_OK(mock_send, mock_status): + """Don't update status if submission NOK.""" + vsp = Vsp() + vsp._status = const.COMMITED + expected_data = '{\n\n "action": "Submit"\n}' + vsp._version = "1234" + vsp._identifier = "12345" + vsp.submit() + mock_send.assert_called_once_with("PUT", "Submit Vsp", 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-software-products/12345/versions/1234/actions', data=expected_data) + +@pytest.mark.parametrize("status", [const.DRAFT, const.COMMITED, const.UPLOADED, const.VALIDATED]) +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message') +def test_create_csar_not_Certified(mock_send, mock_status, status): + """Do nothing if not created.""" + vsp = Vsp() + vsp._status = status + vsp.create_csar() + mock_send.assert_not_called() + assert vsp.csar_uuid == None + +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message') +def test_create_csar_not_OK(mock_send, mock_status): + """Don't update status if submission NOK.""" + vsp = Vsp() + vsp._status = const.CERTIFIED + mock_send.return_value = {} + expected_data = '{\n\n "action": "Create_Package"\n}' + vsp._version = "1234" + vsp._identifier = "12345" + vsp.create_csar() + mock_send.assert_called_once_with("PUT", "Create_Package Vsp", 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-software-products/12345/versions/1234/actions', data=expected_data) + assert vsp.csar_uuid == None + +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message') +def test_create_csar_OK(mock_send, mock_status): + """Don't update status if submission NOK.""" + vsp = Vsp() + vsp._status = const.CERTIFIED + result = requests.Response() + result.status_code = 201 + result._content = json.dumps({'packageId': "64"}).encode('UTF-8') + mock_send.return_value = result + expected_data = '{\n\n "action": "Create_Package"\n}' + vsp._version = "1234" + vsp._identifier = "12345" + vsp.create_csar() + mock_send.assert_called_once_with("PUT", "Create_Package Vsp", 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-software-products/12345/versions/1234/actions', data=expected_data) + assert vsp.csar_uuid == "64" + +@pytest.mark.parametrize("status", [const.DRAFT, const.CERTIFIED, const.UPLOADED, const.COMMITED]) +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message') +def test_commit_not_Validated(mock_send, mock_status, status): + """Do nothing if not created.""" + vsp = Vsp() + vsp._status = status + vsp.commit() + mock_send.assert_not_called() + +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message') +def test_commit_OK(mock_send, mock_status): + """Don't update status if submission NOK.""" + vsp = Vsp() + vsp._status = const.VALIDATED + expected_data = '{\n\n "commitRequest":{"message":"ok"},\n\n "action": "Commit"\n}' + vsp._version = "1234" + vsp._identifier = "12345" + vsp.commit() + mock_send.assert_called_once_with("PUT", "Commit Vsp", 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/items/12345/versions/1234/actions', data=expected_data) + +@pytest.mark.parametrize("status", [const.CERTIFIED, const.COMMITED, const.UPLOADED, const.VALIDATED]) +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message') +def test_upload_not_Draft(mock_send, mock_status, status): + """Do nothing if not created.""" + vsp = Vsp() + vsp._status = status + vsp.upload_package('data') + mock_send.assert_not_called() + +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message') +def test_upload_not_OK(mock_send, mock_status): + """Don't update status if submission NOK.""" + vsp = Vsp() + vsp._status = const.DRAFT + mock_send.return_value = None + vsp._version = "1234" + vsp._identifier = "12345" + vsp.upload_package('data') + mock_send.assert_called_once_with('POST', 'upload ZIP for Vsp', "https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-software-products/12345/versions/1234/orchestration-template-candidate", files={'upload': 'data'}, headers={'Accept': 'application/json', 'USER_ID': 'cs0008', 'Authorization': 'Basic YWFpOktwOGJKNFNYc3pNMFdYbGhhazNlSGxjc2UyZ0F3ODR2YW9HR21KdlV5MlU=', 'X-ECOMP-InstanceID': 'onapsdk', 'Accept-Encoding': 'gzip, deflate'}) + +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message') +def test_upload_error_in_response(mock_send, mock_status): + """Don't update status if submission NOK.""" + vsp = Vsp() + vsp._status = const.DRAFT + mock_send.return_value = mock.MagicMock(text='{"status": "Failure"}') + vsp._version = "1234" + vsp._identifier = "12345" + with pytest.raises(APIError): + vsp.upload_package('data') + mock_send.assert_called_once_with('POST', 'upload ZIP for Vsp', "https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-software-products/12345/versions/1234/orchestration-template-candidate", files={'upload': 'data'}, headers={'Accept': 'application/json', 'USER_ID': 'cs0008', 'Authorization': 'Basic YWFpOktwOGJKNFNYc3pNMFdYbGhhazNlSGxjc2UyZ0F3ODR2YW9HR21KdlV5MlU=', 'X-ECOMP-InstanceID': 'onapsdk', 'Accept-Encoding': 'gzip, deflate'}) + +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message') +def test_upload_OK(mock_send, mock_status): + """Don't update status if submission NOK.""" + vsp = Vsp() + vsp._status = const.DRAFT + mock_send.return_value = mock.MagicMock(text='{"status": "Success"}') + vsp._version = "1234" + vsp._identifier = "12345" + vsp.upload_package('data') + mock_send.assert_called_once_with('POST', 'upload ZIP for Vsp', "https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-software-products/12345/versions/1234/orchestration-template-candidate", files={'upload': 'data'}, headers={'Accept': 'application/json', 'USER_ID': 'cs0008', 'Authorization': 'Basic YWFpOktwOGJKNFNYc3pNMFdYbGhhazNlSGxjc2UyZ0F3ODR2YW9HR21KdlV5MlU=', 'X-ECOMP-InstanceID': 'onapsdk', 'Accept-Encoding': 'gzip, deflate'}) + +@pytest.mark.parametrize("status", [const.CERTIFIED, const.COMMITED, const.DRAFT, const.VALIDATED]) +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message_json') +def test_validate_not_Draft(mock_send, mock_status, status): + """Do nothing if not created.""" + vsp = Vsp() + vsp._status = status + vsp.validate() + mock_send.assert_not_called() + +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message_json') +def test_validate_not_OK(mock_send, mock_status): + """Don't update status if submission NOK.""" + vsp = Vsp() + vsp._status = const.UPLOADED + mock_send.return_value = {} + vsp._version = "1234" + vsp._identifier = "12345" + vsp.validate() + mock_send.assert_called_once_with('PUT', 'Validate artifacts for Vsp', 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-software-products/12345/versions/1234/orchestration-template-candidate/process') + +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message_json') +def test_validate_not_success(mock_send, mock_status): + """Don't update status if submission NOK.""" + vsp = Vsp() + vsp._status = const.UPLOADED + mock_send.return_value = {'status': 'not_success'} + vsp._version = "1234" + vsp._identifier = "12345" + vsp.validate() + mock_send.assert_called_once_with('PUT', 'Validate artifacts for Vsp', 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-software-products/12345/versions/1234/orchestration-template-candidate/process') + +@mock.patch.object(Vsp, 'load_status') +@mock.patch.object(Vsp, 'send_message_json') +def test_validate_OK(mock_send, mock_status): + """Don't update status if submission NOK.""" + vsp = Vsp() + vsp._status = const.UPLOADED + mock_send.return_value = {'status': 'Success'} + vsp._version = "1234" + vsp._identifier = "12345" + vsp.validate() + mock_send.assert_called_once_with('PUT', 'Validate artifacts for Vsp', 'https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/vendor-software-products/12345/versions/1234/orchestration-template-candidate/process') + +@mock.patch.object(Vsp, 'create_csar') +@mock.patch.object(Vsp, 'submit') +@mock.patch.object(Vsp, 'commit') +@mock.patch.object(Vsp, 'validate') +@mock.patch.object(Vsp, 'upload_package') +@mock.patch.object(Vsp, 'create') +@mock.patch.object(Vsp, 'created') +def test_onboard_new_vsp_no_vendor(mock_created, mock_create, mock_upload_package, + mock_validate, mock_commit, mock_submit, + mock_create_csar): + mock_created.return_value = False + getter_mock = mock.Mock(wraps=Vsp.status.fget) + mock_status = Vsp.status.getter(getter_mock) + with mock.patch.object(Vsp, 'status', mock_status): + getter_mock.side_effect = [None, const.APPROVED, const.APPROVED, + const.APPROVED, const.APPROVED, + const.APPROVED, None] + vsp = Vsp() + with pytest.raises(ParameterError): + vsp.onboard() + mock_create.assert_not_called() + mock_upload_package.assert_not_called() + mock_validate.assert_not_called() + mock_commit.assert_not_called() + mock_submit.assert_not_called() + mock_create_csar.assert_not_called() + + +@mock.patch.object(Vsp, 'create_csar') +@mock.patch.object(Vsp, 'submit') +@mock.patch.object(Vsp, 'commit') +@mock.patch.object(Vsp, 'validate') +@mock.patch.object(Vsp, 'upload_package') +@mock.patch.object(Vsp, 'create') +@mock.patch.object(Vsp, 'created') +def test_onboard_new_vsp(mock_created, mock_create, mock_upload_package, + mock_validate, mock_commit, mock_submit, + mock_create_csar): + mock_created.return_value = False + getter_mock = mock.Mock(wraps=Vsp.status.fget) + mock_status = Vsp.status.getter(getter_mock) + with mock.patch.object(Vsp, 'status', mock_status): + getter_mock.side_effect = [None, const.APPROVED, const.APPROVED, + const.APPROVED, const.APPROVED, + const.APPROVED, None] + vendor = Vendor() + vsp = Vsp(vendor=vendor) + vsp.onboard() + mock_create.assert_called_once() + mock_upload_package.assert_not_called() + mock_validate.assert_not_called() + mock_commit.assert_not_called() + mock_submit.assert_not_called() + mock_create_csar.assert_not_called() + +@mock.patch.object(Vsp, 'create_csar') +@mock.patch.object(Vsp, 'submit') +@mock.patch.object(Vsp, 'commit') +@mock.patch.object(Vsp, 'validate') +@mock.patch.object(Vsp, 'upload_package') +@mock.patch.object(Vsp, 'create') +def test_onboard_vsp_upload_no_files(mock_create, mock_upload_package, + mock_validate, mock_commit, mock_submit, + mock_create_csar): + getter_mock = mock.Mock(wraps=Vsp.status.fget) + mock_status = Vsp.status.getter(getter_mock) + with mock.patch.object(Vsp, 'status', mock_status): + getter_mock.side_effect = [const.DRAFT, const.DRAFT, const.APPROVED, + const.APPROVED, const.APPROVED, const.APPROVED, + const.APPROVED, None] + vsp = Vsp() + with pytest.raises(ParameterError): + vsp.onboard() + mock_create.assert_not_called() + mock_upload_package.assert_not_called() + mock_validate.assert_not_called() + mock_commit.assert_not_called() + mock_submit.assert_not_called() + mock_create_csar.assert_not_called() + +@mock.patch.object(Vsp, 'create_csar') +@mock.patch.object(Vsp, 'submit') +@mock.patch.object(Vsp, 'commit') +@mock.patch.object(Vsp, 'validate') +@mock.patch.object(Vsp, 'upload_package') +@mock.patch.object(Vsp, 'create') +def test_onboard_vsp_upload_package(mock_create, mock_upload_package, + mock_validate, mock_commit, mock_submit, + mock_create_csar): + getter_mock = mock.Mock(wraps=Vsp.status.fget) + mock_status = Vsp.status.getter(getter_mock) + with mock.patch.object(Vsp, 'status', mock_status): + getter_mock.side_effect = [const.DRAFT, const.APPROVED, None] + vsp = Vsp(package="yes") + vsp.onboard() + mock_create.assert_not_called() + mock_upload_package.assert_called_once_with("yes") + mock_validate.assert_not_called() + mock_commit.assert_not_called() + mock_submit.assert_not_called() + mock_create_csar.assert_not_called() + +@mock.patch.object(Vsp, 'create_csar') +@mock.patch.object(Vsp, 'submit') +@mock.patch.object(Vsp, 'commit') +@mock.patch.object(Vsp, 'validate') +@mock.patch.object(Vsp, 'upload_package') +@mock.patch.object(Vsp, 'create') +def test_onboard_new_vsp_validate(mock_create, mock_upload_package, + mock_validate, mock_commit, mock_submit, + mock_create_csar): + getter_mock = mock.Mock(wraps=Vsp.status.fget) + mock_status = Vsp.status.getter(getter_mock) + with mock.patch.object(Vsp, 'status', mock_status): + getter_mock.side_effect = [const.UPLOADED, const.APPROVED, None] + vsp = Vsp() + vsp.onboard() + mock_create.assert_not_called() + mock_upload_package.assert_not_called() + mock_validate.assert_called_once() + mock_commit.assert_not_called() + mock_submit.assert_not_called() + mock_create_csar.assert_not_called() + +@mock.patch.object(Vsp, 'create_csar') +@mock.patch.object(Vsp, 'submit') +@mock.patch.object(Vsp, 'commit') +@mock.patch.object(Vsp, 'validate') +@mock.patch.object(Vsp, 'upload_package') +@mock.patch.object(Vsp, 'create') +def test_onboard_new_vsp_commit(mock_create, mock_upload_package, + mock_validate, mock_commit, mock_submit, + mock_create_csar): + getter_mock = mock.Mock(wraps=Vsp.status.fget) + mock_status = Vsp.status.getter(getter_mock) + with mock.patch.object(Vsp, 'status', mock_status): + getter_mock.side_effect = [const.VALIDATED, const.APPROVED, None] + vsp = Vsp() + vsp.onboard() + mock_create.assert_not_called() + mock_upload_package.assert_not_called() + mock_validate.assert_not_called() + mock_commit.assert_called_once() + mock_submit.assert_not_called() + mock_create_csar.assert_not_called() + +@mock.patch.object(Vsp, 'create_csar') +@mock.patch.object(Vsp, 'submit') +@mock.patch.object(Vsp, 'commit') +@mock.patch.object(Vsp, 'validate') +@mock.patch.object(Vsp, 'upload_package') +@mock.patch.object(Vsp, 'create') +def test_onboard_new_vsp_submit(mock_create, mock_upload_package, + mock_validate, mock_commit, mock_submit, + mock_create_csar): + getter_mock = mock.Mock(wraps=Vsp.status.fget) + mock_status = Vsp.status.getter(getter_mock) + with mock.patch.object(Vsp, 'status', mock_status): + getter_mock.side_effect = [const.COMMITED, const.APPROVED, None] + vsp = Vsp() + vsp.onboard() + mock_create.assert_not_called() + mock_upload_package.assert_not_called() + mock_validate.assert_not_called() + mock_commit.assert_not_called() + mock_submit.assert_called_once() + mock_create_csar.assert_not_called() + +@mock.patch.object(Vsp, 'create_csar') +@mock.patch.object(Vsp, 'submit') +@mock.patch.object(Vsp, 'commit') +@mock.patch.object(Vsp, 'validate') +@mock.patch.object(Vsp, 'upload_package') +@mock.patch.object(Vsp, 'create') +def test_onboard_new_vsp_create_csar(mock_create, + mock_upload_package, mock_validate, + mock_commit, mock_submit, + mock_create_csar): + getter_mock = mock.Mock(wraps=Vsp.status.fget) + mock_status = Vsp.status.getter(getter_mock) + with mock.patch.object(Vsp, 'status', mock_status): + getter_mock.side_effect = [const.CERTIFIED, const.APPROVED, None] + vsp = Vsp() + vsp.onboard() + mock_create.assert_not_called() + mock_upload_package.assert_not_called() + mock_validate.assert_not_called() + mock_commit.assert_not_called() + mock_submit.assert_not_called() + mock_create_csar.assert_called_once() + +@mock.patch.object(Vsp, 'create_csar') +@mock.patch.object(Vsp, 'submit') +@mock.patch.object(Vsp, 'commit') +@mock.patch.object(Vsp, 'validate') +@mock.patch.object(Vsp, 'upload_package') +@mock.patch.object(Vsp, 'create') +@mock.patch.object(Vsp, 'created') +@mock.patch.object(Vsp, 'load') +def test_onboard_whole_vsp(mock_load, mock_created, mock_create, + mock_upload_package, mock_validate, + mock_commit, mock_submit, + mock_create_csar): + getter_mock = mock.Mock(wraps=Vsp.status.fget) + mock_status = Vsp.status.getter(getter_mock) + with mock.patch.object(Vsp, 'status', mock_status): + getter_mock.side_effect = [None, const.DRAFT, const.UPLOADED, const.VALIDATED, + const.COMMITED, const.CERTIFIED, None] + vendor = Vendor() + vsp = Vsp(vendor=vendor, package="yes") + vsp.onboard() + mock_create.assert_called_once() + mock_upload_package.assert_called_once() + mock_validate.assert_called_once() + mock_commit.assert_called_once() + mock_submit.assert_called_once() + mock_create_csar.assert_called_once() + + +@mock.patch.object(Vsp, "status", new_callable=mock.PropertyMock) +@mock.patch.object(Vsp, '_upload_action') +def test_update_package(mock_upload, mock_status): + mock_status.return_value = const.DRAFT + vsp = Vsp(vendor=mock.MagicMock()) + vsp.update_package("new") + mock_upload.assert_not_called() + mock_status.return_value = const.COMMITED + vsp.update_package("new") + mock_upload.assert_called_once() + + +@mock.patch.object(Vsp, "send_message_json") +@mock.patch.object(Vsp, 'load') +def test_create_new_version(mock_load, mock_send): + vsp = Vsp(vendor=mock.MagicMock()) + vsp._identifier = "1232" + vsp._version = "4321" + vsp.create_new_version() + mock_load.assert_called_once() + mock_send.assert_called_once_with("POST", + "Create new VSP version", + "https://sdc.api.fe.simpledemo.onap.org:30207/sdc1/feProxy/onboarding-api/v1.0/items/1232/versions/4321", + data='{"creationMethod": "major", "description": "New VSP version"}') diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..40c3a5a --- /dev/null +++ b/tox.ini @@ -0,0 +1,14 @@ +[tox] +envlist = py37,py38,py39,py310,pylint,pydocstyle + +[testenv] +commands = pytest tests/ +deps = -rtest-requirements.txt +setenv = PYTHONPATH = {toxinidir}/src + +[testenv:pylint] +commands = pylint src/ +basepython = python3.8 + +[testenv:pydocstyle] +commands = pydocstyle src/ diff --git a/upload-requirements.txt b/upload-requirements.txt new file mode 100644 index 0000000..9ab57ec --- /dev/null +++ b/upload-requirements.txt @@ -0,0 +1,3 @@ +twine==3.2.0 +wheel==0.35.1 +setuptools==50.3.0 -- cgit 1.2.3-korg