aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore141
-rw-r--r--Dockerfile19
-rw-r--r--LICENSE201
-rw-r--r--README.md129
-rw-r--r--onap_data_provider/__init__.py16
-rw-r--r--onap_data_provider/config_loader.py70
-rw-r--r--onap_data_provider/config_parser.py173
-rw-r--r--onap_data_provider/data_provider.py106
-rw-r--r--onap_data_provider/resources/__init__.py16
-rw-r--r--onap_data_provider/resources/aai_service_resource.py83
-rw-r--r--onap_data_provider/resources/cloud_region_resource.py177
-rw-r--r--onap_data_provider/resources/complex_resource.py92
-rw-r--r--onap_data_provider/resources/customer_resource.py189
-rw-r--r--onap_data_provider/resources/esr_system_info_resource.py114
-rw-r--r--onap_data_provider/resources/line_of_business_resource.py73
-rw-r--r--onap_data_provider/resources/msb_k8s_definition.py85
-rw-r--r--onap_data_provider/resources/msb_k8s_profile.py80
-rw-r--r--onap_data_provider/resources/owning_entity_resource.py75
-rw-r--r--onap_data_provider/resources/platform_resource.py73
-rw-r--r--onap_data_provider/resources/pnf_resource.py78
-rw-r--r--onap_data_provider/resources/project_resource.py73
-rw-r--r--onap_data_provider/resources/resource.py45
-rw-r--r--onap_data_provider/resources/resource_creator.py177
-rw-r--r--onap_data_provider/resources/service_instance_resource.py271
-rw-r--r--onap_data_provider/resources/service_resource.py100
-rw-r--r--onap_data_provider/resources/tenant_resource.py85
-rw-r--r--onap_data_provider/resources/vendor_resource.py75
-rw-r--r--onap_data_provider/resources/vnf_resource.py75
-rw-r--r--onap_data_provider/resources/vsp_resource.py71
-rw-r--r--onap_data_provider/resources/xnf_resource.py60
-rw-r--r--onap_data_provider/schemas/infra.schema533
-rw-r--r--onap_data_provider/schemas/infra_1_1.schema533
-rw-r--r--onap_data_provider/tag_handlers.py52
-rw-r--r--onap_data_provider/validator.py52
-rw-r--r--onap_data_provider/versions.py68
-rw-r--r--requirements.txt3
-rw-r--r--samples/BASIC_VM_enriched.zipbin0 -> 9502 bytes
-rw-r--r--samples/README.md29
-rw-r--r--samples/aai_business.yaml17
-rw-r--r--samples/aai_service.yaml9
-rw-r--r--samples/cloud-region.yaml27
-rw-r--r--samples/complex.yaml8
-rw-r--r--samples/customer.yaml14
-rw-r--r--samples/msb_k8s.yaml11
-rw-r--r--samples/service.yaml52
-rw-r--r--samples/ubuntu.zipbin0 -> 2721 bytes
-rw-r--r--samples/vendor.yaml8
-rw-r--r--samples/vsp.yaml25
-rw-r--r--samples/xnfs.yaml35
-rw-r--r--setup.py45
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/config_dirs/not_yaml0
-rw-r--r--tests/config_dirs/test-data-version.yml48
-rw-r--r--tests/config_dirs/test-data.yml47
-rw-r--r--tests/test-data-version.yml48
-rw-r--r--tests/test-data.yml47
-rw-r--r--tests/test-kube-config1
-rw-r--r--tests/test_aai_service_resource.py51
-rw-r--r--tests/test_cloud_region_resource.py149
-rw-r--r--tests/test_complex_resource.py82
-rw-r--r--tests/test_config_loader.py27
-rw-r--r--tests/test_config_parser.py50
-rw-r--r--tests/test_customer_resource.py84
-rw-r--r--tests/test_esr_resource.py82
-rw-r--r--tests/test_line_of_business_resource.py51
-rw-r--r--tests/test_owning_entity_resource.py51
-rw-r--r--tests/test_platform_resource.py47
-rw-r--r--tests/test_pnf_resource.py28
-rw-r--r--tests/test_project_resource.py47
-rw-r--r--tests/test_resource_creator.py31
-rw-r--r--tests/test_service_instance_resource.py141
-rw-r--r--tests/test_service_resource.py50
-rw-r--r--tests/test_tag_handlers.py15
-rw-r--r--tests/test_tenant_resource.py56
-rw-r--r--tests/test_validator.py123
-rw-r--r--tests/test_vendor_resource.py28
-rw-r--r--tests/test_versions.py15
-rw-r--r--tests/test_vnf_resource.py28
-rw-r--r--tests/test_vsp_resource.py28
-rw-r--r--tox.ini11
80 files changed, 6007 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d563def
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,141 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# VSCode local config directory
+.vscode \ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..6f12536
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,19 @@
+FROM python:3.9-slim AS builder
+
+COPY requirements.txt /opt/app/onap_data_provider/requirements.txt
+
+WORKDIR /opt/app/onap_data_provider/
+
+RUN python -m pip install -r requirements.txt --prefix=/opt/install
+
+FROM nexus3.onap.org:10001/onap/integration-python:9.1.0
+
+COPY --from=builder --chown=onap:onap /opt/install /usr/local
+
+COPY --chown=onap:onap . /opt/app/onap_data_provider
+
+WORKDIR /opt/app/onap_data_provider/
+
+RUN python setup.py install
+
+CMD ["onap-data-provider"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..abe3069
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2021 TNAP / development / system-team
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..578c7fb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,129 @@
+# ONAP data provider
+
+Data ingestion service for ONAP
+
+## Description
+
+Data provider is a project to provide a tool to automate common ONAP resource creation. For many of tasks in ONAP some resources are needed and could be created once, like cloud region, complex or customer in A\&AI. With that tool it can be automated to create them for every ONAP instance. It can be also used to create requested resource on already running instance on demand.
+
+## Usage
+
+This project is intended to be included in automation chain, e.g. triggered from the pipeline.
+You can also run it locally using Python interpreter or Docker image.
+
+### Installation
+
+To run `onap-data-provider` Python >= 3.8 version is required. Install it using
+
+```
+python setup.py install
+```
+
+command. You can call then
+
+```
+onap-data-provider
+```
+
+command.
+
+### Run locally
+
+When installed `onap-data-provider` is ready to work. We need some data to be created. Let's use `samples/vendor.yaml` and create SDC's Vendor resource. Call
+
+```
+onap-data-provider -f samples/vendor.yaml
+```
+
+and in your ONAP instance Vendor resource should be created. If that resource already exists no new data will be created. Check `samples` directory to get more examples of files which describes resources to create.
+
+You can use multiple files as an input:
+
+```
+onap-data-provider -f samples/vendor.yaml -f samples/vsp.yaml
+```
+
+Directories could be used as well:
+
+```
+onap-data-provider -f samples/
+```
+
+### Configuration
+
+Configuration is needed if your environment setup is different that usuall so ONAP components listen on different hosts/ports than default, so are available on other URLs than:
+
+```
+AAI_URL = "https://aai.api.sparky.simpledemo.onap.org:30233"
+CDS_URL = "http://portal.api.simpledemo.onap.org:30449"
+MSB_URL = "https://msb.api.simpledemo.onap.org:30283"
+SDC_BE_URL = "https://sdc.api.be.simpledemo.onap.org:30204"
+SDC_FE_URL = "https://sdc.api.fe.simpledemo.onap.org:30207"
+SDNC_URL = "https://sdnc.api.simpledemo.onap.org:30267"
+SO_URL = "http://so.api.simpledemo.onap.org:30277"
+VID_URL = "https://vid.api.simpledemo.onap.org:30200"
+CLAMP_URL = "https://clamp.api.simpledemo.onap.org:30258"
+VES_URL = "http://ves.api.simpledemo.onap.org:30417"
+DMAAP_URL = "http://dmaap.api.simpledemo.onap.org:3904"
+```
+
+If you want to use another URLs you need to override default `onap-data-provider` settings by create Python file with values you want to use. Example: I want to test `onap-data-provider` data creation on my "test" ONAP instance which is available on "172.17.0.1" IP address, so I need to create `my_test_onap_instance_settings.py` Python file which looks:
+
+```
+AAI_URL = "https://172.17.0.1:30233"
+CDS_URL = "http://172.17.0.1:30449"
+MSB_URL = "https://172.17.0.1:30283"
+SDC_BE_URL = "https://172.17.0.1:30204"
+SDC_FE_URL = "https://172.17.0.1:30207"
+SDNC_URL = "https://172.17.0.1:30267"
+SO_URL = "http://172.17.0.1:30277"
+VID_URL = "https://172.17.0.1:30200"
+CLAMP_URL = "https://172.17.0.1:30258"
+VES_URL = "http://172.17.0.1:30417"
+DMAAP_URL = "http://172.17.0.1:3904"
+```
+
+and then if I call
+
+```
+ONAP_PYTHON_SDK_SETTINGS=my_test_onap_instance_settings onap-data-provider ...
+```
+
+all data are going to be created on my local instance.
+
+### Set proxy
+
+ONAP data provider can be run with proxy configured. You need to pass urls you want to use for proxy connection as `--proxy` arguments. Call `onap-data-provider -f <infra-file> --proxy http://localhost:8080 https://localhost:8080` to setup proxy for `http` and `https` on `localhost:8080` address.
+
+## Data verification
+
+You can verify the data provided is correct, before you would try to actually push it
+to the ONAP instance. To do so, use the flag `--validate-only`:
+
+```
+onap-data-provider -f samples/vendor.yml --validate-only
+```
+
+For reference, please see example data files under `samples/` directory.
+
+## Development and testing
+
+The following utilities are used within the project:
+
+- Black
+- mypy
+- pydocstyle
+
+To run all the tests (unit tests, linter and mypy checks), install tox and then run it:
+
+```
+pip install tox
+tox .
+```
+
+## Licenses
+
+The software that data-provider is built on uses the following licenses.
+
+- Apache 2 License: onapsdk
+- MIT license: PyYAML, jsonschema
diff --git a/onap_data_provider/__init__.py b/onap_data_provider/__init__.py
new file mode 100644
index 0000000..710af4e
--- /dev/null
+++ b/onap_data_provider/__init__.py
@@ -0,0 +1,16 @@
+"""ONAP data provider package."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
diff --git a/onap_data_provider/config_loader.py b/onap_data_provider/config_loader.py
new file mode 100644
index 0000000..5757e1e
--- /dev/null
+++ b/onap_data_provider/config_loader.py
@@ -0,0 +1,70 @@
+"""Data loader module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+from pathlib import Path
+from typing import Any, Iterator, List
+import yaml
+from onap_data_provider.tag_handlers import join, generate_random_uuid
+
+# register custom tag handlers in yaml.SafeLoader
+yaml.add_constructor("!join", join, yaml.SafeLoader)
+yaml.add_constructor("!uuid4", generate_random_uuid, yaml.SafeLoader)
+
+
+class ConfigLoader:
+ """Configuration loader class.
+
+ Loads data from file resource.
+ """
+
+ YAML_EXTENSIONS = {".yml", ".yaml"}
+
+ def __init__(self, config_file_path: List[Path]) -> None:
+ """Initialize configuration loader class.
+
+ Args:
+ config_file_path (str): Path to yaml data source file.
+
+ """
+ self.config_file_path: List[Path] = config_file_path
+
+ def _yamls_from_dir(self, dir: Path) -> Iterator[Path]:
+ for child in dir.iterdir(): # type: Path
+ if child.suffix in self.YAML_EXTENSIONS:
+ yield child
+
+ @property
+ def _yamls(self) -> Iterator[Path]:
+ for config_file_path in self.config_file_path: # type: Path
+ if config_file_path.is_file():
+ yield config_file_path
+ elif config_file_path.is_dir():
+ yield from self._yamls_from_dir(config_file_path)
+ else:
+ raise ValueError("Provided path is neither file nor directory")
+
+ def load(self) -> Iterator[Any]:
+ """Get data from the config file.
+
+ Get data from the config file and return parsed to dictionary resource.
+
+ Returns:
+ Any: Data from yaml file.
+
+ """
+ for yaml_path in self._yamls: # type: Path
+ with yaml_path.open() as f:
+ yield yaml.safe_load(f)
diff --git a/onap_data_provider/config_parser.py b/onap_data_provider/config_parser.py
new file mode 100644
index 0000000..e734b72
--- /dev/null
+++ b/onap_data_provider/config_parser.py
@@ -0,0 +1,173 @@
+"""Data parser module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+from collections import OrderedDict
+from pathlib import Path
+from typing import Any, Dict, Iterator, List, Optional
+from .config_loader import ConfigLoader
+from .resources.resource import Resource
+from .resources.resource_creator import ResourceCreator
+from .validator import Validator
+from .versions import VersionsEnum
+
+
+class Config:
+ """Config class."""
+
+ VERSION_TAG = "odpSchemaVersion"
+
+ def __init__(self, config: Dict[str, Any]) -> None:
+ """Initialize config object.
+
+ Args:
+ config (Dict[str, Any]): Entites files content loaded by loader.
+
+ """
+ self.config: Dict[str, Any] = config
+
+ @property
+ def version(self) -> VersionsEnum:
+ """Config file version.
+
+ Files with entities are versioned to keep backward compatibility.
+ Each config keep the version number and that value is represented
+ by that property.
+
+ Returns:
+ VersionsEnum: VersionsEnum class object
+
+ """
+ return VersionsEnum.get_version_by_number(
+ str(self.config.get(self.VERSION_TAG))
+ )
+
+ @property
+ def resources(self) -> Dict[str, Any]:
+ """Resources dictionary.
+
+ Dictionary with definition of objects to be created in ONAP.
+
+ Returns:
+ Dict[str, Any]: Resources dictionary
+
+ """
+ if self.version == VersionsEnum.NONE:
+ return self.config
+ resources: Dict[str, Any] = self.config["resources"]
+ return resources
+
+
+class ConfigParser:
+ """Configuration parser class.
+
+ Processes data loaded from resource.
+ """
+
+ def __init__(self, config_file_path: List[Path]) -> None:
+ """Initialize configuration parser class.
+
+ Args:
+ config_file_path (str): Path to yaml data source file.
+
+ """
+ self._config_file_path: List[Path] = config_file_path
+ self._config_loader: ConfigLoader = ConfigLoader(self._config_file_path)
+ self._configs: Optional[List[Config]] = None
+ self._validator: Optional[Validator] = None
+ self._PRIORITY_ORDER = (
+ "complexes",
+ "cloud-regions",
+ "vendors",
+ "vsps",
+ "pnfs",
+ "vnfs",
+ "services",
+ "customers",
+ "msb-k8s-definitions",
+ "aai-services",
+ "service-instances",
+ )
+
+ def parse(self) -> Iterator[Resource]:
+ """Parser method.
+
+ Invokes factory method to create objects from nested data dictionary.
+
+ Returns:
+ Iterator[Resource]: Iterator of Resource type objects.
+
+ """
+ for config in self.configs:
+ for resource in self._get_ordered_resources(config.resources):
+ for resource_type, data in resource.items():
+ yield ResourceCreator.create(resource_type, data, config.version)
+
+ def _get_ordered_resources(
+ self, resources_data: Dict[str, Any]
+ ) -> Iterator[Dict[str, Any]]:
+ """Resources helper method.
+
+ Generates data in fixed order defined in _PRIORITY_ORDER property.
+
+ Args:
+ resources_data (Dict[str, Any]): Dictionary generated from YAML infra file.
+
+ Returns:
+ Dict[str, Any]: Iterator of Dict type objects where key is the name
+ of resource type, and the value is actual resource data.
+
+ """
+ ordered_resources: Dict[str, Any] = OrderedDict.fromkeys(
+ self._PRIORITY_ORDER, {}
+ )
+ ordered_resources.update(resources_data)
+ for ordered_resource in ordered_resources.values():
+ for resource_data in ordered_resource:
+ yield resource_data
+
+ @property
+ def configs(self) -> List[Config]:
+ """Config loaded using loader.
+
+ Returns:
+ Dict[str, Any]: Config
+
+ """
+ if self._configs is None:
+ self._configs = [Config(config) for config in self._config_loader.load()]
+ return self._configs
+
+ @property
+ def validator(self) -> Validator:
+ """Property which stores validator object.
+
+ Used to validate provided data.
+
+ Returns:
+ Validator: Validator object
+
+ """
+ if not self._validator:
+ self._validator = Validator()
+ return self._validator
+
+ def validate(self) -> None:
+ """Validate provided resources.
+
+ Checks whether the data provided by the user are correct.
+ """
+ for config in self.configs:
+ self.validator.validate(config.version, config.resources)
diff --git a/onap_data_provider/data_provider.py b/onap_data_provider/data_provider.py
new file mode 100644
index 0000000..0cf941e
--- /dev/null
+++ b/onap_data_provider/data_provider.py
@@ -0,0 +1,106 @@
+"""Main project class."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import argparse
+import logging
+import logging.config
+import os
+import sys
+from pathlib import Path
+
+from onapsdk.onap_service import OnapService # type: ignore
+
+from onap_data_provider.config_parser import ConfigParser
+
+
+logging.config.dictConfig(
+ {
+ "version": 1,
+ "disable_existing_loggers": False,
+ "formatters": {
+ "odp": {
+ "class": "logging.Formatter",
+ "format": "%(asctime)s [%(levelname)s] %(module)s: %(message)s",
+ }
+ },
+ "handlers": {
+ "console": {
+ "class": "logging.StreamHandler",
+ "level": os.getenv("LOGGING_LEVEL", "INFO").upper(),
+ "formatter": "odp",
+ },
+ "file": {
+ "class": "logging.FileHandler",
+ "level": "DEBUG",
+ "filename": "odp.log",
+ "mode": "w",
+ "formatter": "odp",
+ },
+ },
+ "loggers": {
+ "": {"level": "DEBUG", "handlers": ["console", "file"]},
+ },
+ }
+)
+
+
+def create_parser() -> argparse.ArgumentParser:
+ """Create argument parser."""
+ parser: argparse.ArgumentParser = argparse.ArgumentParser(
+ description="ONAP data provider"
+ )
+ parser.add_argument(
+ "-f",
+ "--filename",
+ type=Path,
+ action="append",
+ dest="infra_files",
+ required=True,
+ help="Path to the infra file which describes resources to create. Can be directory as well",
+ )
+ parser.add_argument(
+ "--validate-only",
+ action="store_true",
+ help="Doesn't create any resources - checks only if data in infra file has valid format",
+ )
+ parser.add_argument(
+ "--proxy",
+ nargs="*",
+ help="Setup proxy connection with given url. Provide full URL with protocol, eg. http://localhost:8080",
+ )
+ return parser
+
+
+def run() -> None:
+ """Project main function."""
+ parser: argparse.ArgumentParser = create_parser()
+ args: argparse.Namespace = parser.parse_args()
+ if args.proxy:
+ OnapService.set_proxy(
+ {url.split("://")[0]: url.split("://")[1] for url in args.proxy}
+ )
+ conf_parser = ConfigParser(args.infra_files)
+ conf_parser.validate()
+ if args.validate_only:
+ print("Input data is valid!")
+ sys.exit(0)
+ for x in conf_parser.parse():
+ x.create()
+
+
+if __name__ == "__main__":
+ run()
diff --git a/onap_data_provider/resources/__init__.py b/onap_data_provider/resources/__init__.py
new file mode 100644
index 0000000..dbcdf74
--- /dev/null
+++ b/onap_data_provider/resources/__init__.py
@@ -0,0 +1,16 @@
+"""Resources package."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
diff --git a/onap_data_provider/resources/aai_service_resource.py b/onap_data_provider/resources/aai_service_resource.py
new file mode 100644
index 0000000..8fbc119
--- /dev/null
+++ b/onap_data_provider/resources/aai_service_resource.py
@@ -0,0 +1,83 @@
+"""A&AI service model resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+import logging
+from typing import Any, Dict
+
+from onapsdk.aai.service_design_and_creation import Service as AaiService # type: ignore
+from onapsdk.exceptions import ResourceNotFound # type: ignore
+
+from .resource import Resource
+
+
+class AaiServiceResource(Resource):
+ """A&AI service model resource class."""
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize A&AI SDC service resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+ self._aai_service: AaiService = None
+
+ def create(self) -> None:
+ """Create aai service resource."""
+ if not self.exists:
+ logging.debug("Create AaiService %s", self.data["service-id"])
+ AaiService.create(
+ service_id=self.data["service-id"],
+ service_description=self.data["service-description"],
+ )
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return self.aai_service is not None
+
+ @property
+ def aai_service(self) -> AaiService:
+ """A&AI service property.
+
+ A&AI servic emodel which is represented by the data provided by user.
+
+ Returns:
+ AaiService: A&AI service model object
+
+ """
+ if not self._aai_service:
+ try:
+ for aai_service in AaiService.get_all():
+ if (
+ aai_service.service_id == self.data["service-id"]
+ and aai_service.service_description
+ == self.data["service-description"]
+ ):
+ self._aai_service = aai_service
+ return self._aai_service
+ except ResourceNotFound:
+ logging.error(
+ "A&AI service %s does not exist",
+ self.data["service-id"],
+ )
+ return self._aai_service
diff --git a/onap_data_provider/resources/cloud_region_resource.py b/onap_data_provider/resources/cloud_region_resource.py
new file mode 100644
index 0000000..7bcc3b4
--- /dev/null
+++ b/onap_data_provider/resources/cloud_region_resource.py
@@ -0,0 +1,177 @@
+"""Cloud region resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+from onap_data_provider.resources.esr_system_info_resource import (
+ EsrSystemInfoResource,
+)
+import logging
+from typing import Any, Dict
+
+from onapsdk.aai.cloud_infrastructure import CloudRegion, Complex # type: ignore
+from onapsdk.msb.k8s.connectivity_info import ConnectivityInfo # type: ignore
+from onapsdk.so.so_db_adapter import SoDbAdapter, IdentityService # type: ignore
+
+from .resource import Resource
+from .tenant_resource import TenantResource
+from onapsdk.exceptions import APIError, ResourceNotFound # type: ignore
+
+
+class CloudRegionResource(Resource):
+ """Cloud region resource class.
+
+ Creates cloud region.
+ """
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize cloud region resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+ self._cloud_region: CloudRegion = None
+
+ def create(self) -> None:
+ """Create cloud region resource.
+
+ Create cloud region resource and all related resources.
+
+ """
+ logging.debug("Create CloudRegion %s", self.data["cloud-region-id"])
+ if not self.exists:
+ self._cloud_region = CloudRegion.create(
+ cloud_owner=self.data["cloud-owner"],
+ cloud_region_id=self.data["cloud-region-id"],
+ orchestration_disabled=self.data["orchestration-disabled"],
+ in_maint=self.data["in-maint"],
+ cloud_type=self.data.get("cloud-region-type", "openstack"),
+ cloud_region_version="pike",
+ )
+
+ # Create tenants
+ for tenant_data in self.data.get("tenants", []):
+ tenant_resource = TenantResource(
+ tenant_data, cloud_region=self._cloud_region
+ )
+ tenant_resource.create()
+
+ # Link with complex
+ if (
+ complex_physical_id := self.data.get("complex", {}).get(
+ "physical-location-id"
+ )
+ ) is not None:
+ self._link_to_complex(complex_physical_id)
+
+ # Add availability zones
+ try:
+ for az_data in self.data.get("availability-zones", []):
+ self.cloud_region.add_availability_zone(
+ availability_zone_name=az_data["availability-zone-name"],
+ availability_zone_hypervisor_type=az_data["hypervisor-type"],
+ )
+ except APIError:
+ logging.error("Availability zone update not supported.")
+
+ # Create external system infos
+ for esr_system_info_data in self.data.get("esr-system-infos", []):
+ esr_system_info_resource: EsrSystemInfoResource = EsrSystemInfoResource(
+ esr_system_info_data, cloud_region=self._cloud_region
+ )
+ esr_system_info_resource.create()
+
+ if self.data.get("register-to-multicloud", False):
+ self.cloud_region.register_to_multicloud()
+
+ # Create connectivity info for Cloud region if it's type is k8s
+ if self.cloud_region.cloud_type == "k8s":
+ try:
+ ConnectivityInfo.get_connectivity_info_by_region_id(
+ self.cloud_region.cloud_region_id
+ )
+ except APIError:
+ with open(self.data["kube-config"], "rb") as kube_config:
+ ConnectivityInfo.create(
+ cloud_owner=self.cloud_region.cloud_owner,
+ cloud_region_id=self.cloud_region.cloud_region_id,
+ kubeconfig=kube_config.read(),
+ )
+ if not self.cloud_region.complex:
+ logging.error(
+ "k8s cloud region should have complex linked to create SO cloud site DB entry"
+ )
+ else:
+ SoDbAdapter.add_cloud_site(
+ self.cloud_region.cloud_region_id,
+ self.cloud_region.complex.physical_location_id,
+ IdentityService("DEFAULT_KEYSTONE"),
+ )
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return self.cloud_region is not None
+
+ @property
+ def cloud_region(self) -> CloudRegion:
+ """Cloud region property.
+
+ Cloud region which is represented by the data provided by user.
+
+ Returns:
+ CloudRegion: Cloud region object
+
+ """
+ if not self._cloud_region:
+ try:
+ self._cloud_region = CloudRegion.get_by_id(
+ self.data["cloud-owner"], self.data["cloud-region-id"]
+ )
+ except ResourceNotFound:
+ logging.error(
+ "Cloud region %s does not exist",
+ self.data["cloud-region-id"],
+ )
+ return None
+ return self._cloud_region
+
+ def _link_to_complex(self, complex_physical_id: str) -> None:
+ try: # TODO: change it when https://gitlab.com/Orange-OpenSource/lfn/onap/python-onapsdk/-/issues/120 is fixed
+ if self.cloud_region.complex:
+ logging.info(
+ "Cloud region has relationship with complex: %s. New relationship can't be created",
+ self.cloud_region.complex.physical_location_id,
+ )
+ return
+ except ResourceNotFound:
+ logging.debug("Cloud region has no complex linked with")
+ try:
+ complex: Complex = next(
+ Complex.get_all(physical_location_id=complex_physical_id)
+ )
+ self.cloud_region.link_to_complex(complex)
+ except StopIteration:
+ logging.error(
+ "Complex %s does not exist, please create it before cloud region creation",
+ complex_physical_id,
+ )
diff --git a/onap_data_provider/resources/complex_resource.py b/onap_data_provider/resources/complex_resource.py
new file mode 100644
index 0000000..82ab462
--- /dev/null
+++ b/onap_data_provider/resources/complex_resource.py
@@ -0,0 +1,92 @@
+"""Complex resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import logging
+from typing import Any, Dict
+
+from onapsdk.aai.cloud_infrastructure import Complex # type: ignore
+
+from .resource import Resource
+from onapsdk.exceptions import ResourceNotFound # type: ignore
+
+
+class ComplexResource(Resource):
+ """Complex resource class."""
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Complex resource initialization.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create complex
+
+ """
+ super().__init__(data)
+ self._complex: Complex = None
+
+ def create(self) -> None:
+ """Create complex resource."""
+ if not self.exists:
+ self._complex = Complex.create(
+ physical_location_id=self.data["physical-location-id"],
+ name=self.data.get("complex-name"),
+ data_center_code=self.data.get("data-center-code"),
+ identity_url=self.data.get("identity-url"),
+ physical_location_type=self.data.get("physical-location-type"),
+ street1=self.data.get("street1"),
+ street2=self.data.get("street2"),
+ city=self.data.get("city"),
+ state=self.data.get("state"),
+ postal_code=self.data.get("postal-code"),
+ country=self.data.get("country"),
+ region=self.data.get("region"),
+ latitude=self.data.get("latitude"),
+ longitude=self.data.get("longitude"),
+ elevation=self.data.get("elevation"),
+ lata=self.data.get("lata"),
+ )
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return self.complex is not None
+
+ @property
+ def complex(self) -> Complex:
+ """Complex property.
+
+ Returns:
+ Complex: Complex object
+
+ """
+ if not self._complex:
+ try:
+ self._complex = next(
+ Complex.get_all(
+ physical_location_id=self.data["physical-location-id"]
+ )
+ )
+ except ResourceNotFound:
+ logging.error(
+ "Complex %s does not exist", self.data["physical-location-id"]
+ )
+ return None
+ return self._complex
diff --git a/onap_data_provider/resources/customer_resource.py b/onap_data_provider/resources/customer_resource.py
new file mode 100644
index 0000000..2bbb1ef
--- /dev/null
+++ b/onap_data_provider/resources/customer_resource.py
@@ -0,0 +1,189 @@
+"""Customer resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import logging
+from typing import Any, Dict
+
+from onapsdk.aai.business import Customer, ServiceSubscription # type: ignore
+from onapsdk.aai.cloud_infrastructure import CloudRegion, Tenant # type: ignore
+
+from onapsdk.sdc.service import Service # type: ignore
+
+from .resource import Resource
+from onapsdk.exceptions import ResourceNotFound # type: ignore
+
+
+class CustomerResource(Resource):
+ """Customer resource class.
+
+ Creates customer.
+ """
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize customer resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+ self._customer: Customer = None
+
+ def create(self) -> None:
+ """Create customer resource.
+
+ Create customer resource and all related resources.
+
+ """
+ logging.debug("Create Customer %s", self.data["global-customer-id"])
+ if not self.exists:
+ self._customer = Customer.create(
+ global_customer_id=self.data["global-customer-id"],
+ subscriber_name=self.data["subscriber-name"],
+ subscriber_type=self.data["subscriber-type"],
+ )
+
+ for service_subscription in self.data.get("service-subscriptions", []):
+ resource = CustomerResource.ServiceSubscriptionResource(
+ service_subscription, self._customer
+ )
+ resource.create()
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return self.customer is not None
+
+ @property
+ def customer(self) -> Customer:
+ """Access to customer property.
+
+ Customer property containing Customer object.
+
+ Returns:
+ Customer: Customer object
+
+ """
+ if not self._customer:
+ try:
+ self._customer = Customer.get_by_global_customer_id(
+ self.data["global-customer-id"]
+ )
+ except ResourceNotFound:
+ logging.error(
+ "Customer %s does not exist",
+ self.data["global-customer-id"],
+ )
+ return None
+ return self._customer
+
+ class ServiceSubscriptionResource(Resource):
+ """Service subscription class.
+
+ Creates service subscription.
+ """
+
+ def __init__(self, data: Dict[str, str], customer: Customer) -> None:
+ """Initialize service subscription resource.
+
+ Args:
+ data (Dict[str, str]): Data needed to create resource.
+ customer (Customer): Related Customer object.
+
+ """
+ super().__init__(data)
+ self._service_subscription: ServiceSubscription = None
+ self._customer: Customer = customer
+
+ def create(self) -> None:
+ """Create Service subscription resource.
+
+ Create service subscription resource belonging to a customer.
+
+ """
+ logging.debug("Create ServiceSubscription %s", self.data["service-type"])
+ if not self.exists:
+ self._service_subscription = self._customer.subscribe_service(
+ Service(self.data["service-type"])
+ )
+
+ for tenant_cloud_region_data in self.data.get("tenants", []):
+ try:
+ cloud_region: CloudRegion = CloudRegion.get_by_id(
+ tenant_cloud_region_data["cloud-owner"],
+ tenant_cloud_region_data["cloud-region-id"],
+ )
+ except ResourceNotFound:
+ logging.error(
+ f"Cloud region {tenant_cloud_region_data['cloud-owner']} {tenant_cloud_region_data['cloud-region-id']} does not exists"
+ )
+ continue
+ try:
+ tenant: Tenant = cloud_region.get_tenant(
+ tenant_cloud_region_data["tenant-id"]
+ )
+ except ResourceNotFound:
+ logging.error(
+ f"Tenant {tenant_cloud_region_data['tenant-id']} does not exist"
+ )
+ continue
+
+ self.service_subscription.link_to_cloud_region_and_tenant(
+ cloud_region, tenant
+ )
+ logging.debug(
+ f"Service subscription linked to {tenant.name} tenant and {cloud_region.cloud_region_id} cloud region"
+ )
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return self.service_subscription is not None
+
+ @property
+ def service_subscription(self) -> ServiceSubscription:
+ """Get ServiceSubscription instance.
+
+ Get ServiceSubscription instance.
+
+ Returns:
+ ServiceSubscription: Created `ServiceSubscription` subclass instance.
+ """
+ if not self._service_subscription:
+ try:
+ self._service_subscription = (
+ self._customer.get_service_subscription_by_service_type(
+ self.data["service-type"]
+ )
+ )
+ except ResourceNotFound:
+ logging.error(
+ "Service type %s does not exist",
+ self.data["service-type"],
+ )
+ return None
+ return self._service_subscription
diff --git a/onap_data_provider/resources/esr_system_info_resource.py b/onap_data_provider/resources/esr_system_info_resource.py
new file mode 100644
index 0000000..4c26bbb
--- /dev/null
+++ b/onap_data_provider/resources/esr_system_info_resource.py
@@ -0,0 +1,114 @@
+"""External system info resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+import logging
+from typing import Any, Dict
+
+from onapsdk.aai.cloud_infrastructure import CloudRegion, EsrSystemInfo # type: ignore
+from onapsdk.exceptions import APIError # type: ignore
+
+from .resource import Resource
+
+
+class EsrSystemInfoResource(Resource):
+ """ESR system info resource class."""
+
+ def __init__(self, data: Dict[str, Any], cloud_region: CloudRegion) -> None:
+ """ESR system info resource initialization.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create esr system info
+ cloud_region (CloudRegion): Cloud region for which esr system info is going to be created
+
+ """
+ super().__init__(data)
+ self.cloud_region: CloudRegion = cloud_region
+ self._esr_system_info: EsrSystemInfo = None
+
+ @staticmethod
+ def get_esr_info_by_id(
+ cloud_region: CloudRegion, esr_syste_info_id: str
+ ) -> EsrSystemInfo:
+ """Get esr system info from Cloud region by it's ID.
+
+ Iterate through cloud region's esr system infos and check
+ if it's already have some with provided ID.
+
+ Args:
+ cloud_region (CloudRegion): CloudRegion object to check if esr system info already exists
+ esr_syste_info_id (str): ESR system info ID to check.
+
+ Returns:
+ EsrSystemInfo: ESR system info object
+ """
+ for esr_system_info in cloud_region.esr_system_infos:
+ if esr_system_info.esr_system_info_id == esr_syste_info_id:
+ return esr_system_info
+
+ def create(self) -> None:
+ """Create ESR system info resource.
+
+ Add ESR system info to provided cloud region
+
+ """
+ logging.debug(
+ "Create ESR system info for %s cloud region",
+ self.cloud_region.cloud_region_id,
+ )
+ if not self.exists:
+ self.cloud_region.add_esr_system_info(
+ esr_system_info_id=self.data["esr-system-info-id"],
+ user_name=self.data["user-name"],
+ password=self.data["password"],
+ system_type=self.data["system-type"],
+ service_url=self.data["service-url"],
+ system_status="active",
+ cloud_domain=self.data["cloud-domain"],
+ default_tenant=self.data.get("default-tenant"),
+ )
+ self._esr_system_info = self.get_esr_info_by_id(
+ self.cloud_region, self.data["esr-system-info-id"]
+ )
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return self.esr_system_info is not None
+
+ @property
+ def esr_system_info(self) -> EsrSystemInfo:
+ """External system info property.
+
+ Returns:
+ EsrSystemInfo: EsrSystemInfo object
+
+ """
+ if self._esr_system_info is None:
+ try:
+ if (
+ esr_system_info := self.get_esr_info_by_id(
+ self.cloud_region, self.data["esr-system-info-id"]
+ )
+ ) is not None:
+ self._esr_system_info = esr_system_info
+ except APIError:
+ logging.info("No esr system infos")
+ return self._esr_system_info
diff --git a/onap_data_provider/resources/line_of_business_resource.py b/onap_data_provider/resources/line_of_business_resource.py
new file mode 100644
index 0000000..0150746
--- /dev/null
+++ b/onap_data_provider/resources/line_of_business_resource.py
@@ -0,0 +1,73 @@
+"""Line of business resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+import logging
+from typing import Any, Dict, Optional
+
+from onapsdk.aai.business import LineOfBusiness # type: ignore
+from onapsdk.exceptions import ResourceNotFound # type: ignore
+
+from .resource import Resource
+
+
+class LineOfBusinessResource(Resource):
+ """Line of business resource class.
+
+ Creates A&AI line of business.
+ """
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize line of business resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+ self._line_of_business: Optional[LineOfBusiness] = None
+
+ def create(self) -> None:
+ """Create line of business resource."""
+ logging.debug(f"Create Line of business {self.data['name']}")
+ if not self.exists:
+ self._line_of_business = LineOfBusiness.create(self.data["name"])
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return bool(self.line_of_business)
+
+ @property
+ def line_of_business(self) -> LineOfBusiness:
+ """Line of business property.
+
+ Line of business which is represented by the data provided by user.
+
+ Returns:
+ LineOfBusiness: Line of business object
+
+ """
+ if not self._line_of_business:
+ try:
+ self._line_of_business = LineOfBusiness.get_by_name(self.data["name"])
+ except ResourceNotFound:
+ return None
+ return self._line_of_business
diff --git a/onap_data_provider/resources/msb_k8s_definition.py b/onap_data_provider/resources/msb_k8s_definition.py
new file mode 100644
index 0000000..b4b3342
--- /dev/null
+++ b/onap_data_provider/resources/msb_k8s_definition.py
@@ -0,0 +1,85 @@
+"""MSB K8S definition resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import logging
+from typing import Any, Dict, Optional
+
+from onapsdk.exceptions import ResourceNotFound # type: ignore
+from onapsdk.msb.k8s.definition import Definition # type: ignore
+
+from .msb_k8s_profile import MsbK8SProfileResource
+from .resource import Resource
+
+
+class MsbK8SDefinitionResource(Resource):
+ """Definition resource class.
+
+ Creates MSB Kubernetes plugin's definition.
+ """
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize definition resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+ self._definition: Optional[Definition] = None
+
+ def create(self) -> None:
+ """Create definition if not already exists."""
+ if not self.exists:
+ self._definition = Definition.create(
+ self.data["name"],
+ self.data["version"],
+ self.data.get("chart-name"),
+ self.data.get("description"),
+ )
+ with open(self.data["artifact"], "rb") as artifact:
+ self._definition.upload_artifact(artifact.read())
+ for profile_data in self.data.get("profiles", []):
+ MsbK8SProfileResource(profile_data, self.definition).create()
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return self.definition is not None
+
+ @property
+ def definition(self) -> Optional[Definition]:
+ """Definition property.
+
+ Definition which is represented by the data provided by user.
+
+ Returns:
+ Definition: Definition object
+
+ """
+ if not self._definition:
+ try:
+ self._definition = Definition.get_definition_by_name_version(
+ self.data["name"], self.data["version"]
+ )
+ except ResourceNotFound:
+ logging.error("Definition %s does not exist", self.data["rb-name"])
+ return self._definition
diff --git a/onap_data_provider/resources/msb_k8s_profile.py b/onap_data_provider/resources/msb_k8s_profile.py
new file mode 100644
index 0000000..ae884c2
--- /dev/null
+++ b/onap_data_provider/resources/msb_k8s_profile.py
@@ -0,0 +1,80 @@
+"""MSB K8S definition profile resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import logging
+from typing import Any, Dict, Optional
+
+from onapsdk.exceptions import ResourceNotFound # type: ignore
+from onapsdk.msb.k8s.definition import Definition, Profile # type: ignore
+
+from .resource import Resource
+
+
+class MsbK8SProfileResource(Resource):
+ """Profile resource class.
+
+ Creates MSB Kubernetes plugin's profile
+ """
+
+ def __init__(self, data: Dict[str, Any], definition: Definition) -> None:
+ """Initialize definition resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+ self._profile: Optional[Profile] = None
+ self.definition: Definition = definition
+
+ def create(self) -> None:
+ """Create profile if not already exists."""
+ if not self.exists:
+ self._profile = self.definition.create_profile(
+ self.data["name"], self.data["namespace"], self.data["k8s-version"]
+ )
+ with open(self.data["artifact"], "rb") as artifact:
+ self._profile.upload_artifact(artifact.read())
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return self.profile is not None
+
+ @property
+ def profile(self) -> Optional[Profile]:
+ """Profile property.
+
+ Profile which is represented by the data provided by user.
+
+ Returns:
+ Profile: Profile object
+
+ """
+ if not self._profile:
+ try:
+ self._profile = self.definition.get_profile_by_name(
+ self.data["rb-name"]
+ )
+ except ResourceNotFound:
+ logging.error("Profile %s not found", self.data["name"])
+ return self._profile
diff --git a/onap_data_provider/resources/owning_entity_resource.py b/onap_data_provider/resources/owning_entity_resource.py
new file mode 100644
index 0000000..496ec22
--- /dev/null
+++ b/onap_data_provider/resources/owning_entity_resource.py
@@ -0,0 +1,75 @@
+"""Owning entity resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+import logging
+from typing import Any, Dict, Optional
+
+from onapsdk.aai.business import OwningEntity # type: ignore
+from onapsdk.exceptions import ResourceNotFound # type: ignore
+
+from .resource import Resource
+
+
+class OwningEntityResource(Resource):
+ """Owning entity resource class.
+
+ Creates A&AI line of business.
+ """
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize line of business resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+ self._owning_entity: Optional[OwningEntity] = None
+
+ def create(self) -> None:
+ """Create line of business resource."""
+ logging.debug(f"Create Owning entity {self.data['name']}")
+ if not self.exists:
+ self._owning_entity = OwningEntity.create(self.data["name"])
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return bool(self.owning_entity)
+
+ @property
+ def owning_entity(self) -> OwningEntity:
+ """Owning entity property.
+
+ Owning entity which is represented by the data provided by user.
+
+ Returns:
+ OwningEntity: Owning entity object
+
+ """
+ if not self._owning_entity:
+ try:
+ self._owning_entity = OwningEntity.get_by_owning_entity_name(
+ self.data["name"]
+ )
+ except ResourceNotFound:
+ return None
+ return self._owning_entity
diff --git a/onap_data_provider/resources/platform_resource.py b/onap_data_provider/resources/platform_resource.py
new file mode 100644
index 0000000..5e8893c
--- /dev/null
+++ b/onap_data_provider/resources/platform_resource.py
@@ -0,0 +1,73 @@
+"""Platform resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+import logging
+from typing import Any, Dict, Optional
+
+from onapsdk.aai.business import Platform # type: ignore
+from onapsdk.exceptions import ResourceNotFound # type: ignore
+
+from .resource import Resource
+
+
+class PlatformResource(Resource):
+ """Platform resource class.
+
+ Creates A&AI platform.
+ """
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize platform resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+ self._platform: Optional[Platform] = None
+
+ def create(self) -> None:
+ """Create platform resource."""
+ logging.debug(f"Create Platform {self.data['name']}")
+ if not self.exists:
+ self._platform = Platform.create(self.data["name"])
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return bool(self.platform)
+
+ @property
+ def platform(self) -> Platform:
+ """Platform property.
+
+ Platform which is represented by the data provided by user.
+
+ Returns:
+ Platform: Platform object
+
+ """
+ if not self._platform:
+ try:
+ self._platform = Platform.get_by_name(self.data["name"])
+ except ResourceNotFound:
+ return None
+ return self._platform
diff --git a/onap_data_provider/resources/pnf_resource.py b/onap_data_provider/resources/pnf_resource.py
new file mode 100644
index 0000000..553018b
--- /dev/null
+++ b/onap_data_provider/resources/pnf_resource.py
@@ -0,0 +1,78 @@
+"""Pnf resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import logging
+from typing import Any, Dict
+
+from onapsdk.sdc.pnf import Pnf # type: ignore
+from onapsdk.sdc.vendor import Vendor # type: ignore
+from .resource import Resource
+from .xnf_resource import XnfResource
+
+
+class PnfResource(Resource, XnfResource):
+ """Pnf resource class.
+
+ Creates pnf.
+ """
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize pnf resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+
+ def create(self) -> None:
+ """Create pnf resource.
+
+ Create pnf resource and link to provided resources.
+
+ """
+ if not self.exists:
+ logging.debug("Create Pnf %s", self.data["name"])
+ self._xnf = Pnf(self.data["name"])
+ if (vendor_name := self.data.get("vendor")) is not None:
+ self._xnf.vendor = Vendor(vendor_name)
+ self.onboard_resource_with_properties(self.data)
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return self.pnf is not None
+
+ @property
+ def pnf(self) -> Pnf:
+ """Pnf property.
+
+ Pnf which is represented by the data provided by user.
+
+ Returns:
+ Pnf: Pnf object
+
+ """
+ if (pnf := Pnf(name=self.data["name"])).created():
+ self._xnf = pnf
+ return self._xnf
+ return None
diff --git a/onap_data_provider/resources/project_resource.py b/onap_data_provider/resources/project_resource.py
new file mode 100644
index 0000000..e4c19c2
--- /dev/null
+++ b/onap_data_provider/resources/project_resource.py
@@ -0,0 +1,73 @@
+"""Project resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+import logging
+from typing import Any, Dict, Optional
+
+from onapsdk.aai.business import Project # type: ignore
+from onapsdk.exceptions import ResourceNotFound # type: ignore
+
+from .resource import Resource
+
+
+class ProjectResource(Resource):
+ """Project resource class.
+
+ Creates A&AI project.
+ """
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize project resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+ self._project: Optional[Project] = None
+
+ def create(self) -> None:
+ """Create project resource."""
+ logging.debug(f"Create Project {self.data['name']}")
+ if not self.exists:
+ self._project = Project.create(self.data["name"])
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return bool(self.project)
+
+ @property
+ def project(self) -> Project:
+ """Project property.
+
+ Project which is represented by the data provided by user.
+
+ Returns:
+ Project: Project object
+
+ """
+ if not self._project:
+ try:
+ self._project = Project.get_by_name(self.data["name"])
+ except ResourceNotFound:
+ return None
+ return self._project
diff --git a/onap_data_provider/resources/resource.py b/onap_data_provider/resources/resource.py
new file mode 100644
index 0000000..10477d1
--- /dev/null
+++ b/onap_data_provider/resources/resource.py
@@ -0,0 +1,45 @@
+"""Resource base module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+from abc import ABC, abstractmethod
+from typing import Dict, Any
+
+
+class Resource(ABC):
+ """Base Resource class.
+
+ Abstract class which is a base for all other resource classes.
+ """
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize resource.
+
+ Data contains all needed information to create resource.
+ It's readed from configuration file.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ self.data = data
+
+ @abstractmethod
+ def create(self) -> None:
+ """Create resource.
+
+ Abstract method to create resource
+
+ """
diff --git a/onap_data_provider/resources/resource_creator.py b/onap_data_provider/resources/resource_creator.py
new file mode 100644
index 0000000..34cbafd
--- /dev/null
+++ b/onap_data_provider/resources/resource_creator.py
@@ -0,0 +1,177 @@
+"""Resource creator module."""
+from __future__ import annotations
+
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+from onap_data_provider.resources.platform_resource import PlatformResource
+import typing
+from abc import ABC
+
+from .aai_service_resource import AaiServiceResource
+from .cloud_region_resource import CloudRegionResource
+from .complex_resource import ComplexResource
+from .customer_resource import CustomerResource
+from .line_of_business_resource import LineOfBusinessResource
+from .msb_k8s_definition import MsbK8SDefinitionResource
+from .owning_entity_resource import OwningEntityResource
+from .pnf_resource import PnfResource
+from .project_resource import ProjectResource
+from .service_resource import ServiceResource
+from .service_instance_resource import (
+ ServiceInstanceResource,
+ ServiceInstanceResource_1_1,
+)
+from .vendor_resource import VendorResource
+from .vnf_resource import VnfResource
+from .vsp_resource import VspResource
+from ..versions import VersionsEnum
+
+if typing.TYPE_CHECKING:
+ from .resource import Resource
+
+
+class ResourceCreator(ABC):
+ """Resource creator.
+
+ Provides a method to create `Resource` instances.
+ """
+
+ RESOURCES_TYPES_DICT: typing.Mapping[
+ str, typing.Mapping[VersionsEnum, typing.Type[Resource]]
+ ] = {
+ "aai-service": {
+ VersionsEnum.NONE: AaiServiceResource,
+ VersionsEnum.V1_0: AaiServiceResource,
+ VersionsEnum.V1_1: AaiServiceResource,
+ },
+ "cloud-region": {
+ VersionsEnum.NONE: CloudRegionResource,
+ VersionsEnum.V1_0: CloudRegionResource,
+ VersionsEnum.V1_1: CloudRegionResource,
+ },
+ "complex": {
+ VersionsEnum.NONE: ComplexResource,
+ VersionsEnum.V1_0: ComplexResource,
+ VersionsEnum.V1_1: ComplexResource,
+ },
+ "customer": {
+ VersionsEnum.NONE: CustomerResource,
+ VersionsEnum.V1_0: CustomerResource,
+ VersionsEnum.V1_1: CustomerResource,
+ },
+ "vsp": {
+ VersionsEnum.NONE: VspResource,
+ VersionsEnum.V1_0: VspResource,
+ VersionsEnum.V1_1: VspResource,
+ },
+ "service": {
+ VersionsEnum.NONE: ServiceResource,
+ VersionsEnum.V1_0: ServiceResource,
+ VersionsEnum.V1_1: ServiceResource,
+ },
+ "vendor": {
+ VersionsEnum.NONE: VendorResource,
+ VersionsEnum.V1_0: VendorResource,
+ VersionsEnum.V1_1: VendorResource,
+ },
+ "pnf": {
+ VersionsEnum.NONE: PnfResource,
+ VersionsEnum.V1_0: PnfResource,
+ VersionsEnum.V1_1: PnfResource,
+ },
+ "vnf": {
+ VersionsEnum.NONE: VnfResource,
+ VersionsEnum.V1_0: VnfResource,
+ VersionsEnum.V1_1: VnfResource,
+ },
+ "service-instance": {
+ VersionsEnum.NONE: ServiceInstanceResource,
+ VersionsEnum.V1_0: ServiceInstanceResource,
+ VersionsEnum.V1_1: ServiceInstanceResource_1_1,
+ },
+ "line-of-business": {
+ VersionsEnum.NONE: LineOfBusinessResource,
+ VersionsEnum.V1_0: LineOfBusinessResource,
+ VersionsEnum.V1_1: LineOfBusinessResource,
+ },
+ "project": {
+ VersionsEnum.NONE: ProjectResource,
+ VersionsEnum.V1_0: ProjectResource,
+ VersionsEnum.V1_1: ProjectResource,
+ },
+ "platform": {
+ VersionsEnum.NONE: PlatformResource,
+ VersionsEnum.V1_0: PlatformResource,
+ VersionsEnum.V1_1: PlatformResource,
+ },
+ "owning-entity": {
+ VersionsEnum.NONE: OwningEntityResource,
+ VersionsEnum.V1_0: OwningEntityResource,
+ VersionsEnum.V1_1: OwningEntityResource,
+ },
+ "msb-k8s-definition": {
+ VersionsEnum.NONE: MsbK8SDefinitionResource,
+ VersionsEnum.V1_0: MsbK8SDefinitionResource,
+ VersionsEnum.V1_1: MsbK8SDefinitionResource,
+ },
+ }
+
+ @classmethod
+ def create(
+ cls,
+ resource_type: str,
+ data: typing.Dict[str, typing.Any],
+ version: VersionsEnum,
+ ) -> Resource:
+ """Resources factory method.
+
+ Based on provided `resource_type` creates `Resource` subclass.
+
+ Supported `resource_type` values:
+ - aai-service: AaiServiceResource
+ - cloud-region: CloudRegionResource
+ - complex: ComplexResource
+ - customer: CustomerResource
+ - vsp: VspResource
+ - service: ServiceResource
+ - vendor: VendorResource
+ - pnf: PnfResource
+ - vnf: VnfResource
+ - service-instance: ServiceInstanceResource
+ - line-of-business: LineOfBusinessResource
+ - project: ProjectResource
+ - platform: PlatformResource
+ - owning-entity: OwningEntityResource
+ - msb-k8s-definition: MsbK8SDefinitionResource
+
+ Args:
+ resource_type (str): Resource type to create
+ data (typing.Dict[str, typing.Any]): Resource data
+
+ Raises:
+ ValueError: Not support `resource_type` value provided.
+
+ Returns:
+ Resource: Created `Resource` subclass instance.
+
+ """
+ try:
+ return cls.RESOURCES_TYPES_DICT[resource_type][version](data)
+ except KeyError as key_error:
+ raise ValueError(
+ "Invalid resource type provided: %d", resource_type
+ ) from key_error
diff --git a/onap_data_provider/resources/service_instance_resource.py b/onap_data_provider/resources/service_instance_resource.py
new file mode 100644
index 0000000..b89f9df
--- /dev/null
+++ b/onap_data_provider/resources/service_instance_resource.py
@@ -0,0 +1,271 @@
+"""Service instance resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+import logging
+from typing import Any, Dict
+
+from onapsdk.aai.cloud_infrastructure import CloudRegion, Tenant # type: ignore
+from onapsdk.aai.business import Customer, OwningEntity # type: ignore
+from onapsdk.aai.service_design_and_creation import Service as AaiService # type: ignore
+from onapsdk.sdc.service import Service # type: ignore
+from onapsdk.vid import LineOfBusiness, Platform, Project # type: ignore
+from onapsdk.aai.business import ServiceSubscription
+from onapsdk.aai.business import ServiceInstance
+from onapsdk.so.instantiation import ( # type: ignore
+ ServiceInstantiation,
+ SoService,
+)
+
+from .resource import Resource
+from onapsdk.exceptions import APIError, ResourceNotFound # type: ignore
+
+
+class ServiceInstanceResource(Resource):
+ """Service instance resource class.
+
+ Creates service instance.
+ """
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Service instance resource initialization.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create service instance
+
+ """
+ super().__init__(data)
+ self._customer: Customer = None
+ self._service_subscription: ServiceSubscription = None
+ self._service_instance: ServiceInstance = None
+ self._aai_service: AaiService = None
+
+ def create(self) -> None:
+ """Create ServiceInstance resource."""
+ if not self.exists:
+
+ service: Service = Service(name=self.data["service_name"])
+ if not service.distributed:
+ raise AttributeError(
+ "Service not distrbuted - instance can't be created"
+ )
+ if (cloud_region_id := self.data["cloud_region_id"]) is not None:
+ cloud_region: CloudRegion = CloudRegion.get_by_id(
+ cloud_owner=self.data["cloud_owner"],
+ cloud_region_id=cloud_region_id,
+ )
+ tenant: Tenant = cloud_region.get_tenant(self.data["tenant_id"])
+ self.service_subscription.link_to_cloud_region_and_tenant(
+ cloud_region, tenant
+ )
+ else:
+ cloud_region, tenant = None, None
+ try:
+ owning_entity = OwningEntity.get_by_owning_entity_name(
+ self.data["owning_entity"]
+ )
+ except APIError:
+ owning_entity = OwningEntity.create(self.data["owning_entity"])
+
+ try:
+ aai_service = next(
+ AaiService.get_all(service_id=self.data["aai_service"])
+ )
+ except StopIteration:
+ raise ValueError(
+ f"A&AI Service {self.data['aai_service']} does not exist"
+ )
+
+ service_instantiation: ServiceInstantiation = (
+ ServiceInstantiation.instantiate_macro(
+ sdc_service=service,
+ customer=self.customer,
+ owning_entity=owning_entity,
+ project=Project(self.data["project"]),
+ line_of_business=LineOfBusiness(self.data["line_of_business"]),
+ platform=Platform(self.data["platform"]),
+ cloud_region=cloud_region,
+ tenant=tenant,
+ service_instance_name=self.data["service_instance_name"],
+ so_service=self.so_service,
+ aai_service=aai_service,
+ )
+ )
+ service_instantiation.wait_for_finish(
+ timeout=self.data.get("timeout")
+ ) # 20 minutes timeout
+
+ if service_instantiation.failed == True:
+ logging.error(
+ "Service instantiation failed for %s",
+ self.data["service_instance_name"],
+ )
+ return
+ self._service_instance = (
+ self.service_subscription.get_service_instance_by_name(
+ self.data["service_instance_name"]
+ )
+ )
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return self.service_instance is not None
+
+ @property
+ def service_instance(self) -> ServiceInstance:
+ """Serviceinstance property.
+
+ Returns:
+ ServiceInstance: ServiceInstance object
+
+ """
+ if not self._service_instance:
+ try:
+ service_instance: ServiceInstance = (
+ self.service_subscription.get_service_instance_by_name(
+ self.data["service_instance_name"]
+ )
+ )
+ if service_instance:
+ self._service_instance = service_instance
+ except ResourceNotFound:
+ logging.error(
+ "Customer %s does not exist",
+ self.data["customer_id"],
+ )
+ return self._service_instance
+
+ @property
+ def customer(self) -> Customer:
+ """Access to Customer object property.
+
+ Returns:
+ Customer: Customer object
+
+ """
+ if not self._customer:
+ self._customer = Customer.get_by_global_customer_id(
+ self.data["customer_id"]
+ )
+ return self._customer
+
+ @property
+ def service_subscription(self) -> ServiceSubscription:
+ """Service subscription property.
+
+ Returns:
+ ServiceSubscription: ServiceSubscription object
+
+ """
+ if not self._service_subscription and self.customer:
+ self._service_subscription = (
+ self.customer.get_service_subscription_by_service_type(
+ service_type=self.data.get(
+ "service_subscription_type", self.data["service_name"]
+ )
+ )
+ )
+ return self._service_subscription
+
+ @property
+ def so_service(self) -> SoService:
+ """Create an object with parameters for the service instantiation.
+
+ Based on the instance definition data create an object
+ which is used for instantiation.
+
+ Returns:
+ SoService: SoService object
+
+ """
+ return SoService(
+ subscription_service_type=self.data.get(
+ "service_subscription_type", self.data["service_name"]
+ ),
+ vnfs=[
+ {
+ "model_name": vnf["vnf_name"],
+ "vnf_name": vnf.get("instance_name", vnf["vnf_name"]),
+ "parameters": vnf.get("parameters", {}),
+ "vf_modules": [
+ {
+ "model_name": vf_module["name"],
+ "vf_module_name": vf_module.get(
+ "instance_name", vf_module["name"]
+ ),
+ "parameters": vf_module.get("parameters", {}),
+ }
+ for vf_module in vnf.get("vf_modules", [])
+ ],
+ }
+ for vnf in self.data.get("instantiation_parameters", [])
+ ],
+ )
+
+ @property
+ def aai_service(self) -> AaiService:
+ """A&AI service which is used during the instantiation.
+
+ Raises:
+ ValueError: AaiService with given service id doesn't exist
+
+ Returns:
+ AaiService: AaiService object
+
+ """
+ if (
+ not self._aai_service
+ and (aai_service_id := self.data.get("aai_service")) is not None
+ ):
+ try:
+ self._aai_service = next(AaiService.get_all(service_id=aai_service_id))
+ except StopIteration:
+ raise ValueError(f"A&AI Service {aai_service_id} does not exist")
+ return self._aai_service
+
+
+class ServiceInstanceResource_1_1(ServiceInstanceResource):
+ """Service instance resource class.
+
+ That's the Service instance resource class for 1.1 schema version.
+ """
+
+ @property
+ def aai_service(self) -> AaiService:
+ """A&AI service which is used during the instantiation.
+
+ Raises:
+ ValueError: AaiService with given service id doesn't exist
+
+ Returns:
+ AaiService: AaiService object
+
+ """
+ if not self._aai_service:
+ try:
+ self._aai_service = next(
+ AaiService.get_all(service_id=self.data["aai_service"])
+ )
+ except StopIteration:
+ raise ValueError(
+ f"A&AI Service {self.data['aai_service']} does not exist"
+ )
+ return self._aai_service
diff --git a/onap_data_provider/resources/service_resource.py b/onap_data_provider/resources/service_resource.py
new file mode 100644
index 0000000..8489982
--- /dev/null
+++ b/onap_data_provider/resources/service_resource.py
@@ -0,0 +1,100 @@
+"""Service resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+from typing import Any, Dict, Mapping, Optional, Type
+
+from onapsdk.sdc.pnf import Pnf # type: ignore
+from onapsdk.sdc.properties import Property # type: ignore
+from onapsdk.sdc.sdc_resource import SdcResource # type: ignore
+from onapsdk.sdc.service import Service, ServiceInstantiationType # type: ignore
+from onapsdk.sdc.vf import Vf # type: ignore
+from onapsdk.sdc.vl import Vl # type: ignore
+
+from .resource import Resource
+
+
+class ServiceResource(Resource):
+ """Service resource class."""
+
+ RESOURCES: Mapping[str, Type[SdcResource]] = {
+ "PNF": Pnf,
+ "VF": Vf,
+ "VL": Vl,
+ }
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize Service resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+ self._service: Optional[Service] = None
+
+ def create(self) -> None:
+ """Create Service resource."""
+ if not self.exists:
+ service = Service(
+ name=self.data["name"],
+ instantiation_type=ServiceInstantiationType.MACRO,
+ )
+ service.create()
+ for resource_data in self.data.get("resources", []):
+ resource = self.RESOURCES[resource_data["type"].upper()](
+ name=resource_data["name"]
+ )
+ service.add_resource(resource)
+ component = service.get_component(resource)
+ for prop_key, prop_value in resource_data.get("properties", {}).items():
+ prop = component.get_property(prop_key)
+ prop.value = prop_value
+ for property_data in self.data.get("properties", []):
+ service.add_property(
+ Property(
+ property_data["name"],
+ property_data["type"],
+ value=property_data.get("value"),
+ )
+ )
+ service.checkin()
+ service.onboard()
+ self._service = service
+
+ @property
+ def exists(self) -> bool:
+ """Check if Service exists in SDC.
+
+ Returns:
+ bool: True if Service exists, False otherwise
+
+ """
+ return self.service is not None and self.service.distributed
+
+ @property
+ def service(self) -> Optional[Service]:
+ """Service property.
+
+ Returns:
+ Service: Service object which is describer by provided data. None if does not exist yet.
+
+ """
+ if not self._service:
+ service: Service = Service(name=self.data["name"])
+ if not service.created():
+ return None
+ self._service = service
+ return self._service
diff --git a/onap_data_provider/resources/tenant_resource.py b/onap_data_provider/resources/tenant_resource.py
new file mode 100644
index 0000000..13d003f
--- /dev/null
+++ b/onap_data_provider/resources/tenant_resource.py
@@ -0,0 +1,85 @@
+"""Tenant resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+import logging
+from typing import Any, Dict, Optional
+
+from onapsdk.aai.cloud_infrastructure import CloudRegion, Tenant # type: ignore
+
+from .resource import Resource
+from onapsdk.exceptions import ResourceNotFound # type: ignore
+
+
+class TenantResource(Resource):
+ """Tenant resource class.
+
+ Creates tenant.
+ """
+
+ def __init__(self, data: Dict[str, Any], cloud_region: CloudRegion) -> None:
+ """Tenant resource initialization.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create tenant
+ cloud_region (CloudRegion): Cloud region for which tenant is going to be created
+
+ """
+ super().__init__(data)
+ self.cloud_region: CloudRegion = cloud_region
+ self._tenant: Optional[Tenant] = None
+
+ def create(self) -> None:
+ """Create tenant resource.
+
+ Add tenant to provided cloud region
+
+ """
+ if not self.exists:
+ self.cloud_region.add_tenant(
+ tenant_id=self.data["tenant-id"],
+ tenant_name=self.data["tenant-name"],
+ tenant_context=self.data.get("tenant-context"),
+ )
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return self.tenant is not None
+
+ @property
+ def tenant(self) -> Tenant:
+ """Tenant property.
+
+ Returns:
+ Tenant: Tenant object
+
+ """
+ if not self._tenant:
+ try:
+ self._tenant = self.cloud_region.get_tenant(self.data["tenant-id"])
+ except ResourceNotFound:
+ logging.error(
+ "Tenant %s does not exist in %s cloud region",
+ self.data["tenant-id"],
+ self.cloud_region.cloud_region_id,
+ )
+ return None
+ return self._tenant
diff --git a/onap_data_provider/resources/vendor_resource.py b/onap_data_provider/resources/vendor_resource.py
new file mode 100644
index 0000000..14f2b18
--- /dev/null
+++ b/onap_data_provider/resources/vendor_resource.py
@@ -0,0 +1,75 @@
+"""Vendor resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import logging
+from typing import Any, Dict
+
+from onapsdk.sdc.vendor import Vendor # type: ignore
+from .resource import Resource
+
+
+class VendorResource(Resource):
+ """Vendor resource class.
+
+ Creates vendor.
+ """
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize vendor resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+ self._vendor: Vendor = None
+
+ def create(self) -> None:
+ """Create vendor resource.
+
+ Create vendor resource.
+
+ """
+ if not self.exists:
+ logging.debug("Create Vendor %s", self.data["name"])
+ self._vendor = Vendor(name=self.data["name"])
+ self._vendor.onboard()
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return self.vendor is not None
+
+ @property
+ def vendor(self) -> Vendor:
+ """Vendor property.
+
+ Vendor which is represented by the data provided by user.
+
+ Returns:
+ Vendor: Vendor object
+
+ """
+ if (vendor := Vendor(name=self.data["name"])).created():
+ self._vendor = vendor
+ return self._vendor
+ return None
diff --git a/onap_data_provider/resources/vnf_resource.py b/onap_data_provider/resources/vnf_resource.py
new file mode 100644
index 0000000..1d47413
--- /dev/null
+++ b/onap_data_provider/resources/vnf_resource.py
@@ -0,0 +1,75 @@
+"""Vnf resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import logging
+from typing import Any, Dict
+
+from onapsdk.sdc.vf import Vf # type: ignore
+from .resource import Resource
+from .xnf_resource import XnfResource
+
+
+class VnfResource(Resource, XnfResource):
+ """Vnf resource class.
+
+ Creates vnf.
+ """
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize vnf resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+
+ def create(self) -> None:
+ """Create vnf resource.
+
+ Create vnf resource and link to specified resources.
+
+ """
+ if not self.exists:
+ logging.debug("Create Vnf %s", self.data["name"])
+ self._xnf = Vf(name=self.data["name"])
+ self.onboard_resource_with_properties(self.data)
+
+ @property
+ def exists(self) -> bool:
+ """Determine if resource already exists or not.
+
+ Returns:
+ bool: True if object exists, False otherwise
+
+ """
+ return self.vnf is not None
+
+ @property
+ def vnf(self) -> Vf:
+ """Vnf property.
+
+ Vnf which is represented by the data provided by user.
+
+ Returns:
+ Vf: Vf object
+
+ """
+ if (vnf := Vf(name=self.data["name"])).created():
+ self._xnf = vnf
+ return self._xnf
+ return None
diff --git a/onap_data_provider/resources/vsp_resource.py b/onap_data_provider/resources/vsp_resource.py
new file mode 100644
index 0000000..17a4d5b
--- /dev/null
+++ b/onap_data_provider/resources/vsp_resource.py
@@ -0,0 +1,71 @@
+"""VSP resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+from typing import Any, Dict, Optional
+from onapsdk.sdc.vendor import Vendor # type: ignore
+from onapsdk.sdc.vsp import Vsp # type: ignore
+
+from .resource import Resource
+
+
+class VspResource(Resource):
+ """VSP resource class."""
+
+ def __init__(self, data: Dict[str, Any]) -> None:
+ """Initialize VSP resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ super().__init__(data)
+ self._vsp: Optional[Vsp] = None
+
+ def create(self) -> None:
+ """Create VSP resource."""
+ if not self.exists:
+ with open(self.data["package"], "rb") as package:
+ self._vsp = Vsp(
+ name=self.data["name"],
+ vendor=Vendor(self.data["vendor"]),
+ package=package,
+ )
+ self._vsp.onboard()
+
+ @property
+ def exists(self) -> bool:
+ """Check if VSP exists.
+
+ Returns:
+ bool: True if VSP exists, False otherwise
+
+ """
+ return self.vsp is not None
+
+ @property
+ def vsp(self) -> Vsp:
+ """VSP property.
+
+ Returns:
+ Vsp: VSP object which is describer by provided data. None if does not exist yet.
+
+ """
+ if not self._vsp:
+ vsp: Vsp = Vsp(name=self.data["name"])
+ if not vsp.created():
+ return None
+ self._vsp = vsp
+ return self._vsp
diff --git a/onap_data_provider/resources/xnf_resource.py b/onap_data_provider/resources/xnf_resource.py
new file mode 100644
index 0000000..cada088
--- /dev/null
+++ b/onap_data_provider/resources/xnf_resource.py
@@ -0,0 +1,60 @@
+"""Xnf resource module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+from abc import ABC
+from typing import Any, Dict
+from onapsdk.sdc.vsp import Vsp # type: ignore
+from onapsdk.sdc.sdc_resource import SdcResource # type: ignore
+from onapsdk.sdc.properties import Property # type: ignore
+
+
+class XnfResource(ABC):
+ """Xnf resource class.
+
+ Network function base class.
+ """
+
+ def __init__(self) -> None:
+ """Initialize xnf resource."""
+ self._xnf: SdcResource = None
+
+ def onboard_resource_with_properties(self, data: Dict[str, Any]) -> None:
+ """Set properties provided and instantiate SDC resource.
+
+ Args:
+ data (Dict[str, Any]): Data needed to create resource.
+
+ """
+ if (vsp_name := data.get("vsp")) is not None:
+ self._xnf.vsp = Vsp(vsp_name)
+ self._xnf.create()
+ if (artifact_data := data.get("deployment_artifact")) is not None:
+ self._xnf.add_deployment_artifact(
+ artifact_type=data["deployment_artifact"]["artifact_type"],
+ artifact_name=data["deployment_artifact"]["artifact_name"],
+ artifact_label=data["deployment_artifact"]["artifact_label"],
+ artifact=data["deployment_artifact"]["artifact_file_name"],
+ )
+ for property_data in data.get("properties", []):
+ self._xnf.add_property(
+ Property(
+ name=property_data["name"],
+ property_type=property_data["type"],
+ value=property_data.get("value"),
+ )
+ )
+ self._xnf.onboard()
diff --git a/onap_data_provider/schemas/infra.schema b/onap_data_provider/schemas/infra.schema
new file mode 100644
index 0000000..61e7bf2
--- /dev/null
+++ b/onap_data_provider/schemas/infra.schema
@@ -0,0 +1,533 @@
+---
+"$schema": http://json-schema.org/draft-04/schema#
+type: object
+properties:
+ aai-services:
+ type: array
+ items:
+ - type: object
+ properties:
+ aai-service:
+ type: object
+ properties:
+ service-id:
+ type: string
+ service-description:
+ type: string
+ required:
+ - service-id
+ - service-description
+ required:
+ - aai-service
+ complexes:
+ type: array
+ items:
+ - type: object
+ properties:
+ complex:
+ type: object
+ properties:
+ physical-location-id:
+ type: string
+ complex-name:
+ type: string
+ data-center-code:
+ type: string
+ identity-url:
+ type: string
+ physical-location-type:
+ type: string
+ street1:
+ type: string
+ street2:
+ type: string
+ city:
+ type: string
+ state:
+ type: string
+ postal-code:
+ type: string
+ country:
+ type: string
+ region:
+ type: string
+ latitude:
+ type: string
+ longitude:
+ type: string
+ elevation:
+ type: string
+ lata:
+ type: string
+ required:
+ - physical-location-id
+ required:
+ - complex
+ cloud-regions:
+ type: array
+ items:
+ - type: object
+ properties:
+ cloud-region:
+ type: object
+ properties:
+ cloud-owner:
+ type: string
+ cloud-region-id:
+ type: string
+ orchestration-disabled:
+ type: boolean
+ in-maint:
+ type: boolean
+ cloud-type:
+ type: string
+ kube-config:
+ type: string
+ tenants:
+ type: array
+ items:
+ - type: object
+ properties:
+ tenant-id:
+ type: string
+ tenant-name:
+ type: string
+ tenant-context:
+ type: string
+ required:
+ - tenant-id
+ - tenant-name
+ esr-system-infos:
+ type: array
+ items:
+ - type: object
+ properties:
+ esr-system-info-id:
+ type: string
+ user-name:
+ type: string
+ password:
+ type: string
+ system-type:
+ type: string
+ service-url:
+ type: string
+ cloud-domain:
+ type: string
+ default-tenant:
+ type: string
+ required:
+ - esr-system-info-id
+ - user-name
+ - password
+ - system-type
+ - service-url
+ - cloud-domain
+ complex:
+ type: object
+ properties:
+ physical-location-id:
+ type: string
+ required:
+ - physical-location-id
+ availability-zones:
+ type: array
+ items:
+ - type: object
+ properties:
+ availability-zone-name:
+ type: string
+ hypervisor-type:
+ type: string
+ required:
+ - availability-zone-name
+ - hypervisor-type
+ required:
+ - cloud-owner
+ - cloud-region-id
+ - orchestration-disabled
+ - in-maint
+ required:
+ - cloud-region
+ customers:
+ type: array
+ items:
+ - type: object
+ properties:
+ customer:
+ type: object
+ properties:
+ global-customer-id:
+ type: string
+ subscriber-name:
+ type: string
+ subscriber-type:
+ type: string
+ service-subscriptions:
+ type: array
+ items:
+ - type: object
+ properties:
+ service-type:
+ type: string
+ tenants:
+ type: array
+ items:
+ - type: object
+ properities:
+ tenant-id:
+ type: string
+ cloud-owner:
+ type: string
+ cloud-region-id:
+ type: string
+ required:
+ - tenant-id
+ - cloud-owner
+ - cloud-region-id
+ required:
+ - service-type
+ required:
+ - global-customer-id
+ - subscriber-name
+ - subscriber-type
+ required:
+ - customer
+ vendors:
+ type: array
+ items:
+ - type: object
+ properties:
+ vendor:
+ type: object
+ properties:
+ name:
+ type: string
+ required:
+ - name
+ required:
+ - vendor
+ vsps:
+ type: array
+ items:
+ - type: object
+ properties:
+ vsp:
+ type: object
+ properties:
+ name:
+ type: string
+ vendor:
+ type: string
+ package:
+ type: string
+ required:
+ - name
+ - vendor
+ - package
+ required:
+ - vsp
+ services:
+ type: array
+ items:
+ - type: object
+ properties:
+ service:
+ type: object
+ properties:
+ name:
+ type: string
+ resources:
+ type: array
+ items:
+ - type: object
+ properties:
+ name:
+ type: string
+ type:
+ type: string
+ required:
+ - name
+ - type
+ properties:
+ type: array
+ items:
+ - type: object
+ properties:
+ name:
+ type: string
+ type:
+ type: string
+ value:
+ type: string
+ required:
+ - name
+ - type
+ required:
+ - name
+ required:
+ - service
+ pnfs:
+ type: array
+ items:
+ - type: object
+ properties:
+ pnf:
+ type: object
+ properties:
+ name:
+ type: string
+ vendor:
+ type: string
+ vsp:
+ type: string
+ deployment_artifact:
+ type: object
+ properties:
+ artifact_type:
+ type: string
+ artifact_name:
+ type: string
+ artifact_label:
+ type: string
+ artifact_file_name:
+ type: string
+ required:
+ - artifact_type
+ - artifact_name
+ - artifact_label
+ - artifact_file_name
+ properties:
+ type: array
+ items:
+ - type: object
+ properties:
+ name:
+ type: string
+ type:
+ type: string
+ value:
+ type: string
+ required:
+ - name
+ - type
+ required:
+ - name
+ required:
+ - pnf
+ vnfs:
+ type: array
+ items:
+ - type: object
+ properties:
+ vnf:
+ type: object
+ properties:
+ name:
+ type: string
+ vsp:
+ type: string
+ deployment_artifact:
+ type: object
+ properties:
+ artifact_type:
+ type: string
+ artifact_name:
+ type: string
+ artifact_label:
+ type: string
+ artifact_file_name:
+ type: string
+ required:
+ - artifact_type
+ - artifact_name
+ - artifact_label
+ - artifact_file_name
+ properties:
+ type: array
+ items:
+ - type: object
+ properties:
+ name:
+ type: string
+ type:
+ type: string
+ value:
+ type: string
+ required:
+ - name
+ - type
+ required:
+ - name
+ required:
+ - vnf
+ service-instances:
+ type: array
+ items:
+ - type: object
+ properties:
+ service-instance:
+ type: object
+ properties:
+ service_instance_name:
+ type: string
+ service_name:
+ type: string
+ cloud_region:
+ type: string
+ customer_id:
+ type: string
+ owning_entity:
+ type: string
+ project:
+ type: string
+ platform:
+ type: string
+ line_of_business:
+ type: string
+ cloud_region_id:
+ type: string
+ cloud_owner:
+ type: string
+ timeout:
+ type: number
+ minimum: 1
+ maximum: 99999
+ aai_service:
+ type: string
+ service_subscription_type:
+ type: string
+ instantiation_parameters:
+ type: array
+ items:
+ - type: object
+ properties:
+ vnf_name:
+ type: string
+ sec_group:
+ type: string
+ public_net_id:
+ type: string
+ onap_private_net_id:
+ type: string
+ onap_private_subnet_id:
+ type: string
+ image_name:
+ type: string
+ flavor_name:
+ type: string
+ install_script_version:
+ type: string
+ demo_artifacts_version:
+ type: string
+ cloud_env:
+ type: string
+ aic-cloud-region:
+ type: string
+ pub_key:
+ type: string
+ required:
+ - service_instance_name
+ - service_name
+ - cloud_region
+ - customer_id
+ - owning_entity
+ - project
+ - platform
+ - line_of_business
+ - cloud_region_id
+ - cloud_owner
+ - instantiation_parameters
+ owning-entities:
+ type: array
+ items:
+ - type: object
+ properities:
+ owning-entity:
+ type: object
+ properties:
+ name:
+ type: string
+ required:
+ - name
+ required:
+ - owning-entity
+ projects:
+ type: array
+ items:
+ - type: object
+ properties:
+ project:
+ type: object
+ properities:
+ name:
+ type: string
+ required:
+ - name
+ required:
+ - project
+ platforms:
+ type: array
+ items:
+ - type: object
+ properities:
+ platform:
+ type: object
+ properities:
+ name:
+ type: string
+ required:
+ - name
+ required:
+ - platform
+ lines-of-business:
+ type: array
+ items:
+ - type: object
+ properties:
+ line-of-business:
+ type: object
+ properities:
+ name:
+ type: string
+ required:
+ - name
+ required:
+ - line-of-business
+ msb-k8s-definitions:
+ type: array
+ items:
+ type: object
+ properties:
+ name:
+ type: string
+ version:
+ type: string
+ chart-name:
+ type: string
+ description:
+ type: string
+ artifact:
+ type: string
+ profiles:
+ type: array
+ items:
+ - type: object
+ properties:
+ name:
+ type: string
+ namespace:
+ type: string
+ k8s-version:
+ type: string
+ artifact:
+ type: string
+ required:
+ - name
+ - namespace
+ - k8s-version
+ - artifact
+ required:
+ - name
+ - version
+ - artifact
diff --git a/onap_data_provider/schemas/infra_1_1.schema b/onap_data_provider/schemas/infra_1_1.schema
new file mode 100644
index 0000000..9cb1f09
--- /dev/null
+++ b/onap_data_provider/schemas/infra_1_1.schema
@@ -0,0 +1,533 @@
+---
+"$schema": http://json-schema.org/draft-04/schema#
+type: object
+properties:
+ aai-services:
+ type: array
+ items:
+ - type: object
+ properties:
+ aai-service:
+ type: object
+ properties:
+ service-id:
+ type: string
+ service-description:
+ type: string
+ required:
+ - service-id
+ - service-description
+ required:
+ - aai-service
+ complexes:
+ type: array
+ items:
+ - type: object
+ properties:
+ complex:
+ type: object
+ properties:
+ physical-location-id:
+ type: string
+ complex-name:
+ type: string
+ data-center-code:
+ type: string
+ identity-url:
+ type: string
+ physical-location-type:
+ type: string
+ street1:
+ type: string
+ street2:
+ type: string
+ city:
+ type: string
+ state:
+ type: string
+ postal-code:
+ type: string
+ country:
+ type: string
+ region:
+ type: string
+ latitude:
+ type: string
+ longitude:
+ type: string
+ elevation:
+ type: string
+ lata:
+ type: string
+ required:
+ - physical-location-id
+ required:
+ - complex
+ cloud-regions:
+ type: array
+ items:
+ - type: object
+ properties:
+ cloud-region:
+ type: object
+ properties:
+ cloud-owner:
+ type: string
+ cloud-region-id:
+ type: string
+ orchestration-disabled:
+ type: boolean
+ in-maint:
+ type: boolean
+ cloud-type:
+ type: string
+ kube-config:
+ type: string
+ tenants:
+ type: array
+ items:
+ - type: object
+ properties:
+ tenant-id:
+ type: string
+ tenant-name:
+ type: string
+ tenant-context:
+ type: string
+ required:
+ - tenant-id
+ - tenant-name
+ esr-system-infos:
+ type: array
+ items:
+ - type: object
+ properties:
+ esr-system-info-id:
+ type: string
+ user-name:
+ type: string
+ password:
+ type: string
+ system-type:
+ type: string
+ service-url:
+ type: string
+ cloud-domain:
+ type: string
+ default-tenant:
+ type: string
+ required:
+ - esr-system-info-id
+ - user-name
+ - password
+ - system-type
+ - service-url
+ - cloud-domain
+ complex:
+ type: object
+ properties:
+ physical-location-id:
+ type: string
+ required:
+ - physical-location-id
+ availability-zones:
+ type: array
+ items:
+ - type: object
+ properties:
+ availability-zone-name:
+ type: string
+ hypervisor-type:
+ type: string
+ required:
+ - availability-zone-name
+ - hypervisor-type
+ required:
+ - cloud-owner
+ - cloud-region-id
+ - orchestration-disabled
+ - in-maint
+ required:
+ - cloud-region
+ customers:
+ type: array
+ items:
+ - type: object
+ properties:
+ customer:
+ type: object
+ properties:
+ global-customer-id:
+ type: string
+ subscriber-name:
+ type: string
+ subscriber-type:
+ type: string
+ service-subscriptions:
+ type: array
+ items:
+ - type: object
+ properties:
+ service-type:
+ type: string
+ tenants:
+ type: array
+ items:
+ - type: object
+ properities:
+ tenant-id:
+ type: string
+ cloud-owner:
+ type: string
+ cloud-region-id:
+ type: string
+ required:
+ - tenant-id
+ - cloud-owner
+ - cloud-region-id
+ required:
+ - service-type
+ required:
+ - global-customer-id
+ - subscriber-name
+ - subscriber-type
+ required:
+ - customer
+ vendors:
+ type: array
+ items:
+ - type: object
+ properties:
+ vendor:
+ type: object
+ properties:
+ name:
+ type: string
+ required:
+ - name
+ required:
+ - vendor
+ vsps:
+ type: array
+ items:
+ - type: object
+ properties:
+ vsp:
+ type: object
+ properties:
+ name:
+ type: string
+ vendor:
+ type: string
+ package:
+ type: string
+ required:
+ - name
+ - vendor
+ - package
+ required:
+ - vsp
+ services:
+ type: array
+ items:
+ - type: object
+ properties:
+ service:
+ type: object
+ properties:
+ name:
+ type: string
+ resources:
+ type: array
+ items:
+ - type: object
+ properties:
+ name:
+ type: string
+ type:
+ type: string
+ required:
+ - name
+ - type
+ properties:
+ type: array
+ items:
+ - type: object
+ properties:
+ name:
+ type: string
+ type:
+ type: string
+ value:
+ type: string
+ required:
+ - name
+ - type
+ required:
+ - name
+ required:
+ - service
+ pnfs:
+ type: array
+ items:
+ - type: object
+ properties:
+ pnf:
+ type: object
+ properties:
+ name:
+ type: string
+ vendor:
+ type: string
+ vsp:
+ type: string
+ deployment_artifact:
+ type: object
+ properties:
+ artifact_type:
+ type: string
+ artifact_name:
+ type: string
+ artifact_label:
+ type: string
+ artifact_file_name:
+ type: string
+ required:
+ - artifact_type
+ - artifact_name
+ - artifact_label
+ - artifact_file_name
+ properties:
+ type: array
+ items:
+ - type: object
+ properties:
+ name:
+ type: string
+ type:
+ type: string
+ value:
+ type: string
+ required:
+ - name
+ - type
+ required:
+ - name
+ required:
+ - pnf
+ vnfs:
+ type: array
+ items:
+ - type: object
+ properties:
+ vnf:
+ type: object
+ properties:
+ name:
+ type: string
+ vsp:
+ type: string
+ deployment_artifact:
+ type: object
+ properties:
+ artifact_type:
+ type: string
+ artifact_name:
+ type: string
+ artifact_label:
+ type: string
+ artifact_file_name:
+ type: string
+ required:
+ - artifact_type
+ - artifact_name
+ - artifact_label
+ - artifact_file_name
+ properties:
+ type: array
+ items:
+ - type: object
+ properties:
+ name:
+ type: string
+ type:
+ type: string
+ value:
+ type: string
+ required:
+ - name
+ - type
+ required:
+ - name
+ required:
+ - vnf
+ service-instances:
+ type: array
+ items:
+ - type: object
+ properties:
+ service-instance:
+ type: object
+ properties:
+ service_instance_name:
+ type: string
+ service_name:
+ type: string
+ cloud_region:
+ type: string
+ customer_id:
+ type: string
+ owning_entity:
+ type: string
+ project:
+ type: string
+ platform:
+ type: string
+ line_of_business:
+ type: string
+ cloud_region_id:
+ type: string
+ cloud_owner:
+ type: string
+ timeout:
+ type: number
+ minimum: 1
+ maximum: 99999
+ aai_service:
+ type: string
+ service_subscription_type:
+ type: string
+ instantiation_parameters:
+ type: array
+ items:
+ - type: object
+ properties:
+ vnf_name:
+ type: string
+ sec_group:
+ type: string
+ public_net_id:
+ type: string
+ onap_private_net_id:
+ type: string
+ onap_private_subnet_id:
+ type: string
+ image_name:
+ type: string
+ flavor_name:
+ type: string
+ install_script_version:
+ type: string
+ demo_artifacts_version:
+ type: string
+ cloud_env:
+ type: string
+ aic-cloud-region:
+ type: string
+ pub_key:
+ type: string
+ required:
+ - service_instance_name
+ - service_name
+ - cloud_region
+ - customer_id
+ - owning_entity
+ - project
+ - platform
+ - line_of_business
+ - cloud_region_id
+ - cloud_owner
+ - aai_service
+ owning-entities:
+ type: array
+ items:
+ - type: object
+ properities:
+ owning-entity:
+ type: object
+ properties:
+ name:
+ type: string
+ required:
+ - name
+ required:
+ - owning-entity
+ projects:
+ type: array
+ items:
+ - type: object
+ properties:
+ project:
+ type: object
+ properities:
+ name:
+ type: string
+ required:
+ - name
+ required:
+ - project
+ platforms:
+ type: array
+ items:
+ - type: object
+ properities:
+ platform:
+ type: object
+ properities:
+ name:
+ type: string
+ required:
+ - name
+ required:
+ - platform
+ lines-of-business:
+ type: array
+ items:
+ - type: object
+ properties:
+ line-of-business:
+ type: object
+ properities:
+ name:
+ type: string
+ required:
+ - name
+ required:
+ - line-of-business
+ msb-k8s-definitions:
+ type: array
+ items:
+ type: object
+ properties:
+ name:
+ type: string
+ version:
+ type: string
+ chart-name:
+ type: string
+ description:
+ type: string
+ artifact:
+ type: string
+ profiles:
+ type: array
+ items:
+ - type: object
+ properties:
+ name:
+ type: string
+ namespace:
+ type: string
+ k8s-version:
+ type: string
+ artifact:
+ type: string
+ required:
+ - name
+ - namespace
+ - k8s-version
+ - artifact
+ required:
+ - name
+ - version
+ - artifact
diff --git a/onap_data_provider/tag_handlers.py b/onap_data_provider/tag_handlers.py
new file mode 100644
index 0000000..8f29d0d
--- /dev/null
+++ b/onap_data_provider/tag_handlers.py
@@ -0,0 +1,52 @@
+"""Custom yaml tag handlers module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+import yaml
+import uuid
+
+
+def join(loader: yaml.SafeLoader, node: yaml.Node) -> str:
+ """Concatinates the nodes fields for !join tag.
+
+ Concatinates multiple strings in yaml value f.e. !join [a, b, c] results in 'abc'.
+ join supports separator syntax f.e. !join ['_', [a, b, c]] results in 'a_b_c'.
+
+ Args:
+ node (yaml.Node): the yaml node
+
+ Returns:
+ str: the joined string of node
+
+ """
+ seq = loader.construct_sequence(node, deep=True) # type: ignore
+ if len(seq) == 2 and isinstance(seq[0], str) and isinstance(seq[1], list):
+ sep = seq[0]
+ return sep.join([str(i) for i in seq[1]])
+ else:
+ return "".join([str(i) for i in seq])
+
+
+def generate_random_uuid(*_) -> str:
+ """Random UUID generator.
+
+ Args:
+ loader (yaml.SafeLoader): SafeLoader object
+ node (yaml.Node): Node object
+
+ Returns:
+ str: randomly generated UUID
+ """
+ return str(uuid.uuid4())
diff --git a/onap_data_provider/validator.py b/onap_data_provider/validator.py
new file mode 100644
index 0000000..3589f85
--- /dev/null
+++ b/onap_data_provider/validator.py
@@ -0,0 +1,52 @@
+"""Infra file schema validatior module."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+from typing import Any, Dict
+
+import yaml
+from jsonschema import validate # type: ignore
+
+from .versions import VersionsEnum
+
+
+class Validator:
+ """Validate input schema class."""
+
+ def __init__(self) -> None:
+ """Validate class initialization.
+
+ Load schema file.
+
+ """
+ self.schemas: Dict[str, Any] = {}
+
+ def validate(self, version: VersionsEnum, input_data: Dict[str, Any]) -> None:
+ """Check if given input is valid from schema perspective.
+
+ Args:
+ input_data (Dict[str, Any]): Input to check
+
+ Raises:
+ ValidationError: Raises if input is invalid
+
+ """
+ if not version.value.version_number in self.schemas:
+ with open(version.value.schema_path, "r") as schema_file:
+ self.schemas[version.value.version_number] = yaml.safe_load(
+ schema_file.read()
+ )
+ validate(input_data, schema=self.schemas[version.value.version_number])
diff --git a/onap_data_provider/versions.py b/onap_data_provider/versions.py
new file mode 100644
index 0000000..7651bec
--- /dev/null
+++ b/onap_data_provider/versions.py
@@ -0,0 +1,68 @@
+"""Versions class."""
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+import logging
+from collections import namedtuple
+from enum import Enum
+from pathlib import Path
+
+
+Version = namedtuple("Version", ["version_number", "schema_path", "deprecated"])
+
+
+class VersionsEnum(Enum):
+ """Class for storing information about supported versions."""
+
+ V1_1 = Version(
+ version_number="1.1",
+ schema_path=Path(Path(__file__).parent, "schemas/infra_1_1.schema"),
+ deprecated=False,
+ )
+ V1_0 = Version(
+ version_number="1.0",
+ schema_path=Path(Path(__file__).parent, "schemas/infra.schema"),
+ deprecated=False,
+ )
+ NONE = Version(
+ version_number="None",
+ schema_path=Path(Path(__file__).parent, "schemas/infra.schema"),
+ deprecated=True,
+ )
+
+ @classmethod
+ def get_version_by_number(cls, version_number: str) -> "VersionsEnum":
+ """Get an enum element based on the given string version value.
+
+ Because the version enum elements are not simple objects,
+ but also have information about the path to the supported schema and
+ whether this version is deprecated this method allows to retrieve
+ the version only based on its value stored in the string format.
+
+ Raises:
+ ValueError: Provided version number is not supported
+
+ Returns:
+ VersionsEnum: The version enum
+
+ """
+ for version in cls:
+ if version.value.version_number == version_number:
+ if version.value.deprecated:
+ logging.warning(
+ f"This version [{version.value.version_number}] is deprecated, consider using the newer one!"
+ )
+ return version
+ raise ValueError(f"Version number {version_number} not supported")
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..3bdc671
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+onapsdk==8.2.0
+PyYAML~=5.4.1
+jsonschema==3.2.0
diff --git a/samples/BASIC_VM_enriched.zip b/samples/BASIC_VM_enriched.zip
new file mode 100644
index 0000000..28c14bb
--- /dev/null
+++ b/samples/BASIC_VM_enriched.zip
Binary files differ
diff --git a/samples/README.md b/samples/README.md
new file mode 100644
index 0000000..bf62051
--- /dev/null
+++ b/samples/README.md
@@ -0,0 +1,29 @@
+# Data provider infra file samples
+
+## vendor.yaml
+
+Creates vendor
+
+## vsp.yaml
+
+Creates vendor and two vsps. Shows YAML anchor usage example.
+
+## xnfs.yaml
+
+Creates PNFs and VNFs
+
+## service.yaml
+
+Creates and distribute SDC service
+
+## complex.yaml
+
+Creates Complex
+
+## cloud-region.yaml
+
+Creates cloud region and register it in multicloud (all data \[like tenants] from OpenStack are going to be created by ONAP)
+
+## customer.yaml
+
+Creates customer with subscribed service
diff --git a/samples/aai_business.yaml b/samples/aai_business.yaml
new file mode 100644
index 0000000..65a70bd
--- /dev/null
+++ b/samples/aai_business.yaml
@@ -0,0 +1,17 @@
+# A&AI business sample
+# Creates one owning entity, project, platform and line of business
+#
+odpSchemaVersion: 1.0
+resources:
+ owning_entities:
+ - owning-entity:
+ name: oran_owner
+ projects:
+ - project:
+ name: oran_town
+ platforms:
+ - platform:
+ name: oran_platform
+ lines-of-business:
+ - line-of-business:
+ name: oran_lob
diff --git a/samples/aai_service.yaml b/samples/aai_service.yaml
new file mode 100644
index 0000000..1200d38
--- /dev/null
+++ b/samples/aai_service.yaml
@@ -0,0 +1,9 @@
+# A&AI service sample
+# Creates one A&AI service model resource
+#
+odpSchemaVersion: 1.0
+resources:
+ aai-services:
+ - aai-service:
+ service-id: test_aai_service
+ service-description: test_aai_service
diff --git a/samples/cloud-region.yaml b/samples/cloud-region.yaml
new file mode 100644
index 0000000..86c7273
--- /dev/null
+++ b/samples/cloud-region.yaml
@@ -0,0 +1,27 @@
+# Cloud region sample
+# Cloud region resource is one of the biggest to describe (if you want to configure it with OpenStack)
+# Please fill the data based on your OpenStack instance RC v3 file (ask OpenStack admin).
+#
+odpSchemaVersion: 1.0
+resources:
+ cloud-regions:
+ - cloud-region:
+ cloud-owner: sample-cloud-owner
+ cloud-region-id: RegionOne
+ orchestration-disabled: false
+ in-maint: false
+ complex:
+ physical-location-id: sample-complex # Make sure it exists!
+ register-to-multicloud: true
+ availability-zones:
+ - cloud-owner: sample-cloud-owner
+ availability-zone-name: sample-availbility-zone
+ hypervisor-type: nova
+ esr-system-infos: # Take these information from openstack config file
+ - esr-system-info-id: 5433b0ac-594d-41f7-911d-dfe413e1cb2c # Has to be unique
+ user-name: username
+ password: password
+ system-type: VIM
+ service-url: http://127.0.0.1:5000/v3
+ cloud-domain: Default
+ default-tenant: default-tenant
diff --git a/samples/complex.yaml b/samples/complex.yaml
new file mode 100644
index 0000000..8b989c7
--- /dev/null
+++ b/samples/complex.yaml
@@ -0,0 +1,8 @@
+# Complex sample
+# Creates one complex with `sample-complex` physical location id
+#
+odpSchemaVersion: 1.0
+resources:
+ complexes:
+ - complex:
+ physical-location-id: sample-complex
diff --git a/samples/customer.yaml b/samples/customer.yaml
new file mode 100644
index 0000000..e8fdbec
--- /dev/null
+++ b/samples/customer.yaml
@@ -0,0 +1,14 @@
+# Customer sample
+# Creates a customer with one service subscribed - sample-service. It's not required to create customer
+# with service subscription, but it's useful if you want to use that customer later for service
+# instantiation.
+# Service type has to be the name of the SDC service!
+odpSchemaVersion: 1.0
+resources:
+ customers:
+ - customer:
+ global-customer-id: sample-customer
+ subscriber-name: sample-customer
+ subscriber-type: Customer
+ service-subscriptions:
+ - service-type: sample-service # Make sure it exists!
diff --git a/samples/msb_k8s.yaml b/samples/msb_k8s.yaml
new file mode 100644
index 0000000..a6efaf3
--- /dev/null
+++ b/samples/msb_k8s.yaml
@@ -0,0 +1,11 @@
+odpSchemaVersion: 1.0
+resources:
+ msb-k8s-definitions:
+ - name: test
+ version: test
+ artifact: definition.tar.gz
+ profiles:
+ - name: test
+ namespace: test
+ k8s-version: "1.19"
+ artifact: profile.tar.gz
diff --git a/samples/service.yaml b/samples/service.yaml
new file mode 100644
index 0000000..36c92d7
--- /dev/null
+++ b/samples/service.yaml
@@ -0,0 +1,52 @@
+# Service sample
+# Using that file you will create SDC Services both with custom properties and not.
+# * sample-service-with-vf is a simple service with VF resource
+# * sample-service-with-vf-and-properties is a service with VF resource and
+# it's properties - it's ready to create service instance using Macro flow
+# * sample-service-with-pnf is a simple service with PNF resource
+# * sample-service-with-pnf-and-properties is a service with PNF resource and
+# it's properties - it's ready to create service instance using Macro flow
+# * sample-service-with-vl is a simple service with VL resource
+# Make sure that resources you want to use are already created. If not - use `xnfs.yaml`
+# sample file and create needed xNFs.
+#
+odpSchemaVersion: 1.0
+resources:
+ services:
+ - service:
+ name: sample-service-with-vf
+ resources:
+ - name: sample-vnf # Make sure it exists!
+ type: VF
+ - service:
+ name: sample-service-with-vf-and-properties
+ resources:
+ - name: sample-vnf # Make sure it exists!
+ type: VF
+ properties:
+ controller_actor: "CDS"
+ skip_post_instantiation_configuration: False
+ sdnc_artifact_name: "vnf"
+ sdnc_model_version: "1.0.0"
+ sdnc_model_name: "ubuntu20"
+ - service:
+ name: sample-service-with-pnf
+ resources:
+ - name: sample-pnf # Make sure it exists!
+ type: PNF
+ - service:
+ name: sample-service-with-pnf-and-properties
+ resources:
+ - name: sample-pnf # Make sure it exists!
+ type: PNF
+ properties:
+ controller_actor: "CDS"
+ skip_post_instantiation_configuration: False
+ sdnc_artifact_name: "vnf"
+ sdnc_model_version: "1.0.0"
+ sdnc_model_name: "ubuntu20"
+ - service:
+ name: sample-service-with-vl
+ resources:
+ - name: sample-vl # Make sure it exists!
+ type: VL
diff --git a/samples/ubuntu.zip b/samples/ubuntu.zip
new file mode 100644
index 0000000..2dc60aa
--- /dev/null
+++ b/samples/ubuntu.zip
Binary files differ
diff --git a/samples/vendor.yaml b/samples/vendor.yaml
new file mode 100644
index 0000000..abffca8
--- /dev/null
+++ b/samples/vendor.yaml
@@ -0,0 +1,8 @@
+# Vendor sample
+# Using that file you will create one SDC Vendor resource with "sample-vendor" name
+#
+odpSchemaVersion: 1.0
+resources:
+ vendors:
+ - vendor:
+ name: sample-vendor
diff --git a/samples/vsp.yaml b/samples/vsp.yaml
new file mode 100644
index 0000000..6336f44
--- /dev/null
+++ b/samples/vsp.yaml
@@ -0,0 +1,25 @@
+# VSP sample
+# Using that file you will create:
+# - one SDC Vendor resource with "sample-vendor" name
+# - two SDC VSPs:
+# * one with "sample-vsp-anchor" name, where we use YAML's anchor to share vendor name between two resource,
+# * onw with "sample-vsp-no-anchor" name where we just simply copy&paste the name of the vendor we want to use.
+# It will also use "ubuntu.zip" package as an VSP artifact.
+# Remember: package value is a path, it has to point to the file from the `onap-data-provider` runner perspective.
+# I recommend to use absulute path to be sure there will be no errors.
+#
+odpSchemaVersion: 1.0
+resources:
+ vendors:
+ - vendor:
+ name: &vendor sample-vendor
+
+ vsps:
+ - vsp:
+ name: sample-vsp-anchor
+ vendor: *vendor
+ package: ubuntu.zip
+ - vsp:
+ name: sample-vsp-no-anchor
+ vendor: sample-vendor
+ package: ubuntu.zip
diff --git a/samples/xnfs.yaml b/samples/xnfs.yaml
new file mode 100644
index 0000000..35f3024
--- /dev/null
+++ b/samples/xnfs.yaml
@@ -0,0 +1,35 @@
+# VNFs and PNFs sample.
+# In that sample we don't create any additional resources - just xNFs. Make
+# sure that VSP you want to use already exists or use sample from `vsp.yaml`
+# file and create it.
+# It will also use "BASIC_VM_enriched.zip" package as an xNF artifact.
+# Remember: package value is a path, it has to point to the file from the `onap-data-provider` runner perspective.
+# I recommend to use absulute path to be sure there will be no errors.
+
+odpSchemaVersion: 1.0
+resources:
+ vnfs:
+ - vnf:
+ name: sample-vnf-without-artifact
+ vsp: sample-vsp # Make sure it exists!
+ - vnf:
+ name: sample-vnf-with-artifact
+ vsp: sample-vsp # Make sure it exists!
+ deployment_artifact:
+ artifact_type: CONTROLLER_BLUEPRINT_ARCHIVE
+ artifact_name: BASIC_VM_enriched.zip
+ artifact_label: vfwcds
+ artifact_file_name: BASIC_VM_enriched.zip
+
+ pnfs:
+ - pnf:
+ name: sample-pnf-without-artifact
+ vsp: sample-vsp # Make sure it exists!
+ - pnf:
+ name: sample-pnf-with-artifact
+ vsp: sample-vsp # Make sure it exists!
+ deployment_artifact:
+ artifact_type: CONTROLLER_BLUEPRINT_ARCHIVE
+ artifact_name: BASIC_VM_enriched.zip
+ artifact_label: vfwcds
+ artifact_file_name: BASIC_VM_enriched.zip
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..3345e64
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,45 @@
+"""
+ Copyright 2021 Deutsche Telekom AG
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import setuptools
+
+with open("README.md", "r", encoding="utf-8") as readme:
+ long_description = readme.read()
+
+setuptools.setup(
+ name="onap_data_provider",
+ version="0.4.1",
+ author="Michal Jagiello <michal.jagiello@t-mobile.pl>, Piotr Stanior <piotr.stanior@t-mobile.pl>",
+ description="Tool to provide data for ONAP instances",
+ long_description=long_description,
+ long_description_content_type="text/markdown",
+ keywords="ONAP",
+ packages=setuptools.find_packages(),
+ package_data={"onap_data_provider": ["schemas/*"]},
+ python_requires=">=3.8",
+ entry_points={
+ "console_scripts": [
+ "onap-data-provider=onap_data_provider.data_provider:run",
+ ]
+ },
+ install_requires=["onapsdk==8.2.0", "PyYAML~=5.4.1", "jsonschema==3.2.0"],
+ classifiers=[
+ "Development Status :: 5 - Production/Stable",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ ],
+)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/config_dirs/not_yaml b/tests/config_dirs/not_yaml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/config_dirs/not_yaml
diff --git a/tests/config_dirs/test-data-version.yml b/tests/config_dirs/test-data-version.yml
new file mode 100644
index 0000000..51e92c9
--- /dev/null
+++ b/tests/config_dirs/test-data-version.yml
@@ -0,0 +1,48 @@
+odpSchemaVersion: 1.0
+resources:
+ complexes:
+ - complex:
+ data-center-code: AMICPL1
+ complex-name: AMIST-COMPLEX-1
+ physical-location-id: &complex_id AMIST-COMPLEX-1
+ physical-location-type: Office
+ street1: '505'
+ street2: Terry Fox Drive
+ city: Kanata
+ state: Ontario
+ postal-code: A1A1A1
+ region: Eastern
+ country: Canada
+
+ cloud-regions:
+ - cloud-region:
+ cloud-owner: &clown AMIST
+ cloud-region-id: AMCR1
+ cloud-region-version: '11.0'
+ orchestration-disabled: true
+ in-maint: false
+ complex:
+ physical-location-id: *complex_id
+ tenants:
+ - tenant-id: !join ['-', [*clown, 'TENANT', 1]]
+ tenant-name: AMIST-TENANT-1-NAME
+ - tenant-id: !join [*clown, '-', 'TENANT', '-', 2]
+ tenant-name: AMIST-TENANT-2-NAME
+ availability-zones:
+ - cloud-owner: *clown
+ availability-zone-name: AMIST-AZ-1
+ hypervisor-type: OpenStackAmd
+
+ customers:
+ - customer:
+ global-customer-id: AMIST-CUST-11
+ subscriber-name: AAIIST-TESTER-11
+ subscriber-type: Customer
+ service-subscriptions:
+ - service-type: amist-voip
+ - customer:
+ global-customer-id: AMIST-CUST-12
+ subscriber-name: AAIIST-TESTER-12
+ subscriber-type: Customer
+ service-subscriptions:
+ - service-type: amist-voip
diff --git a/tests/config_dirs/test-data.yml b/tests/config_dirs/test-data.yml
new file mode 100644
index 0000000..281e754
--- /dev/null
+++ b/tests/config_dirs/test-data.yml
@@ -0,0 +1,47 @@
+---
+complexes:
+- complex:
+ data-center-code: AMICPL1
+ complex-name: AMIST-COMPLEX-1
+ physical-location-id: &complex_id AMIST-COMPLEX-1
+ physical-location-type: Office
+ street1: '505'
+ street2: Terry Fox Drive
+ city: Kanata
+ state: Ontario
+ postal-code: A1A1A1
+ region: Eastern
+ country: Canada
+
+cloud-regions:
+- cloud-region:
+ cloud-owner: &clown AMIST
+ cloud-region-id: AMCR1
+ cloud-region-version: '11.0'
+ orchestration-disabled: true
+ in-maint: false
+ complex:
+ physical-location-id: *complex_id
+ tenants:
+ - tenant-id: !join ['-', [*clown, 'TENANT', 1]]
+ tenant-name: AMIST-TENANT-1-NAME
+ - tenant-id: !join [*clown, '-', 'TENANT', '-', 2]
+ tenant-name: AMIST-TENANT-2-NAME
+ availability-zones:
+ - cloud-owner: *clown
+ availability-zone-name: AMIST-AZ-1
+ hypervisor-type: OpenStackAmd
+
+customers:
+- customer:
+ global-customer-id: AMIST-CUST-11
+ subscriber-name: AAIIST-TESTER-11
+ subscriber-type: Customer
+ service-subscriptions:
+ - service-type: amist-voip
+- customer:
+ global-customer-id: AMIST-CUST-12
+ subscriber-name: AAIIST-TESTER-12
+ subscriber-type: Customer
+ service-subscriptions:
+ - service-type: amist-voip
diff --git a/tests/test-data-version.yml b/tests/test-data-version.yml
new file mode 100644
index 0000000..51e92c9
--- /dev/null
+++ b/tests/test-data-version.yml
@@ -0,0 +1,48 @@
+odpSchemaVersion: 1.0
+resources:
+ complexes:
+ - complex:
+ data-center-code: AMICPL1
+ complex-name: AMIST-COMPLEX-1
+ physical-location-id: &complex_id AMIST-COMPLEX-1
+ physical-location-type: Office
+ street1: '505'
+ street2: Terry Fox Drive
+ city: Kanata
+ state: Ontario
+ postal-code: A1A1A1
+ region: Eastern
+ country: Canada
+
+ cloud-regions:
+ - cloud-region:
+ cloud-owner: &clown AMIST
+ cloud-region-id: AMCR1
+ cloud-region-version: '11.0'
+ orchestration-disabled: true
+ in-maint: false
+ complex:
+ physical-location-id: *complex_id
+ tenants:
+ - tenant-id: !join ['-', [*clown, 'TENANT', 1]]
+ tenant-name: AMIST-TENANT-1-NAME
+ - tenant-id: !join [*clown, '-', 'TENANT', '-', 2]
+ tenant-name: AMIST-TENANT-2-NAME
+ availability-zones:
+ - cloud-owner: *clown
+ availability-zone-name: AMIST-AZ-1
+ hypervisor-type: OpenStackAmd
+
+ customers:
+ - customer:
+ global-customer-id: AMIST-CUST-11
+ subscriber-name: AAIIST-TESTER-11
+ subscriber-type: Customer
+ service-subscriptions:
+ - service-type: amist-voip
+ - customer:
+ global-customer-id: AMIST-CUST-12
+ subscriber-name: AAIIST-TESTER-12
+ subscriber-type: Customer
+ service-subscriptions:
+ - service-type: amist-voip
diff --git a/tests/test-data.yml b/tests/test-data.yml
new file mode 100644
index 0000000..281e754
--- /dev/null
+++ b/tests/test-data.yml
@@ -0,0 +1,47 @@
+---
+complexes:
+- complex:
+ data-center-code: AMICPL1
+ complex-name: AMIST-COMPLEX-1
+ physical-location-id: &complex_id AMIST-COMPLEX-1
+ physical-location-type: Office
+ street1: '505'
+ street2: Terry Fox Drive
+ city: Kanata
+ state: Ontario
+ postal-code: A1A1A1
+ region: Eastern
+ country: Canada
+
+cloud-regions:
+- cloud-region:
+ cloud-owner: &clown AMIST
+ cloud-region-id: AMCR1
+ cloud-region-version: '11.0'
+ orchestration-disabled: true
+ in-maint: false
+ complex:
+ physical-location-id: *complex_id
+ tenants:
+ - tenant-id: !join ['-', [*clown, 'TENANT', 1]]
+ tenant-name: AMIST-TENANT-1-NAME
+ - tenant-id: !join [*clown, '-', 'TENANT', '-', 2]
+ tenant-name: AMIST-TENANT-2-NAME
+ availability-zones:
+ - cloud-owner: *clown
+ availability-zone-name: AMIST-AZ-1
+ hypervisor-type: OpenStackAmd
+
+customers:
+- customer:
+ global-customer-id: AMIST-CUST-11
+ subscriber-name: AAIIST-TESTER-11
+ subscriber-type: Customer
+ service-subscriptions:
+ - service-type: amist-voip
+- customer:
+ global-customer-id: AMIST-CUST-12
+ subscriber-name: AAIIST-TESTER-12
+ subscriber-type: Customer
+ service-subscriptions:
+ - service-type: amist-voip
diff --git a/tests/test-kube-config b/tests/test-kube-config
new file mode 100644
index 0000000..3c546eb
--- /dev/null
+++ b/tests/test-kube-config
@@ -0,0 +1 @@
+dummy file \ No newline at end of file
diff --git a/tests/test_aai_service_resource.py b/tests/test_aai_service_resource.py
new file mode 100644
index 0000000..9056299
--- /dev/null
+++ b/tests/test_aai_service_resource.py
@@ -0,0 +1,51 @@
+from unittest.mock import patch, PropertyMock
+
+from onap_data_provider.resources.aai_service_resource import AaiService, AaiServiceResource, ResourceNotFound
+
+
+AAI_SERVICE_DATA = {
+ "service-id": "123",
+ "service-description": "123"
+}
+
+
+@patch("onap_data_provider.resources.aai_service_resource.AaiService.get_all")
+def test_aai_service_resource_aai_resource(mock_aai_service_get_all):
+ mock_aai_service_get_all.side_effect = ResourceNotFound
+ mock_aai_service_get_all.return_value = iter([])
+ aai_service_resource = AaiServiceResource(AAI_SERVICE_DATA)
+ assert aai_service_resource.aai_service is None
+ mock_aai_service_get_all.side_effect = None
+ mock_aai_service_get_all.return_value = iter([AaiService(service_id="123", service_description="123", resource_version="123")])
+ assert aai_service_resource.aai_service is not None
+
+
+@patch(
+ "onap_data_provider.resources.aai_service_resource.AaiServiceResource.aai_service",
+ new_callable=PropertyMock,
+)
+def test_aai_service_resource_exists(mock_aai_service):
+ mock_aai_service.return_value = None
+ aai_service_resource = AaiServiceResource(AAI_SERVICE_DATA)
+ assert aai_service_resource.exists is False
+ mock_aai_service.return_value = 1 # Anything but not None
+ assert aai_service_resource.exists is True
+
+
+@patch(
+ "onap_data_provider.resources.aai_service_resource.AaiServiceResource.exists",
+ new_callable=PropertyMock,
+)
+@patch("onap_data_provider.resources.aai_service_resource.AaiService.create")
+def test_aai_service_resource_create(mock_aai_service_create, mock_exists):
+ mock_exists.return_value = True
+ aai_service_resource = AaiServiceResource(AAI_SERVICE_DATA)
+ aai_service_resource.create()
+ mock_aai_service_create.assert_not_called()
+
+ mock_exists.return_value = False
+ aai_service_resource.create()
+ mock_aai_service_create.assert_called_once_with(
+ service_id="123",
+ service_description="123"
+ )
diff --git a/tests/test_cloud_region_resource.py b/tests/test_cloud_region_resource.py
new file mode 100644
index 0000000..b704720
--- /dev/null
+++ b/tests/test_cloud_region_resource.py
@@ -0,0 +1,149 @@
+from pathlib import Path
+from unittest.mock import MagicMock, patch, PropertyMock
+
+from onapsdk.aai.cloud_infrastructure.complex import Complex
+
+from onap_data_provider.resources.cloud_region_resource import (
+ CloudRegion,
+ CloudRegionResource,
+)
+from onapsdk.exceptions import ResourceNotFound
+
+CLOUD_REGION_DATA = {
+ "cloud-owner": "test",
+ "cloud-region-id": "test",
+ "orchestration-disabled": True,
+ "in-maint": False,
+}
+
+CLOUD_REGION_K8S_TYPE = {
+ "cloud-region-id": "k8s-test",
+ "cloud-owner": "k8s-test",
+ "orchestration-disabled": True,
+ "in-maint": False,
+ "cloud-type": "k8s",
+ "kube-config": Path(Path(__file__).parent, "test-kube-config"),
+}
+
+
+@patch("onap_data_provider.resources.cloud_region_resource.CloudRegion.get_by_id")
+def test_cloud_region_resource_cloud_region(mock_cloud_region_get_by_id):
+ mock_cloud_region_get_by_id.side_effect = ResourceNotFound
+ cloud_region_resource = CloudRegionResource(CLOUD_REGION_DATA)
+ assert cloud_region_resource.cloud_region is None
+
+ mock_cloud_region_get_by_id.side_effect = None
+ mock_cloud_region_get_by_id.return_value = 1
+ assert cloud_region_resource.cloud_region == 1
+
+
+@patch(
+ "onap_data_provider.resources.cloud_region_resource.CloudRegionResource.cloud_region",
+ new_callable=PropertyMock,
+)
+def test_cloud_region_resource_exists(mock_cloud_region):
+ mock_cloud_region.return_value = None
+ cloud_region_resource = CloudRegionResource(CLOUD_REGION_DATA)
+ assert cloud_region_resource.exists is False
+ mock_cloud_region.return_value = 1 # Anything but not None
+ assert cloud_region_resource.exists is True
+
+
+@patch(
+ "onap_data_provider.resources.cloud_region_resource.CloudRegionResource.exists",
+ new_callable=PropertyMock,
+)
+@patch("onap_data_provider.resources.cloud_region_resource.CloudRegion.create")
+def test_cloud_region_create(mock_cloud_region_create, mock_exists):
+
+ cloud_region_resource = CloudRegionResource(CLOUD_REGION_DATA)
+ assert cloud_region_resource.data == CLOUD_REGION_DATA
+
+ mock_exists.return_value = False
+ cloud_region_resource.create()
+ assert mock_cloud_region_create.called_once_with(
+ cloud_owner="test",
+ cloud_region_id="test",
+ orchestration_disabled=True,
+ in_maint=False,
+ )
+
+ mock_exists.reset_mock()
+ mock_cloud_region_create.reset_mock()
+
+ mock_exists.return_value = True
+ cloud_region_resource.create()
+ mock_cloud_region_create.assert_not_called()
+
+
+@patch(
+ "onap_data_provider.resources.cloud_region_resource.CloudRegionResource.cloud_region",
+ new_callable=PropertyMock,
+)
+@patch("onap_data_provider.resources.cloud_region_resource.Complex.get_all")
+def test_cloud_region_resource_link_to_complex(
+ mock_complex_get_all, mock_cloud_region_property
+):
+ mock_cloud_region_property.return_value.complex = MagicMock()
+ cloud_region_resource = CloudRegionResource(CLOUD_REGION_DATA)
+ cloud_region_resource._link_to_complex("test")
+ mock_complex_get_all.assert_not_called()
+
+ mock_cloud_region_property.return_value.complex = None
+ mock_complex_get_all.return_value = iter(())
+ cloud_region_resource._link_to_complex("test")
+ mock_cloud_region_property.return_value.link_to_complex.assert_not_called()
+
+ mock_complex_get_all.return_value = iter([Complex("test")])
+ cloud_region_resource._link_to_complex("test")
+ mock_cloud_region_property.return_value.link_to_complex.assert_called_once()
+
+
+@patch(
+ "onap_data_provider.resources.cloud_region_resource.CloudRegionResource.cloud_region",
+ new_callable=PropertyMock,
+)
+def test_cloud_region_resource_create_availability_zones(mock_cloud_region_property):
+ cloud_region_resource = CloudRegionResource(CLOUD_REGION_DATA)
+ cloud_region_resource.data["availability-zones"] = [
+ {"availability-zone-name": "testzone1", "hypervisor-type": "OpenStackTest"}
+ ]
+ cloud_region_resource.create()
+ mock_cloud_region_property.return_value.add_availability_zone.assert_called_once()
+
+
+@patch(
+ "onap_data_provider.resources.cloud_region_resource.CloudRegionResource.exists",
+ new_callable=PropertyMock,
+)
+@patch("onap_data_provider.resources.cloud_region_resource.ConnectivityInfo")
+@patch("onap_data_provider.resources.cloud_region_resource.CloudRegion.create")
+@patch("onap_data_provider.resources.cloud_region_resource.CloudRegion.complex")
+@patch("onap_data_provider.resources.cloud_region_resource.SoDbAdapter.add_cloud_site")
+def test_cloud_region_k8s_type(
+ mock_add_cloud_site,
+ _,
+ mock_cloud_region_create,
+ mock_connectivity_info,
+ mock_exists,
+):
+ mock_exists.return_value = False
+ mock_cloud_region_create.return_value = CloudRegion(
+ cloud_owner=CLOUD_REGION_K8S_TYPE["cloud-owner"],
+ cloud_region_id=CLOUD_REGION_K8S_TYPE["cloud-region-id"],
+ orchestration_disabled=CLOUD_REGION_K8S_TYPE["orchestration-disabled"],
+ in_maint=CLOUD_REGION_K8S_TYPE["in-maint"],
+ cloud_type=CLOUD_REGION_K8S_TYPE["cloud-type"],
+ )
+ cloud_region_resource = CloudRegionResource(CLOUD_REGION_K8S_TYPE)
+ cloud_region_resource.create()
+ mock_connectivity_info.get_connectivity_info_by_region_id.assert_called_once_with(
+ CLOUD_REGION_K8S_TYPE["cloud-region-id"]
+ )
+ mock_add_cloud_site.assert_called_once()
+
+ mock_connectivity_info.get_connectivity_info_by_region_id.side_effect = (
+ ResourceNotFound
+ )
+ cloud_region_resource.create()
+ mock_connectivity_info.create.assert_called_once()
diff --git a/tests/test_complex_resource.py b/tests/test_complex_resource.py
new file mode 100644
index 0000000..441b37c
--- /dev/null
+++ b/tests/test_complex_resource.py
@@ -0,0 +1,82 @@
+from unittest.mock import patch, PropertyMock
+
+from onapsdk.aai.cloud_infrastructure.complex import Complex
+
+from onap_data_provider.resources.complex_resource import ComplexResource
+from onapsdk.exceptions import ResourceNotFound
+
+
+COMPLEX_DATA = {
+ "physical-location-id": "123",
+ "complex-name": "NB central office 1",
+ "data-center-code": "veniam",
+ "identity-url": "https://estevan.org",
+ "physical-location-type": "centraloffice",
+ "street1": "Ravensburgstraße",
+ "street2": "123",
+ "city": "Neubrandenburg",
+ "state": "Mecklenburg-Vorpommern",
+ "postal-code": "17034",
+ "country": "DE",
+ "region": "Mecklenburg Lakeland",
+ "latitude": "53.5630015",
+ "longitude": "13.2722710",
+ "elevation": "100",
+ "lata": "dolorem",
+}
+
+
+@patch("onap_data_provider.resources.complex_resource.Complex.get_all")
+def test_complex_resource_complex(mock_complex_get_all):
+ mock_complex_get_all.side_effect = ResourceNotFound
+ mock_complex_get_all.return_value = iter([])
+ complex_resource = ComplexResource(COMPLEX_DATA)
+ assert complex_resource.complex is None
+ mock_complex_get_all.side_effect = None
+ mock_complex_get_all.return_value = iter([Complex(physical_location_id="123")])
+ assert complex_resource.complex is not None
+
+
+@patch(
+ "onap_data_provider.resources.complex_resource.ComplexResource.complex",
+ new_callable=PropertyMock,
+)
+def test_complex_resource_exists(mock_complex):
+ mock_complex.return_value = None
+ complex_resource = ComplexResource(COMPLEX_DATA)
+ assert complex_resource.exists is False
+ mock_complex.return_value = 1 # Anything but not None
+ assert complex_resource.exists is True
+
+
+@patch(
+ "onap_data_provider.resources.complex_resource.ComplexResource.exists",
+ new_callable=PropertyMock,
+)
+@patch("onap_data_provider.resources.complex_resource.Complex.create")
+def test_complex_resource_create(mock_complex_create, mock_exists):
+ mock_exists.return_value = True
+ complex_resource = ComplexResource(COMPLEX_DATA)
+ complex_resource.create()
+ mock_complex_create.assert_not_called()
+
+ mock_exists.return_value = False
+ complex_resource.create()
+ mock_complex_create.assert_called_once_with(
+ physical_location_id="123",
+ name="NB central office 1",
+ data_center_code="veniam",
+ identity_url="https://estevan.org",
+ physical_location_type="centraloffice",
+ street1="Ravensburgstraße",
+ street2="123",
+ city="Neubrandenburg",
+ state="Mecklenburg-Vorpommern",
+ postal_code="17034",
+ country="DE",
+ region="Mecklenburg Lakeland",
+ latitude="53.5630015",
+ longitude="13.2722710",
+ elevation="100",
+ lata="dolorem",
+ )
diff --git a/tests/test_config_loader.py b/tests/test_config_loader.py
new file mode 100644
index 0000000..c10e329
--- /dev/null
+++ b/tests/test_config_loader.py
@@ -0,0 +1,27 @@
+
+from pathlib import Path
+
+from onap_data_provider.config_loader import ConfigLoader
+
+
+def test_config_loader_no_dirs():
+ config_loader = ConfigLoader([Path(Path(__file__).parent, "test-data.yml")])
+ configs = list(config_loader.load())
+ assert len(configs) == 1
+
+ config_loader = ConfigLoader([Path(Path(__file__).parent, "test-data.yml"),
+ Path(Path(__file__).parent, "test-data-version.yml")])
+ configs = list(config_loader.load())
+ assert len(configs) == 2
+
+def test_config_loader_dir():
+ config_loader = ConfigLoader([Path(Path(__file__).parent, "config_dirs")])
+ configs = list(config_loader.load())
+ assert len(configs) == 2
+
+def test_config_loader_both_dirs_and_files():
+ config_loader = ConfigLoader([Path(Path(__file__).parent, "test-data.yml"),
+ Path(Path(__file__).parent, "test-data-version.yml"),
+ Path(Path(__file__).parent, "config_dirs")])
+ configs = list(config_loader.load())
+ assert len(configs) == 4
diff --git a/tests/test_config_parser.py b/tests/test_config_parser.py
new file mode 100644
index 0000000..dc118c9
--- /dev/null
+++ b/tests/test_config_parser.py
@@ -0,0 +1,50 @@
+from pathlib import Path
+
+from onap_data_provider.resources.cloud_region_resource import CloudRegionResource
+from onap_data_provider.config_parser import ConfigParser
+
+
+def test_create_cloud_region_resource():
+ parser = ConfigParser([Path("tests/test-data.yml")])
+ parsed_objects = list(parser.parse())
+ assert isinstance(parsed_objects[1], CloudRegionResource)
+ assert parsed_objects[1].data['cloud-owner'] == 'AMIST'
+ assert parsed_objects[1].data['tenants'][0]['tenant-id'] == '-'.join(
+ [parsed_objects[1].data['cloud-owner'], 'TENANT', '1'])
+ assert parsed_objects[1].data['tenants'][1]['tenant-id'] == ''.join(
+ [parsed_objects[1].data['cloud-owner'], '-', 'TENANT', '-', '2'])
+
+
+def test_config_parser_versioning():
+ parser = ConfigParser([Path("tests/test-data.yml")])
+ config = parser.configs[0]
+ assert config.version.value.version_number == "None"
+ parsed_objects = list(parser.parse())
+ assert isinstance(parsed_objects[1], CloudRegionResource)
+ assert parsed_objects[1].data['cloud-owner'] == 'AMIST'
+ assert parsed_objects[1].data['tenants'][0]['tenant-id'] == '-'.join(
+ [parsed_objects[1].data['cloud-owner'], 'TENANT', '1'])
+ assert parsed_objects[1].data['tenants'][1]['tenant-id'] == ''.join(
+ [parsed_objects[1].data['cloud-owner'], '-', 'TENANT', '-', '2'])
+
+ parser = ConfigParser([Path("tests/test-data-version.yml")])
+ config = parser.configs[0]
+ assert config.version.value.version_number == "1.0"
+ parsed_objects = list(parser.parse())
+ assert isinstance(parsed_objects[1], CloudRegionResource)
+ assert parsed_objects[1].data['cloud-owner'] == 'AMIST'
+ assert parsed_objects[1].data['tenants'][0]['tenant-id'] == '-'.join(
+ [parsed_objects[1].data['cloud-owner'], 'TENANT', '1'])
+ assert parsed_objects[1].data['tenants'][1]['tenant-id'] == ''.join(
+ [parsed_objects[1].data['cloud-owner'], '-', 'TENANT', '-', '2'])
+
+ parser = ConfigParser([Path("tests/test-data.yml"), Path("tests/test-data-version.yml")])
+ assert parser.configs[0].version.value.version_number == "None"
+ assert parser.configs[1].version.value.version_number == "1.0"
+ parsed_objects = list(parser.parse())
+ assert isinstance(parsed_objects[1], CloudRegionResource)
+ assert parsed_objects[1].data['cloud-owner'] == 'AMIST'
+ assert parsed_objects[1].data['tenants'][0]['tenant-id'] == '-'.join(
+ [parsed_objects[1].data['cloud-owner'], 'TENANT', '1'])
+ assert parsed_objects[1].data['tenants'][1]['tenant-id'] == ''.join(
+ [parsed_objects[1].data['cloud-owner'], '-', 'TENANT', '-', '2'])
diff --git a/tests/test_customer_resource.py b/tests/test_customer_resource.py
new file mode 100644
index 0000000..23b51ef
--- /dev/null
+++ b/tests/test_customer_resource.py
@@ -0,0 +1,84 @@
+from unittest.mock import MagicMock, patch, PropertyMock
+
+from onap_data_provider.resources.customer_resource import CustomerResource
+
+from onapsdk.exceptions import ResourceNotFound
+
+CUSTOMER_DATA = {
+ "global-customer-id": "test_id",
+ "subscriber-name": "test_name",
+ "subscriber-type": "Customer",
+ "service-subscriptions": [{"service-type": "test_voip"}],
+}
+
+SERVICE_SUBSCRIPTION_WITH_TENANTS_DATA = {
+ "service-type": "test-service-subscription",
+ "tenants": [
+ {
+ "tenant-id": "1234",
+ "cloud-owner": "test-cloud-owner",
+ "cloud-region-id": "test-cloud-region",
+ }
+ ],
+}
+
+
+@patch(
+ "onap_data_provider.resources.customer_resource.Customer.get_by_global_customer_id"
+)
+def test_customer_resource_customer(mock_customer_get_by_global_customer_id):
+ mock_customer_get_by_global_customer_id.side_effect = ResourceNotFound
+ customer_resource = CustomerResource(CUSTOMER_DATA)
+ assert customer_resource.customer is None
+ mock_customer_get_by_global_customer_id.side_effect = None
+ mock_customer_get_by_global_customer_id.return_value = 1
+ assert customer_resource.customer == 1
+
+
+@patch(
+ "onap_data_provider.resources.customer_resource.CustomerResource.customer",
+ new_callable=PropertyMock,
+)
+def test_customer_exists(mock_customer):
+ mock_customer.return_value = None
+ customer_resource = CustomerResource(CUSTOMER_DATA)
+ assert customer_resource.exists is False
+ mock_customer.return_value = 1 # Anything but not None
+ assert customer_resource.exists is True
+
+
+@patch(
+ "onap_data_provider.resources.customer_resource.CustomerResource.exists",
+ new_callable=PropertyMock,
+)
+@patch("onap_data_provider.resources.customer_resource.Customer.create")
+def test_customer_create(mock_customer_create, mock_exists):
+ customer_resource = CustomerResource(CUSTOMER_DATA)
+ assert customer_resource.data == CUSTOMER_DATA
+ mock_exists.return_value = False
+ customer_resource.create()
+ assert mock_customer_create.called_once_with(
+ global_customer_id="test_id",
+ subscriber_name="test_name",
+ subscriber_type="Customer",
+ )
+
+
+@patch(
+ "onap_data_provider.resources.customer_resource.CustomerResource.ServiceSubscriptionResource.service_subscription",
+ new_callable=PropertyMock,
+)
+@patch("onap_data_provider.resources.customer_resource.CloudRegion")
+def test_service_subscription_with_tenants(
+ mock_cloud_region, _
+):
+ cloud_region_mock = MagicMock()
+ mock_cloud_region.get_by_id.return_value = cloud_region_mock
+ service_subscription_resource = CustomerResource.ServiceSubscriptionResource(
+ SERVICE_SUBSCRIPTION_WITH_TENANTS_DATA, MagicMock()
+ )
+ service_subscription_resource.create()
+ mock_cloud_region.get_by_id.assert_called_once_with(
+ "test-cloud-owner", "test-cloud-region"
+ )
+ cloud_region_mock.get_tenant.assert_called_once_with("1234")
diff --git a/tests/test_esr_resource.py b/tests/test_esr_resource.py
new file mode 100644
index 0000000..d6c8901
--- /dev/null
+++ b/tests/test_esr_resource.py
@@ -0,0 +1,82 @@
+from collections import namedtuple
+from unittest.mock import MagicMock, patch, PropertyMock
+
+from onap_data_provider.resources.esr_system_info_resource import (
+ CloudRegion,
+ EsrSystemInfoResource,
+)
+
+
+ESR_RESOURCE_DATA = {
+ "esr-system-info-id": "Test ID",
+ "user-name": "Test name",
+ "password": "testpass",
+ "system-type": "test type",
+ "service-url": "test url",
+ "cloud-domain": "test cloud domain",
+}
+
+
+EsrSystemInfoNamedtuple = namedtuple("EsrSystemInfo", ["esr_system_info_id"])
+
+
+@patch(
+ "onap_data_provider.resources.esr_system_info_resource.CloudRegion.esr_system_infos",
+ new_callable=PropertyMock,
+)
+def test_esr_system_info_resource_esr_system_info(mock_cloud_region_esr_system_infos):
+ cloud_region = CloudRegion(
+ cloud_owner="test",
+ cloud_region_id="test",
+ orchestration_disabled=True,
+ in_maint=True,
+ )
+ esr_resource = EsrSystemInfoResource(ESR_RESOURCE_DATA, cloud_region)
+ mock_cloud_region_esr_system_infos.return_value = iter([])
+ assert esr_resource.esr_system_info is None
+
+ mock_cloud_region_esr_system_infos.return_value = iter(
+ [EsrSystemInfoNamedtuple("Test ID")]
+ )
+ assert esr_resource.esr_system_info is not None
+
+
+@patch(
+ "onap_data_provider.resources.esr_system_info_resource.EsrSystemInfoResource.esr_system_info",
+ new_callable=PropertyMock,
+)
+def test_esr_system_info_resource_exists(mock_esr_system_info):
+ mock_esr_system_info.return_value = None
+ cloud_region_mock = MagicMock()
+ esr_resource = EsrSystemInfoResource(ESR_RESOURCE_DATA, cloud_region_mock)
+ assert esr_resource.exists is False
+
+ mock_esr_system_info.return_value = 1
+ assert esr_resource.exists is True
+
+
+@patch(
+ "onap_data_provider.resources.esr_system_info_resource.EsrSystemInfoResource.exists",
+ new_callable=PropertyMock,
+)
+def test_esr_system_info_resource_create(mock_exists):
+
+ cloud_region_mock = MagicMock()
+ esr_resource = EsrSystemInfoResource(ESR_RESOURCE_DATA, cloud_region_mock)
+
+ mock_exists.return_value = True
+ esr_resource.create()
+ cloud_region_mock.add_esr_system_info.assert_not_called()
+
+ mock_exists.return_value = False
+ esr_resource.create()
+ cloud_region_mock.add_esr_system_info.assert_called_once_with(
+ esr_system_info_id="Test ID",
+ user_name="Test name",
+ password="testpass",
+ system_type="test type",
+ system_status="active",
+ service_url="test url",
+ cloud_domain="test cloud domain",
+ default_tenant=None,
+ )
diff --git a/tests/test_line_of_business_resource.py b/tests/test_line_of_business_resource.py
new file mode 100644
index 0000000..1a60e8f
--- /dev/null
+++ b/tests/test_line_of_business_resource.py
@@ -0,0 +1,51 @@
+from unittest import mock
+
+from onap_data_provider.resources.line_of_business_resource import (
+ LineOfBusinessResource,
+ ResourceNotFound,
+)
+
+
+LINE_OF_BUSINESS = {"name": "test-name"}
+
+
+@mock.patch(
+ "onap_data_provider.resources.line_of_business_resource.LineOfBusiness.get_by_name"
+)
+def test_line_of_business_resource_line_of_business_property(mock_get_by_name):
+
+ lob = LineOfBusinessResource(LINE_OF_BUSINESS)
+ mock_get_by_name.side_effect = ResourceNotFound
+ assert lob.line_of_business is None
+
+ mock_get_by_name.side_effect = None
+ assert lob.line_of_business is not None
+
+
+@mock.patch(
+ "onap_data_provider.resources.line_of_business_resource.LineOfBusinessResource.line_of_business",
+ new_callable=mock.PropertyMock,
+)
+def test_line_of_business_resource_exists(mock_line_of_business):
+
+ lob = LineOfBusinessResource(LINE_OF_BUSINESS)
+ assert lob.exists is True
+ mock_line_of_business.return_value = None
+ assert lob.exists is False
+
+
+@mock.patch(
+ "onap_data_provider.resources.line_of_business_resource.LineOfBusinessResource.exists",
+ new_callable=mock.PropertyMock,
+)
+@mock.patch(
+ "onap_data_provider.resources.line_of_business_resource.LineOfBusiness.send_message"
+)
+def test_line_of_business_create(mock_send_message, mock_exists):
+ mock_exists.return_value = True
+ lob = LineOfBusinessResource(LINE_OF_BUSINESS)
+ lob.create()
+ mock_send_message.assert_not_called()
+ mock_exists.return_value = False
+ lob.create()
+ mock_send_message.assert_called()
diff --git a/tests/test_owning_entity_resource.py b/tests/test_owning_entity_resource.py
new file mode 100644
index 0000000..c32351c
--- /dev/null
+++ b/tests/test_owning_entity_resource.py
@@ -0,0 +1,51 @@
+from unittest import mock
+
+from onap_data_provider.resources.owning_entity_resource import (
+ OwningEntityResource,
+ ResourceNotFound,
+)
+
+
+OWNING_ENTITY = {"name": "test-name"}
+
+
+@mock.patch(
+ "onap_data_provider.resources.owning_entity_resource.OwningEntity.get_by_owning_entity_name"
+)
+def test_owning_entity_resource_owning_entity_property(mock_get_by_name):
+
+ owning_entity = OwningEntityResource(OWNING_ENTITY)
+ mock_get_by_name.side_effect = ResourceNotFound
+ assert owning_entity.owning_entity is None
+
+ mock_get_by_name.side_effect = None
+ assert owning_entity.owning_entity is not None
+
+
+@mock.patch(
+ "onap_data_provider.resources.owning_entity_resource.OwningEntityResource.owning_entity",
+ new_callable=mock.PropertyMock,
+)
+def test_owning_entity_resource_exists(mock_owning_entity):
+
+ owning_entity = OwningEntityResource(OWNING_ENTITY)
+ assert owning_entity.exists is True
+ mock_owning_entity.return_value = None
+ assert owning_entity.exists is False
+
+
+@mock.patch(
+ "onap_data_provider.resources.owning_entity_resource.OwningEntityResource.exists",
+ new_callable=mock.PropertyMock,
+)
+@mock.patch(
+ "onap_data_provider.resources.owning_entity_resource.OwningEntity.send_message"
+)
+def test_owning_entity_create(mock_send_message, mock_exists):
+ mock_exists.return_value = True
+ owning_entity = OwningEntityResource(OWNING_ENTITY)
+ owning_entity.create()
+ mock_send_message.assert_not_called()
+ mock_exists.return_value = False
+ owning_entity.create()
+ mock_send_message.assert_called()
diff --git a/tests/test_platform_resource.py b/tests/test_platform_resource.py
new file mode 100644
index 0000000..eafbae4
--- /dev/null
+++ b/tests/test_platform_resource.py
@@ -0,0 +1,47 @@
+from unittest import mock
+
+from onap_data_provider.resources.platform_resource import (
+ PlatformResource,
+ ResourceNotFound,
+)
+
+
+PLATFORM = {"name": "test-name"}
+
+
+@mock.patch("onap_data_provider.resources.platform_resource.Platform.get_by_name")
+def test_platform_resource_platform_property(mock_get_by_name):
+
+ platform = PlatformResource(PLATFORM)
+ mock_get_by_name.side_effect = ResourceNotFound
+ assert platform.platform is None
+
+ mock_get_by_name.side_effect = None
+ assert platform.platform is not None
+
+
+@mock.patch(
+ "onap_data_provider.resources.platform_resource.PlatformResource.platform",
+ new_callable=mock.PropertyMock,
+)
+def test_platform_resource_exists(mock_platform):
+
+ platform = PlatformResource(PLATFORM)
+ assert platform.exists is True
+ mock_platform.return_value = None
+ assert platform.exists is False
+
+
+@mock.patch(
+ "onap_data_provider.resources.platform_resource.PlatformResource.exists",
+ new_callable=mock.PropertyMock,
+)
+@mock.patch("onap_data_provider.resources.platform_resource.Platform.send_message")
+def test_platform_create(mock_send_message, mock_exists):
+ mock_exists.return_value = True
+ platform = PlatformResource(PLATFORM)
+ platform.create()
+ mock_send_message.assert_not_called()
+ mock_exists.return_value = False
+ platform.create()
+ mock_send_message.assert_called()
diff --git a/tests/test_pnf_resource.py b/tests/test_pnf_resource.py
new file mode 100644
index 0000000..c58717a
--- /dev/null
+++ b/tests/test_pnf_resource.py
@@ -0,0 +1,28 @@
+from unittest.mock import patch, PropertyMock
+
+from onap_data_provider.resources.pnf_resource import PnfResource
+
+PNF_RESOURCE_DATA = {"name": "test_pnf"}
+
+
+@patch(
+ "onap_data_provider.resources.pnf_resource.PnfResource.pnf",
+ new_callable=PropertyMock,
+)
+def test_pnf_resource_exists(mock_pnf):
+ mock_pnf.return_value = None
+ pnf_resource = PnfResource(PNF_RESOURCE_DATA)
+ assert pnf_resource.exists is False
+ mock_pnf.return_value = 1 # Anything but not None
+ assert pnf_resource.exists is True
+
+
+@patch(
+ "onap_data_provider.resources.pnf_resource.Pnf.created",
+)
+def test_pnf_resource_pnf(mock_pnf_created):
+ mock_pnf_created.return_value = False
+ pnf_resource = PnfResource(PNF_RESOURCE_DATA)
+ assert pnf_resource.pnf is None
+ mock_pnf_created.return_value = True
+ assert pnf_resource.pnf is not None
diff --git a/tests/test_project_resource.py b/tests/test_project_resource.py
new file mode 100644
index 0000000..c1ff167
--- /dev/null
+++ b/tests/test_project_resource.py
@@ -0,0 +1,47 @@
+from unittest import mock
+
+from onap_data_provider.resources.project_resource import (
+ ProjectResource,
+ ResourceNotFound,
+)
+
+
+PROJECT = {"name": "test-name"}
+
+
+@mock.patch("onap_data_provider.resources.project_resource.Project.get_by_name")
+def test_project_resource_project_property(mock_get_by_name):
+
+ project = ProjectResource(PROJECT)
+ mock_get_by_name.side_effect = ResourceNotFound
+ assert project.project is None
+
+ mock_get_by_name.side_effect = None
+ assert project.project is not None
+
+
+@mock.patch(
+ "onap_data_provider.resources.project_resource.ProjectResource.project",
+ new_callable=mock.PropertyMock,
+)
+def test_project_resource_exists(mock_project):
+
+ project = ProjectResource(PROJECT)
+ assert project.exists is True
+ mock_project.return_value = None
+ assert project.exists is False
+
+
+@mock.patch(
+ "onap_data_provider.resources.project_resource.ProjectResource.exists",
+ new_callable=mock.PropertyMock,
+)
+@mock.patch("onap_data_provider.resources.project_resource.Project.send_message")
+def test_project_create(mock_send_message, mock_exists):
+ mock_exists.return_value = True
+ project = ProjectResource(PROJECT)
+ project.create()
+ mock_send_message.assert_not_called()
+ mock_exists.return_value = False
+ project.create()
+ mock_send_message.assert_called()
diff --git a/tests/test_resource_creator.py b/tests/test_resource_creator.py
new file mode 100644
index 0000000..a8e62ed
--- /dev/null
+++ b/tests/test_resource_creator.py
@@ -0,0 +1,31 @@
+import pytest
+
+from onap_data_provider.resources.cloud_region_resource import CloudRegionResource
+from onap_data_provider.resources.complex_resource import ComplexResource
+from onap_data_provider.resources.resource_creator import ResourceCreator
+from onap_data_provider.versions import VersionsEnum
+
+
+def test_create_cloud_region_resource():
+ cloud_region_resource = ResourceCreator.create("cloud-region", {"a": "B"}, VersionsEnum.NONE)
+ assert isinstance(cloud_region_resource, CloudRegionResource)
+ assert cloud_region_resource.data == {"a": "B"}
+ cloud_region_resource = ResourceCreator.create("cloud-region", {"a": "B"}, VersionsEnum.V1_0)
+ assert isinstance(cloud_region_resource, CloudRegionResource)
+ assert cloud_region_resource.data == {"a": "B"}
+
+
+def test_create_complex_resource():
+ complex_resource = ResourceCreator.create("complex", {"a": "B"}, VersionsEnum.NONE)
+ assert isinstance(complex_resource, ComplexResource)
+ assert complex_resource.data == {"a": "B"}
+ complex_resource = ResourceCreator.create("complex", {"a": "B"}, VersionsEnum.V1_0)
+ assert isinstance(complex_resource, ComplexResource)
+ assert complex_resource.data == {"a": "B"}
+
+
+def test_create_invalid_resource():
+ with pytest.raises(ValueError):
+ ResourceCreator.create("invalid", {}, VersionsEnum.NONE)
+ with pytest.raises(ValueError):
+ ResourceCreator.create("invalid", {}, VersionsEnum.V1_0)
diff --git a/tests/test_service_instance_resource.py b/tests/test_service_instance_resource.py
new file mode 100644
index 0000000..69a9e57
--- /dev/null
+++ b/tests/test_service_instance_resource.py
@@ -0,0 +1,141 @@
+from unittest.mock import MagicMock, patch, PropertyMock
+
+from onap_data_provider.resources.service_instance_resource import (
+ ServiceInstanceResource
+)
+from onapsdk.exceptions import APIError
+
+RESOURCE_DATA_1_0 = {
+ "service_instance_name": "vFW-Macro-1",
+ "service_name": "service1",
+ "cloud_region": "test",
+ "customer_id": "*cust1",
+ "owning_entity": "test",
+ "project": "test",
+ "platform": "test",
+ "line_of_business": "test",
+ "cloud_region_id": "*cloudregionid1",
+ "cloud_owner": "*cloudowner1",
+ "tenant_id": "test",
+ "instantiation_parameters": [],
+}
+
+
+RESOURCE_DATA_1_1 = {
+ "service_instance_name": "vFW-Macro-1",
+ "service_name": "service1",
+ "cloud_region": "test",
+ "customer_id": "*cust1",
+ "owning_entity": "test",
+ "project": "test",
+ "platform": "test",
+ "line_of_business": "test",
+ "cloud_region_id": "*cloudregionid1",
+ "cloud_owner": "*cloudowner1",
+ "tenant_id": "test",
+ "instantiation_parameters": [],
+ "aai_service": "test"
+}
+
+
+INSTANTIATION_PARAMETERS_DATA = {
+ "service_name": "service1",
+ "instantiation_parameters": [
+ {
+ "vnf_name": "test",
+ "parameters": {"a": "b", "c": "d"},
+ "vf_modules": [
+ {
+ "name": "base_ubuntu20",
+ "parameters": {
+ "ubuntu20_image_name": "Ubuntu_2004",
+ "ubuntu20_key_name": "cleouverte",
+ "ubuntu20_pub_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDY15cdBmIs2XOpe4EiFCsaY6bmUmK/GysMoLl4UG51JCfJwvwoWCoA+6mDIbymZxhxq9IGxilp/yTA6WQ9s/5pBag1cUMJmFuda9PjOkXl04jgqh5tR6I+GZ97AvCg93KAECis5ubSqw1xOCj4utfEUtPoF1OuzqM/lE5mY4N6VKXn+fT7pCD6cifBEs6JHhVNvs5OLLp/tO8Pa3kKYQOdyS0xc3rh+t2lrzvKUSWGZbX+dLiFiEpjsUL3tDqzkEMNUn4pdv69OJuzWHCxRWPfdrY9Wg0j3mJesP29EBht+w+EC9/kBKq+1VKdmsXUXAcjEvjovVL8l1BrX3BY0R8D imported-openssh-key",
+ "ubuntu20_flavor_name": "m1.smaller",
+ "VM_name": "ubuntu20agent-VM-01",
+ "vnf_id": "ubuntu20agent-VNF-instance",
+ "vf_module_id": "ubuntu20agent-vfmodule-instance",
+ "vnf_name": "ubuntu20agent-VNF",
+ "admin_plane_net_name": "admin",
+ "ubuntu20_name_0": "ubuntu20agent-VNF",
+ },
+ }
+ ],
+ }
+ ]
+}
+
+
+@patch(
+ "onap_data_provider.resources.service_instance_resource.ServiceInstanceResource.service_instance",
+ new_callable=PropertyMock,
+)
+def test_si_resource_exists(mock_si):
+ mock_si.return_value = None
+ si_resource = ServiceInstanceResource(RESOURCE_DATA_1_1)
+ assert si_resource.exists is False
+ mock_si.return_value = 1 # Anything but not None
+ assert si_resource.exists is True
+
+
+@patch(
+ "onap_data_provider.resources.service_instance_resource.ServiceInstanceResource.exists",
+ new_callable=PropertyMock,
+)
+@patch("onap_data_provider.resources.service_instance_resource.Customer")
+@patch("onap_data_provider.resources.service_instance_resource.Project")
+@patch("onap_data_provider.resources.service_instance_resource.OwningEntity")
+@patch("onap_data_provider.resources.service_instance_resource.CloudRegion")
+@patch("onap_data_provider.resources.service_instance_resource.ServiceInstantiation")
+@patch("onap_data_provider.resources.service_instance_resource.Service")
+@patch("onap_data_provider.resources.service_instance_resource.AaiService")
+def test_si_resource_create(
+ mock_aai_service,
+ mock_service,
+ mock_service_instantionation,
+ mock_cr,
+ mock_oe,
+ mock_project,
+ mock_customer,
+ mock_si_resource_exists,
+):
+ si_resource = ServiceInstanceResource(RESOURCE_DATA_1_1)
+ mock_oe.get_by_owning_entity_name.side_effect = APIError
+ mock_si_resource_exists.return_value = True
+ si_resource.create()
+ mock_service.assert_not_called()
+
+ mock_si_resource_exists.return_value = False
+ si_resource.create()
+ mock_oe.create.assert_called_once()
+ mock_oe.get_by_owning_entity_name.assert_called_once()
+ mock_aai_service.get_all.called_once_with(service_id="test")
+ mock_service_instantionation.instantiate_macro.assert_called_once()
+
+
+def test_so_service():
+ si_resource = ServiceInstanceResource(INSTANTIATION_PARAMETERS_DATA)
+ so_service = si_resource.so_service
+ assert so_service.subscription_service_type == "service1"
+ assert len(so_service.vnfs) == 1
+ vnf = so_service.vnfs[0]
+ assert vnf["model_name"] == "test"
+ assert vnf["vnf_name"] == "test"
+ assert len(vnf["parameters"]) == 2
+ assert len(vnf["vf_modules"]) == 1
+ vf_module = vnf["vf_modules"][0]
+ assert vf_module["model_name"] == "base_ubuntu20"
+ assert vf_module["vf_module_name"] == "base_ubuntu20"
+ assert len(vf_module["parameters"]) == 10
+
+
+@patch("onap_data_provider.resources.service_instance_resource.AaiService.get_all")
+def test_service_instance_resource_version_1_0_and_1_1(mock_aai_service_get_all):
+ si_resource_1_0 = ServiceInstanceResource(RESOURCE_DATA_1_0)
+ assert si_resource_1_0.aai_service is None
+ mock_aai_service_get_all.assert_not_called()
+
+ mock_aai_service_get_all.return_value = iter([MagicMock()])
+ si_resource_1_0 = ServiceInstanceResource(RESOURCE_DATA_1_1)
+ assert si_resource_1_0.aai_service is not None
+ mock_aai_service_get_all.assert_called_once()
diff --git a/tests/test_service_resource.py b/tests/test_service_resource.py
new file mode 100644
index 0000000..f664676
--- /dev/null
+++ b/tests/test_service_resource.py
@@ -0,0 +1,50 @@
+from collections import namedtuple
+from unittest.mock import patch, PropertyMock
+
+from onap_data_provider.resources.service_resource import ServiceResource
+
+
+SERVICE_RESOURCE_DATA = {
+ "name": "test",
+}
+
+
+@patch("onap_data_provider.resources.service_resource.Service.created")
+def test_service_resource_service_property(mock_service_created):
+ service_resource = ServiceResource(SERVICE_RESOURCE_DATA)
+ mock_service_created.return_value = False
+ assert service_resource.service is None
+
+ mock_service_created.return_value = True
+ assert service_resource.service is not None
+
+
+@patch(
+ "onap_data_provider.resources.service_resource.ServiceResource.service",
+ new_callable=PropertyMock,
+)
+def test_service_resource_exists(mock_service_resource_service):
+ service_resource = ServiceResource(SERVICE_RESOURCE_DATA)
+ mock_service_resource_service.return_value = None
+ assert service_resource.exists is False
+ ServiceNamedtuple = namedtuple(
+ "ServiceNamedtuple", ["distributed"], defaults=[True]
+ )
+ mock_service_resource_service.return_value = ServiceNamedtuple()
+ assert service_resource.exists is True
+
+
+@patch(
+ "onap_data_provider.resources.service_resource.ServiceResource.exists",
+ new_callable=PropertyMock,
+)
+@patch("onap_data_provider.resources.service_resource.Service")
+def test_service_resource_create(mock_service, mock_service_resource_exists):
+ service_resource = ServiceResource(SERVICE_RESOURCE_DATA)
+ mock_service_resource_exists.return_value = True
+ service_resource.create()
+ mock_service.assert_not_called()
+
+ mock_service_resource_exists.return_value = False
+ service_resource.create()
+ mock_service.assert_called_once()
diff --git a/tests/test_tag_handlers.py b/tests/test_tag_handlers.py
new file mode 100644
index 0000000..719295f
--- /dev/null
+++ b/tests/test_tag_handlers.py
@@ -0,0 +1,15 @@
+from unittest.mock import patch, PropertyMock
+from onap_data_provider.tag_handlers import join, generate_random_uuid
+
+
+def test_generate_random_uuid():
+ uuid1 = generate_random_uuid(None, None)
+ uuid2 = generate_random_uuid(None, None)
+ assert isinstance(uuid1, str)
+ assert uuid1 != uuid2
+
+
+@patch("yaml.SafeLoader", new_callable=PropertyMock)
+def test_join(mock_safe_loader):
+ mock_safe_loader.construct_sequence.return_value = ["-", ["cloud", "owner", "DC1"]]
+ assert join(mock_safe_loader, None) == "cloud-owner-DC1"
diff --git a/tests/test_tenant_resource.py b/tests/test_tenant_resource.py
new file mode 100644
index 0000000..2c89651
--- /dev/null
+++ b/tests/test_tenant_resource.py
@@ -0,0 +1,56 @@
+from unittest.mock import MagicMock, patch, PropertyMock
+
+from onap_data_provider.resources.tenant_resource import TenantResource
+from onapsdk.exceptions import ResourceNotFound
+
+
+TENANT_RESOURCE_DATA = {"tenant-id": "Test ID", "tenant-name": "Test name"}
+
+
+def test_tenant_resource_tenant():
+ cloud_region_mock = MagicMock()
+ tenant_resource = TenantResource(TENANT_RESOURCE_DATA, cloud_region_mock)
+ cloud_region_mock.get_tenant.side_effect = ResourceNotFound
+ assert tenant_resource.tenant is None
+
+ cloud_region_mock.get_tenant.side_effect = None
+ cloud_region_mock.get_tenant.return_value = 1
+ assert tenant_resource.tenant == 1
+
+ cloud_region_mock.reset_mock()
+ assert tenant_resource.tenant == 1
+ cloud_region_mock.assert_not_called()
+
+
+@patch(
+ "onap_data_provider.resources.tenant_resource.TenantResource.tenant",
+ new_callable=PropertyMock,
+)
+def test_tenant_resource_exists(mock_tenant):
+ mock_tenant.return_value = None
+ cloud_region_mock = MagicMock()
+ tenant_resource = TenantResource(TENANT_RESOURCE_DATA, cloud_region_mock)
+ assert tenant_resource.exists is False
+
+ mock_tenant.return_value = 1
+ assert tenant_resource.exists is True
+
+
+@patch(
+ "onap_data_provider.resources.tenant_resource.TenantResource.exists",
+ new_callable=PropertyMock,
+)
+def test_tenant_resource_create(mock_exists):
+
+ cloud_region_mock = MagicMock()
+ tenant_resource = TenantResource(TENANT_RESOURCE_DATA, cloud_region_mock)
+
+ mock_exists.return_value = True
+ tenant_resource.create()
+ cloud_region_mock.add_tenant.assert_not_called()
+
+ mock_exists.return_value = False
+ tenant_resource.create()
+ cloud_region_mock.add_tenant.assert_called_once_with(
+ tenant_id="Test ID", tenant_name="Test name", tenant_context=None
+ )
diff --git a/tests/test_validator.py b/tests/test_validator.py
new file mode 100644
index 0000000..ad291a9
--- /dev/null
+++ b/tests/test_validator.py
@@ -0,0 +1,123 @@
+from pytest import raises
+from jsonschema import ValidationError
+
+from onap_data_provider.validator import Validator
+from onap_data_provider.versions import VersionsEnum
+
+
+def test_validator_customer():
+ validator = Validator()
+ input_data = {
+ "customers": [
+ {
+ "customer": {
+ "global-customer-id": "test",
+ "subscriber-name": "test",
+ "subscriber-type": "test",
+ }
+ }
+ ]
+ }
+ validator.validate(VersionsEnum.NONE, input_data)
+ input_data = {
+ "customers": [
+ {
+ "customer": {
+ "global-customer-id": "test",
+ "subscriber-name": "test",
+ "subscriber-type": "test",
+ }
+ }
+ ]
+ }
+ validator.validate(VersionsEnum.V1_0, input_data)
+
+ invalid_input_data = { # Missing subscriber-type
+ "customers": [
+ {"customer": {"global-customer-id": "test", "subscriber-name": "test"}}
+ ]
+ }
+ with raises(ValidationError):
+ validator.validate(VersionsEnum.V1_0, invalid_input_data)
+
+
+def test_validator_vsps():
+ validator = Validator()
+ input_data = {
+ "vsps": [
+ {
+ "vsp": {
+ "name": "test",
+ "vendor": "test",
+ "package": "test",
+ }
+ }
+ ]
+ }
+ validator.validate(VersionsEnum.NONE, input_data)
+
+ input_data = {
+ "vsps": [
+ {
+ "vsp": {
+ "name": "test",
+ "vendor": "test",
+ "package": "test",
+ }
+ }
+ ]
+ }
+ validator.validate(VersionsEnum.V1_0, input_data)
+
+ input_data = {
+ "vsps": [
+ {
+ "vsp": {
+ "name": "test",
+ }
+ }
+ ]
+ }
+ with raises(ValidationError):
+ validator.validate(VersionsEnum.V1_0, input_data)
+
+
+def test_validator_service():
+ validator = Validator()
+ input_data = {
+ "services": [
+ {
+ "service": {
+ "name": "test",
+ "resources": [
+ {"name": "test", "type": "test"},
+ {"name": "test1", "type": "test2"},
+ ],
+ "properties": [
+ {"name": "test", "type": "test", "value": "test"},
+ {"name": "test1", "type": "test1"},
+ ],
+ }
+ }
+ ]
+ }
+ validator.validate(VersionsEnum.NONE, input_data)
+
+ input_data = {
+ "services": [
+ {
+ "service": {
+ "name": "test",
+ "resources": [
+ {"name": "test", "type": "test"},
+ {"name": "test1", "type": "test2"},
+ ],
+ "properties": [
+ {"name": "test", "type": "test", "value": "test"},
+ {"name": "test1", "type": "test1"},
+ ],
+ }
+ }
+ ]
+ }
+ validator.validate(VersionsEnum.V1_0, input_data)
diff --git a/tests/test_vendor_resource.py b/tests/test_vendor_resource.py
new file mode 100644
index 0000000..2a4d0aa
--- /dev/null
+++ b/tests/test_vendor_resource.py
@@ -0,0 +1,28 @@
+from unittest.mock import patch, PropertyMock
+
+from onap_data_provider.resources.vendor_resource import VendorResource
+
+VENDOR_RESOURCE_DATA = {"name": "testVendor"}
+
+
+@patch(
+ "onap_data_provider.resources.vendor_resource.VendorResource.vendor",
+ new_callable=PropertyMock,
+)
+def test_vendor_resource_exists(mock_vendor):
+ mock_vendor.return_value = None
+ vendor_resource = VendorResource(VENDOR_RESOURCE_DATA)
+ assert vendor_resource.exists is False
+ mock_vendor.return_value = 1 # Anything but not None
+ assert vendor_resource.exists is True
+
+
+@patch(
+ "onap_data_provider.resources.vendor_resource.Vendor.created",
+)
+def test_vendor_resource_vendor(mock_vendor_created):
+ mock_vendor_created.return_value = False
+ vendor_resource = VendorResource(VENDOR_RESOURCE_DATA)
+ assert vendor_resource.vendor is None
+ mock_vendor_created.return_value = True
+ assert vendor_resource.vendor is not None
diff --git a/tests/test_versions.py b/tests/test_versions.py
new file mode 100644
index 0000000..7571854
--- /dev/null
+++ b/tests/test_versions.py
@@ -0,0 +1,15 @@
+import warnings
+
+from onap_data_provider.versions import VersionsEnum
+
+
+def test_versions_init():
+ v_none = VersionsEnum.get_version_by_number("None")
+ assert v_none == VersionsEnum.NONE
+ assert v_none.value.version_number == "None"
+ assert v_none.value.schema_path
+
+ v_1_0 = VersionsEnum.get_version_by_number("1.0")
+ assert v_1_0 == VersionsEnum.V1_0
+ assert v_1_0.value.version_number == "1.0"
+ assert v_1_0.value.schema_path
diff --git a/tests/test_vnf_resource.py b/tests/test_vnf_resource.py
new file mode 100644
index 0000000..6398aec
--- /dev/null
+++ b/tests/test_vnf_resource.py
@@ -0,0 +1,28 @@
+from unittest.mock import patch, PropertyMock
+
+from onap_data_provider.resources.vnf_resource import VnfResource
+
+VNF_RESOURCE_DATA = {"name": "test_vnf"}
+
+
+@patch(
+ "onap_data_provider.resources.vnf_resource.VnfResource.vnf",
+ new_callable=PropertyMock,
+)
+def test_vnf_resource_exists(mock_vnf):
+ mock_vnf.return_value = None
+ vnf_resource = VnfResource(VNF_RESOURCE_DATA)
+ assert vnf_resource.exists is False
+ mock_vnf.return_value = 1 # Anything but not None
+ assert vnf_resource.exists is True
+
+
+@patch(
+ "onap_data_provider.resources.vnf_resource.Vf.created",
+)
+def test_vnf_resource_vnf(mock_vnf_created):
+ mock_vnf_created.return_value = False
+ vnf_resource = VnfResource(VNF_RESOURCE_DATA)
+ assert vnf_resource.vnf is None
+ mock_vnf_created.return_value = True
+ assert vnf_resource.vnf is not None
diff --git a/tests/test_vsp_resource.py b/tests/test_vsp_resource.py
new file mode 100644
index 0000000..9ad5bb6
--- /dev/null
+++ b/tests/test_vsp_resource.py
@@ -0,0 +1,28 @@
+from unittest.mock import patch, PropertyMock
+
+from onap_data_provider.resources.vsp_resource import VspResource
+
+
+VSP_RESOURCE_DATA = {"name": "test", "vendor": "test", "package": "test"}
+
+
+@patch("onap_data_provider.resources.vsp_resource.Vsp.created")
+def test_vsp_resource_vsp_property(mock_vsp_created):
+ vsp_resource = VspResource(VSP_RESOURCE_DATA)
+ mock_vsp_created.return_value = False
+ assert vsp_resource.vsp is None
+
+ mock_vsp_created.return_value = True
+ assert vsp_resource.vsp is not None
+
+
+@patch(
+ "onap_data_provider.resources.vsp_resource.VspResource.vsp",
+ new_callable=PropertyMock,
+)
+def test_vsp_resource_exists(mock_vsp):
+ mock_vsp.return_value = None
+ vsp_resource = VspResource(VSP_RESOURCE_DATA)
+ assert not vsp_resource.exists
+ mock_vsp.return_value = 1
+ assert vsp_resource.exists
diff --git a/tox.ini b/tox.ini
index 9b02325..9885622 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,11 +1,11 @@
[tox]
minversion = 3.2.0
-envlist = json,yaml,py,rst,md
+envlist = json,yaml,py,rst,md,mypy
skipsdist = true
requires = pip >= 8
[testenv]
-basepython = python3
+basepython = python3.8
whitelist_externals =
git
bash
@@ -53,3 +53,10 @@ commands_pre =
/bin/sh -c "git --no-pager diff HEAD HEAD^ --name-only '*.md' > /tmp/.coalist_md"
commands =
/bin/bash -c "coala --non-interactive --disable-caching --no-autoapply-warn md --files $(</tmp/.coalist_md) \ "
+
+[testenv:mypy]
+deps =
+ mypy
+ types-PyYAML
+ -rrequirements.txt
+commands = mypy --strict onap_data_provider/