From a8e2a839a4cb65e341c610fd2bd2e8968c754b3d Mon Sep 17 00:00:00 2001 From: Michal Jagiello Date: Wed, 9 Feb 2022 12:30:27 +0000 Subject: Custom YAML tag to get already existed ONAP resources properties Would be useful for Alloted Resources Issue-ID: INT-2075 Signed-off-by: Michal Jagiello Change-Id: Iad398a2f0c049ba73fa4569a0f2caed1311f077c --- docs/source/index.rst | 1 + docs/source/tags.rst | 33 ++++++++++ onap_data_provider/config_loader.py | 3 +- onap_data_provider/property_tag/__init__.py | 21 +++++++ onap_data_provider/property_tag/base.py | 73 ++++++++++++++++++++++ .../property_tag/properties_getter.py | 64 +++++++++++++++++++ onap_data_provider/property_tag/sdc_service.py | 52 +++++++++++++++ onap_data_provider/tag_handlers.py | 22 +++++++ tests/test_tag_handlers.py | 16 ++++- 9 files changed, 283 insertions(+), 2 deletions(-) create mode 100644 docs/source/tags.rst create mode 100644 onap_data_provider/property_tag/__init__.py create mode 100644 onap_data_provider/property_tag/base.py create mode 100644 onap_data_provider/property_tag/properties_getter.py create mode 100644 onap_data_provider/property_tag/sdc_service.py diff --git a/docs/source/index.rst b/docs/source/index.rst index 5cadde1..9360ed3 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -12,6 +12,7 @@ Welcome to ONAP data provider's documentation! description schemas + tags usage diff --git a/docs/source/tags.rst b/docs/source/tags.rst new file mode 100644 index 0000000..b16e83e --- /dev/null +++ b/docs/source/tags.rst @@ -0,0 +1,33 @@ +ONAP data provider custom YAML tags +=================================== + +In ONAP data provider we created a few custom YAML tags which could be used (and useful) in entities files. + +!join tag +--------- + +Concatenate multiple strings in YAML value. + +`!join [a, b, c]` result is `abc` + +`!join ['_', [a, b, c]]` result is `a_b_c` + +!uuid4 tag +---------- + +Generates a random UUID4 + +`!uuid4` result is random UUID4 value + +!onap_resource_property +----------------------- + +Gets the property value from the already existing ONAP resources. + +Available resources: + +* SDC service + +`!onap_resource_property service identifier service-model-name` result is a SDC service "service-model-name" model "identifier" property value + +`!onap_resource_property service identifier service-model-name 1.0` result is a SDC service "service-model-name" version 1.0 model "identifier" property value diff --git a/onap_data_provider/config_loader.py b/onap_data_provider/config_loader.py index 5757e1e..4507a14 100644 --- a/onap_data_provider/config_loader.py +++ b/onap_data_provider/config_loader.py @@ -17,11 +17,12 @@ from pathlib import Path from typing import Any, Iterator, List import yaml -from onap_data_provider.tag_handlers import join, generate_random_uuid +from onap_data_provider.tag_handlers import join, generate_random_uuid, resource_property # register custom tag handlers in yaml.SafeLoader yaml.add_constructor("!join", join, yaml.SafeLoader) yaml.add_constructor("!uuid4", generate_random_uuid, yaml.SafeLoader) +yaml.add_constructor("!onap_resource_property", resource_property, yaml.SafeLoader) class ConfigLoader: diff --git a/onap_data_provider/property_tag/__init__.py b/onap_data_provider/property_tag/__init__.py new file mode 100644 index 0000000..1e88be1 --- /dev/null +++ b/onap_data_provider/property_tag/__init__.py @@ -0,0 +1,21 @@ +"""Resource property tag package. + +There the package modules are going to be implemented. +Each module represents one resource which can be used with "!onap_resource_property" tag. + +""" +""" + Copyright 2022 Deutsche Telekom AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" \ No newline at end of file diff --git a/onap_data_provider/property_tag/base.py b/onap_data_provider/property_tag/base.py new file mode 100644 index 0000000..7ae7102 --- /dev/null +++ b/onap_data_provider/property_tag/base.py @@ -0,0 +1,73 @@ +"""Base module for property tag classes.""" +""" + Copyright 2022 Deutsche Telekom AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES 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, Type + +from onapsdk.onap_service import OnapService # type: ignore + + +class BasePropertyTagResource(ABC): + """Base property tag resource class. + + Abstract class which all resource classes should inherit from. + Subclasses has to implement `resource` property to get the valid object + using Python ONAP SDK classes. + + Subclass could also implement `__init__` method to get more attributes from + the user which uses the tag. + + """ + + def __init__(self, property_name: str) -> None: + """Init property tag resource object. + + Args: + property_name (str): Name of the property to get + + """ + self.property_name: str = property_name + + @property + def resource(self) -> OnapService: + """Resource property abstract method. + + Returns an object from which the property is going to be get. + + Raises: + NotImplementedError: That method is an abstract one + + Returns: + OnapService: Any OnapService subclass + + """ + raise NotImplementedError + + @property + def resource_property(self) -> Any: + """Resource property. + + Using `getattr` function get the property from resource. + + Returns: + Any: Property value + + Raises: + AttributeError: Resource has no property with given name. + + """ + return getattr(self.resource, self.property_name) diff --git a/onap_data_provider/property_tag/properties_getter.py b/onap_data_provider/property_tag/properties_getter.py new file mode 100644 index 0000000..df29314 --- /dev/null +++ b/onap_data_provider/property_tag/properties_getter.py @@ -0,0 +1,64 @@ +"""Properties getter module.""" +""" + Copyright 2022 Deutsche Telekom AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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, Mapping, Type, TYPE_CHECKING + +from .base import BasePropertyTagResource +from .sdc_service import SdcServicePropertyTagResource + +if TYPE_CHECKING: + from typing import Any, Mapping + + +class PropertiesGetter: + """Properties getter class. + + Uses to get values of properties from already existing ONAP resources. + + Contains a mapper to select right class to get the values from. + """ + + RESOURCES: Mapping[str, Type[BasePropertyTagResource]] = { + "service": SdcServicePropertyTagResource + } + + @classmethod + def get_property(cls, resource_type: str, *args: str) -> Any: + """Get property class method. + + Maps the input `resource_type` into `BasePropertyTagResource` class + and get it's `resource_property` property + + Args: + resource_type (str): Type of the resource - uses by a mapper + *args (str): Args to be passed to the service class init + + Raises: + ValueError: Given resource type is not supported + + Returns: + Any: Resource property value + + """ + try: + return cls.RESOURCES[resource_type](*args).resource_property + except KeyError: + msg = f"Resource type \"{resource_type}\" is not supported" + logging.error(msg) + raise ValueError(msg) + diff --git a/onap_data_provider/property_tag/sdc_service.py b/onap_data_provider/property_tag/sdc_service.py new file mode 100644 index 0000000..b8eca80 --- /dev/null +++ b/onap_data_provider/property_tag/sdc_service.py @@ -0,0 +1,52 @@ +"""SDC property module used by tag to get resource's property""" +""" + Copyright 2022 Deutsche Telekom AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" + +from typing import Optional + +from onapsdk.onap_service import OnapService # type: ignore +from onapsdk.sdc.service import Service # type: ignore + +from .base import BasePropertyTagResource + + +class SdcServicePropertyTagResource(BasePropertyTagResource): + """Class to get property from SDC service module objects.""" + + def __init__(self, property_name: str, service_name: str, version: Optional[str] = None) -> None: + """Initialize object. + + Get the name of the property to get, serivce name and optional version of the service. + + Args: + property_name (str): Property name + service_name (str): Service name + version (Optional[str], optional): Optional version. If no version is given + the latest version of the service will be loaded. Defaults to None. + """ + super().__init__(property_name) + self.service_name: str = service_name + self.version: Optional[str] = version + + @property + def resource(self) -> Service: + """Service resource. + + Returns: + Service: Service with the provided name and version. + + """ + return Service(self.service_name, version=self.version) diff --git a/onap_data_provider/tag_handlers.py b/onap_data_provider/tag_handlers.py index 1f8a21a..d8c9738 100644 --- a/onap_data_provider/tag_handlers.py +++ b/onap_data_provider/tag_handlers.py @@ -19,6 +19,8 @@ from typing import Any import yaml +from .property_tag.properties_getter import PropertiesGetter + def join(loader: yaml.SafeLoader, node: yaml.Node) -> str: """Concatinates the nodes fields for !join tag. @@ -52,3 +54,23 @@ def generate_random_uuid(*_: Any) -> str: str: randomly generated UUID """ return str(uuid.uuid4()) + + +def resource_property(loader: yaml.SafeLoader, node: yaml.Node) -> Any: + """Resource property tag method. + + Constructs a scalar from the YAML node and uses `PropertiesGetter` + to get a property value from already existing ONAP resource. + + Args: + loader (yaml.SafeLoader): YAML Safe Loader + node (yaml.Node): Node to get values from + + Raises: + ValueError: Given property type from the node is not supported by a tag + + Returns: + Any: Resource's property value + """ + resource_type, *args = loader.construct_scalar(node).split(" ") # type: ignore + return PropertiesGetter.get_property(resource_type, *args) diff --git a/tests/test_tag_handlers.py b/tests/test_tag_handlers.py index 719295f..c7c9ffc 100644 --- a/tests/test_tag_handlers.py +++ b/tests/test_tag_handlers.py @@ -1,5 +1,8 @@ from unittest.mock import patch, PropertyMock -from onap_data_provider.tag_handlers import join, generate_random_uuid + +import pytest + +from onap_data_provider.tag_handlers import join, generate_random_uuid, resource_property def test_generate_random_uuid(): @@ -13,3 +16,14 @@ def test_generate_random_uuid(): def test_join(mock_safe_loader): mock_safe_loader.construct_sequence.return_value = ["-", ["cloud", "owner", "DC1"]] assert join(mock_safe_loader, None) == "cloud-owner-DC1" + + +@patch("yaml.SafeLoader", new_callable=PropertyMock) +@patch("onap_data_provider.property_tag.sdc_service.Service") +def test_resource_property(mock_service, mock_safe_loader): + mock_safe_loader.construct_scalar.return_value = "unknown" + with pytest.raises(ValueError, match="Resource type \"unknown\" is not supported"): + resource_property(mock_safe_loader, None) + mock_service.return_value.identifier = "123" + mock_safe_loader.construct_scalar.return_value = "service identifier test_name" + assert resource_property(mock_safe_loader, None) == "123" -- cgit 1.2.3-korg