"""Instantiation module.""" # Copyright 2022 Orange, Deutsche Telekom AG # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json from typing import Any, Dict, Iterator from dataclasses import dataclass from onapsdk.utils.jinja import jinja_env from .k8splugin_service import QueryResourceStatusMixin, ResourceStatus, RemovableK8sPlugin # pylint: disable=too-many-arguments @dataclass class InstantiationRequest: """Instantiation Request class.""" def __init__(self, request: dict) -> None: """Request object initialization. Args: cloud_region_id (str): Cloud region ID profile_name (str): Name of profile rb_name (str): Definition name rb_version (str): Definition version override_values (dict): Optional parameters labels (dict): Optional labels """ super().__init__() self.cloud_region_id: str = request["cloud-region"] self.profile_name: str = request["profile-name"] self.rb_name: str = request["rb-name"] self.rb_version: str = request["rb-version"] self.override_values: dict = request["override-values"] self.labels: dict = request["labels"] @dataclass class InstantiationParameter: """Class to store instantiation parameters used to pass override_values and labels. Contains two values: name of parameter and it's value """ name: str value: str class InstanceBase(RemovableK8sPlugin): """InstanceBase class.""" def __init__(self, instance_id: str) -> None: """Instance-Base object initialization. Args: instance_id (str): instance ID """ super().__init__() self.instance_id: str = instance_id @property def url(self) -> str: """URL address. Returns: str: URL to Instance """ return f"{self.base_url_and_version()}/instance/{self.instance_id}" @dataclass class ConfigurationTag: """Class to store configuration tag information. Contains two values: name of tag and version of associated configuration """ config_tag: str config_version: str class Configuration(InstanceBase): """Configuration class.""" def __init__(self, instance_id: str, config_name: str, template_name: str, description: str, config_version: str, config_tag: str, values: dict = None) -> None: """Initialize Configuration object. Args: instance_id (str): instance ID config_name (str): Name of the configuration template_name (str): Name of the template description (str): Description config_version (str): Config version config_tag (str): Config tag values (dict): Overrides values used to create configuration """ super().__init__(instance_id) self.name = config_name self.template_name = template_name self.config_version = str(config_version) self.config_tag = config_tag self.description = description self.values = values @property def url(self) -> str: """URL address for Configuration calls. Returns: str: URL to Configuration """ return f"{self.base_url_and_version()}/instance/{self.instance_id}/config/{self.name}" def rollback_to(self, config_version: str, config_tag: str) -> None: """Rollback configuration to specific version. Args: config_version (str): version of configuration config_tag (str): tag of configuration """ url: str = f"{self.url}/rollback" params = dict() if config_version is not None: params["config-version"] = config_version if config_tag is not None: params["config-tag"] = config_tag self.send_message_json( "POST", "Rollback Configuration", url, data=json.dumps(params), headers={} ) self.config_version = config_version self.config_tag = config_tag def update(self, override_values: dict) -> "Configuration": """Update configuration. Args: override_values (dict): Override values Returns: Configuration: Updated object """ body = json.dumps(override_values) config: dict = self.send_message_json( "PUT", "Update configuration", self.url, data=jinja_env().get_template("multicloud_k8s_create_configuration_for_" "instance.json.j2").render( config_name=self.name, template_name=self.template_name, description=self.description, override_values=body ) ) return self.get_config_by_version(str(config["config-version"])) def tag_config_version(self, config_tag: str) -> None: """Tag configuration. Args: config_tag (str): Tag name of the configuration version """ body = json.dumps({"tag-name": config_tag}) self.send_message( "POST", "Tag configuration", f"{self.url}/tagit", data=body ) def get_config_by_version(self, config_version: str) -> "Configuration": """Get configuration by version. Args: config_version (str): Config version Returns: Configuration: object """ config: dict = self.send_message_json( "GET", "Get Configuration", f"{self.url}/version/{config_version}" ) return self.__class__( self.instance_id, self.name, config["template-name"], config["description"], config["config-version"], config["config-tag"], config["values"] ) def get_config_versions(self) -> Iterator["Configuration"]: """Get List of configuration versions. Returns: List of versions for configuration """ for config in self.send_message_json("GET", "Get config versions", f"{self.url}/version"): yield self.__class__( self.instance_id, self.name, config["template-name"], config["description"], config["config-version"], config["config-tag"], config["values"] ) def get_config_by_tag(self, config_tag: str) -> "Configuration": """Get configuration by tag. Args: config_tag (str): Name of tag Returns: Configuration: object """ config: dict = self.send_message_json( "GET", "Get Configuration", f"{self.url}/tag/{config_tag}" ) return self.__class__( self.instance_id, self.name, config["template-name"], config["description"], config["config-version"], config["config-tag"], config["values"] ) def get_config_tags(self) -> Iterator["ConfigurationTag"]: """Get List of Tags. Returns: List of tags for configuration """ for tag in self.send_message_json("GET", "Get config tags", f"{self.url}/tag"): yield ConfigurationTag( config_tag=tag["config-tag"], config_version=str(tag["config-version"]) ) def create_delete_version(self) -> "Configuration": """Create delete version of configuration.""" url: str = f"{self.url}/delete" config: dict = self.send_message_json( "POST", "Delete Resources Configuration", url ) return self.__class__( self.instance_id, self.name, config["template-name"], config["description"], config["config-version"], config["config-tag"], config["values"] ) def delete_without_resources(self) -> None: """Delete Instance Based object.""" self.send_message( "DELETE", f"Delete {self.__class__.__name__}", f"{self.url}?deleteConfigOnly=true" ) @dataclass class InstanceStatus: """Class to store status of the Instance.""" instance_id: str request: InstantiationRequest resource_count: str ready: bool resources_status: list class Instance(InstanceBase, QueryResourceStatusMixin): """Instance class.""" config_class = Configuration request_class = InstantiationRequest def __init__(self, instance_id: str, namespace: str, request: InstantiationRequest, resources: dict = None, override_values: dict = None) -> None: """Instance object initialization. Args: instance_id (str): instance ID namespace (str): namespace that instance is created in request (InstantiationRequest): datails of the instantiation request resources (dict): Created resources override_values (dict): Optional values """ super().__init__(instance_id) self.namespace: str = namespace self.request: InstantiationRequest = request self.resources: dict = resources self.override_values: dict = override_values @classmethod def get_all(cls) -> Iterator["Instance"]: """Get all instantiated Kubernetes resources. Yields: Instantiation: Instantiation object """ for resource in cls.send_message_json("GET", "Get Kubernetes resources", f"{cls.base_url_and_version()}/instance"): yield cls( instance_id=resource["id"], namespace=resource["namespace"], request=cls.request_class(resource["request"]) ) @classmethod def get_by_id(cls, instance_id: str) -> "Instance": """Get Kubernetes resource by id. Args: instance_id (str): instance ID Returns: Instantiation: Instantiation object """ url: str = f"{cls.base_url_and_version()}/instance/{instance_id}" resource: dict = cls.send_message_json( "GET", "Get Kubernetes resource by id", url ) return cls( instance_id=resource["id"], namespace=resource["namespace"], request=cls.request_class(resource["request"]), resources=resource["resources"], override_values=resource.get("override-values") ) @classmethod def create(cls, cloud_region_id: str, profile_name: str, rb_name: str, rb_version: str, override_values: dict = None, labels: dict = None) -> "Instance": """Create Instance. Args: cloud_region_id (str): Cloud region ID profile_name (str): Name of profile to be instantiated rb_name: (bytes): Definition name rb_version (str): Definition version override_values (dict): List of optional override values labels (dict): List of optional labels Returns: Instance: Created object """ if labels is None: labels = {} if override_values is None: override_values = {} url: str = f"{cls.base_url_and_version()}/instance" response: dict = cls.send_message_json( "POST", "Create Instance", url, data=jinja_env().get_template("multicloud_k8s_instantiate.json.j2").render( cloud_region_id=cloud_region_id, profile_name=profile_name, rb_name=rb_name, rb_version=rb_version, override_values=override_values, labels=labels), headers={} ) return cls( instance_id=response["id"], namespace=response["namespace"], request=cls.request_class(response["request"]), resources=response["resources"], override_values=response.get("override-values") ) def upgrade(self, cloud_region_id: str, profile_name: str, rb_name: str, rb_version: str, override_values: dict = None, labels: dict = None) -> "Instance": """Upgrade Instance. Args: cloud_region_id (str): Cloud region ID profile_name (str): Name of profile to be instantiated rb_name: (bytes): Definition name rb_version (str): Definition version override_values (dict): List of optional override values labels (dict): List of optional labels Returns: Instance: Created object """ if labels is None: labels = {} if override_values is None: override_values = {} url: str = f"{self.url}/upgrade" response: dict = self.send_message_json( "POST", "Upgrade Instance", url, data=jinja_env().get_template("multicloud_k8s_instantiate.json.j2").render( cloud_region_id=cloud_region_id, profile_name=profile_name, rb_name=rb_name, rb_version=rb_version, override_values=override_values, labels=labels), headers={} ) return self.__class__( instance_id=response["id"], namespace=response["namespace"], request=self.request_class(response["request"]), resources=response["resources"], override_values=response.get("override-values") ) def create_configuration(self, config_name: str, template_name: str, override_values: dict = None, description="") -> "Configuration": """Create configuration instance. Args: config_name (str): Name of the configuration template_name (str): Name of the template description (str): Description override_values (dict): Override values Returns: Configuration: Created object """ url: str = f"{self.url}/config" body = json.dumps(override_values) self.send_message( "POST", "Create configuration", url, data=jinja_env().get_template("multicloud_k8s_create_configuration_for_" "instance.json.j2").render( config_name=config_name, template_name=template_name, description=description, override_values=body ) ) return self.get_configuration_by_name(config_name) def get_all_configurations(self) -> Iterator["Configuration"]: """Get List of configurations. Returns: List of configurations """ for config in self.send_message_json("GET", "Get configurations", f"{self.url}/config"): yield self.config_class( self.instance_id, config["config-name"], config["template-name"], config["description"], config["config-version"], config["config-tag"], config["values"] ) def get_configuration_by_name(self, config_name: str) -> "Configuration": """Get configuration. Args: config_name (str): Name of the configuration Returns: Configuration: object """ url: str = f"{self.url}/config/{config_name}" config: dict = self.send_message_json( "GET", "Get Configuration", url ) return self.config_class( self.instance_id, config_name, config["template-name"], config["description"], config["config-version"], config["config-tag"], config["values"] ) def get_status(self) -> "InstanceStatus": """Get instance status. Returns: Status of the instance """ status: dict = self.send_message_json( "GET", "Get status", f"{self.url}/status" ) resources_status = [] for res_status in status["resourcesStatus"]: resources_status.append(ResourceStatus(res_status)) return InstanceStatus( self.instance_id, request=self.request_class(status["request"]), resource_count=int(status["resourceCount"]), ready=bool(status["ready"]), resources_status=resources_status ) def query_status(self, kind: str, api_version: str, name: str = None, labels: dict = None) -> "InstanceStatus": """Query instance status. Args: kind (str): Kind of k8s resource api_version (str): Api version of k8s resource name (str): Name of k8s resource labels (dict): Lables of k8s resource Returns: Filtered status of the instance """ url = f"{self.url}/query" status: Dict[Any] = self.query_resource_status(url, api_version=api_version, kind=kind, name=name, labels=labels) return InstanceStatus( self.instance_id, request=self.request_class(status["request"]), resource_count=int(status["resourceCount"]), ready=bool(status["ready"]), resources_status=[ResourceStatus(res_status) for res_status in status["resourcesStatus"]] )