aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichal Jagiello <michal.jagiello@t-mobile.pl>2022-10-17 12:46:49 +0000
committerMichal Jagiello <michal.jagiello@t-mobile.pl>2022-10-17 14:05:09 +0000
commitf2adf542e878c96895210f97ebf1ebb763b2f465 (patch)
tree91fc0faeb3436e723d07aed1f38ce59a6e7cc7c5
parent49071a0d0425ef67fa552dbf14c81e5a11cc49e7 (diff)
Release ONAP SDKv10.2
Issue-ID: INT-2150 Signed-off-by: Michal Jagiello <michal.jagiello@t-mobile.pl> Change-Id: I650047c599a5aae6de7c6b42d38e34aea88578e2
-rw-r--r--.coveragerc26
-rw-r--r--.dockerignore134
-rw-r--r--.gitignore139
-rw-r--r--.gitlab-ci.yml145
-rw-r--r--.pyup.yml37
-rw-r--r--.pyup.yml.old37
-rw-r--r--CHANGELOG.md12
-rw-r--r--CODEOWNER10
-rw-r--r--Dockerfile17
-rw-r--r--LICENSE201
-rw-r--r--MANIFEST.in1
-rw-r--r--README.md119
-rw-r--r--dev-requirements.txt11
-rw-r--r--doc-requirements.txt4
-rw-r--r--docs/Makefile20
-rw-r--r--docs/_static/css/custom.css3
-rw-r--r--docs/architecture.rst2
-rw-r--r--docs/conf.py75
-rw-r--r--docs/description.rst13
-rw-r--r--docs/development.rst122
-rw-r--r--docs/examples/e2e_artifact_upload.rst58
-rw-r--r--docs/examples/e2e_basicvm_nomulticloud_instantiation.rst414
-rw-r--r--docs/examples/e2e_closed_loop_instantiation.rst108
-rw-r--r--docs/examples/e2e_net_instantiation.rst332
-rw-r--r--docs/examples/e2e_vfw_instantiation.rst429
-rw-r--r--docs/examples/e2e_vfw_macro_instantiation.rst493
-rw-r--r--docs/examples/examples.rst13
-rw-r--r--docs/examples/k8s_plugin_usage.rst94
-rw-r--r--docs/glossary.rst20
-rw-r--r--docs/index.rst34
-rw-r--r--docs/modules/modules.rst7
-rw-r--r--docs/modules/onapsdk.aai.business.rst109
-rw-r--r--docs/modules/onapsdk.aai.cloud_infrastructure.rst37
-rw-r--r--docs/modules/onapsdk.aai.rst46
-rw-r--r--docs/modules/onapsdk.cds.rst53
-rw-r--r--docs/modules/onapsdk.clamp.rst29
-rw-r--r--docs/modules/onapsdk.configuration.rst29
-rw-r--r--docs/modules/onapsdk.cps.rst45
-rw-r--r--docs/modules/onapsdk.dmaap.rst29
-rw-r--r--docs/modules/onapsdk.msb.k8s.rst37
-rw-r--r--docs/modules/onapsdk.msb.rst45
-rw-r--r--docs/modules/onapsdk.nbi.rst21
-rw-r--r--docs/modules/onapsdk.rst65
-rw-r--r--docs/modules/onapsdk.sdc.rst101
-rw-r--r--docs/modules/onapsdk.sdnc.rst29
-rw-r--r--docs/modules/onapsdk.so.rst45
-rw-r--r--docs/modules/onapsdk.utils.rst53
-rw-r--r--docs/modules/onapsdk.ves.rst29
-rw-r--r--docs/modules/onapsdk.vid.rst21
-rw-r--r--docs/usage/installation.rst32
-rw-r--r--docs/usage/intro.rst16
-rw-r--r--docs/usage/usage.rst14
-rw-r--r--docs/usage/usage/cds.rst173
-rw-r--r--docs/usage/usage/cloud_configuration.rst210
-rw-r--r--docs/usage/usage/cps.rst32
-rw-r--r--docs/usage/usage/deletion.rst28
-rw-r--r--docs/usage/usage/design_time.rst351
-rw-r--r--docs/usage/usage/dmaap.rst47
-rw-r--r--docs/usage/usage/instantiation.rst485
-rw-r--r--docs/usage/usage/ves.rst42
-rw-r--r--integration_tests/__init__.py21
-rw-r--r--integration_tests/docker-compose.yml53
-rw-r--r--integration_tests/local_urls.py23
-rw-r--r--integration_tests/tca_clampnode.yaml171
-rw-r--r--integration_tests/test_01_vendor.py42
-rw-r--r--integration_tests/test_02_vsp.py63
-rw-r--r--integration_tests/test_03_vf.py90
-rw-r--r--integration_tests/test_04_service.py122
-rw-r--r--integration_tests/test_05_cloud_infrastructure.py88
-rw-r--r--integration_tests/test_06_customer.py85
-rw-r--r--integration_tests/test_07_instantiation.py427
-rw-r--r--integration_tests/test_08_cds.py65
-rw-r--r--integration_tests/test_09_clamp.py78
-rw-r--r--integration_tests/test_10_msb_k8s.py125
-rw-r--r--integration_tests/test_11_ves.py109
-rw-r--r--integration_tests/test_12_dmaap.py31
-rw-r--r--integration_tests/test_files/test_dd.json48
-rw-r--r--integration_tests/test_files/test_vLB_CBA_Python.zipbin0 -> 25559 bytes
-rw-r--r--integration_tests/ubuntu16.zipbin0 -> 1641 bytes
-rw-r--r--integration_tests/urls.py23
-rw-r--r--requirements.txt8
-rwxr-xr-xscripts/build_all_branches_in.sh36
-rw-r--r--setup.cfg53
-rw-r--r--setup.py7
-rw-r--r--src/onapsdk/__init__.py14
-rw-r--r--src/onapsdk/aai/__init__.py14
-rw-r--r--src/onapsdk/aai/aai_element.py192
-rw-r--r--src/onapsdk/aai/bulk.py90
-rw-r--r--src/onapsdk/aai/business/__init__.py27
-rw-r--r--src/onapsdk/aai/business/customer.py603
-rw-r--r--src/onapsdk/aai/business/instance.py55
-rw-r--r--src/onapsdk/aai/business/line_of_business.py123
-rw-r--r--src/onapsdk/aai/business/network.py223
-rw-r--r--src/onapsdk/aai/business/owning_entity.py154
-rw-r--r--src/onapsdk/aai/business/platform.py123
-rw-r--r--src/onapsdk/aai/business/pnf.py267
-rw-r--r--src/onapsdk/aai/business/project.py123
-rw-r--r--src/onapsdk/aai/business/service.py484
-rw-r--r--src/onapsdk/aai/business/sp_partner.py176
-rw-r--r--src/onapsdk/aai/business/vf_module.py199
-rw-r--r--src/onapsdk/aai/business/vnf.py536
-rw-r--r--src/onapsdk/aai/cloud_infrastructure/__init__.py18
-rw-r--r--src/onapsdk/aai/cloud_infrastructure/cloud_region.py621
-rw-r--r--src/onapsdk/aai/cloud_infrastructure/complex.py300
-rw-r--r--src/onapsdk/aai/cloud_infrastructure/geo_region.py191
-rw-r--r--src/onapsdk/aai/cloud_infrastructure/tenant.py101
-rw-r--r--src/onapsdk/aai/network/__init__.py16
-rw-r--r--src/onapsdk/aai/network/site_resource.py244
-rw-r--r--src/onapsdk/aai/service_design_and_creation.py186
-rw-r--r--src/onapsdk/aai/templates/aai_add_relationship.json.j211
-rw-r--r--src/onapsdk/aai/templates/aai_bulk.json.j211
-rw-r--r--src/onapsdk/aai/templates/aai_line_of_business_create.json.j23
-rw-r--r--src/onapsdk/aai/templates/aai_owning_entity_create.json.j24
-rw-r--r--src/onapsdk/aai/templates/aai_platform_create.json.j23
-rw-r--r--src/onapsdk/aai/templates/aai_project_create.json.j23
-rw-r--r--src/onapsdk/aai/templates/aai_service_create.json.j24
-rw-r--r--src/onapsdk/aai/templates/aai_service_instance_create.json.j222
-rw-r--r--src/onapsdk/aai/templates/aai_sp_partner_create.json.j221
-rw-r--r--src/onapsdk/aai/templates/cloud_region_add_availability_zone.json.j27
-rw-r--r--src/onapsdk/aai/templates/cloud_region_add_esr_system_info.json.j254
-rw-r--r--src/onapsdk/aai/templates/cloud_region_add_tenant.json.j25
-rw-r--r--src/onapsdk/aai/templates/cloud_region_create.json.j216
-rw-r--r--src/onapsdk/aai/templates/complex_create.json.j223
-rw-r--r--src/onapsdk/aai/templates/customer_create.json.j215
-rw-r--r--src/onapsdk/aai/templates/customer_service_subscription_create.json.j23
-rw-r--r--src/onapsdk/aai/templates/geo_region_create.json.j210
-rw-r--r--src/onapsdk/aai/templates/site_resource_create.json.j216
-rw-r--r--src/onapsdk/cds/README.md71
-rw-r--r--src/onapsdk/cds/__init__.py18
-rw-r--r--src/onapsdk/cds/blueprint.py830
-rw-r--r--src/onapsdk/cds/blueprint_model.py222
-rw-r--r--src/onapsdk/cds/blueprint_processor.py53
-rw-r--r--src/onapsdk/cds/cds_element.py47
-rw-r--r--src/onapsdk/cds/data_dictionary.py266
-rw-r--r--src/onapsdk/cds/templates/cds_blueprintprocessor_bootstrap.json.j25
-rw-r--r--src/onapsdk/cds/templates/data_dictionary_base.json.j252
-rw-r--r--src/onapsdk/cds/templates/data_dictionary_source_rest.json.j213
-rw-r--r--src/onapsdk/clamp/__init__.py14
-rw-r--r--src/onapsdk/clamp/clamp_element.py79
-rw-r--r--src/onapsdk/clamp/loop_instance.py349
-rw-r--r--src/onapsdk/clamp/schema_details.json138
-rw-r--r--src/onapsdk/clamp/templates/clamp_MinMax_config.json.j294
-rw-r--r--src/onapsdk/clamp/templates/clamp_add_drools_policy.json.j2325
-rw-r--r--src/onapsdk/clamp/templates/clamp_add_frequency.json.j2102
-rw-r--r--src/onapsdk/clamp/templates/clamp_add_tca_config.json.j230
-rw-r--r--src/onapsdk/configuration/__init__.py18
-rw-r--r--src/onapsdk/configuration/global_settings.py71
-rw-r--r--src/onapsdk/configuration/loader.py115
-rw-r--r--src/onapsdk/constants.py61
-rw-r--r--src/onapsdk/cps/__init__.py18
-rw-r--r--src/onapsdk/cps/anchor.py193
-rw-r--r--src/onapsdk/cps/cps_element.py24
-rw-r--r--src/onapsdk/cps/dataspace.py193
-rw-r--r--src/onapsdk/cps/schemaset.py73
-rw-r--r--src/onapsdk/dmaap/__init__.py14
-rw-r--r--src/onapsdk/dmaap/dmaap.py87
-rw-r--r--src/onapsdk/dmaap/dmaap_service.py26
-rw-r--r--src/onapsdk/exceptions.py109
-rw-r--r--src/onapsdk/msb/__init__.py17
-rw-r--r--src/onapsdk/msb/esr.py85
-rw-r--r--src/onapsdk/msb/k8s/__init__.py17
-rw-r--r--src/onapsdk/msb/k8s/connectivity_info.py105
-rw-r--r--src/onapsdk/msb/k8s/definition.py424
-rw-r--r--src/onapsdk/msb/k8s/instance.py190
-rw-r--r--src/onapsdk/msb/msb_service.py24
-rw-r--r--src/onapsdk/msb/multicloud.py55
-rw-r--r--src/onapsdk/msb/templates/msb_esr_vim_registration.json.j231
-rw-r--r--src/onapsdk/msb/templates/multicloud_k8s_add_connectivity_info.json.j28
-rw-r--r--src/onapsdk/msb/templates/multicloud_k8s_add_definition.json.j27
-rw-r--r--src/onapsdk/msb/templates/multicloud_k8s_create_configuration_template.json.j24
-rw-r--r--src/onapsdk/msb/templates/multicloud_k8s_create_profile_for_definition.json.j28
-rw-r--r--src/onapsdk/msb/templates/multicloud_k8s_instantiate.json.j218
-rw-r--r--src/onapsdk/nbi/__init__.py16
-rw-r--r--src/onapsdk/nbi/nbi.py490
-rw-r--r--src/onapsdk/nbi/templates/nbi_service_order_create.json.j228
-rw-r--r--src/onapsdk/onap_service.py327
-rw-r--r--src/onapsdk/sdc/__init__.py486
-rw-r--r--src/onapsdk/sdc/category_management.py285
-rw-r--r--src/onapsdk/sdc/component.py162
-rw-r--r--src/onapsdk/sdc/pnf.py74
-rw-r--r--src/onapsdk/sdc/properties.py202
-rw-r--r--src/onapsdk/sdc/sdc_element.py227
-rw-r--r--src/onapsdk/sdc/sdc_resource.py960
-rw-r--r--src/onapsdk/sdc/service.py932
-rw-r--r--src/onapsdk/sdc/templates/add_artifact_to_vf.json.j29
-rw-r--r--src/onapsdk/sdc/templates/add_resource_to_service.json.j210
-rw-r--r--src/onapsdk/sdc/templates/component_declare_input.json.j237
-rw-r--r--src/onapsdk/sdc/templates/pnf_create.json.j229
-rw-r--r--src/onapsdk/sdc/templates/sdc_element_action.json.j26
-rw-r--r--src/onapsdk/sdc/templates/sdc_resource_action.json.j23
-rw-r--r--src/onapsdk/sdc/templates/sdc_resource_add_deployment_artifact.json.j28
-rw-r--r--src/onapsdk/sdc/templates/sdc_resource_add_input.json.j239
-rw-r--r--src/onapsdk/sdc/templates/sdc_resource_add_nested_input.json.j235
-rw-r--r--src/onapsdk/sdc/templates/sdc_resource_add_property.json.j217
-rw-r--r--src/onapsdk/sdc/templates/sdc_resource_category.json.j213
-rw-r--r--src/onapsdk/sdc/templates/sdc_resource_component_set_property_value.json.j213
-rw-r--r--src/onapsdk/sdc/templates/sdc_resource_set_input_default_value.json.j28
-rw-r--r--src/onapsdk/sdc/templates/sdc_resource_set_property_value.json.j213
-rw-r--r--src/onapsdk/sdc/templates/service_create.json.j229
-rw-r--r--src/onapsdk/sdc/templates/vendor_create.json.j25
-rw-r--r--src/onapsdk/sdc/templates/vf_create.json.j227
-rw-r--r--src/onapsdk/sdc/templates/vf_vsp_update.json.j261
-rw-r--r--src/onapsdk/sdc/templates/vsp_create.json.j211
-rw-r--r--src/onapsdk/sdc/vendor.py108
-rw-r--r--src/onapsdk/sdc/vf.py164
-rw-r--r--src/onapsdk/sdc/vfc.py46
-rw-r--r--src/onapsdk/sdc/vl.py46
-rw-r--r--src/onapsdk/sdc/vsp.py370
-rw-r--r--src/onapsdk/sdnc/__init__.py15
-rw-r--r--src/onapsdk/sdnc/preload.py148
-rw-r--r--src/onapsdk/sdnc/sdnc_element.py48
-rw-r--r--src/onapsdk/sdnc/templates/instantiate_network_ala_carte_upload_preload_gr_api.json.j242
-rw-r--r--src/onapsdk/sdnc/templates/instantiate_vf_module_ala_carte_upload_preload_gr_api.json.j241
-rw-r--r--src/onapsdk/so/__init__.py14
-rw-r--r--src/onapsdk/so/deletion.py167
-rw-r--r--src/onapsdk/so/instantiation.py957
-rw-r--r--src/onapsdk/so/so_db_adapter.py94
-rw-r--r--src/onapsdk/so/so_element.py223
-rw-r--r--src/onapsdk/so/templates/add_cloud_site_with_identity_service.json.j222
-rw-r--r--src/onapsdk/so/templates/deletion_network.json.j222
-rw-r--r--src/onapsdk/so/templates/deletion_service.json.j226
-rw-r--r--src/onapsdk/so/templates/deletion_vf_module.json.j227
-rw-r--r--src/onapsdk/so/templates/deletion_vnf.json.j228
-rw-r--r--src/onapsdk/so/templates/instantiate_multi_vnf_service_macro.json.j2121
-rw-r--r--src/onapsdk/so/templates/instantiate_network_ala_carte.json.j210
-rw-r--r--src/onapsdk/so/templates/instantiate_network_vnf_ala_carte_base.json.j244
-rw-r--r--src/onapsdk/so/templates/instantiate_service_ala_carte.json.j245
-rw-r--r--src/onapsdk/so/templates/instantiate_service_macro.json.j2173
-rw-r--r--src/onapsdk/so/templates/instantiate_vf_module_ala_carte.json.j266
-rw-r--r--src/onapsdk/so/templates/instantiate_vnf_ala_carte.json.j218
-rw-r--r--src/onapsdk/so/templates/instantiate_vnf_macro.json.j2153
-rw-r--r--src/onapsdk/so/templates/instantiate_vnf_macro_so_vnf.json.j2151
-rw-r--r--src/onapsdk/so/templates/service_instance_model_info.json.j27
-rw-r--r--src/onapsdk/so/templates/vf_model_info.json.j215
-rw-r--r--src/onapsdk/so/templates/vnf_model_info.json.j29
-rw-r--r--src/onapsdk/utils/__init__.py40
-rw-r--r--src/onapsdk/utils/configuration.py25
-rw-r--r--src/onapsdk/utils/gui.py35
-rw-r--r--src/onapsdk/utils/headers_creator.py245
-rw-r--r--src/onapsdk/utils/jinja.py50
-rw-r--r--src/onapsdk/utils/mixins.py99
-rw-r--r--src/onapsdk/utils/tosca_file_handler.py106
-rw-r--r--src/onapsdk/version.py16
-rw-r--r--src/onapsdk/ves/__init__.py14
-rw-r--r--src/onapsdk/ves/templates/ves7_batch_with_stndDefined_valid.json.j2109
-rw-r--r--src/onapsdk/ves/templates/ves_stnd_event.json.j254
-rw-r--r--src/onapsdk/ves/templates/ves_stnd_valid_event.json.j254
-rw-r--r--src/onapsdk/ves/ves.py84
-rw-r--r--src/onapsdk/ves/ves_service.py27
-rw-r--r--src/onapsdk/vid/__init__.py16
-rw-r--r--src/onapsdk/vid/templates/vid_declare_resource.json.j23
-rw-r--r--src/onapsdk/vid/vid.py134
-rw-r--r--test-requirements.txt7
-rw-r--r--tests/__init__.py21
-rw-r--r--tests/data/__init__.py13
-rw-r--r--tests/data/bad.csar1
-rw-r--r--tests/data/bad_no_service.csarbin0 -> 60062 bytes
-rw-r--r--tests/data/csar.meta2
-rw-r--r--tests/data/service-Foo-template.yml1228
-rw-r--r--tests/data/service-TestPnfVsp-template.yml146
-rw-r--r--tests/data/service-TestServiceFyx-template.yml646
-rw-r--r--tests/data/service-Ubuntu16-template.yml543
-rw-r--r--tests/data/service-VfwcdsService-template.yml1439
-rw-r--r--tests/data/test.csarbin0 -> 58950 bytes
-rw-r--r--tests/data/test_so_service_data.yaml35
-rw-r--r--tests/data/tests_settings.py15
-rw-r--r--tests/data/utils_load_json_file_test.json1
-rwxr-xr-xtests/data/vLB_CBA_Python.zipbin0 -> 25559 bytes
-rw-r--r--tests/test_aai_bulk.py90
-rw-r--r--tests/test_aai_cloud_region.py71
-rw-r--r--tests/test_aai_complex.py138
-rw-r--r--tests/test_aai_customer.py580
-rw-r--r--tests/test_aai_geo_region.py54
-rw-r--r--tests/test_aai_line_of_business.py82
-rw-r--r--tests/test_aai_network.py159
-rw-r--r--tests/test_aai_owning_entity.py87
-rw-r--r--tests/test_aai_platform.py82
-rw-r--r--tests/test_aai_pnf.py140
-rw-r--r--tests/test_aai_project.py82
-rw-r--r--tests/test_aai_resource.py60
-rw-r--r--tests/test_aai_service.py770
-rw-r--r--tests/test_aai_service_instance.py275
-rw-r--r--tests/test_aai_service_subscription.py191
-rw-r--r--tests/test_aai_site_resource.py57
-rw-r--r--tests/test_aai_vf_module.py83
-rw-r--r--tests/test_aai_vnf.py449
-rw-r--r--tests/test_cds.py435
-rw-r--r--tests/test_cds_blueprint_models.py201
-rw-r--r--tests/test_clamp.py532
-rw-r--r--tests/test_configuration.py24
-rw-r--r--tests/test_cps.py240
-rw-r--r--tests/test_dmaap.py47
-rw-r--r--tests/test_esr.py39
-rw-r--r--tests/test_exceptions.py23
-rw-r--r--tests/test_generic_instance.txt0
-rw-r--r--tests/test_gui.py55
-rw-r--r--tests/test_headers_creator.py84
-rw-r--r--tests/test_jinja.py26
-rw-r--r--tests/test_msb_k8s.py378
-rw-r--r--tests/test_multicloud.py38
-rw-r--r--tests/test_nbi.py563
-rw-r--r--tests/test_onap_service.py380
-rw-r--r--tests/test_pnf.py485
-rw-r--r--tests/test_preload.py150
-rw-r--r--tests/test_sdc_category_management.py349
-rw-r--r--tests/test_sdc_component.py44
-rw-r--r--tests/test_sdc_element.py109
-rw-r--r--tests/test_sdc_resource.py487
-rw-r--r--tests/test_sdc_resource_properties.py1080
-rw-r--r--tests/test_sdc_vfc.py82
-rw-r--r--tests/test_sdc_vl.py82
-rw-r--r--tests/test_sdnc_element.py26
-rwxr-xr-xtests/test_service.py1498
-rw-r--r--tests/test_settings.py79
-rw-r--r--tests/test_so_db_adapter.py136
-rw-r--r--tests/test_so_deletion.py74
-rw-r--r--tests/test_so_element.py28
-rw-r--r--tests/test_so_instantiation.py1002
-rw-r--r--tests/test_so_orchestration_request.py103
-rw-r--r--tests/test_sp_partner.py84
-rw-r--r--tests/test_subnet.py49
-rw-r--r--tests/test_tosca_file_handler.py88
-rw-r--r--tests/test_utils.py46
-rw-r--r--tests/test_vendor.py316
-rw-r--r--tests/test_version.py20
-rw-r--r--tests/test_ves.py56
-rw-r--r--tests/test_vf.py564
-rw-r--r--tests/test_vid.py54
-rw-r--r--tests/test_vsp.py799
-rw-r--r--tox.ini14
-rw-r--r--upload-requirements.txt3
331 files changed, 44777 insertions, 0 deletions
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 = <user> # FILL ME
+ VIM_PASSWORD = <password> # FILL ME
+ VIM_SERVICE_URL = "http://<vim-url>/v3" # FILL ME
+ TENANT_NAME = <tenant> # FILL ME
+ TENANT_SEC_GROUP = <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 <none> 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 <none> 8080:30449/TCP
+
+#. Load ModelType via Bootstrap
+
+ .. code-block:: sh
+
+ curl --location --request POST 'http://<k8s-host>: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 <none> 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 <none> 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 <ks220y@att.com>",
+ "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 <ks220y@att.com>",
+ "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
--- /dev/null
+++ b/integration_tests/test_files/test_vLB_CBA_Python.zip
Binary files differ
diff --git a/integration_tests/ubuntu16.zip b/integration_tests/ubuntu16.zip
new file mode 100644
index 0000000..9a98baa
--- /dev/null
+++ b/integration_tests/ubuntu16.zip
Binary files 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
--- /dev/null
+++ b/tests/data/bad_no_service.csar
Binary files 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
--- /dev/null
+++ b/tests/data/test.csar
Binary files 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
--- /dev/null
+++ b/tests/data/vLB_CBA_Python.zip
Binary files 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 <ks220y@att.com>",
+ "definition": {
+ "tags": "vf-module-name",
+ "name": "vf-module-name",
+ "property": {
+ "description": "vf-module-name",
+ "type": "string"
+ },
+ "updated-by": "Singal, Kapil <ks220y@att.com>",
+ "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 <ks220y@att.com>",
+ "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 <platania@research.att.com>\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 <platania@research.att.com>"
+ 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 <platania@research.att.com>"
+ 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 <carlos.santana@onap.com>",
+ "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 <carlos.santana@onap.com>"
+ 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 <carlos.santana@onap.com>"
+ 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 <carlos.santana@onap.com>"
+ 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
--- /dev/null
+++ b/tests/test_generic_instance.txt
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 <morgan.richomme@orange.com>"
+
+
+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