From 5bf44a5c305380964b989314ecca94d0e854b4bb Mon Sep 17 00:00:00 2001 From: Richomme Date: Mon, 8 Jun 2020 21:16:24 +0000 Subject: [PATCH] WIP: work on service instantiation (VNF API) Signed-off-by: umry8364 --- .gitlab-ci.yml | 217 +-- integration_tests/test_01_vendor.py | 6 +- integration_tests/test_02_vsp.py | 12 +- integration_tests/test_03_vf.py | 11 +- integration_tests/test_04_service.py | 16 +- .../test_05_cloud_infrastructure.py | 85 ++ integration_tests/test_06_customer.py | 82 ++ integration_tests/test_07_instantiation.py | 134 ++ integration_tests/urls.py | 4 + setup.cfg | 2 +- src/onapsdk/aai/__init__.py | 7 + src/onapsdk/aai/aai_element.py | 99 ++ src/onapsdk/aai/business/__init__.py | 6 + src/onapsdk/aai/business/customer.py | 538 ++++++++ src/onapsdk/aai/business/instance.py | 49 + src/onapsdk/aai/business/owning_entity.py | 133 ++ src/onapsdk/aai/business/service.py | 193 +++ src/onapsdk/aai/business/vf_module.py | 158 +++ src/onapsdk/aai/business/vnf.py | 361 +++++ .../aai/cloud_infrastructure/__init__.py | 4 + .../aai/cloud_infrastructure/cloud_region.py | 533 +++++++ .../aai/cloud_infrastructure/complex.py | 208 +++ .../aai/cloud_infrastructure/tenant.py | 72 + .../aai/service_design_and_creation.py | 160 +++ src/onapsdk/cds/blueprint.py | 7 +- src/onapsdk/esr.py | 75 + src/onapsdk/msb.py | 13 + src/onapsdk/multicloud.py | 45 + src/onapsdk/nbi.py | 380 +++++ src/onapsdk/onap_service.py | 12 +- src/onapsdk/sdc.py | 2 - src/onapsdk/sdc_element.py | 3 - src/onapsdk/sdc_resource.py | 15 +- src/onapsdk/sdnc/__init__.py | 5 + src/onapsdk/sdnc/preload.py | 70 + src/onapsdk/sdnc/sdnc_element.py | 11 + src/onapsdk/service.py | 249 +++- src/onapsdk/so/__init__.py | 4 + src/onapsdk/so/deletion.py | 130 ++ src/onapsdk/so/instantiation.py | 419 ++++++ src/onapsdk/so/so_element.py | 193 +++ .../templates/aai_add_relationship.json.j2 | 11 + .../aai_owning_entity_create.json.j2 | 4 + .../templates/aai_service_create.json.j2 | 4 + .../templates/cloud_configuration.json.j2 | 5 + ...cloud_region_add_availability_zone.json.j2 | 7 + .../cloud_region_add_esr_system_info.json.j2 | 54 + .../templates/cloud_region_add_tenant.json.j2 | 5 + .../templates/cloud_region_create.json.j2 | 16 + src/onapsdk/templates/complex_create.json.j2 | 19 + src/onapsdk/templates/customer_create.json.j2 | 5 + ...stomer_service_subscription_create.json.j2 | 3 + .../templates/deletion_service.json.j2 | 23 + .../templates/deletion_vf_module.json.j2 | 25 + src/onapsdk/templates/deletion_vnf.json.j2 | 25 + .../instantiate_so_ala_carte.json.j2 | 38 + .../instantiate_vf_module_ala_carte.json.j2 | 63 + ...le_ala_carte_upload_preload_gr_api.json.j2 | 41 + ...e_ala_carte_upload_preload_vnf_api.json.j2 | 31 + .../instantiate_vnf_ala_carte.json.j2 | 48 + .../msb_esr_vim_registration.json.j2 | 31 + .../nbi_service_order_create.json.j2 | 28 + .../templates/service_instance_create.json.j2 | 22 + .../templates/service_instance_macro.json.j2 | 152 ++ .../service_instance_model_info.json.j2 | 7 + .../templates/service_model_info.json.j2 | 8 + src/onapsdk/templates/vf_model_info.json.j2 | 15 + .../vid_declare_line_of_business.json.j2 | 3 + .../vid_declare_owning_entity.json.j2 | 3 + .../templates/vid_declare_platform.json.j2 | 3 + .../templates/vid_declare_project.json.j2 | 3 + .../templates/vid_declare_resource.json.j2 | 3 + .../templates/vnf_instance_macro.json.j2 | 16 + src/onapsdk/templates/vnf_model_info.json.j2 | 9 + src/onapsdk/utils/__init__.py | 16 + src/onapsdk/utils/headers_creator.py | 73 + src/onapsdk/utils/tosca_file_handler.py | 83 ++ src/onapsdk/vendor.py | 3 - src/onapsdk/vf.py | 6 +- src/onapsdk/vid.py | 110 ++ src/onapsdk/vsp.py | 3 - tests/data/service-Foo-template.yml | 1228 +++++++++++++++++ tests/data/service-Ubuntu16-template.yml | 543 ++++++++ tests/test_aai_complex.py | 63 + tests/test_aai_customer.py | 404 ++++++ tests/test_aai_owning_entity.py | 73 + tests/test_aai_service.py | 681 +++++++++ tests/test_aai_service_instance.py | 62 + tests/test_aai_service_subscription.py | 5 + tests/test_aai_vf_module.py | 29 + tests/test_aai_vnf.py | 250 ++++ tests/test_esr.py | 28 + tests/test_generic_instance.txt | 0 tests/test_headers_creator.py | 39 +- tests/test_multicloud.py | 27 + tests/test_nbi.py | 332 +++++ tests/test_onap_service.py | 4 +- tests/test_preload.py | 33 + tests/test_sdc_element.py | 3 +- tests/test_sdc_resource.py | 19 +- tests/test_service.py | 226 +-- tests/test_so_deletion.py | 65 + tests/test_so_instantiation.py | 252 ++++ tests/test_so_orchestration_request.py | 71 + tests/test_tosca_file_handler.py | 83 ++ tests/test_vendor.py | 3 +- tests/test_vf.py | 5 +- tests/test_vid.py | 42 + tests/test_vsp.py | 6 +- 109 files changed, 9955 insertions(+), 368 deletions(-) create mode 100644 integration_tests/test_05_cloud_infrastructure.py create mode 100644 integration_tests/test_06_customer.py create mode 100644 integration_tests/test_07_instantiation.py create mode 100644 integration_tests/urls.py create mode 100644 src/onapsdk/aai/__init__.py create mode 100644 src/onapsdk/aai/aai_element.py create mode 100644 src/onapsdk/aai/business/__init__.py create mode 100644 src/onapsdk/aai/business/customer.py create mode 100644 src/onapsdk/aai/business/instance.py create mode 100644 src/onapsdk/aai/business/owning_entity.py create mode 100644 src/onapsdk/aai/business/service.py create mode 100644 src/onapsdk/aai/business/vf_module.py create mode 100644 src/onapsdk/aai/business/vnf.py create mode 100644 src/onapsdk/aai/cloud_infrastructure/__init__.py create mode 100644 src/onapsdk/aai/cloud_infrastructure/cloud_region.py create mode 100644 src/onapsdk/aai/cloud_infrastructure/complex.py create mode 100644 src/onapsdk/aai/cloud_infrastructure/tenant.py create mode 100644 src/onapsdk/aai/service_design_and_creation.py create mode 100644 src/onapsdk/esr.py create mode 100644 src/onapsdk/msb.py create mode 100644 src/onapsdk/multicloud.py create mode 100644 src/onapsdk/nbi.py create mode 100644 src/onapsdk/sdnc/__init__.py create mode 100644 src/onapsdk/sdnc/preload.py create mode 100644 src/onapsdk/sdnc/sdnc_element.py create mode 100644 src/onapsdk/so/__init__.py create mode 100644 src/onapsdk/so/deletion.py create mode 100644 src/onapsdk/so/instantiation.py create mode 100644 src/onapsdk/so/so_element.py create mode 100644 src/onapsdk/templates/aai_add_relationship.json.j2 create mode 100644 src/onapsdk/templates/aai_owning_entity_create.json.j2 create mode 100644 src/onapsdk/templates/aai_service_create.json.j2 create mode 100644 src/onapsdk/templates/cloud_configuration.json.j2 create mode 100644 src/onapsdk/templates/cloud_region_add_availability_zone.json.j2 create mode 100644 src/onapsdk/templates/cloud_region_add_esr_system_info.json.j2 create mode 100644 src/onapsdk/templates/cloud_region_add_tenant.json.j2 create mode 100644 src/onapsdk/templates/cloud_region_create.json.j2 create mode 100644 src/onapsdk/templates/complex_create.json.j2 create mode 100644 src/onapsdk/templates/customer_create.json.j2 create mode 100644 src/onapsdk/templates/customer_service_subscription_create.json.j2 create mode 100644 src/onapsdk/templates/deletion_service.json.j2 create mode 100644 src/onapsdk/templates/deletion_vf_module.json.j2 create mode 100644 src/onapsdk/templates/deletion_vnf.json.j2 create mode 100644 src/onapsdk/templates/instantiate_so_ala_carte.json.j2 create mode 100644 src/onapsdk/templates/instantiate_vf_module_ala_carte.json.j2 create mode 100644 src/onapsdk/templates/instantiate_vf_module_ala_carte_upload_preload_gr_api.json.j2 create mode 100644 src/onapsdk/templates/instantiate_vf_module_ala_carte_upload_preload_vnf_api.json.j2 create mode 100644 src/onapsdk/templates/instantiate_vnf_ala_carte.json.j2 create mode 100644 src/onapsdk/templates/msb_esr_vim_registration.json.j2 create mode 100644 src/onapsdk/templates/nbi_service_order_create.json.j2 create mode 100644 src/onapsdk/templates/service_instance_create.json.j2 create mode 100644 src/onapsdk/templates/service_instance_macro.json.j2 create mode 100644 src/onapsdk/templates/service_instance_model_info.json.j2 create mode 100644 src/onapsdk/templates/service_model_info.json.j2 create mode 100644 src/onapsdk/templates/vf_model_info.json.j2 create mode 100644 src/onapsdk/templates/vid_declare_line_of_business.json.j2 create mode 100644 src/onapsdk/templates/vid_declare_owning_entity.json.j2 create mode 100644 src/onapsdk/templates/vid_declare_platform.json.j2 create mode 100644 src/onapsdk/templates/vid_declare_project.json.j2 create mode 100644 src/onapsdk/templates/vid_declare_resource.json.j2 create mode 100644 src/onapsdk/templates/vnf_instance_macro.json.j2 create mode 100644 src/onapsdk/templates/vnf_model_info.json.j2 create mode 100644 src/onapsdk/utils/__init__.py create mode 100644 src/onapsdk/utils/tosca_file_handler.py create mode 100644 src/onapsdk/vid.py create mode 100644 tests/data/service-Foo-template.yml create mode 100644 tests/data/service-Ubuntu16-template.yml create mode 100644 tests/test_aai_complex.py create mode 100644 tests/test_aai_customer.py create mode 100644 tests/test_aai_owning_entity.py create mode 100644 tests/test_aai_service.py create mode 100644 tests/test_aai_service_instance.py create mode 100644 tests/test_aai_service_subscription.py create mode 100644 tests/test_aai_vf_module.py create mode 100644 tests/test_aai_vnf.py create mode 100644 tests/test_esr.py create mode 100644 tests/test_generic_instance.txt create mode 100644 tests/test_multicloud.py create mode 100644 tests/test_nbi.py create mode 100644 tests/test_preload.py create mode 100644 tests/test_so_deletion.py create mode 100644 tests/test_so_instantiation.py create mode 100644 tests/test_so_orchestration_request.py create mode 100644 tests/test_tosca_file_handler.py create mode 100644 tests/test_vid.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 17f59f06..e0696040 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,107 +1,116 @@ --- -stages: - - linting - - unit_test - - build - - test - - deploy - -image: docker:git -services: - - docker:dind -variables: - DOCKER_DRIVER: overlay - # 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' - -.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" - only: - - master - except: - variables: - - $JOBS_DISABLED - -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}" - only: - - branches - except: - refs: - - master - variables: - - $JOBS_DISABLED - -build_stable: - 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}" - only: - - tags - except: - variables: - - $JOBS_DISABLED - -integration_tests: - stage: test + stages: + - linting + - unit_test + - build + - test + - deploy + + image: docker:git services: - - name: registry.gitlab.com/orange-opensource/lfn/onap/mock_servers/mock-sdc:develop - alias: sdc.api.fe.simpledemo.onap.org - image: python:3.7 - allow_failure: true - script: - - pip install . - - pip install pytest mock # mock is needed as pytest parse all files before selection - - mv setup.cfg setup.old # pytest tries to use setup.cfg but we don't want - - pytest --verbose --junitxml=pytest-integration.xml integration_tests - artifacts: - reports: - junit: pytest-*.xml - except: - variables: - - $JOBS_DISABLED - -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: + - docker:dind + variables: + DOCKER_DRIVER: overlay + # 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' + + .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" + only: + - master + except: + variables: + - $JOBS_DISABLED + + 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}" + only: + - branches + except: + refs: + - master + variables: + - $JOBS_DISABLED + + build_stable: + 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}" + only: + - tags + except: + variables: + - $JOBS_DISABLED + + integration_tests: + stage: test variables: - - $JOBS_DISABLED - -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/Container-Scanning.gitlab-ci.yml' - - remote: 'https://gitlab.com/Orange-OpenSource/lfn/ci_cd/gitlab-ci-templates/raw/master/Code-Quality.gitlab-ci.yml' - - remote: 'https://gitlab.com/Orange-OpenSource/lfn/ci_cd/gitlab-ci-templates/raw/master/SAST.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 - + 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 + image: python:3.7 + allow_failure: true + script: + - pip install . + - pip install pytest mock # mock is needed as pytest parse all files before selection + - mv setup.cfg setup.old # pytest tries to use setup.cfg but we don't want + - pytest --verbose --junitxml=pytest-integration.xml integration_tests + artifacts: + reports: + junit: pytest-*.xml + except: + variables: + - $JOBS_DISABLED + + 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 + + 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/Container-Scanning.gitlab-ci.yml' + - remote: 'https://gitlab.com/Orange-OpenSource/lfn/ci_cd/gitlab-ci-templates/raw/master/Code-Quality.gitlab-ci.yml' + - remote: 'https://gitlab.com/Orange-OpenSource/lfn/ci_cd/gitlab-ci-templates/raw/master/SAST.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 + + \ No newline at end of file diff --git a/integration_tests/test_01_vendor.py b/integration_tests/test_01_vendor.py index c3cea6ff..5556bc2b 100644 --- a/integration_tests/test_01_vendor.py +++ b/integration_tests/test_01_vendor.py @@ -9,11 +9,13 @@ from onapsdk.vendor import Vendor import onapsdk.constants as const +from .urls import SDC_MOCK_URL + @pytest.mark.integration def test_vendor_unknown(): """Integration tests for Vendor.""" - SDC.base_front_url = "http://sdc.api.fe.simpledemo.onap.org:30206" + SDC.base_front_url = SDC_MOCK_URL SDC.base_back_url = Vendor.base_front_url response = requests.post("{}/reset".format(SDC.base_front_url)) response.raise_for_status() @@ -26,7 +28,7 @@ def test_vendor_unknown(): @pytest.mark.integration def test_vendor_onboard_unknown(): """Integration tests for Vendor.""" - SDC.base_front_url = "http://sdc.api.fe.simpledemo.onap.org:30206" + SDC.base_front_url = SDC_MOCK_URL SDC.base_back_url = Vendor.base_front_url response = requests.post("{}/reset".format(SDC.base_front_url)) response.raise_for_status() diff --git a/integration_tests/test_02_vsp.py b/integration_tests/test_02_vsp.py index bb92cb1c..c75d19b2 100644 --- a/integration_tests/test_02_vsp.py +++ b/integration_tests/test_02_vsp.py @@ -7,15 +7,19 @@ import requests +from onapsdk.sdc import SDC from onapsdk.vendor import Vendor from onapsdk.vsp import Vsp import onapsdk.constants as const +from .urls import SDC_MOCK_URL + + @pytest.mark.integration def test_vsp_unknown(): """Integration tests for Vsp.""" - Vendor.base_front_url = "http://sdc.api.fe.simpledemo.onap.org:30206" - Vendor.base_back_url = Vendor.base_front_url + SDC.base_front_url = SDC_MOCK_URL + SDC.base_back_url = SDC_MOCK_URL response = requests.post("{}/reset".format(Vendor.base_front_url)) response.raise_for_status() vendor = Vendor(name="test") @@ -40,8 +44,8 @@ def test_vsp_unknown(): @pytest.mark.integration def test_vsp_onboard_unknown(): """Integration tests for Vsp.""" - Vendor.base_front_url = "http://sdc.api.fe.simpledemo.onap.org:30206" - Vendor.base_back_url = Vendor.base_front_url + SDC.base_front_url = SDC_MOCK_URL + SDC.base_back_url = SDC_MOCK_URL response = requests.post("{}/reset".format(Vendor.base_front_url)) response.raise_for_status() vendor = Vendor(name="test") diff --git a/integration_tests/test_03_vf.py b/integration_tests/test_03_vf.py index f72b914d..13906f44 100644 --- a/integration_tests/test_03_vf.py +++ b/integration_tests/test_03_vf.py @@ -7,17 +7,20 @@ import requests +from onapsdk.sdc import SDC from onapsdk.vendor import Vendor from onapsdk.vsp import Vsp from onapsdk.vf import Vf import onapsdk.constants as const +from .urls import SDC_MOCK_URL + @pytest.mark.integration def test_vf_unknown(): """Integration tests for Vf.""" - Vendor.base_front_url = "http://sdc.api.fe.simpledemo.onap.org:30206" - Vendor.base_back_url = Vendor.base_front_url + SDC.base_front_url = SDC_MOCK_URL + SDC.base_back_url = SDC_MOCK_URL response = requests.post("{}/reset".format(Vendor.base_front_url)) response.raise_for_status() vendor = Vendor(name="test") @@ -42,8 +45,8 @@ def test_vf_unknown(): @pytest.mark.integration def test_vf_onboard_unknown(): """Integration tests for Vf.""" - Vendor.base_front_url = "http://sdc.api.fe.simpledemo.onap.org:30206" - Vendor.base_back_url = Vendor.base_front_url + SDC.base_front_url = SDC_MOCK_URL + SDC.base_back_url = SDC_MOCK_URL response = requests.post("{}/reset".format(Vendor.base_front_url)) response.raise_for_status() vendor = Vendor(name="test") diff --git a/integration_tests/test_04_service.py b/integration_tests/test_04_service.py index 547cc1db..ca04a3d7 100644 --- a/integration_tests/test_04_service.py +++ b/integration_tests/test_04_service.py @@ -14,12 +14,14 @@ from onapsdk.service import Service import onapsdk.constants as const +from .urls import SDC_MOCK_URL + @pytest.mark.integration def test_service_unknown(): """Integration tests for Service.""" - SDC.base_front_url = "http://sdc.api.fe.simpledemo.onap.org:30206" - SDC.base_back_url = Vendor.base_front_url + SDC.base_front_url = SDC_MOCK_URL + SDC.base_back_url = SDC_MOCK_URL response = requests.post("{}/reset".format(SDC.base_front_url)) response.raise_for_status() vendor = Vendor(name="test") @@ -39,14 +41,8 @@ def test_service_unknown(): svc.add_resource(vf) svc.checkin() assert svc.status == const.CHECKED_IN - svc.submit() - assert svc.status == const.SUBMITTED - svc.start_certification() - assert svc.status == const.UNDER_CERTIFICATION svc.certify() assert svc.status == const.CERTIFIED - svc.approve() - assert svc.status == const.APPROVED svc.distribute() assert svc.status == const.DISTRIBUTED assert svc.distributed @@ -54,8 +50,8 @@ def test_service_unknown(): @pytest.mark.integration def test_service_onboard_unknown(): """Integration tests for Service.""" - SDC.base_front_url = "http://sdc.api.fe.simpledemo.onap.org:30206" - SDC.base_back_url = Vendor.base_front_url + SDC.base_front_url = SDC_MOCK_URL + SDC.base_back_url = SDC_MOCK_URL response = requests.post("{}/reset".format(SDC.base_front_url)) response.raise_for_status() vendor = Vendor(name="test") diff --git a/integration_tests/test_05_cloud_infrastructure.py b/integration_tests/test_05_cloud_infrastructure.py new file mode 100644 index 00000000..eea37706 --- /dev/null +++ b/integration_tests/test_05_cloud_infrastructure.py @@ -0,0 +1,85 @@ +import pytest +import requests + +from onapsdk.aai.cloud_infrastructure import CloudRegion, Complex + +from .urls import AAI_MOCK_URL + + +@pytest.mark.integration +def test_cloud_region_get_all(): + CloudRegion.base_url = AAI_MOCK_URL + + requests.get(f"{CloudRegion.base_url}/reset") + cloud_regions = list(CloudRegion.get_all()) + assert len(cloud_regions) == 0 + + with pytest.raises(ValueError): + 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(): + + Complex.base_url = AAI_MOCK_URL + + 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(): + + CloudRegion.base_url = AAI_MOCK_URL + Complex.base_url = AAI_MOCK_URL + + 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(): + + CloudRegion.base_url = AAI_MOCK_URL + 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 00000000..22dda393 --- /dev/null +++ b/integration_tests/test_06_customer.py @@ -0,0 +1,82 @@ +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.service import Service + +from .urls import AAI_MOCK_URL + + +@pytest.mark.integration +def test_create_customer(): + + Customer.base_url = AAI_MOCK_URL + 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(): + + Customer.base_url = AAI_MOCK_URL + 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 + + service = Service("test_service") + service.unique_uuid = str(uuid4()) + customer.subscribe_service(service) + assert len(list(customer.service_subscriptions)) == 1 + assert customer.get_service_subscription_by_service_type(service.name) + + +@pytest.mark.integration +def test_link_service_subscription_to_cloud_region_and_tenant(): + + Customer.base_url = AAI_MOCK_URL + CloudRegion.base_url = AAI_MOCK_URL + ServiceSubscription.base_url = AAI_MOCK_URL + 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") + service = Service("test_service") + service.unique_uuid = str(uuid4()) + customer.subscribe_service(service) + service_subscription = customer.get_service_subscription_by_service_type(service.name) + + assert len(list(service_subscription.relationships)) == 0 + with pytest.raises(AttributeError): + service_subscription.cloud_region + with pytest.raises(AttributeError): + 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 00000000..868b8865 --- /dev/null +++ b/integration_tests/test_07_instantiation.py @@ -0,0 +1,134 @@ +import time +from unittest.mock import MagicMock, patch +from uuid import uuid4 + +import pytest +import requests +from onapsdk.aai.business import Customer, ServiceInstance, ServiceSubscription, VnfInstance +from onapsdk.aai.cloud_infrastructure import CloudRegion, Tenant +from onapsdk.sdnc.preload import VfModulePreload +from onapsdk.service import Service +from onapsdk.so.deletion import ServiceDeletionRequest, VfModuleDeletionRequest, VnfDeletionRequest +from onapsdk.so.instantiation import (ServiceInstantiation, + VfModuleInstantiation, VnfInstantiation) +from onapsdk.vid import LineOfBusiness, OwningEntity, Platform, Project + +from .urls import AAI_MOCK_URL, SO_MOCK_URL, SDNC_MOCK_URL + + +@pytest.mark.integration +def test_a_la_carte_instantiation(): + Customer.base_url = AAI_MOCK_URL + CloudRegion.base_url = AAI_MOCK_URL + ServiceSubscription.base_url = AAI_MOCK_URL + ServiceInstance.base_url = AAI_MOCK_URL + VnfInstance.base_url = AAI_MOCK_URL + ServiceInstantiation.base_url = SO_MOCK_URL + VnfInstantiation.base_url = SO_MOCK_URL + VfModuleInstantiation.base_url = SO_MOCK_URL + VfModulePreload.base_url = SDNC_MOCK_URL + ServiceDeletionRequest.base_url = SO_MOCK_URL + VfModuleDeletionRequest.base_url = SO_MOCK_URL + VnfDeletionRequest.base_url = SO_MOCK_URL + + 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": AAI_MOCK_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) + service_subscription = customer.get_service_subscription_by_service_type(service.name) + 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 = OwningEntity(name="test_owning_entity") + project = Project(name="test_project") + + # Service instantiation + service._distributed = True + assert len(list(service_subscription.service_instances)) == 0 + service_instantiation_request = ServiceInstantiation.instantiate_so_ala_carte( + service, + cloud_region, + tenant, + customer, + owning_entity, + project + ) + 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 = OwningEntity(name="test_owning_entity") + project = Project(name="test_project") + vnf = MagicMock() + line_of_business = LineOfBusiness(name="test_line_of_business") + platform = Platform(name="test_platform") + with pytest.raises(AttributeError): + service_instance.add_vnf( + vnf, + line_of_business, + platform + ) + service_instance.orchestration_status = "Active" + with patch.object(ServiceSubscription, "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.metadata = {"UUID": vnf_instance.model_version_id} + vf_module = MagicMock() + + with patch.object(ServiceSubscription, "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(ServiceSubscription, "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/urls.py b/integration_tests/urls.py new file mode 100644 index 00000000..29e84260 --- /dev/null +++ b/integration_tests/urls.py @@ -0,0 +1,4 @@ +SDC_MOCK_URL = "http://sdc.api.fe.simpledemo.onap.org:30206" +AAI_MOCK_URL = "http://aai.api.sparky.simpledemo.onap.org:5000" +SO_MOCK_URL = "http://so.api.simpledemo.onap.org:5001" +SDNC_MOCK_URL = "http://sdnc.api.simpledemo.onap.org:5002" \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 0ce2903d..e41cf05d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,6 +21,7 @@ install_requires = requests[socks]==2.23.0 jinja2==2.11.2 simplejson==3.17.0 + oyaml==0.9 setup_requires = pytest-runner==5.2 tests_require = @@ -29,7 +30,6 @@ tests_require = pytest-cov pytest-mock requests-mock - oyaml [options.packages.find] where=src diff --git a/src/onapsdk/aai/__init__.py b/src/onapsdk/aai/__init__.py new file mode 100644 index 00000000..e8512c31 --- /dev/null +++ b/src/onapsdk/aai/__init__.py @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 +"""ONAP SDK AAI package.""" + +# from .aai_element import * +# from .cloud_infrastructure import * +# from .instances import * +# from .service_design_and_creation import * diff --git a/src/onapsdk/aai/aai_element.py b/src/onapsdk/aai/aai_element.py new file mode 100644 index 00000000..0af6b22e --- /dev/null +++ b/src/onapsdk/aai/aai_element.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +"""AAI Element module.""" +from dataclasses import dataclass, field +from typing import Dict, Iterator, List, Optional + +from onapsdk.onap_service import OnapService +from onapsdk.utils.headers_creator import headers_aai_creator +from onapsdk.utils.jinja import jinja_env + + +@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) + + +class AaiElement(OnapService): + """Mother Class of all A&AI elements.""" + + name: str = "AAI" + server: str = "AAI" + base_url = "https://aai.api.sparky.simpledemo.onap.org:30233" + api_version = "/aai/v16" + headers = headers_aai_creator(OnapService.headers) + + @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 throught given dictionary and filter None values. + + Args: + dict_to_filter (Dict): Dictionary to filter out None + + Returns: + 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 + + """ + for relationship in self.send_message_json("GET", + "Get object relationships", + f"{self.url}/relationship-list")\ + .get("relationship", []): + 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"), + ) + + 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", + "add relationship to cloud region", + 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/business/__init__.py b/src/onapsdk/aai/business/__init__.py new file mode 100644 index 00000000..ae1a9399 --- /dev/null +++ b/src/onapsdk/aai/business/__init__.py @@ -0,0 +1,6 @@ +"""A&AI business package.""" +from .customer import Customer, ServiceSubscription +from .owning_entity import OwningEntity +from .service import ServiceInstance +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 00000000..0486b001 --- /dev/null +++ b/src/onapsdk/aai/business/customer.py @@ -0,0 +1,538 @@ +"""AAI business module.""" +from dataclasses import dataclass +from typing import Iterator +from urllib.parse import urlencode + +from onapsdk.service import Service as SdcService +from onapsdk.utils.jinja import jinja_env + +from ..aai_element import AaiElement, Relationship +from ..cloud_infrastructure.cloud_region import CloudRegion +from .service import ServiceInstance + + +@dataclass +class ServiceSubscription(AaiElement): + """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 + + self._cloud_region: "CloudRegion" = None + self._tenant: "Tenant" = None + + 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 + + Raises: + ValueError: Service instance with given filter parameters + doesn't exist + + 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}", + exception=ValueError + )["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 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. + + Raises: + AttributeError: Service subscription has no associated cloud region. + + Returns: + CloudRegion: CloudRegion object + + """ + if not self._cloud_region: + cloud_owner: str = None + cloud_region: str = None + for relationship in self.tenant_relationships: + for data in relationship.relationship_data: + if data["relationship-key"] == "cloud-region.cloud-owner": + cloud_owner = data["relationship-value"] + if data["relationship-key"] == "cloud-region.cloud-region-id": + cloud_region = data["relationship-value"] + if not all([cloud_owner, cloud_region]): + raise AttributeError("ServiceSubscription has no CloudOwner and/or " + "CloudRegion relationship") + self._cloud_region = CloudRegion.get_by_id(cloud_owner, cloud_region) + return self._cloud_region + + @property + def tenant(self) -> "Tenant": + """Tenant associated with service subscription. + + Raises: + AttributeError: Service subscription has no associated tenants + + Returns: + Tenant: Tenant object + + """ + if not self._tenant: + for relationship in self.tenant_relationships: + for data in relationship.relationship_data: + if data["relationship-key"] == "tenant.tenant-id": + try: + self._tenant = self.cloud_region.\ + get_tenant(data["relationship-value"]) + return self._tenant + except ValueError: + self._logger.info("Cloud region has no related tenant") + raise AttributeError("Cloud region has no related tenant") + raise AttributeError("ServiceSubscription has no tenant relationship") + return self._tenant + + @property + def sdc_service(self) -> "SdcService": + """Sdc service. + + SDC service associated with service subscription. + + Returns: + SdcService: SdcService object + + """ + return SdcService(self.service_type) + + 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 + + Raises: + ValueError: service subscription has no related service instance with given ID + + 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 + + Raises: + ValueError: service subscription has no related service instance with given name + + 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(AaiElement): + """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 + + Raises: + ValueError: No service subscription with given 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}"), + exception=ValueError + ) + return ServiceSubscription.create_from_api_response(response["service-subscription"][0], + self) + + @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.base_url}{cls.api_version}/business/customers?" + f"{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 + + Raises: + ValueError: Customer with given global_customer_id doesn't exist + + """ + 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}", + exception=ValueError + ) + 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) -> "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. + + 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, + ), + ) + 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 + + """ + 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 + ) + + def subscribe_service(self, service: SdcService) -> "ServiceSubscription": + """Create SDC Service subscription. + + If service is already subscribed it won't create a new resource but use the + existing one. + + Args: + service (SdcService): SdcService object to subscribe. + + Raises: + ValueError: Request response with HTTP error code + + """ + try: + return self.get_service_subscription_by_service_type(service.name) + except ValueError: + 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.name}"), + data=jinja_env() + .get_template("customer_service_subscription_create.json.j2") + .render( + service_id=service.unique_uuid, + ), + exception=ValueError + ) + return self.get_service_subscription_by_service_type(service.name) diff --git a/src/onapsdk/aai/business/instance.py b/src/onapsdk/aai/business/instance.py new file mode 100644 index 00000000..7ae13cf5 --- /dev/null +++ b/src/onapsdk/aai/business/instance.py @@ -0,0 +1,49 @@ +"""Base instance module.""" + +from abc import ABC, abstractmethod + +from ..aai_element import AaiElement + + +class Instance(AaiElement, 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, + persona_model_version: str = None, + widget_model_id: str = None, + widget_model_version: str = None) -> None: + """Instance initialization. + + Args: + 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): he 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. + """ + super().__init__() + self.resource_version: str = resource_version + self.model_invariant_id: str = model_invariant_id + self.model_version_id: str = model_version_id + self.persona_model_version: str = persona_model_version + self.widget_model_id: str = widget_model_id + self.widget_model_version: str = widget_model_version + + @abstractmethod + def delete(self) -> "DeletionRequest": + """Create instance deletion request. + + Send request to delete instance + + Returns: + DeletionRequest: Deletion request + + """ diff --git a/src/onapsdk/aai/business/owning_entity.py b/src/onapsdk/aai/business/owning_entity.py new file mode 100644 index 00000000..d24c535a --- /dev/null +++ b/src/onapsdk/aai/business/owning_entity.py @@ -0,0 +1,133 @@ +"""A&AI owning entity module.""" + +from uuid import uuid4 +from typing import Iterator + +from onapsdk.utils.jinja import jinja_env + +from ..aai_element import AaiElement + + +class OwningEntity(AaiElement): + """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}?resource-version={self.resource_version}") + + @classmethod + def get_all(cls) -> Iterator["OwningEntity"]: + """Get all owning entities. + + Yields: + OwningEntity: OwningEntity object + + """ + url: str = f"{cls.base_url}{cls.api_version}/business/owning-entities" + 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}"), + exception=ValueError + ) + 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: + ValueError: Owning entity with given name doesn't exist + + Returns: + OwningEntity: Owning entity with given name + + """ + for owning_entity in cls.get_all(): + if owning_entity.name == owning_entity_name: + return owning_entity + raise ValueError + + @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. + + Raises: + ValueError: request response with HTTP error code + + 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 + ), + exception=ValueError + ) + return cls.get_by_owning_entity_id(owning_entity_id) diff --git a/src/onapsdk/aai/business/service.py b/src/onapsdk/aai/business/service.py new file mode 100644 index 00000000..631cc2f2 --- /dev/null +++ b/src/onapsdk/aai/business/service.py @@ -0,0 +1,193 @@ +"""Service instance module.""" + +from typing import Iterator + +from onapsdk.so.deletion import ServiceDeletionRequest +from onapsdk.so.instantiation import VnfInstantiation + +from .instance import Instance +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_version_id=model_version_id, + persona_model_version=persona_model_version, + widget_model_id=widget_model_id, + widget_model_version=widget_model_version) + 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 + + 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})") + + @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 + + Raises: + ValueError: Request sent to get vnf instances returns HTTP error code. + + Yields: + VnfInstance: VnfInstance object + + """ + for relationship in self.relationships: + if relationship.related_to == "generic-vnf": + yield VnfInstance.create_from_api_response(\ + self.send_message_json("GET", + f"Get {self.instance_id} VNF", + f"{self.base_url}{relationship.related_link}", + exception=ValueError), + self) + + def add_vnf(self, # pylint: disable=too-many-arguments + vnf: "Vnf", + line_of_business: "LineOfBusiness", + platform: "Platform", + vnf_instance_name: str = None, + use_vnf_api: bool = False) -> "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 + 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. + use_vnf_api (bool, optional): Flague to determine if VNF_API or GR_API + should be used to instantiate. Defaults to False. + + Raises: + AttributeError: Service orchestration status is not "Active" + ValueError: Instantiation request error. + + Returns: + VnfInstantiation: VnfInstantiation request object + + """ + if self.orchestration_status != "Active": + raise AttributeError("Service has invalid orchestration status") + return VnfInstantiation.instantiate_ala_carte( + self, + vnf, + line_of_business, + platform, + vnf_instance_name, + use_vnf_api + ) + + def delete(self) -> "ServiceDeletionRequest": + """Create service deletion request. + + Send a request to delete service instance + + Returns: + ServiceDeletionRequest: Deletion request object + + """ + self._logger.debug("Delete %s service instance", self.instance_id) + return ServiceDeletionRequest.send_request(self) diff --git a/src/onapsdk/aai/business/vf_module.py b/src/onapsdk/aai/business/vf_module.py new file mode 100644 index 00000000..aed5c4b6 --- /dev/null +++ b/src/onapsdk/aai/business/vf_module.py @@ -0,0 +1,158 @@ +"""VF module instance.""" + +from onapsdk.so.deletion import VfModuleDeletionRequest + +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, + persona_model_version=persona_model_version, + widget_model_id=widget_model_id, + widget_model_version=widget_model_version) + 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 + + 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})") + + @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. + + Raises: + AttributeError: Could not find VF module for that VfModule instance + + Returns: + VfModule: VfModule object associated with vf module instance + + """ + return self.vnf_instance.vnf.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) -> "VfModuleDeletionRequest": + """Create deletion request. + + Send request to delete VF module instance + + Returns: + VfModuleDeletionRequest: Deletion request object + + """ + self._logger.debug("Delete %s VF module", self.vf_module_id) + return VfModuleDeletionRequest.send_request(self) diff --git a/src/onapsdk/aai/business/vnf.py b/src/onapsdk/aai/business/vnf.py new file mode 100644 index 00000000..819c01fd --- /dev/null +++ b/src/onapsdk/aai/business/vnf.py @@ -0,0 +1,361 @@ +"""Vnf instance module.""" + +from typing import Iterable, Iterator + +from onapsdk.so.deletion import VnfDeletionRequest +from onapsdk.so.instantiation import VfModuleInstantiation + +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_version_id=model_version_id, + persona_model_version=persona_model_version, + widget_model_id=widget_model_id, + widget_model_version=widget_model_version) + 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._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})") + + @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: + AttributeError: 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.service_subscription.sdc_service.vnfs: + if vnf.metadata["UUID"] == self.model_version_id: + self._vnf = vnf + return self._vnf + raise AttributeError("Couldn't find VNF for VNF instance") + 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, + vf_module: "VfModule", + vf_module_instance_name: str = None, + use_vnf_api=False, + vnf_parameters: Iterable["VnfParameter"] = None) -> "VfModuleInstantiation": + """Instantiate vf module for that VNF instance. + + Args: + vf_module (VfModule): VfModule to instantiate + vf_module_instance_name (str, optional): VfModule instance name. Defaults to None. + use_vnf_api (bool, optional): Flague which determines if VNF_API should be used. + Set to False to use GR_API. Defaults to False. + vnf_parameters (Iterable[VnfParameter], optional): VnfParameters to use for preloading. + Defaults to None. + + Returns: + VfModuleInstantiation: VfModuleInstantiation request object + + """ + return VfModuleInstantiation.instantiate_ala_carte( + vf_module, + self, + vf_module_instance_name, + use_vnf_api, + vnf_parameters + ) + + def delete(self) -> "VnfDeletionRequest": + """Create VNF deletion request. + + Send request to delete VNF instance + + Returns: + VnfDeletionRequest: Deletion request + + """ + self._logger.debug("Delete %s VNF", self.vnf_id) + return VnfDeletionRequest.send_request(self) diff --git a/src/onapsdk/aai/cloud_infrastructure/__init__.py b/src/onapsdk/aai/cloud_infrastructure/__init__.py new file mode 100644 index 00000000..007f3eeb --- /dev/null +++ b/src/onapsdk/aai/cloud_infrastructure/__init__.py @@ -0,0 +1,4 @@ +"""A&AI cloud infrastructure package.""" +from .cloud_region import AvailabilityZone, CloudRegion, EsrSystemInfo +from .complex import Complex +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 00000000..f8410505 --- /dev/null +++ b/src/onapsdk/aai/cloud_infrastructure/cloud_region.py @@ -0,0 +1,533 @@ +"""Cloud region module.""" +from dataclasses import dataclass +from typing import Any, Dict, Iterator, List, Optional +from urllib.parse import urlencode + +from onapsdk.multicloud import Multicloud +from onapsdk.utils.jinja import jinja_env + +from ..aai_element import AaiElement, 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(AaiElement): # 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(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.base_url}{cls.api_version}/cloud-infrastructure/" + f"cloud-regions?{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, 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: + ValueError: 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: + raise ValueError(f"CloudRegion with {cloud_owner},{cloud_region_id} cloud-id not found") + + @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.get("availability-zone-name"), + hypervisor_type=availability_zone.get("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", []) + ) + + 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), + exception=ValueError + ) + + def get_tenant(self, tenant_id: str) -> "Tenant": + """Get tenant with provided ID. + + Args: + tenant_id (str): Tenant ID + + Returns: + Tenant: Tenant object + + Raises: + ValueError: Tenant with provided ID doesn't exist + + """ + response: dict = self.send_message_json( + "GET", + "get tenants", + f"{self.url}/tenants/tenant/{tenant_id}", + exception=ValueError + ) + 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 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 00000000..d6d0cefc --- /dev/null +++ b/src/onapsdk/aai/cloud_infrastructure/complex.py @@ -0,0 +1,208 @@ +"""A&AI Complex module.""" +from typing import Iterator +from urllib.parse import urlencode + +from onapsdk.utils.jinja import jinja_env + +from ..aai_element import AaiElement + + +class Complex(AaiElement): # 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 + name: str, + physical_location_id: 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 = "") -> 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 "". + + """ + 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 + + def __repr__(self) -> str: + """Complex object description. + + Returns: + str: Complex object description + + """ + return (f"Complex(name={self.name}, 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}?resource-version={self.resource_version}") + + @classmethod + def create(cls, # pylint: disable=too-many-locals + name: str, + physical_location_id: 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 = "") -> "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, + ) + 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(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.base_url}{cls.api_version}/cloud-infrastructure/" + f"complexes?{urlencode(filter_parameters)}") + for complex_json in cls.send_message_json("GET", + "get cloud regions", + url).get("complex", []): + yield Complex( + name=complex_json["complex-name"], + physical_location_id=complex_json["physical-location-id"], + data_center_code=complex_json.get("data-center-code"), + identity_url=complex_json.get("identity-url"), + resource_version=complex_json.get("resource-version"), + physical_location_type=complex_json.get("physical-location-type"), + street1=complex_json.get("street1"), + street2=complex_json.get("street2"), + city=complex_json.get("city"), + state=complex_json.get("state"), + postal_code=complex_json.get("postal-code"), + country=complex_json.get("country"), + region=complex_json.get("region"), + latitude=complex_json.get("latitude"), + longitude=complex_json.get("longitude"), + elevation=complex_json.get("elevation"), + lata=complex_json.get("lata"), + ) diff --git a/src/onapsdk/aai/cloud_infrastructure/tenant.py b/src/onapsdk/aai/cloud_infrastructure/tenant.py new file mode 100644 index 00000000..d046531e --- /dev/null +++ b/src/onapsdk/aai/cloud_infrastructure/tenant.py @@ -0,0 +1,72 @@ +"""A&AI Tenant module.""" +from ..aai_element import AaiElement + + +class Tenant(AaiElement): + """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})" + ) + + @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/service_design_and_creation.py b/src/onapsdk/aai/service_design_and_creation.py new file mode 100644 index 00000000..fbc583d2 --- /dev/null +++ b/src/onapsdk/aai/service_design_and_creation.py @@ -0,0 +1,160 @@ +"""AAI service-design-and-creation module.""" +from typing import Iterator +from urllib.parse import urlencode + +from onapsdk.utils.jinja import jinja_env + +from .aai_element import AaiElement + + +class Service(AaiElement): + """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(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.base_url}{cls.api_version}/service-design-and-creation/" + f"services?{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 + + Raises: + ValueError: Creation request returns HTTP error code + + """ + 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 + ), + exception=ValueError + ) + + +class Model(AaiElement): + """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(cls) -> Iterator["Model"]: + """Get all models. + + Yields: + Model: Model object + + """ + for model in cls.send_message_json("GET", + "Get A&AI sdc models", + (f"{cls.base_url}{cls.api_version}/" + "service-design-and-creation/models")).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/cds/blueprint.py b/src/onapsdk/cds/blueprint.py index dec6a62d..17cf3056 100644 --- a/src/onapsdk/cds/blueprint.py +++ b/src/onapsdk/cds/blueprint.py @@ -458,7 +458,8 @@ def get_data_dictionaries(self) -> DataDictionarySet: dd_set.add(DataDictionary(mapping.generate_data_dictionary())) return dd_set - def get_cba_metadata(self, cba_tosca_meta_bytes: bytes) -> CbaMetadata: # pylint: disable=R0201 + @staticmethod + def get_cba_metadata(cba_tosca_meta_bytes: bytes) -> CbaMetadata: """Parse CBA TOSCA.meta file and get values from it. Args: @@ -484,8 +485,8 @@ def get_cba_metadata(self, cba_tosca_meta_bytes: bytes) -> CbaMetadata: # pylin template_tags=meta_dict.get("Template-Tags"), ) - def get_mappings_from_mapping_file(self, # pylint: disable=R0201 - cba_mapping_file_bytes: bytes + @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. diff --git a/src/onapsdk/esr.py b/src/onapsdk/esr.py new file mode 100644 index 00000000..e7bc57be --- /dev/null +++ b/src/onapsdk/esr.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +"""ESR module.""" +from onapsdk.msb import MSB +from onapsdk.utils.jinja import jinja_env + + +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.py b/src/onapsdk/msb.py new file mode 100644 index 00000000..08036b91 --- /dev/null +++ b/src/onapsdk/msb.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +"""Microsevice bus module.""" +from onapsdk.onap_service import OnapService +from onapsdk.utils.headers_creator import headers_msb_creator + + +class MSB(OnapService): + """Microservice Bus base class.""" + + base_url = "https://msb.api.simpledemo.onap.org:30283" + headers = headers_msb_creator(OnapService.headers) diff --git a/src/onapsdk/multicloud.py b/src/onapsdk/multicloud.py new file mode 100644 index 00000000..3979698e --- /dev/null +++ b/src/onapsdk/multicloud.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +"""Multicloud module.""" + +from onapsdk.msb 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/nbi.py b/src/onapsdk/nbi.py new file mode 100644 index 00000000..d7a90e7b --- /dev/null +++ b/src/onapsdk/nbi.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +"""NBI module.""" +from abc import ABC +from typing import Iterator +from uuid import uuid4 + +from onapsdk.aai.business.customer import Customer +from onapsdk.onap_service import OnapService +from onapsdk.utils import get_zulu_time_isoformat +from onapsdk.utils.jinja import jinja_env + + +class Nbi(OnapService, ABC): + """NBI base class.""" + + base_url = "https://nbi.api.simpledemo.onap.org:30274" + api_version = "/nbi/api/v4" + + @classmethod + def is_status_ok(cls) -> bool: + """Check NBI service status. + + Returns: + bool: True if NBI works fine, False otherwise + + Raises: + ValueError: Health check request returns HTTP error + + """ + try: + cls.send_message( + "GET", + "Check NBI status", + f"{cls.base_url}{cls.api_version}/status", + exception=ValueError + ) + except ValueError: + cls._logger.error("NBI Status check returns error") + 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) -> Iterator["Service"]: + """Get all services. + + 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"): + 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): # pylint: disable=too-many-instance-attributes + """Service order class.""" + + 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 + + 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"): + 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.get("relatedParty", [{}])[0].get("id"), + 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 + ) diff --git a/src/onapsdk/onap_service.py b/src/onapsdk/onap_service.py index 038ffdd2..93ec6aa3 100644 --- a/src/onapsdk/onap_service.py +++ b/src/onapsdk/onap_service.py @@ -35,7 +35,7 @@ class OnapService(ABC): """ - _logger: logging.Logger = logging.getLogger(__name__) + _logger: logging.Logger = logging.getLogger(__qualname__) server: str = None headers: Dict[str, str] = { "Content-Type": "application/json", @@ -43,6 +43,14 @@ class OnapService(ABC): } proxy: Dict[str, str] = None + 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 @@ -112,7 +120,7 @@ def send_message(cls, method: str, action: str, url: str, @classmethod def send_message_json(cls, method: str, action: str, url: str, - **kwargs) -> Union[Dict[Any, Any], None]: + **kwargs) -> Dict[Any, Any]: """ Send a message to an ONAP service and parse the response as JSON. diff --git a/src/onapsdk/sdc.py b/src/onapsdk/sdc.py index 568c2a3f..d44a7428 100644 --- a/src/onapsdk/sdc.py +++ b/src/onapsdk/sdc.py @@ -5,7 +5,6 @@ from typing import Any, Dict, List from abc import ABC, abstractmethod -import logging from requests import Response from onapsdk.onap_service import OnapService @@ -21,7 +20,6 @@ class SDC(OnapService, ABC): ACTION_METHOD: str base_front_url = "https://sdc.api.fe.simpledemo.onap.org:30207" base_back_url = "https://sdc.api.be.simpledemo.onap.org:30204" - _logger: logging.Logger = logging.getLogger(__name__) def __init__(self): """Initialize the object.""" diff --git a/src/onapsdk/sdc_element.py b/src/onapsdk/sdc_element.py index 07c7e645..51af9214 100644 --- a/src/onapsdk/sdc_element.py +++ b/src/onapsdk/sdc_element.py @@ -5,8 +5,6 @@ from typing import Any, Dict, List from abc import ABC, abstractmethod -import logging - from onapsdk.sdc import SDC import onapsdk.constants as const @@ -14,7 +12,6 @@ class SdcElement(SDC, ABC): """Mother Class of all SDC elements.""" - _logger: logging.Logger = logging.getLogger(__name__) ACTION_TEMPLATE = 'sdc_element_action.json.j2' ACTION_METHOD = 'PUT' diff --git a/src/onapsdk/sdc_resource.py b/src/onapsdk/sdc_resource.py index 461980a8..b051c96d 100644 --- a/src/onapsdk/sdc_resource.py +++ b/src/onapsdk/sdc_resource.py @@ -17,7 +17,6 @@ class SdcResource(SDC, ABC): # pylint: disable=too-many-instance-attributes """Mother Class of all SDC resources.""" - _logger: logging.Logger = logging.getLogger(__name__) RESOURCE_PATH = 'resources' ACTION_TEMPLATE = 'sdc_resource_action.json.j2' ACTION_METHOD = 'POST' @@ -73,7 +72,10 @@ def load(self) -> None: def deep_load(self) -> None: """Deep load Object informations from SDC.""" - url = "{}/sdc1/feProxy/rest/v1/followed".format(self.base_front_url) + 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) @@ -300,13 +302,8 @@ def _parse_sdc_status(sdc_status: str, distribution_state: str, """ logger.debug("Parse status for SDC Resource") if sdc_status.capitalize() == const.CERTIFIED: - if distribution_state: - if distribution_state == const.DISTRIBUTION_NOT_APPROVED: - return const.CERTIFIED - if distribution_state == const.DISTRIBUTION_APPROVED: - return const.APPROVED - if distribution_state == const.SDC_DISTRIBUTED: - return const.DISTRIBUTED + 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 diff --git a/src/onapsdk/sdnc/__init__.py b/src/onapsdk/sdnc/__init__.py new file mode 100644 index 00000000..4a375741 --- /dev/null +++ b/src/onapsdk/sdnc/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +"""SDNC package.""" +from .preload import VfModulePreload diff --git a/src/onapsdk/sdnc/preload.py b/src/onapsdk/sdnc/preload.py new file mode 100644 index 00000000..a00d3ec4 --- /dev/null +++ b/src/onapsdk/sdnc/preload.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +"""SDNC preload module.""" +from typing import Dict, Iterable + +from onapsdk.utils.headers_creator import headers_sdnc_creator +from onapsdk.utils.jinja import jinja_env + +from .sdnc_element import SdncElement + + +class VfModulePreload(SdncElement): + """Class to upload vf module preload.""" + + headers: Dict[str, str] = headers_sdnc_creator(SdncElement.headers) + + @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["VnfParameter"] = None, + use_vnf_api=False) -> 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[VnfParameter], optional): Iterable object of VnfParameters. + Defaults to None. + use_vnf_api (bool, optional): Flague which determines if VNF_API should be used. + Set to False to use GR_API. Defaults to False. + + Raises: + ValueError: Preload request returns HTTP response with error code + + """ + vnf_para = [] + if vnf_parameters: + for vnf_parameter in vnf_parameters: + vnf_para.append({ + "name": vnf_parameter.name, + "value": vnf_parameter.value + }) + if use_vnf_api: + url: str = (f"{cls.base_url}/restconf/operations/" + "VNF-API:preload-vnf-topology-operation") + description: str = "Upload VF module preload using VNF-API" + template: str = "instantiate_vf_module_ala_carte_upload_preload_vnf_api.json.j2" + else: + url: str = (f"{cls.base_url}/restconf/operations/" + "GENERIC-RESOURCE-API:preload-vf-module-topology-operation") + description: str = "Upload VF module preload using GENERIC-RESOURCE-API" + template: str = "instantiate_vf_module_ala_carte_upload_preload_gr_api.json.j2" + cls.send_message_json( + "POST", + description, + url, + data=jinja_env().get_template( + template). + render( + vnf_instance=vnf_instance, + vf_module_instance_name=vf_module_instance_name, + vf_module=vf_module, + vnf_parameters=vnf_para + ), + exception=ValueError + ) diff --git a/src/onapsdk/sdnc/sdnc_element.py b/src/onapsdk/sdnc/sdnc_element.py new file mode 100644 index 00000000..37564968 --- /dev/null +++ b/src/onapsdk/sdnc/sdnc_element.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +"""SDNC base module.""" +from onapsdk.onap_service import OnapService + + +class SdncElement(OnapService): + """SDNC base class.""" + + base_url = "https://sdnc.api.simpledemo.onap.org:30267" diff --git a/src/onapsdk/service.py b/src/onapsdk/service.py index 139e8e42..3cff2837 100644 --- a/src/onapsdk/service.py +++ b/src/onapsdk/service.py @@ -2,23 +2,82 @@ # -*- coding: utf-8 -*- # SPDX-License-Identifier: Apache-2.0 """Service module.""" +from collections import namedtuple +from dataclasses import dataclass +from difflib import SequenceMatcher +from io import BytesIO from os import makedirs -import logging -from typing import Dict, List +import time +import re +from typing import Dict, Iterable, List from zipfile import ZipFile, BadZipFile +import oyaml as yaml + import onapsdk.constants as const from onapsdk.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_governor, - headers_sdc_operator, - headers_sdc_tester) +from onapsdk.utils.headers_creator import headers_sdc_creator from onapsdk.utils.jinja import jinja_env -class Service(SdcResource): +@dataclass +class VfModule: + """VfModule dataclass.""" + + name: str + group_type: str + metadata: dict + properties: dict + + +@dataclass +class NodeTemplate: + """Node template dataclass. + + Base class for Vnf and Network classes. + """ + + name: str + node_template_type: str + metadata: dict + properties: dict + capabilities: dict + + +@dataclass +class Vnf(NodeTemplate): + """Vnf dataclass.""" + + vf_module: VfModule = None + + def associate_vf_module(self, vf_modules: Iterable[VfModule]) -> None: + """Iterate through Service vf modules and found the valid one. + + This is experimental! To be honest we are not sure if it works + correctly, it should be clarified with ONAP community. + + Args: + vf_modules (Iterable[VfModule]): Service vf modules + + """ + AssociateMatch = namedtuple("AssociateMatch", ["ratio", "object"]) + best_match: AssociateMatch = AssociateMatch(0.0, None) + for vf_module in vf_modules: # type: VfModule + current_ratio: float = SequenceMatcher(None, + self.name.lower(), + vf_module.name.lower()).ratio() + if current_ratio > best_match.ratio: + best_match = AssociateMatch(current_ratio, vf_module) + self.vf_module = best_match.object + + +class Network(NodeTemplate): # pylint: disable=too-few-public-methods + """Network dataclass.""" + + +class Service(SdcResource): # pylint: disable=too-many-instance-attributes """ ONAP Service Object used for SDC operations. @@ -39,7 +98,6 @@ class Service(SdcResource): """ SERVICE_PATH = "services" - _logger: logging.Logger = logging.getLogger(__name__) headers = headers_sdc_creator(SdcResource.headers) def __init__(self, name: str = None, sdc_values: Dict[str, str] = None, @@ -60,12 +118,18 @@ def __init__(self, name: str = None, sdc_values: Dict[str, str] = None, 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._networks: list = None + self._vf_modules: list = None def onboard(self) -> None: """Onboard the Service in SDC.""" # first Lines are equivalent for all onboard functions but it's more readable - if not self.status: # # pylint: disable=R0801 + if not self.status: self.create() + time.sleep(10) self.onboard() elif self.status == const.DRAFT: if not self.resources: @@ -73,21 +137,17 @@ def onboard(self) -> None: for resource in self.resources: self.add_resource(resource) self.checkin() + time.sleep(10) self.onboard() elif self.status == const.CHECKED_IN: - self.submit() - self.onboard() - elif self.status == const.SUBMITTED: - self.start_certification() - self.onboard() - elif self.status == const.UNDER_CERTIFICATION: self.certify() + time.sleep(10) self.onboard() + time.sleep(10) elif self.status == const.CERTIFIED: - self.approve() - self.onboard() - elif self.status == const.APPROVED: self.distribute() + else: + self._logger.error("Service has invalid status") @property def distribution_id(self) -> str: @@ -108,6 +168,137 @@ def distributed(self) -> bool: self._check_distributed() return self._distributed + @property + def tosca_template(self) -> str: + """Service tosca template file. + + Get tosca template from service tosca model bytes. + + Raises: + AttributeError: Tosca model can't be downloaded using HTTP API + + Returns: + str: Tosca template file + + """ + if not self._tosca_template and self.tosca_model: + with ZipFile(BytesIO(self.tosca_model)) as myzip: + for name in myzip.namelist(): + if (name[-13:] == "-template.yml" + and name[:20] == "Definitions/service-"): + service_template = name + with myzip.open(service_template) as template_file: + self._tosca_template = yaml.safe_load(template_file.read()) + return self._tosca_template + + @property + def tosca_model(self) -> bytes: + """Service's tosca model file. + + Send request to get service TOSCA model, + + Raises: + AttributeError: Tosca model can't be downloaded using HTTP API + + 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, + exception=AttributeError).content + return self._tosca_model + + @property + def vnfs(self) -> List[Vnf]: + """Service Vnfs. + + Load VNFs from service's tosca file + + Raises: + AttributeError: Service has no TOSCA template + + Returns: + List[Vnf]: Vnf objects list + + """ + if not self.tosca_template: + raise AttributeError("Service has no TOSCA template") + if self._vnfs is None: + self._vnfs = [] + for node_template_name, values in \ + self.tosca_template.get("topology_template", {}).get("node_templates", {}).items(): + if re.match("org.openecomp.resource.vf.*", values["type"]): + vnf: Vnf = Vnf( + name=node_template_name, + node_template_type=values["type"], + metadata=values["metadata"], + properties=values["properties"], + capabilities=values.get("capabilities", {}) + ) + vnf.associate_vf_module(self.vf_modules) + self._vnfs.append(vnf) + return self._vnfs + + @property + def networks(self) -> List[Network]: + """Service networks. + + Load networks from service's tosca file + + Raises: + AttributeError: Service has no TOSCA template + + Returns: + List[Network]: Network objects list + + """ + if not self.tosca_template: + raise AttributeError("Service has no TOSCA template") + if self._networks is None: + self._networks = [] + for node_template_name, values in \ + self.tosca_template.get("topology_template", {}).get("node_templates", {}).items(): + if re.match("org.openecomp.resource.vl.*", values["type"]): + self._networks.append(Network( + name=node_template_name, + node_template_type=values["type"], + metadata=values["metadata"], + properties=values["properties"], + capabilities=values["capabilities"] + )) + return self._networks + + @property + def vf_modules(self) -> List[VfModule]: + """Service VF modules. + + Load VF modules from service's tosca file + + Returns: + List[VfModule]: VfModule objects list + + """ + if self._vf_modules is None: + self._vf_modules = [] + groups: dict = self.tosca_template.get("topology_template", {}).get("groups", {}) + for group_name, values in groups.items(): + self._vf_modules.append(VfModule( + name=group_name, + group_type=values["type"], + metadata=values["metadata"], + properties=values["properties"] + )) + return self._vf_modules + def create(self) -> None: """Create the Service in SDC if not already existing.""" self._create("service_create.json.j2", name=self.name) @@ -157,29 +348,29 @@ def submit(self) -> None: def start_certification(self) -> None: """Start Certification on Service.""" - headers = headers_sdc_tester(SdcResource.headers) - self._verify_lcm_to_sdc(const.SUBMITTED, + 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_tester(SdcResource.headers) - self._verify_lcm_to_sdc(const.UNDER_CERTIFICATION, + 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_governor(SdcResource.headers) + 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_operator(SdcResource.headers) - self._verify_distribute_to_sdc(const.APPROVED, + headers = headers_sdc_creator(SdcResource.headers) + self._verify_distribute_to_sdc(const.CERTIFIED, const.DISTRIBUTE, headers=headers) @@ -217,7 +408,7 @@ 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_operator(SdcResource.headers) + headers = headers_sdc_creator(SdcResource.headers) result = self.send_message_json("GET", "Check distribution for {}".format( self.name), @@ -247,7 +438,7 @@ 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_operator(SdcResource.headers) + headers = headers_sdc_creator(SdcResource.headers) result = self.send_message_json("GET", "Get Metadata for {}".format( self.name), @@ -255,7 +446,9 @@ def load_metadata(self) -> None: headers=headers) if (result and 'distributionStatusOfServiceList' in result and len(result['distributionStatusOfServiceList']) > 0): - dist_status = result['distributionStatusOfServiceList'][-1] + # 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 diff --git a/src/onapsdk/so/__init__.py b/src/onapsdk/so/__init__.py new file mode 100644 index 00000000..78d9e542 --- /dev/null +++ b/src/onapsdk/so/__init__.py @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 +"""ONAP SDK SO package.""" + +# from .so_element import OrchestrationRequest diff --git a/src/onapsdk/so/deletion.py b/src/onapsdk/so/deletion.py new file mode 100644 index 00000000..bea87b01 --- /dev/null +++ b/src/onapsdk/so/deletion.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +"""Deletion module.""" +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: "AaiElement", use_vnf_api: bool = False) -> "Deletion": + """Abstract method to send instance deletion request. + + Raises: + NotImplementedError: Needs to be implemented in inheriting classes + + """ + raise NotImplementedError + + +class VfModuleDeletionRequest(DeletionRequest): + """VF module deletion class.""" + + @classmethod + def send_request(cls, + instance: "VfModuleInstance", + use_vnf_api: bool = False) -> "VfModuleDeletion": + """Send request to SO to delete VNF instance. + + Args: + vnf_instance (VnfInstance): VNF instance to delete + use_vnf_api (bool, optional): Flague to determine if VNF_API or GR_API + should be used to deletion. Defaults to False. + + 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, + use_vnf_api=use_vnf_api), + exception=ValueError, + headers=headers_so_creator(OnapService.headers)) + return cls(request_id=response["requestReferences"]["requestId"]) + + +class VnfDeletionRequest(DeletionRequest): + """VNF deletion class.""" + + @classmethod + def send_request(cls, + instance: "VnfInstance", + use_vnf_api: bool = False) -> "VnfDeletionRequest": + """Send request to SO to delete VNF instance. + + Args: + instance (VnfInstance): VNF instance to delete + use_vnf_api (bool, optional): Flague to determine if VNF_API or GR_API + should be used to deletion. Defaults to False. + + 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, + use_vnf_api=use_vnf_api), + exception=ValueError, + headers=headers_so_creator(OnapService.headers)) + return cls(request_id=response["requestReferences"]["requestId"]) + + +class ServiceDeletionRequest(DeletionRequest): + """Service deletion request class.""" + + @classmethod + def send_request(cls, + instance: "ServiceInstance", + use_vnf_api: bool = False) -> "ServiceDeletionRequest": + """Send request to SO to delete service instance. + + Args: + instance (ServiceInstance): service instance to delete + use_vnf_api (bool, optional): Flague to determine if VNF_API or GR_API + should be used to deletion. Defaults to False. + + 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, + use_vnf_api=use_vnf_api), + exception=ValueError, + 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 00000000..b21315f7 --- /dev/null +++ b/src/onapsdk/so/instantiation.py @@ -0,0 +1,419 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +"""Instantion module.""" +from abc import ABC +from dataclasses import dataclass +from typing import Iterable +from uuid import uuid4 + +from onapsdk.onap_service import OnapService +from onapsdk.sdnc import VfModulePreload +from onapsdk.service import Service as SdcService, Vnf, VfModule +from onapsdk.utils.jinja import jinja_env +from onapsdk.utils.headers_creator import headers_so_creator +from onapsdk.vid import LineOfBusiness, Platform + +from .so_element import OrchestrationRequest + + +@dataclass +class VnfParameter: + """Class to store vnf parameter used for preload. + + Contains two values: name of vnf parameter and it's value + """ + + name: str + value: str + + +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): + """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, + vnf_instance, + vf_module_instance_name: str = None, + use_vnf_api=False, + vnf_parameters: Iterable[VnfParameter] = None + ) -> "VfModuleInstantiation": + """Instantiate VF module. + + Iterate throught vf modules from service Tosca file and instantiate vf modules. + + Args: + 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. + use_vnf_api (bool, optional): Flague which determines if VNF_API or + GR_API should be used. + Defaults to False. + vnf_parameters (Iterable[VnfParameter], optional): Parameters which are + going to be used in preload upload for vf modules. Defaults to None. + + Raises: + AttributeError: VNF is not successfully instantiated. + ValueError: VF module instnatiation request returns HTTP error code. + + Yields: + Iterator[VfModuleInstantiation]: VfModuleInstantiation class object. + + """ + sdc_service: SdcService = vnf_instance.service_instance.service_subscription.sdc_service + if vf_module_instance_name is None: + vf_module_instance_name = \ + f"Python_ONAP_SDK_vf_module_instance_{str(uuid4())}" + VfModulePreload.upload_vf_module_preload( + vnf_instance, + vf_module_instance_name, + vf_module, + vnf_parameters, + use_vnf_api=use_vnf_api + ) + 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=vnf_instance.service_instance.service_subscription.cloud_region, + tenant=vnf_instance.service_instance.service_subscription.tenant, + vnf_instance=vnf_instance, + use_vnf_api=use_vnf_api + ), + headers=headers_so_creator(OnapService.headers), + exception=ValueError + ) + return VfModuleInstantiation( + name=vf_module_instance_name, + request_id=response["requestReferences"].get("requestId"), + instance_id=response["requestReferences"].get("instanceId"), + vf_module=vf_module + ) + + +class VnfInstantiation(Instantiation): + """VNF instantiation class.""" + + def __init__(self, # pylint: disable=too-many-arguments + name: str, + request_id: str, + instance_id: str, + line_of_business: LineOfBusiness, + platform: Platform, + 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 (LineOfBusiness): LineOfBusiness class object + platform (Platform): Platform class object + vnf (Vnf): Vnf class object + """ + super().__init__(name, request_id, instance_id) + self.line_of_business = line_of_business + self.platform = platform + self.vnf = vnf + + @classmethod + def create_from_request_response(cls, request_response: dict) -> "VnfInstantiation": + """Create VNF instantiation object based on request details. + + Raises: + ValueError: Service related with given object doesn't exist + ValueError: No ServiceInstantiation related with given VNF instantiation + ValueError: VNF related with given object doesn't exist + ValueError: 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 ValueError("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 ValueError("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=LineOfBusiness.create(request_response.get("request", {})\ + .get("requestDetails", {}).get("lineOfBusiness", {}).get("lineOfBusinessName")), + platform=Platform.create(request_response.get("request", {})\ + .get("requestDetails", {}).get("platform", {}).get("platformName")), + vnf=vnf + ) + raise ValueError("Invalid vnf instantions request dictionary") + + @classmethod + def get_by_vnf_instance_name(cls, vnf_instance_name: str) -> "VnfInstantiation": + """Get VNF instantiation request by instance name. + + Raises: + ValueError: Vnf instance with given name doesn't exist + + 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) + ) + if not response.get("requestList", []): + raise ValueError("Vnf instance doesn't exist") + for details in response["requestList"]: + return cls.create_from_request_response(details) + raise ValueError("No createInstance request found") + + @classmethod + def instantiate_ala_carte(cls, # pylint: disable=too-many-arguments + aai_service_instance: "ServiceInstance", + vnf_object: "Vnf", + line_of_business_object: "LineOfBusiness", + platform_object: "Platform", + vnf_instance_name: str = None, + use_vnf_api: bool = False) -> "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 + vnf_instance_name (str, optional): Vnf instance name. Defaults to None. + use_vnf_api (bool, optional): Flague which determines if VF_API should be used. + Set False if you want to use GR_API. Defaults to False. + + Raises: + ValueError: Instantiate request returns response with HTTP error code + + Returns: + VnfInstantiation: VnfInstantiation object + + """ + sdc_service: SdcService = aai_service_instance.service_subscription.sdc_service + 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 {aai_service_instance.service_subscription.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( + vnf_service_instance_name=vnf_instance_name, + vnf=vnf_object, + service=sdc_service, + cloud_region=aai_service_instance.service_subscription.cloud_region, + tenant=aai_service_instance.service_subscription.tenant, + line_of_business=line_of_business_object, + platform=platform_object, + service_instance=aai_service_instance, + use_vnf_api=use_vnf_api + ), + headers=headers_so_creator(OnapService.headers), + exception=ValueError + ) + return VnfInstantiation( + name=vnf_instance_name, + request_id=response["requestReferences"]["requestId"], + instance_id=response["requestReferences"]["instanceId"], + line_of_business=line_of_business_object, + platform=platform_object, + vnf=vnf_object + ) + +class ServiceInstantiation(Instantiation): + """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: "Project") -> 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 (Project): Project class object + + """ + 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_so_ala_carte(cls, # pylint: disable=too-many-arguments + sdc_service: "SdcService", + cloud_region: "CloudRegion", + tenant: "Tenant", + customer: "Customer", + owning_entity: "OwningEntity", + project: "Project", + service_instance_name: str = None, + use_vnf_api: bool = False) -> "ServiceInstantiationc": + """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 (Project): Project to use in instantiation request + service_instance_name (str, optional): Service instance name. Defaults to None. + use_vnf_api (bool, optional): Flague to determine if VNF_API or GR_API + should be used to instantiate. Defaults to False. + + Raises: + ValueError: Instantiation request returns HTTP error code. + + Returns: + ServiceInstantiation: instantiation request object + + """ + if not sdc_service.distributed: + raise ValueError("Service is not distributed") + 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_so_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, + use_vnf_api=use_vnf_api + ), + headers=headers_so_creator(OnapService.headers), + exception=ValueError + ) + 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 instane associated with service instantiation request. + + Raises: + AttributeError: Service is not instantiated + AttributeError: A&AI resource is not created + + Returns: + ServiceInstance: ServiceInstance + + """ + if self.status != self.StatusEnum.COMPLETED: + raise AttributeError("Service not instantiated") + 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 ValueError: + self._logger.error("A&AI resources not created properly") + raise AttributeError diff --git a/src/onapsdk/so/so_element.py b/src/onapsdk/so/so_element.py new file mode 100644 index 00000000..75ce102d --- /dev/null +++ b/src/onapsdk/so/so_element.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +"""SO Element module.""" +from abc import ABC +from dataclasses import dataclass +from enum import Enum +from typing import Dict + +import json + +from onapsdk.service import Service +from onapsdk.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.tosca_file_handler import get_modules_list_from_tosca_file + + +@dataclass +class SoElement(OnapService): + """Mother Class of all SO elements.""" + + name: str = None + _server: str = "SO" + base_url = "http://so.api.simpledemo.onap.org:30277" + api_version = "v7" + _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 tosca + 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 + ) + + +class OrchestrationRequest(SoElement, ABC): + """Base SO orchestration request class.""" + + 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/templates/aai_add_relationship.json.j2 b/src/onapsdk/templates/aai_add_relationship.json.j2 new file mode 100644 index 00000000..5d7acb89 --- /dev/null +++ b/src/onapsdk/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/templates/aai_owning_entity_create.json.j2 b/src/onapsdk/templates/aai_owning_entity_create.json.j2 new file mode 100644 index 00000000..2877a3d9 --- /dev/null +++ b/src/onapsdk/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/templates/aai_service_create.json.j2 b/src/onapsdk/templates/aai_service_create.json.j2 new file mode 100644 index 00000000..ee360cc1 --- /dev/null +++ b/src/onapsdk/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/templates/cloud_configuration.json.j2 b/src/onapsdk/templates/cloud_configuration.json.j2 new file mode 100644 index 00000000..ca88bb5c --- /dev/null +++ b/src/onapsdk/templates/cloud_configuration.json.j2 @@ -0,0 +1,5 @@ +{ + "lcpCloudRegionId": "{{ cloud_region_id }}", + "tenantId": "{{ tenant_id }}", + "cloudOwner": "{{ cloud_owner }}" +} diff --git a/src/onapsdk/templates/cloud_region_add_availability_zone.json.j2 b/src/onapsdk/templates/cloud_region_add_availability_zone.json.j2 new file mode 100644 index 00000000..be6ebc5c --- /dev/null +++ b/src/onapsdk/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/templates/cloud_region_add_esr_system_info.json.j2 b/src/onapsdk/templates/cloud_region_add_esr_system_info.json.j2 new file mode 100644 index 00000000..ab03de3b --- /dev/null +++ b/src/onapsdk/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/templates/cloud_region_add_tenant.json.j2 b/src/onapsdk/templates/cloud_region_add_tenant.json.j2 new file mode 100644 index 00000000..fd7bcb72 --- /dev/null +++ b/src/onapsdk/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/templates/cloud_region_create.json.j2 b/src/onapsdk/templates/cloud_region_create.json.j2 new file mode 100644 index 00000000..65a70571 --- /dev/null +++ b/src/onapsdk/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/templates/complex_create.json.j2 b/src/onapsdk/templates/complex_create.json.j2 new file mode 100644 index 00000000..47c8a259 --- /dev/null +++ b/src/onapsdk/templates/complex_create.json.j2 @@ -0,0 +1,19 @@ +{ + "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 }}" +} \ No newline at end of file diff --git a/src/onapsdk/templates/customer_create.json.j2 b/src/onapsdk/templates/customer_create.json.j2 new file mode 100644 index 00000000..fda29404 --- /dev/null +++ b/src/onapsdk/templates/customer_create.json.j2 @@ -0,0 +1,5 @@ +{ + "global-customer-id": "{{ global_customer_id }}", + "subscriber-name": "{{ subscriber_name }}", + "subscriber-type": "{{ subscriber_type }}" +} \ No newline at end of file diff --git a/src/onapsdk/templates/customer_service_subscription_create.json.j2 b/src/onapsdk/templates/customer_service_subscription_create.json.j2 new file mode 100644 index 00000000..c1ee61e1 --- /dev/null +++ b/src/onapsdk/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/templates/deletion_service.json.j2 b/src/onapsdk/templates/deletion_service.json.j2 new file mode 100644 index 00000000..816e7680 --- /dev/null +++ b/src/onapsdk/templates/deletion_service.json.j2 @@ -0,0 +1,23 @@ +{ + "requestDetails": { + "requestInfo": { + "source": "VID", + "requestorId": "demo" + }, + "modelInfo": { + "modelType": "service", + "modelName": "{{ service_instance.service_subscription.sdc_service.name }}", + "modelInvariantId": "{{ service_instance.service_subscription.sdc_service.unique_uuid }}", + "modelVersion": "1.0", + "modelVersionId": "{{ service_instance.service_subscription.sdc_service.identifier }}" + }, + "requestParameters": { + "testApi": {% if use_vnf_api %}"VNF_API"{% else %}"GR_API"{% endif %} + }, + "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 }}" + } + } +} \ No newline at end of file diff --git a/src/onapsdk/templates/deletion_vf_module.json.j2 b/src/onapsdk/templates/deletion_vf_module.json.j2 new file mode 100644 index 00000000..fb5c85e1 --- /dev/null +++ b/src/onapsdk/templates/deletion_vf_module.json.j2 @@ -0,0 +1,25 @@ +{ + "requestDetails": { + "requestInfo": { + "source": "VID", + "requestorId": "demo" + }, + "modelInfo": { + "modelType": "vfModule", + "modelInvariantId": "{{ vf_module_instance.vf_module.metadata["vfModuleModelInvariantUUID"] }}", + "modelVersionId": "{{ vf_module_instance.vf_module.metadata["vfModuleModelUUID"] }}", + "modelName": "{{ vf_module_instance.vf_module.metadata["vfModuleModelName"] }}", + "modelVersion": "{{ vf_module_instance.vf_module.metadata["vfModuleModelVersion"] }}", + "modelCustomizationId": "{{vf_module_instance.vf_module.metadata["vfModuleModelCustomizationUUID"] }}", + "modelCustomizationName": "{{ vf_module_instance.vf_module.metadata["vfModuleModelName"] }}" + }, + "requestParameters": { + "testApi": {% if use_vnf_api %}"VNF_API"{% else %}"GR_API"{% endif %} + }, + "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/templates/deletion_vnf.json.j2 b/src/onapsdk/templates/deletion_vnf.json.j2 new file mode 100644 index 00000000..653943ec --- /dev/null +++ b/src/onapsdk/templates/deletion_vnf.json.j2 @@ -0,0 +1,25 @@ +{ + "requestDetails": { + "requestInfo": { + "source": "VID", + "requestorId": "demo" + }, + "modelInfo": { + "modelType": "vnf", + "modelName": "{{ vnf_instance.vnf.metadata["name"] }}", + "modelInvariantId": "{{ vnf_instance.vnf.metadata["invariantUUID"] }}", + "modelVersion": "{{ vnf_instance.vnf.metadata["version"] }}", + "modelVersionId": "{{ vnf_instance.vnf.metadata["UUID"] }}", + "modelCustomizationId": "{{ vnf_instance.vnf.metadata["customizationUUID"] }}", + "modelCustomizationName": "{{ vnf_instance.vnf.name }}" + }, + "requestParameters": { + "testApi": {% if use_vnf_api %}"VNF_API"{% else %}"GR_API"{% endif %} + }, + "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/templates/instantiate_so_ala_carte.json.j2 b/src/onapsdk/templates/instantiate_so_ala_carte.json.j2 new file mode 100644 index 00000000..24894bab --- /dev/null +++ b/src/onapsdk/templates/instantiate_so_ala_carte.json.j2 @@ -0,0 +1,38 @@ +{ + "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": [], + "testApi": {% if use_vnf_api %}"VNF_API"{% else %}"GR_API"{% endif %}, + "subscriptionServiceType": "{{ sdc_service.name }}", + "aLaCarte": true + }, + "subscriberInfo": { + "globalSubscriberId": "{{ customer.global_customer_id }}" + }, + "project": { + "projectName": "{{ project.name }}" + }, + "owningEntity": { + "owningEntityId": "{{ owning_entity.owning_entity_id }}", + "owningEntityName": "{{ owning_entity.name }}" + } + } +} \ No newline at end of file diff --git a/src/onapsdk/templates/instantiate_vf_module_ala_carte.json.j2 b/src/onapsdk/templates/instantiate_vf_module_ala_carte.json.j2 new file mode 100644 index 00000000..9b7e10f7 --- /dev/null +++ b/src/onapsdk/templates/instantiate_vf_module_ala_carte.json.j2 @@ -0,0 +1,63 @@ +{ + "requestDetails": { + "requestInfo": + { + "instanceName": "{{ vf_module_instance_name }}", + "source": "VID", + "suppressRollback": false, + "requestorId": "test" + }, + "modelInfo": { + "modelType": "vfModule", + "modelInvariantId": "{{ vf_module.metadata["vfModuleModelInvariantUUID"] }}", + "modelVersionId": "{{ vf_module.metadata["vfModuleModelUUID"] }}", + "modelName": "{{ vf_module.metadata["vfModuleModelName"] }}", + "modelVersion": "{{ vf_module.metadata["vfModuleModelVersion"] }}", + "modelCustomizationId": "{{ vf_module.metadata["vfModuleModelCustomizationUUID"] }}", + "modelCustomizationName": "{{ vf_module.metadata["vfModuleModelName"] }}" + }, + "requestParameters": { + "userParams": [], + {% if use_vnf_api %} + "testApi": "VNF_API", + "usePreload": true + {% else %} + "testApi": "GR_API", + "usePreload": true + {% endif %} + }, + "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.metadata["name"] }}", + "modelInvariantId": "{{ vnf_instance.vnf.metadata["invariantUUID"] }}", + "modelVersion": "{{ vnf_instance.vnf.metadata["version"] }}", + "modelVersionId": "{{ vnf_instance.vnf.metadata["UUID"] }}", + "modelCustomizationId": "{{ vnf_instance.vnf.metadata["customizationUUID"] }}", + "modelCustomizationName": "{{ vnf_instance.vnf.name }}" + } + } + } + ] + } +} diff --git a/src/onapsdk/templates/instantiate_vf_module_ala_carte_upload_preload_gr_api.json.j2 b/src/onapsdk/templates/instantiate_vf_module_ala_carte_upload_preload_gr_api.json.j2 new file mode 100644 index 00000000..eaa59365 --- /dev/null +++ b/src/onapsdk/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/templates/instantiate_vf_module_ala_carte_upload_preload_vnf_api.json.j2 b/src/onapsdk/templates/instantiate_vf_module_ala_carte_upload_preload_vnf_api.json.j2 new file mode 100644 index 00000000..002a74d4 --- /dev/null +++ b/src/onapsdk/templates/instantiate_vf_module_ala_carte_upload_preload_vnf_api.json.j2 @@ -0,0 +1,31 @@ +{ + "input": { + "request-information": { + "notification-url": "onap.org", + "order-number": "1", + "order-version": "1", + "request-action": "PreloadVNFRequest", + "request-id": "test" + }, + "sdnc-request-header": { + "svc-action": "reserve", + "svc-notification-url": "http:\/\/onap.org:8080\/adapters\/rest\/SDNCNotify", + "svc-request-id": "test" + }, + "vnf-topology-information": { + "vnf-assignments": { + "availability-zones": [], + "vnf-networks": [], + "vnf-vms": [] + }, + "vnf-parameters": {{ vnf_parameters }}, + "vnf-topology-identifier": { + "generic-vnf-name": "{{ vnf_instance.vnf_name }}", + "generic-vnf-type": "{{ vnf_instance.vnf_type }}", + "service-type": "{{ vnf_instance.service_instance.instance_id }}", + "vnf-name": "{{ vf_module_instance_name }}", + "vnf-type": "{{ vf_module.metadata["vfModuleModelName"] }}" + } + } + } +} diff --git a/src/onapsdk/templates/instantiate_vnf_ala_carte.json.j2 b/src/onapsdk/templates/instantiate_vnf_ala_carte.json.j2 new file mode 100644 index 00000000..02f37674 --- /dev/null +++ b/src/onapsdk/templates/instantiate_vnf_ala_carte.json.j2 @@ -0,0 +1,48 @@ +{ + "requestDetails": { + "requestInfo": { + "instanceName": "{{ vnf_service_instance_name }}", + "source": "VID", + "suppressRollback": false, + "requestorId": "test", + "productFamilyId": "1234" + }, + "modelInfo": { + "modelType": "vnf", + "modelInvariantId": "{{ vnf.metadata["invariantUUID"] }}", + "modelVersionId": "{{ vnf.metadata["UUID"] }}", + "modelName": "{{ vnf.metadata["name"] }}", + "modelVersion": "{{ vnf.metadata["version"] }}", + "modelCustomizationId": "{{ vnf.metadata["customizationUUID"] }}", + "modelCustomizationName": "{{ vnf.name }}" + }, + "requestParameters": { + "userParams": [], + "aLaCarte": true, + "testApi": {% if use_vnf_api %}"VNF_API"{% else %}"GR_API"{% endif %} + }, + "cloudConfiguration": { + "tenantId": "{{ tenant.tenant_id }}", + "cloudOwner": "{{ cloud_region.cloud_owner }}", + "lcpCloudRegionId": "{{ cloud_region.cloud_region_id }}" + }, + "lineOfBusiness": { + "lineOfBusinessName": "{{ line_of_business.name }}" + }, + "platform": { + "platformName": "{{ platform.name }}" + }, + "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/templates/msb_esr_vim_registration.json.j2 b/src/onapsdk/templates/msb_esr_vim_registration.json.j2 new file mode 100644 index 00000000..ba192580 --- /dev/null +++ b/src/onapsdk/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/templates/nbi_service_order_create.json.j2 b/src/onapsdk/templates/nbi_service_order_create.json.j2 new file mode 100644 index 00000000..aee124da --- /dev/null +++ b/src/onapsdk/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/templates/service_instance_create.json.j2 b/src/onapsdk/templates/service_instance_create.json.j2 new file mode 100644 index 00000000..09d3bc16 --- /dev/null +++ b/src/onapsdk/templates/service_instance_create.json.j2 @@ -0,0 +1,22 @@ +{ + "requestDetails": { + "requestInfo": { + "instanceName": "{{ instance_name }}", + "source": "VID", + "suppressRollback": false + }, + "modelInfo": {{ service_model }}" + }, + "requestParameters": { + "userParams": [], + "subscriptionServiceType": "{{ subscription_service_type }}" + }, + "cloudConfiguration": { + "lcpCloudRegionId": "{{ cloud[name] }}", + "tenantId": "{{ cloud[tenant_id] }}" + }, + "subscriberInfo": { + "globalSubscriberId": "{{ global_subscriber_id }}" + } + } +} diff --git a/src/onapsdk/templates/service_instance_macro.json.j2 b/src/onapsdk/templates/service_instance_macro.json.j2 new file mode 100644 index 00000000..e3121b9e --- /dev/null +++ b/src/onapsdk/templates/service_instance_macro.json.j2 @@ -0,0 +1,152 @@ +{ +"requestDetails": { + "subscriberInfo": { + "globalSubscriberId": "{{ global_subscriber_id }}" + }, + "requestInfo": { + "suppressRollback": false, + "productFamilyId": "Useless_But_Mandatory", + "requestorId": "adt", + "instanceName": "{{ ns_instance_name }}", + "source": "VID" + }, + "cloudConfiguration": {{ cloud_configuration }}, + "requestParameters": { + "subscriptionServiceType": "{{ service_name }}", + "userParams": [ + { + "Homing_Solution": "none" + }, + { + "service": { + "instanceParams": [], + "instanceName": "{{ ns_instance_name }}", + "resources": { + "vnfs": [ {{ vnf_instances }} ] + }, + "modelInfo": {{ service_model }} + } + } + ], + "aLaCarte": false, + "usePreload": false + }, + "owningEntity": { + "owningEntityId": "Useless_But_Mandatory", + "owningEntityName": "Useless_But_Mandatory" + }, + "modelInfo": {{ service_model }} +} +} + + {# curl -X POST \ + http://so.api.simpledemo.onap.org:30277/onap/so/infra/serviceInstantiation/v7/serviceInstances \ + -H 'Accept: application/json' \ + -H 'Authorization: Basic SW5mcmFQb3J0YWxDbGllbnQ6cGFzc3dvcmQxJA==' \ + -H 'Content-Type: application/json' \ + -H 'X-ONAP-PartnerName: NBI' \ + -H 'cache-control: no-cache' \ + -d '{ + "requestDetails": { + "subscriberInfo": { + "globalSubscriberId": "JohnDoe" + }, + "requestInfo": { + "suppressRollback": false, + "productFamilyId": "Useless_But_Mandatory", + "requestorId": "adt", + "instanceName": "My_ubuntuCDS_service_instance_001", + "source": "VID" + }, + "cloudConfiguration": { + "lcpCloudRegionId": "RegionOne", + "tenantId": "71cf9d931d9e4b8e9fcca50d97c1cf96", + "cloudOwner": "ONAP" + }, + "requestParameters": { + "subscriptionServiceType": "ubuntuCDS", + "userParams": [ + { + "Homing_Solution": "none" + }, + { + "service": { + "instanceParams": [], + "instanceName": "My_ubuntuCDS_service_instance_001", + "resources": { + "vnfs": [ + { + "modelInfo": { + "modelName": "ubuntuCDS", + "modelVersionId": "c6a5534e-76d5-4128-97bf-ad3b72208d53", + "modelInvariantUuid": "ed3064e7-62c0-494c-bb9b-4f56d1ad157e", + "modelVersion": "1.0", + "modelCustomizationId": "6a32fb56-191e-4d11-a0cc-44b779aba4fc", + "modelInstanceName": "ubuntuCDS 0" + }, + "cloudConfiguration": { + "lcpCloudRegionId": "RegionOne", + "tenantId": "71cf9d931d9e4b8e9fcca50d97c1cf96" + }, + "platform": { + "platformName": "Useless_But_Mandatory" + }, + "productFamilyId": "Useless_But_Mandatory", + "instanceName": "My_VNF_ubuntuCDS_instance_001", + "instanceParams": [ + { + "vnf_name": "My_VNF_ubuntuCDS_instance_001" + } + ], + "vfModules": [ + { + "modelInfo": { + "modelName": "Ubuntucds..base_ubuntuCDS..module-0", + "modelVersionId": "3025cd36-b170-4667-abb1-2bae1f297844", + "modelInvariantUuid": "0101f9e0-7beb-4b58-92c7-ba3324b5a54d", + "modelVersion": "1", + "modelCustomizationId": "9bca4d4b-e27c-4652-a61e-b1b4ebca503d" + }, + "instanceName": "My_vfModule_ubuntuCDS_instance_001", + "instanceParams": [ + { + "vnf_name": "My_VNF_ubuntuCDS_instance_001", + "vf_module_name": "My_vfModule_ubuntuCDS_instance_001", + "ubuntuCDS_pub_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDY15cdBmIs2XOpe4EiFCsaY6bmUmK/GysMoLl4UG51JCfJwvwoWCoA+6mDIbymZxhxq9IGxilp/yTA6WQ9s/5pBag1cUMJmFuda9PjOkXl04jgqh5tR6I+GZ97AvCg93KAECis5ubSqw1xOCj4utfEUtPoF1OuzqM/lE5mY4N6VKXn+fT7pCD6cifBEs6JHhVNvs5OLLp/tO8Pa3kKYQOdyS0xc3rh+t2lrzvKUSWGZbX+dLiFiEpjsUL3tDqzkEMNUn4pdv69OJuzWHCxRWPfdrY9Wg0j3mJesP29EBht+w+EC9/kBKq+1VKdmsXUXAcjEvjovVL8l1BrX3BY0R8D imported-openssh-key", + "ubuntuCDS_image_name": "ubuntu-18.04-daily", + "ubuntuCDS_flavor_name": "onap.small", + "ubuntuCDS_name_0": "ubuntuCDS-VM-001", + "admin_plane_net_name": "admin" + } + ] + } + ] + } + ] + }, + "modelInfo": { + "modelVersion": "1.0", + "modelVersionId": "10369444-1e06-4d5d-974b-362bcfd19533", + "modelInvariantId": "32e00b49-eff8-443b-82a8-b75fbb6e3867", + "modelName": "ubuntuCDS", + "modelType": "service" + } + } + } + ], + "aLaCarte": false, + "usePreload": false + }, + "owningEntity": { + "owningEntityId": "Useless_But_Mandatory", + "owningEntityName": "Useless_But_Mandatory" + }, + "modelInfo": { + "modelVersion": "1.0", + "modelVersionId": "10369444-1e06-4d5d-974b-362bcfd19533", + "modelInvariantId": "32e00b49-eff8-443b-82a8-b75fbb6e3867", + "modelName": "ubuntuCDS", + "modelType": "service" + } + } + }' #} diff --git a/src/onapsdk/templates/service_instance_model_info.json.j2 b/src/onapsdk/templates/service_instance_model_info.json.j2 new file mode 100644 index 00000000..fc66de47 --- /dev/null +++ b/src/onapsdk/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/templates/service_model_info.json.j2 b/src/onapsdk/templates/service_model_info.json.j2 new file mode 100644 index 00000000..f340df22 --- /dev/null +++ b/src/onapsdk/templates/service_model_info.json.j2 @@ -0,0 +1,8 @@ +{ "modelInfo": { + "modelName": "{{ service_model_name }}", + "modelVersion": "{{ service_model_version }}", + "modelVersionId": "{{ service_model_version_id }}", + "modelInvariantId": "{{ service_model_invariant_id }}", + "modelType": "{{ service_model_customization_type }}" + +}} diff --git a/src/onapsdk/templates/vf_model_info.json.j2 b/src/onapsdk/templates/vf_model_info.json.j2 new file mode 100644 index 00000000..4b40898e --- /dev/null +++ b/src/onapsdk/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/templates/vid_declare_line_of_business.json.j2 b/src/onapsdk/templates/vid_declare_line_of_business.json.j2 new file mode 100644 index 00000000..ff48d814 --- /dev/null +++ b/src/onapsdk/templates/vid_declare_line_of_business.json.j2 @@ -0,0 +1,3 @@ +{ + "options": ["{{ line_of_business }}"] +} \ No newline at end of file diff --git a/src/onapsdk/templates/vid_declare_owning_entity.json.j2 b/src/onapsdk/templates/vid_declare_owning_entity.json.j2 new file mode 100644 index 00000000..804f6dec --- /dev/null +++ b/src/onapsdk/templates/vid_declare_owning_entity.json.j2 @@ -0,0 +1,3 @@ +{ + "options": ["{{ owning_entity_name }}"] +} \ No newline at end of file diff --git a/src/onapsdk/templates/vid_declare_platform.json.j2 b/src/onapsdk/templates/vid_declare_platform.json.j2 new file mode 100644 index 00000000..f34db8a1 --- /dev/null +++ b/src/onapsdk/templates/vid_declare_platform.json.j2 @@ -0,0 +1,3 @@ +{ + "options": ["{{ platform }}"] +} \ No newline at end of file diff --git a/src/onapsdk/templates/vid_declare_project.json.j2 b/src/onapsdk/templates/vid_declare_project.json.j2 new file mode 100644 index 00000000..90cbf067 --- /dev/null +++ b/src/onapsdk/templates/vid_declare_project.json.j2 @@ -0,0 +1,3 @@ +{ + "options": ["{{ project }}"] +} \ No newline at end of file diff --git a/src/onapsdk/templates/vid_declare_resource.json.j2 b/src/onapsdk/templates/vid_declare_resource.json.j2 new file mode 100644 index 00000000..b14e6f47 --- /dev/null +++ b/src/onapsdk/templates/vid_declare_resource.json.j2 @@ -0,0 +1,3 @@ +{ + "options": ["{{ name }}"] +} \ No newline at end of file diff --git a/src/onapsdk/templates/vnf_instance_macro.json.j2 b/src/onapsdk/templates/vnf_instance_macro.json.j2 new file mode 100644 index 00000000..b9e15c1b --- /dev/null +++ b/src/onapsdk/templates/vnf_instance_macro.json.j2 @@ -0,0 +1,16 @@ +{ + "modelInfo": {{ vnf_model_info }}, + "cloudConfiguration": {{ cloud_configuration }}, + "platform": { + "platformName": "Useless_But_Mandatory" + }, + "productFamilyId": "Useless_But_Mandatory", + "instanceName": "{{ vnf_instance_name }}", + "instanceParams": [{{ vnf_instance_param }}], + "vfModules": {{ vf_modules }} + {#{ + "modelInfo": "{{ vf_model }}", + "instanceName": "{{ vnf_model_instance_name }}", + "instanceParams": [ "{{ vf_instance_params }}"] + }]#} +} diff --git a/src/onapsdk/templates/vnf_model_info.json.j2 b/src/onapsdk/templates/vnf_model_info.json.j2 new file mode 100644 index 00000000..ecc788b0 --- /dev/null +++ b/src/onapsdk/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 00000000..318a1e50 --- /dev/null +++ b/src/onapsdk/utils/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +"""ONAP SDK utils package.""" + +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') diff --git a/src/onapsdk/utils/headers_creator.py b/src/onapsdk/utils/headers_creator.py index cd3dcd2e..b944d306 100644 --- a/src/onapsdk/utils/headers_creator.py +++ b/src/onapsdk/utils/headers_creator.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 """Header creator package.""" from typing import Dict +from uuid import uuid4 def headers_sdc_creator(base_header: Dict[str, str], @@ -104,3 +105,75 @@ def headers_sdc_generic(base_header: Dict[str, str], "9HR21KdlV5MlU=") 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"] = "Basic QUFJOkFBSQ==" + 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"] = "Basic SW5mcmFQb3J0YWxDbGllbnQ6cGFzc3dvcmQxJA==" + 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"] = \ + "Basic YWRtaW46S3A4Yko0U1hzek0wV1hsaGFrM2VIbGNzZTJnQXc4NHZhb0dHbUp2VXkyVQ==" + headers["x-transactionid"] = str(uuid4()) + headers["x-fromappid"] = "API client" + return headers diff --git a/src/onapsdk/utils/tosca_file_handler.py b/src/onapsdk/utils/tosca_file_handler.py new file mode 100644 index 00000000..d35211a0 --- /dev/null +++ b/src/onapsdk/utils/tosca_file_handler.py @@ -0,0 +1,83 @@ +#!/usr/bin/python +# +# This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +"""Utils class.""" +import json +import string +import random +from typing import Dict + +def get_parameter_from_yaml(parameter, config_file): + """ + Get the value of a given parameter in file.yaml. + + Parameter must be given in string format with dots + Example: general.openstack.image_name + :param config_file: yaml file of configuration formtatted as string + :return: the value of the parameter + """ + # with open(config_file) as my_file + # file_yaml = yaml.safe_load(my_file) + # my_file.close() + # value = file_yaml + value = json.loads(config_file) + # Ugly fix as 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: + raise ValueError("Parameter %s not defined" % parameter) + + return value + +def get_vf_list_from_tosca_file(model): + """ + Get the list of Vfs of a VNF based on the tosca file. + + :param model: the model retrieved from the tosca file at Vnf instantiation + + :return: the 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. + + :param model: the model retrieved from the tosca file at Vnf instantiation + :return: the list of modules + :raises: + ValueError: no modules in Tosca file + """ + return get_parameter_from_yaml( + "topology_template.groups", model + ) + +def random_string_generator(size=6, + chars=string.ascii_uppercase + string.digits): + """ + Get a random String for VNF. + + 6 alphanumerical char for CI (to get single instances) + :return: a random sequence of 6 characters + """ + return ''.join(random.choice(chars) for _ in range(size)) diff --git a/src/onapsdk/vendor.py b/src/onapsdk/vendor.py index 7552aee6..90d1d640 100644 --- a/src/onapsdk/vendor.py +++ b/src/onapsdk/vendor.py @@ -5,8 +5,6 @@ from typing import Any from typing import Dict -import logging - from onapsdk.sdc_element import SdcElement import onapsdk.constants as const from onapsdk.utils.headers_creator import headers_sdc_creator @@ -25,7 +23,6 @@ class Vendor(SdcElement): """ VENDOR_PATH = "vendor-license-models" - _logger: logging.Logger = logging.getLogger(__name__) headers = headers_sdc_creator(SdcElement.headers) def __init__(self, name: str = None): diff --git a/src/onapsdk/vf.py b/src/onapsdk/vf.py index 062d41a7..00b820ea 100644 --- a/src/onapsdk/vf.py +++ b/src/onapsdk/vf.py @@ -4,8 +4,7 @@ """Vf module.""" from typing import Dict -import logging - +import time from onapsdk.sdc_resource import SdcResource from onapsdk.vsp import Vsp import onapsdk.constants as const @@ -28,7 +27,6 @@ class Vf(SdcResource): """ - _logger: logging.Logger = logging.getLogger(__name__) headers = headers_sdc_creator(SdcResource.headers) def __init__(self, name: str = None, sdc_values: Dict[str, str] = None, @@ -50,9 +48,11 @@ def onboard(self) -> None: if not self.vsp: raise ValueError("No Vsp was given") self.create() + time.sleep(10) self.onboard() elif self.status == const.DRAFT: self.submit() + time.sleep(10) self.onboard() elif self.status == const.CERTIFIED: self.load() diff --git a/src/onapsdk/vid.py b/src/onapsdk/vid.py new file mode 100644 index 00000000..86b8e381 --- /dev/null +++ b/src/onapsdk/vid.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: Apache-2.0 +"""VID module.""" +from abc import ABC + +from onapsdk.onap_service import OnapService +from onapsdk.utils.jinja import jinja_env + + +class Vid(OnapService, ABC): + """VID base class.""" + + base_url = "https://vid.api.simpledemo.onap.org:30200" + api_version = "/vid" + + def __init__(self, name: str) -> None: + """VID resource object initialization. + + Args: + name (str): Resource name + """ + 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 + + """ + 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 + + """ + 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 + + """ + 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 + + """ + 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 + + """ + return f"{cls.base_url}{cls.api_version}/maintenance/category_parameter/platform" diff --git a/src/onapsdk/vsp.py b/src/onapsdk/vsp.py index a6764427..eaad4b5f 100644 --- a/src/onapsdk/vsp.py +++ b/src/onapsdk/vsp.py @@ -7,8 +7,6 @@ from typing import Callable from typing import Dict -import logging - from onapsdk.sdc_element import SdcElement from onapsdk.vendor import Vendor import onapsdk.constants as const @@ -30,7 +28,6 @@ class Vsp(SdcElement): # pylint: disable=too-many-instance-attributes """ VSP_PATH = "vendor-software-products" - _logger: logging.Logger = logging.getLogger(__name__) headers = headers_sdc_creator(SdcElement.headers) def __init__(self, name: str = None, package: BinaryIO = None, diff --git a/tests/data/service-Foo-template.yml b/tests/data/service-Foo-template.yml new file mode 100644 index 00000000..12ba4f48 --- /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-Ubuntu16-template.yml b/tests/data/service-Ubuntu16-template.yml new file mode 100644 index 00000000..63cee88d --- /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/test_aai_complex.py b/tests/test_aai_complex.py new file mode 100644 index 00000000..4da1475e --- /dev/null +++ b/tests/test_aai_complex.py @@ -0,0 +1,63 @@ +from unittest import mock + +import pytest + +from onapsdk.aai.cloud_infrastructure import Complex + + +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":"" + } + ] +} + + +@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?resource-version=1234") + + 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?resource-version=") + 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" diff --git a/tests/test_aai_customer.py b/tests/test_aai_customer.py new file mode 100644 index 00000000..d5758c3d --- /dev/null +++ b/tests/test_aai_customer.py @@ -0,0 +1,404 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Test A&AI Customer module.""" +from unittest import mock + +import pytest + +from onapsdk.aai.business import Customer, ServiceSubscription +from onapsdk.aai.cloud_infrastructure import CloudRegion, Tenant +from onapsdk.service import Service as SdcService + + +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", + }, + ], + }, + ] + }, +} + + +@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(AttributeError): + service_subscription.cloud_region + with pytest.raises(AttributeError): + service_subscription.tenant + + 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 = service_subscription.cloud_region + assert service_subscription._cloud_region == cloud_region + assert cloud_region.cloud_owner == "OPNFV" + assert cloud_region.cloud_region_id == "RegionOne" + assert cloud_region.cloud_type == "openstack" + + mock_cloud_region.side_effect = ValueError + with pytest.raises(AttributeError): + service_subscription.tenant + mock_cloud_region.side_effect = None + mock_cloud_region.return_value = TENANT + tenant = service_subscription.tenant + assert tenant == service_subscription._tenant + 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 + + +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") + service = SdcService("test_service") + mock_send_message_json.side_effect = (ValueError, SERVICE_SUBSCRIPTION) + customer.subscribe_service(service) diff --git a/tests/test_aai_owning_entity.py b/tests/test_aai_owning_entity.py new file mode 100644 index 00000000..bbf5dbab --- /dev/null +++ b/tests/test_aai_owning_entity.py @@ -0,0 +1,73 @@ +from unittest import mock + +import pytest + +from onapsdk.aai.business import OwningEntity + + +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}?resource-version=" + f"{owning_entity.resource_version}") + + +@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(ValueError): + OwningEntity.get_by_owning_entity_name("invalid name") + 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_service.py b/tests/test_aai_service.py new file mode 100644 index 00000000..70e89651 --- /dev/null +++ b/tests/test_aai_service.py @@ -0,0 +1,681 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Test AaiElement module.""" +from unittest import mock + +import pytest + +from onapsdk.aai.aai_element import AaiElement, 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 +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.api_version == "/aai/v16" + 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 = AaiElement.filter_none_key_values({"a": None}) + assert not ret + + ret: dict = AaiElement.filter_none_key_values({"a": "b", "c": None}) + assert ret == {"a": "b"} + + ret: dict = AaiElement.filter_none_key_values({"a": "b", "c": "d"}) + assert ret == {"a": "b", "c": "d"} + + +@mock.patch.object(AaiElement, "send_message_json") +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 diff --git a/tests/test_aai_service_instance.py b/tests/test_aai_service_instance.py new file mode 100644 index 00000000..9a1a5bd4 --- /dev/null +++ b/tests/test_aai_service_instance.py @@ -0,0 +1,62 @@ +from unittest import mock + +import pytest + +from onapsdk.aai.business import ServiceInstance, VnfInstance +from onapsdk.so.deletion import ServiceDeletionRequest +from onapsdk.so.instantiation import VnfInstantiation + + +RELATIONSHIPS = { + "relationship": [ + { + "related-to": "generic-vnf", + "relationship_label": "anything", + "related_link": "test_relationship_related_link", + "relationship_data": [] + } + ] +} + + +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 + assert len(list(service_instance.vnf_instances)) == 1 + + +@mock.patch.object(VnfInstantiation, "instantiate_ala_carte") +def test_service_instance_add_vnf(mock_vnf_instantiation): + service_instance = ServiceInstance(service_subscription=mock.MagicMock(), + instance_id="test_service_instance_id") + service_instance.orchestration_status = "Inactive" + with pytest.raises(AttributeError): + service_instance.add_vnf(mock.MagicMock(), + mock.MagicMock(), + mock.MagicMock()) + service_instance.orchestration_status = "Active" + service_instance.add_vnf(mock.MagicMock(), + mock.MagicMock(), + mock.MagicMock()) + mock_vnf_instantiation.assert_called_once() + + +@mock.patch.object(ServiceDeletionRequest, "send_request") +def test_service_instance_deletion(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) diff --git a/tests/test_aai_service_subscription.py b/tests/test_aai_service_subscription.py new file mode 100644 index 00000000..8351f6e2 --- /dev/null +++ b/tests/test_aai_service_subscription.py @@ -0,0 +1,5 @@ +from unittest import mock + +import pytest + + diff --git a/tests/test_aai_vf_module.py b/tests/test_aai_vf_module.py new file mode 100644 index 00000000..e99b2f24 --- /dev/null +++ b/tests/test_aai_vf_module.py @@ -0,0 +1,29 @@ +from unittest import mock + +import pytest + +from onapsdk.aai.business import VfModuleInstance +from onapsdk.so.deletion import VfModuleDeletionRequest + + +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) + \ No newline at end of file diff --git a/tests/test_aai_vnf.py b/tests/test_aai_vnf.py new file mode 100644 index 00000000..178ba65c --- /dev/null +++ b/tests/test_aai_vnf.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 +"""Test A&AI VNF module.""" + +from unittest import mock + +import pytest + +from onapsdk.aai.business import ServiceInstance, VnfInstance +from onapsdk.so.deletion import VnfDeletionRequest +from onapsdk.so.instantiation import VfModuleInstantiation + + +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" + } + ] +} + + +@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) + + +@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.service_subscription.sdc_service.vnfs = [] + with pytest.raises(AttributeError): + vnf_instance.vnf + assert vnf_instance._vnf is None + + vnf = mock.MagicMock() + vnf.metadata = {"UUID": "test_model_version_id"} + service_instance.service_subscription.sdc_service.vnfs = [vnf] + assert vnf == vnf_instance.vnf + assert vnf_instance._vnf is not None + + +@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() diff --git a/tests/test_esr.py b/tests/test_esr.py new file mode 100644 index 00000000..4e426ebd --- /dev/null +++ b/tests/test_esr.py @@ -0,0 +1,28 @@ +from unittest import mock + +import pytest + +from onapsdk.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_generic_instance.txt b/tests/test_generic_instance.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_headers_creator.py b/tests/test_headers_creator.py index 1e2ef9e6..508c653e 100644 --- a/tests/test_headers_creator.py +++ b/tests/test_headers_creator.py @@ -1,10 +1,14 @@ # SPDX-License-Identifier: Apache-2.0 -from onapsdk.utils.headers_creator import headers_sdc_creator -from onapsdk.utils.headers_creator import headers_sdc_tester -from onapsdk.utils.headers_creator import headers_sdc_governor -from onapsdk.utils.headers_creator import headers_sdc_operator - +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, +) def test_headers_sdc_creator(): base_header = {} @@ -33,3 +37,28 @@ def test_headers_sdc_operator(): 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_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_multicloud.py b/tests/test_multicloud.py new file mode 100644 index 00000000..b3317439 --- /dev/null +++ b/tests/test_multicloud.py @@ -0,0 +1,27 @@ +from unittest import mock + +import pytest + +from onapsdk.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 00000000..65874b19 --- /dev/null +++ b/tests/test_nbi.py @@ -0,0 +1,332 @@ +from unittest import mock + +import pytest + +from onapsdk.aai.business import Customer +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" + } +] + + +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 + } + ] + } +] + + +@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 = ValueError + 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.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(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" diff --git a/tests/test_onap_service.py b/tests/test_onap_service.py index 8fcdc302..8f639f31 100644 --- a/tests/test_onap_service.py +++ b/tests/test_onap_service.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 """Test OnapService module.""" -import mock -import pytest +from unittest import mock +import pytest from jinja2 import Environment from requests import Response, Timeout, Session diff --git a/tests/test_preload.py b/tests/test_preload.py new file mode 100644 index 00000000..f5ce4a26 --- /dev/null +++ b/tests/test_preload.py @@ -0,0 +1,33 @@ +from unittest import mock + +import pytest + +from onapsdk.sdnc.preload import VfModulePreload + + +@mock.patch.object(VfModulePreload, "send_message_json") +def test_vf_module_preload_vnf_api(mock_send_message_json): + VfModulePreload.upload_vf_module_preload(vnf_instance=mock.MagicMock(), + vf_module_instance_name="test", + vf_module=mock.MagicMock(), + use_vnf_api=True) + 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 VNF-API" + assert url == (f"{VfModulePreload.base_url}/restconf/operations/" + "VNF-API:preload-vnf-topology-operation") + + +@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(), + use_vnf_api=False) + 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") diff --git a/tests/test_sdc_element.py b/tests/test_sdc_element.py index 85611196..f6de26a3 100644 --- a/tests/test_sdc_element.py +++ b/tests/test_sdc_element.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 """Test SdcElement module.""" -import mock +from unittest import mock + import pytest from onapsdk.onap_service import OnapService diff --git a/tests/test_sdc_resource.py b/tests/test_sdc_resource.py index e2e91ce2..711c5866 100644 --- a/tests/test_sdc_resource.py +++ b/tests/test_sdc_resource.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 """Test SdcResource module.""" -import mock -import pytest +from unittest import mock import logging +import pytest + import onapsdk.constants as const from onapsdk.onap_service import OnapService from onapsdk.sdc_resource import SdcResource @@ -123,7 +124,7 @@ def test__deep_load_no_response(mock_send, mock_created): vf.deep_load() assert vf._unique_identifier is None mock_send.assert_called_once_with('GET', 'Deep Load Vf', - "{}/sdc1/feProxy/rest/v1/followed".format(vf.base_front_url), + "{}/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') @@ -138,7 +139,7 @@ def test__deep_load_response_OK(mock_send, mock_created): vf.deep_load() assert vf.unique_identifier == "71011" mock_send.assert_called_once_with('GET', 'Deep Load Vf', - "{}/sdc1/feProxy/rest/v1/followed".format(vf.base_front_url), + "{}/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') @@ -153,7 +154,7 @@ def test__deep_load_response_NOK(mock_send, mock_created): vf.deep_load() assert vf._unique_identifier is None mock_send.assert_called_once_with('GET', 'Deep Load Vf', - "{}/sdc1/feProxy/rest/v1/followed".format(vf.base_front_url), + "{}/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') @@ -168,7 +169,7 @@ def test__deep_load_response_OK_under_cert(mock_send, mock_created): vf.deep_load() assert vf.unique_identifier == "71011" mock_send.assert_called_once_with('GET', 'Deep Load Vf', - "{}/sdc1/feProxy/rest/v1/followed".format(vf.base_front_url), + "{}/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') @@ -183,7 +184,7 @@ def test__deep_load_response_NOK_under_cert(mock_send, mock_created): vf.deep_load() assert vf._unique_identifier is None mock_send.assert_called_once_with('GET', 'Deep Load Vf', - "{}/sdc1/feProxy/rest/v1/followed".format(vf.base_front_url), + "{}/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(): @@ -194,10 +195,10 @@ def test__parse_sdc_status_certified_not_approved(): const.DISTRIBUTION_NOT_APPROVED, logging.getLogger()) == const.CERTIFIED -def test__parse_sdc_status_approved(): +def test__parse_sdc_status_certified_approved(): assert SdcResource._parse_sdc_status("CERTIFIED", const.DISTRIBUTION_APPROVED, - logging.getLogger()) == const.APPROVED + logging.getLogger()) == const.CERTIFIED def test__parse_sdc_status_distributed(): assert SdcResource._parse_sdc_status("CERTIFIED", const.SDC_DISTRIBUTED, diff --git a/tests/test_service.py b/tests/test_service.py index c3b5f400..8babcd6c 100644 --- a/tests/test_service.py +++ b/tests/test_service.py @@ -3,12 +3,13 @@ """Test Service module.""" from os import path +from pathlib import Path +from unittest import mock +from unittest.mock import MagicMock import shutil -import mock -from unittest.mock import MagicMock +import oyaml as yaml import pytest -import requests import onapsdk.constants as const from onapsdk.service import Service @@ -189,37 +190,21 @@ def test_submit(mock_verify): 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_start_certification(mock_verify): - svc = Service() - svc.start_certification() - mock_verify.assert_called_once_with( - const.SUBMITTED, const.START_CERTIFICATION, 'lifecycleState', - headers=headers_sdc_tester(svc.headers)) - @mock.patch.object(Service, '_verify_action_to_sdc') def test_certify(mock_verify): svc = Service() svc.certify() mock_verify.assert_called_once_with( - const.UNDER_CERTIFICATION, const.CERTIFY, 'lifecycleState', - headers=headers_sdc_tester(svc.headers)) - -@mock.patch.object(Service, '_verify_action_to_sdc') -def test_approve(mock_verify): - svc = Service() - svc.approve() - mock_verify.assert_called_once_with( - const.CERTIFIED, const.APPROVE, 'distribution-state', - headers=headers_sdc_governor(svc.headers)) + 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.APPROVED, const.DISTRIBUTE, 'distribution', - headers=headers_sdc_operator(svc.headers)) + const.CERTIFIED, const.DISTRIBUTE, 'distribution', + headers=headers_sdc_creator(svc.headers)) @mock.patch.object(Service, 'send_message') def test_get_tosca_no_result(mock_send): @@ -297,7 +282,7 @@ def test_distributed_not_distributed(mock_send): 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)) + headers=headers_sdc_creator(svc.headers)) @mock.patch.object(Service, 'send_message_json') def test_distributed_distributed(mock_send): @@ -312,7 +297,7 @@ def test_distributed_distributed(mock_send): 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)) + headers=headers_sdc_creator(svc.headers)) @mock.patch.object(Service, 'send_message_json') def test_load_metadata_no_result(mock_send): @@ -324,7 +309,7 @@ def test_load_metadata_no_result(mock_send): 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_operator(svc.headers)) + headers=headers_sdc_creator(svc.headers)) @mock.patch.object(Service, 'send_message_json') def test_load_metadata_bad_json(mock_send): @@ -336,7 +321,7 @@ def test_load_metadata_bad_json(mock_send): 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_operator(svc.headers)) + headers=headers_sdc_creator(svc.headers)) @mock.patch.object(Service, 'send_message_json') def test_load_metadata_OK(mock_send): @@ -345,11 +330,11 @@ def test_load_metadata_OK(mock_send): svc = Service() svc.identifier = "1" svc.load_metadata() - assert svc._distribution_id == "12" + 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_operator(svc.headers)) + 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" @@ -551,70 +536,6 @@ def test_onboard_service_several_resources(mock_create, 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_submit(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.DISTRIBUTED, - const.DISTRIBUTED, const.DISTRIBUTED, - const.DISTRIBUTED, const.DISTRIBUTED, - const.DISTRIBUTED, const.DISTRIBUTED, None] - service = Service() - service.onboard() - mock_create.assert_not_called() - mock_add_resource.assert_not_called() - mock_checkin.assert_not_called() - mock_submit.assert_called_once() - 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_certification(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.SUBMITTED, const.SUBMITTED, - const.SUBMITTED, const.SUBMITTED, - const.DISTRIBUTED, const.DISTRIBUTED, - const.DISTRIBUTED, const.DISTRIBUTED, - const.DISTRIBUTED, const.DISTRIBUTED, - const.DISTRIBUTED, None] - service = Service() - 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_called_once() - 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') @@ -631,11 +552,11 @@ def test_onboard_service_certifi(mock_create, 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.UNDER_CERTIFICATION, - const.UNDER_CERTIFICATION, - const.UNDER_CERTIFICATION, - const.UNDER_CERTIFICATION, - const.UNDER_CERTIFICATION, + 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, @@ -652,58 +573,21 @@ def test_onboard_service_certifi(mock_create, 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_approve(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.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.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_called_once() - 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_distribute(mock_create, - mock_add_resource, mock_checkin, - mock_submit, mock_start_certification, - mock_certify, mock_approve, + 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.APPROVED, const.APPROVED, const.APPROVED, - const.APPROVED, const.APPROVED, const.APPROVED, - const.APPROVED, const.DISTRIBUTED, + 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] @@ -712,54 +596,68 @@ def test_onboard_service_distribute(mock_create, 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_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_whole_service(mock_create, - mock_add_resource, mock_checkin, - mock_submit, mock_start_certification, - mock_certify, mock_approve, + 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.SUBMITTED, const.SUBMITTED, - const.SUBMITTED, const.SUBMITTED, - const.UNDER_CERTIFICATION, - const.UNDER_CERTIFICATION, - const.UNDER_CERTIFICATION, - const.UNDER_CERTIFICATION, - const.UNDER_CERTIFICATION, const.CERTIFIED, const.CERTIFIED, const.CERTIFIED, const.CERTIFIED, const.CERTIFIED, const.CERTIFIED, - const.APPROVED, const.APPROVED, const.APPROVED, - const.APPROVED, const.APPROVED, const.APPROVED, - const.APPROVED, const.DISTRIBUTED, const.DISTRIBUTED, const.DISTRIBUTED, const.DISTRIBUTED, const.DISTRIBUTED, - const.DISTRIBUTED, const.DISTRIBUTED, None] + const.DISTRIBUTED, const.DISTRIBUTED, + const.DISTRIBUTED, None] resource = SdcResource() service = Service(resources=[resource]) service.onboard() mock_create.assert_called_once() mock_add_resource.assert_called_once_with(resource) mock_checkin.assert_called_once() - mock_submit.assert_called_once() - mock_start_certification.assert_called_once() mock_certify.assert_called_once() - mock_approve.assert_called_once() mock_distribute.assert_called_once() + + +def test_vnf_vf_modules_one(): + """Test parsing TOSCA file with one VNF which has associated one VFmodule""" + service = Service(name="test") + with open(Path(Path(__file__).resolve().parent, "data/service-Ubuntu16-template.yml"), "r") as ubuntu: + service._tosca_template = yaml.safe_load(ubuntu.read()) + assert len(service.vnfs) == 1 + vnf = service.vnfs[0] + assert vnf.name == "ubuntu16_VF 0" + assert vnf.node_template_type == "org.openecomp.resource.vf.Ubuntu16Vf" + assert vnf.vf_module + assert vnf.vf_module.name == "ubuntu16_vf0..Ubuntu16Vf..base_ubuntu16..module-0" + + +def test_vnf_vf_modules_two(): + """Test parsing TOSCA file with two VNF which has associated one VFmodule""" + service = Service(name="test") + with open(Path(Path(__file__).resolve().parent, "data/service-Foo-template.yml"), "r") as ubuntu: + service._tosca_template = yaml.safe_load(ubuntu.read()) + assert len(service.vnfs) == 2 + vnf = service.vnfs[0] + assert vnf.name == "vFWCL_vPKG-vf 0" + assert vnf.node_template_type == "org.openecomp.resource.vf.VfwclVpkgVf" + assert vnf.vf_module + assert vnf.vf_module.name == "vfwcl_vpkgvf0..VfwclVpkgVf..base_vpkg..module-0" + + vnf = service.vnfs[1] + assert vnf.name == "vFWCL_vFWSNK-vf 0" + assert vnf.node_template_type == "org.openecomp.resource.vf.VfwclVfwsnkVf" + assert vnf.vf_module + assert vnf.vf_module.name == "vfwcl_vfwsnkvf0..VfwclVfwsnkVf..base_vfw..module-0" diff --git a/tests/test_so_deletion.py b/tests/test_so_deletion.py new file mode 100644 index 00000000..7ad2368c --- /dev/null +++ b/tests/test_so_deletion.py @@ -0,0 +1,65 @@ +from unittest import mock + +import pytest + +from onapsdk.so.so_element import OrchestrationRequest +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_instantiation.py b/tests/test_so_instantiation.py new file mode 100644 index 00000000..35a594ca --- /dev/null +++ b/tests/test_so_instantiation.py @@ -0,0 +1,252 @@ +from unittest import mock + +import pytest + +from onapsdk.sdnc import VfModulePreload +from onapsdk.service import Service as SdcService +from onapsdk.so.so_element import OrchestrationRequest +from onapsdk.so.instantiation import ( + ServiceInstantiation, + VfModuleInstantiation, + VnfInstantiation +) + + +@mock.patch.object(ServiceInstantiation, "send_message_json") +def test_service_instantiation(mock_service_instantiation_send_message): + mock_sdc_service = mock.MagicMock() + mock_sdc_service.distributed = False + with pytest.raises(ValueError): + ServiceInstantiation.\ + instantiate_so_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") + mock_sdc_service.distributed = True + service_instance = ServiceInstantiation.\ + instantiate_so_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") + assert service_instance.name == "test" + + service_instance = ServiceInstantiation.\ + instantiate_so_ala_carte(sdc_service=mock_sdc_service, + cloud_region=mock.MagicMock(), + tenant=mock.MagicMock(), + customer=mock.MagicMock(), + owning_entity=mock.MagicMock(), + project=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") + + +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(AttributeError): + 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 = ValueError + with pytest.raises(AttributeError): + service_instantiation.aai_service_instance + + +@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_object=mock.MagicMock(), + platform_object=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_object=mock.MagicMock(), + platform_object=mock.MagicMock(), + vnf_instance_name="test") + assert vnf_instantiation.name == "test" + + +@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_sdc_service.return_value.vnfs = [] + mock_send_message_json.return_value = {} + with pytest.raises(ValueError): + VnfInstantiation.get_by_vnf_instance_name("test_vnf_instance_name") + mock_send_message_json.return_value = { + "requestList": [ + { + "request": { + "requestScope": "not_vnf" + } + } + ] + } + with pytest.raises(ValueError): + 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(ValueError): + 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(ValueError): + 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(ValueError): + 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(ValueError): + 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) + 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") + assert instantiation.name == "test" diff --git a/tests/test_so_orchestration_request.py b/tests/test_so_orchestration_request.py new file mode 100644 index 00000000..c400a8c4 --- /dev/null +++ b/tests/test_so_orchestration_request.py @@ -0,0 +1,71 @@ +from unittest import mock + +import pytest + +from onapsdk.so.so_element import OrchestrationRequest + + +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 + + diff --git a/tests/test_tosca_file_handler.py b/tests/test_tosca_file_handler.py new file mode 100644 index 00000000..57314676 --- /dev/null +++ b/tests/test_tosca_file_handler.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python + +# Copyright (c) 2017 Orange and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 + +import logging +import json +import oyaml as yaml +import os +import os.path +import unittest + +from onapsdk.utils.jinja import jinja_env +import onapsdk.utils.tosca_file_handler as tosca_file_handler + + +__author__ = "Morgan Richomme " + + +class ToscaFileHandlerTestingBase(unittest.TestCase): + + """The super class which testing classes could inherit.""" + + logging.disable(logging.CRITICAL) + + _root_path = os.getcwd().rsplit('/onapsdk')[0] + _foo_path = _root_path +"/tests/data/service-Ubuntu16-template.yml" + + + def setUp(self): + pass + + def test_get_parameter_from_yaml(self): + with open(self._foo_path) as f: + model = json.dumps(yaml.safe_load(f)) + param = tosca_file_handler.get_parameter_from_yaml( + "metadata", model) + self.assertEqual(param['name'], "ubuntu16") + + def test_get_wrong_parameter_from_yaml(self): + with open(self._foo_path) as f: + model = json.dumps(yaml.safe_load(f)) + with self.assertRaises(ValueError): + 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_vendor.py b/tests/test_vendor.py index a7f70b54..732c0b9d 100644 --- a/tests/test_vendor.py +++ b/tests/test_vendor.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 """Test vendor module.""" -import mock +from unittest import mock + import pytest from onapsdk.vendor import Vendor diff --git a/tests/test_vf.py b/tests/test_vf.py index 91212b75..c17f16c4 100644 --- a/tests/test_vf.py +++ b/tests/test_vf.py @@ -2,10 +2,11 @@ # SPDX-License-Identifier: Apache-2.0 """Test vf module.""" -import mock -import pytest +from unittest import mock from unittest.mock import MagicMock +import pytest + import onapsdk.constants as const from onapsdk.sdc_resource import SdcResource from onapsdk.vf import Vf diff --git a/tests/test_vid.py b/tests/test_vid.py new file mode 100644 index 00000000..376fc3cb --- /dev/null +++ b/tests/test_vid.py @@ -0,0 +1,42 @@ + + +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 index ec7e541a..29c05334 100644 --- a/tests/test_vsp.py +++ b/tests/test_vsp.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 """Test vsp module.""" -import mock -import pytest - +from unittest import mock import json + +import pytest import requests from onapsdk.vsp import Vsp