diff options
author | Michal Jagiello <michal.jagiello@t-mobile.pl> | 2021-11-30 08:25:09 +0000 |
---|---|---|
committer | Michal Jagiello <michal.jagiello@t-mobile.pl> | 2021-12-03 09:58:59 +0000 |
commit | 66e44262b8eb996c06670dcededd899dd1cbd7dc (patch) | |
tree | 3fcea0fe3317f8069281cb93c61add4b1599ab83 /onap_data_provider | |
parent | 2416a1a546c1d2922c37d513df42e9d26bbaaa42 (diff) |
Data provider release
Change-Id: Ia041a07152e8dabd87de05992d3670cbdc1ddaae
Issue-ID: INT-2010
Signed-off-by: Michal Jagiello <michal.jagiello@t-mobile.pl>
Diffstat (limited to 'onap_data_provider')
31 files changed, 3770 insertions, 0 deletions
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") |