From 04d216408b1fe94337775a6e528175733d055f25 Mon Sep 17 00:00:00 2001 From: Lukasz Rajewski Date: Mon, 27 Feb 2023 14:18:18 +0100 Subject: Update K8sPlugin API - add new methods for Instance API - added option to use K8sPlugin without MSB Issue-ID: TEST-391 Signed-off-by: Lukasz Rajewski Signed-off-by: Michal Jagiello Change-Id: I35b6c8ba9574ca2385c97edde5dbb036b30aebc9 --- src/onapsdk/configuration/global_settings.py | 1 + src/onapsdk/k8s/__init__.py | 20 + src/onapsdk/k8s/connectivity_info.py | 103 ++++ src/onapsdk/k8s/definition.py | 507 +++++++++++++++++ src/onapsdk/k8s/instance.py | 621 +++++++++++++++++++++ src/onapsdk/k8s/k8splugin_service.py | 160 ++++++ src/onapsdk/k8s/region.py | 111 ++++ .../k8s/templates/msb_esr_vim_registration.json.j2 | 31 + .../multicloud_k8s_add_connectivity_info.json.j2 | 8 + .../multicloud_k8s_add_definition.json.j2 | 7 + ...d_k8s_create_configuration_for_instance.json.j2 | 6 + ...cloud_k8s_create_configuration_template.json.j2 | 4 + ...cloud_k8s_create_profile_for_definition.json.j2 | 10 + .../templates/multicloud_k8s_instantiate.json.j2 | 18 + src/onapsdk/msb/k8s/__init__.py | 5 +- src/onapsdk/msb/k8s/connectivity_info.py | 94 +--- src/onapsdk/msb/k8s/definition.py | 413 +------------- src/onapsdk/msb/k8s/instance.py | 183 +----- src/onapsdk/msb/k8s/k8splugin_msb_service.py | 20 + .../msb/templates/msb_esr_vim_registration.json.j2 | 31 - .../multicloud_k8s_add_connectivity_info.json.j2 | 8 - .../multicloud_k8s_add_definition.json.j2 | 7 - ...cloud_k8s_create_configuration_template.json.j2 | 4 - ...cloud_k8s_create_profile_for_definition.json.j2 | 8 - .../templates/multicloud_k8s_instantiate.json.j2 | 18 - src/onapsdk/utils/headers_creator.py | 3 +- src/onapsdk/utils/jinja.py | 2 +- 27 files changed, 1650 insertions(+), 753 deletions(-) create mode 100644 src/onapsdk/k8s/__init__.py create mode 100644 src/onapsdk/k8s/connectivity_info.py create mode 100644 src/onapsdk/k8s/definition.py create mode 100644 src/onapsdk/k8s/instance.py create mode 100644 src/onapsdk/k8s/k8splugin_service.py create mode 100644 src/onapsdk/k8s/region.py create mode 100644 src/onapsdk/k8s/templates/msb_esr_vim_registration.json.j2 create mode 100644 src/onapsdk/k8s/templates/multicloud_k8s_add_connectivity_info.json.j2 create mode 100644 src/onapsdk/k8s/templates/multicloud_k8s_add_definition.json.j2 create mode 100644 src/onapsdk/k8s/templates/multicloud_k8s_create_configuration_for_instance.json.j2 create mode 100644 src/onapsdk/k8s/templates/multicloud_k8s_create_configuration_template.json.j2 create mode 100644 src/onapsdk/k8s/templates/multicloud_k8s_create_profile_for_definition.json.j2 create mode 100644 src/onapsdk/k8s/templates/multicloud_k8s_instantiate.json.j2 create mode 100644 src/onapsdk/msb/k8s/k8splugin_msb_service.py delete mode 100644 src/onapsdk/msb/templates/msb_esr_vim_registration.json.j2 delete mode 100644 src/onapsdk/msb/templates/multicloud_k8s_add_connectivity_info.json.j2 delete mode 100644 src/onapsdk/msb/templates/multicloud_k8s_add_definition.json.j2 delete mode 100644 src/onapsdk/msb/templates/multicloud_k8s_create_configuration_template.json.j2 delete mode 100644 src/onapsdk/msb/templates/multicloud_k8s_create_profile_for_definition.json.j2 delete mode 100644 src/onapsdk/msb/templates/multicloud_k8s_instantiate.json.j2 (limited to 'src/onapsdk') diff --git a/src/onapsdk/configuration/global_settings.py b/src/onapsdk/configuration/global_settings.py index 7672abc..45dc249 100644 --- a/src/onapsdk/configuration/global_settings.py +++ b/src/onapsdk/configuration/global_settings.py @@ -31,6 +31,7 @@ CPS_URL = "http://portal.api.simpledemo.onap.org:8080" CPS_AUTH = ("cpsuser", "cpsr0cks!") CPS_VERSION = "v1" MSB_URL = "https://msb.api.simpledemo.onap.org:30283" +K8SPLUGIN_URL = "http://k8splugin.api.simpledemo.onap.org:30455" SDC_BE_URL = "https://sdc.api.be.simpledemo.onap.org:30204" SDC_FE_URL = "https://sdc.api.fe.simpledemo.onap.org:30207" SDC_AUTH = "Basic YWFpOktwOGJKNFNYc3pNMFdYbGhhazNlSGxjc2UyZ0F3ODR2YW9HR21KdlV5MlU=" diff --git a/src/onapsdk/k8s/__init__.py b/src/onapsdk/k8s/__init__.py new file mode 100644 index 0000000..c450319 --- /dev/null +++ b/src/onapsdk/k8s/__init__.py @@ -0,0 +1,20 @@ +"""K8s package.""" +# Copyright 2023 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 .definition import Definition, Profile, ConfigurationTemplate +from .connectivity_info import ConnectivityInfo +from .instance import InstantiationParameter, InstantiationRequest, Instance +from .instance import InstanceStatus, Configuration, ConfigurationTag +from .k8splugin_service import K8sPlugin, GVK, ResourceStatus +from .region import CloudRegionStatus, CloudRegion diff --git a/src/onapsdk/k8s/connectivity_info.py b/src/onapsdk/k8s/connectivity_info.py new file mode 100644 index 0000000..512c366 --- /dev/null +++ b/src/onapsdk/k8s/connectivity_info.py @@ -0,0 +1,103 @@ +"""Connectivity-Info 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. +from onapsdk.utils.jinja import jinja_env +from .k8splugin_service import RemovableK8sPlugin + + +class ConnectivityInfo(RemovableK8sPlugin): + """Connectivity-Info class.""" + + def __init__(self, cloud_region_id: str, + cloud_owner: str, + other_connectivity_list: dict, + kubeconfig: str) -> None: + """Connectivity-info object initialization. + + Args: + cloud_region_id (str): Cloud region ID + cloud_owner (str): Cloud owner name + other_connectivity_list (dict): Optional other connectivity list + kubeconfig (str): kubernetes cluster kubeconfig + """ + super().__init__() + self.cloud_region_id: str = cloud_region_id + self.cloud_owner: str = cloud_owner + self.other_connectivity_list: dict = other_connectivity_list + self.kubeconfig: str = kubeconfig + + @property + def url(self) -> str: + """URL address for Definition Based calls. + + Returns: + str: URL to RB Definition + + """ + return f"{self.base_url_and_version()}/connectivity-info/{self.cloud_region_id}" + + @classmethod + def get_connectivity_info_by_region_id(cls, cloud_region_id: str) -> "ConnectivityInfo": + """Get connectivity-info by its name (cloud region id). + + Args: + cloud_region_id (str): Cloud region ID + + Returns: + ConnectivityInfo: Connectivity-Info object + + """ + url: str = f"{cls.base_url_and_version()}/connectivity-info/{cloud_region_id}" + connectivity_info: dict = cls.send_message_json( + "GET", + "Get Connectivity Info", + url + ) + return cls( + connectivity_info["cloud-region"], + connectivity_info["cloud-owner"], + connectivity_info.get("other-connectivity-list"), + connectivity_info["kubeconfig"] + ) + + @classmethod + def create(cls, + cloud_region_id: str, + cloud_owner: str, + kubeconfig: bytes = None) -> "ConnectivityInfo": + """Create Connectivity Info. + + Args: + cloud_region_id (str): Cloud region ID + cloud_owner (str): Cloud owner name + kubeconfig (bytes): kubernetes cluster kubeconfig file + + Returns: + ConnectivityInfo: Created object + + """ + json_file = jinja_env().get_template("multicloud_k8s_add_connectivity_info.json.j2").render( + cloud_region_id=cloud_region_id, + cloud_owner=cloud_owner + ) + url: str = f"{cls.base_url_and_version()}/connectivity-info" + cls.send_message( + "POST", + "Create Connectivity Info", + url, + files={"file": kubeconfig, + "metadata": (None, json_file)}, + headers={} + ) + return cls.get_connectivity_info_by_region_id(cloud_region_id) diff --git a/src/onapsdk/k8s/definition.py b/src/onapsdk/k8s/definition.py new file mode 100644 index 0000000..4d86afc --- /dev/null +++ b/src/onapsdk/k8s/definition.py @@ -0,0 +1,507 @@ +"""Definition 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. +from typing import Iterator +from dataclasses import dataclass +from onapsdk.utils.jinja import jinja_env +from .k8splugin_service import GVK, RemovableK8sPlugin + + +# pylint: disable=too-many-arguments, too-few-public-methods +class DefinitionBase(RemovableK8sPlugin): + """DefinitionBase class.""" + + def __init__(self, rb_name: str, + rb_version: str) -> None: + """Definition-Base object initialization. + + Args: + rb_name (str): Definition name + rb_version (str): Definition version + """ + super().__init__() + self.rb_name: str = rb_name + self.rb_version: str = rb_version + + @property + def url(self) -> str: + """URL address for Definition Based calls. + + Returns: + str: URL to RB Definition + + """ + return f"{self.base_url_and_version()}/rb/definition/{self.rb_name}/{self.rb_version}" + + def upload_artifact(self, package: bytes = None): + """Upload artifact. + + Args: + package (bytes): Artifact to be uploaded to multicloud-k8s plugin + + """ + url: str = f"{self.url}/content" + self.send_message( + "POST", + "Upload Artifact content", + url, + data=package, + headers={} + ) + + +@dataclass +class Profile(DefinitionBase): + """Profile class.""" + + def __init__(self, rb_name: str, + rb_version: str, + profile_name: str, + namespace: str, + kubernetes_version: str, + labels: dict = None, + release_name: str = None, + extra_resource_types: list = None) -> None: + """Profile object initialization. + + Args: + rb_name (str): Definition name + rb_version (str): Definition version + profile_name (str): Name of profile + release_name (str): Release name, if release_name is not provided, + namespace (str): Namespace that service is created in + kubernetes_version (str): Required Kubernetes version + labels (dict): Extra Labels for k8s resources + extra_resource_types (list): Extra k8s resources types (GVK) for status monitoring + """ + super().__init__(rb_name, rb_version) + self.profile_name: str = profile_name + if release_name is None: + release_name = profile_name + self.release_name: str = release_name + self.namespace: str = namespace + self.kubernetes_version: str = kubernetes_version + self.labels: dict = labels + if self.labels is None: + self.labels = dict() + self.extra_resource_types: dict = extra_resource_types + if self.extra_resource_types is None: + self.extra_resource_types = dict() + + @property + def url(self) -> str: + """URL address for Profile calls. + + Returns: + str: URL to RB Profile + + """ + return f"{self.base_url_and_version()}/rb/definition/{self.rb_name}/{self.rb_version}"\ + f"/profile/{self.profile_name}" + + def update(self) -> "Profile": + """Update Profile for Definition. + + Returns: + Profile: Updated object + + """ + profile: dict = self.send_message_json( + "PUT", + "Update profile for definition", + self.url, + data=jinja_env().get_template("multicloud_k8s_create_profile_" + "for_definition.json.j2").render( + rb_name=self.rb_name, + rb_version=self.rb_version, + profile_name=self.profile_name, + release_name=self.release_name, + namespace=self.namespace, + kubernetes_version=self.kubernetes_version, + labels=self.labels, + extra_types=self.extra_resource_types + ) + ) + return self.__class__( + self.rb_name, + self.rb_version, + profile["profile-name"], + profile["namespace"], + profile.get("kubernetes-version"), + profile.get("labels"), + profile.get("release-name") + ) + + +@dataclass +class ConfigurationTemplate(DefinitionBase): + """ConfigurationTemplate class.""" + + @property + def url(self) -> str: + """URL address for ConfigurationTemplate calls. + + Returns: + str: URL to Configuration template in Multicloud-k8s API. + + """ + return f"{self.base_url_and_version()}/rb/definition/{self.rb_name}/{self.rb_version}"\ + f"/config-template/{self.template_name}" + + def __init__(self, rb_name: str, + rb_version: str, + template_name: str, + description: str, + chart_name="", + has_content=False) -> None: + """Configuration-Template object initialization. + + Args: + rb_name (str): Definition name + rb_version (str): Definition version + template_name (str): Configuration template name + description (str): Namespace that service is created in + chart_name (str): Name of the charft of uploaded content + has_content (bool): If false cotent is taken fro mthe main definition + """ + super().__init__(rb_name, rb_version) + self.template_name: str = template_name + self.description: str = description + self.chart_name: str = chart_name + self.has_content: bool = has_content + + def update(self) -> "ConfigurationTemplate": + """Update Configuration Template for Definition. + + Returns: + ConfigurationTemplate: Updated object + + """ + template: dict = self.send_message_json( + "PUT", + "Update config template for definition", + self.url, + data=jinja_env().get_template("multicloud_k8s_create_configuration_" + "template.json.j2").render( + template_name=self.template_name, + description=self.description + ) + ) + return self.__class__( + self.rb_name, + self.rb_version, + template["template-name"], + template.get("description"), + template.get("chart-name"), + template.get("has-content") + ) + + +@dataclass +class Definition(DefinitionBase): + """Definition class.""" + + profile_class = Profile + config_template_class = ConfigurationTemplate + + def __init__(self, rb_name: str, + rb_version: str, + chart_name: str, + description: str, + labels: dict) -> None: + """Definition object initialization. + + Args: + rb_name (str): Definition name + rb_version (str): Definition version + chart_name (str): Chart name, optional field, will be detected if it is not provided + description (str): Definition description + labels (str): Labels + """ + super().__init__(rb_name, rb_version) + self.chart_name: str = chart_name + self.description: str = description + self.labels: dict = labels + + @classmethod + def get_all(cls): + """Get all definitions. + + Yields: + Definition: Definition object + + """ + for definition in cls.send_message_json("GET", + "Get definitions", + f"{cls.base_url_and_version()}/rb/definition"): + yield cls( + definition["rb-name"], + definition["rb-version"], + definition.get("chart-name"), + definition.get("description"), + definition.get("labels") + ) + + @classmethod + def get_definition_by_name_version(cls, rb_name: str, rb_version: str) -> "Definition": + """Get definition by it's name and version. + + Args: + rb_name (str): definition name + rb_version (str): definition version + + Returns: + Definition: Definition object + + """ + url: str = f"{cls.base_url_and_version()}/rb/definition/{rb_name}/{rb_version}" + definition: dict = cls.send_message_json( + "GET", + "Get definition", + url + ) + return cls( + definition["rb-name"], + definition["rb-version"], + definition.get("chart-name"), + definition.get("description"), + definition.get("labels") + ) + + @classmethod + def create(cls, rb_name: str, + rb_version: str, + chart_name: str = "", + description: str = "", + labels=None) -> "Definition": + """Create Definition. + + Args: + rb_name (str): Definition name + rb_version (str): Definition version + chart_name (str): Chart name, optional field, will be detected if it is not provided + description (str): Definition description + labels (str): Labels + + Returns: + Definition: Created object + + """ + if labels is None: + labels = {} + url: str = f"{cls.base_url_and_version()}/rb/definition" + cls.send_message( + "POST", + "Create definition", + url, + data=jinja_env().get_template("multicloud_k8s_add_definition.json.j2").render( + rb_name=rb_name, + rb_version=rb_version, + chart_name=chart_name, + description=description, + labels=labels + ) + ) + return cls.get_definition_by_name_version(rb_name, rb_version) + + def update(self) -> "Definition": + """Update Definition. + + Returns: + Definition: Updated object + + """ + self.send_message( + "PUT", + "Update definition", + self.url, + data=jinja_env().get_template("multicloud_k8s_add_definition.json.j2").render( + rb_name=self.rb_name, + rb_version=self.rb_version, + chart_name=self.chart_name, + description=self.description, + labels=self.labels + ) + ) + return self.get_definition_by_name_version(self.rb_name, self.rb_version) + + def create_profile(self, profile_name: str, + namespace: str, + kubernetes_version: str, + release_name: str = None, + labels: dict = None, + extra_resource_types: list = None) -> "Profile": + """Create Profile for Definition. + + Args: + profile_name (str): Name of profile + namespace (str): Namespace that service is created in + kubernetes_version (str): Required Kubernetes version + release_name (str): Release name + labels (dict): Extra labels to assign for each + extra_resource_types (list): GVK list for extra k8s resource types to check status + + Returns: + Profile: Created object + + """ + url: str = f"{self.url}/profile" + if release_name is None: + release_name = profile_name + if labels is None: + labels = {} + if extra_resource_types is None: + extra_resource_types = [] + self.send_message( + "POST", + "Create profile for definition", + url, + data=jinja_env().get_template("multicloud_k8s_create_profile_" + "for_definition.json.j2").render( + rb_name=self.rb_name, + rb_version=self.rb_version, + profile_name=profile_name, + release_name=release_name, + namespace=namespace, + kubernetes_version=kubernetes_version, + labels=labels, + extra_types=extra_resource_types + ) + ) + return self.get_profile_by_name(profile_name) + + def get_all_profiles(self) -> Iterator["Profile"]: + """Get all profiles. + + Yields: + Profile: Profile object + + """ + url: str = f"{self.url}/profile" + + for profile in self.send_message_json("GET", + "Get profiles", + url): + yield self.profile_class( + profile["rb-name"], + profile["rb-version"], + profile["profile-name"], + profile["namespace"], + profile.get("kubernetes-version"), + profile.get("labels"), + profile.get("release-name"), + GVK.to_list_of_gvk(profile.get("extra-resource-types")) + ) + + def get_profile_by_name(self, profile_name: str) -> "Profile": + """Get profile by it's name. + + Args: + profile_name (str): profile name + + Returns: + Profile: Profile object + + """ + url: str = f"{self.url}/profile/{profile_name}" + + profile: dict = self.send_message_json( + "GET", + "Get profile", + url + ) + return self.profile_class( + profile["rb-name"], + profile["rb-version"], + profile["profile-name"], + profile["namespace"], + profile.get("kubernetes-version"), + profile.get("labels"), + profile.get("release-name"), + GVK.to_list_of_gvk(profile.get("extra-resource-types")) + ) + + def get_all_configuration_templates(self): + """Get all configuration templates. + + Yields: + ConfigurationTemplate: ConfigurationTemplate object + + """ + url: str = f"{self.url}/config-template" + + for template in self.send_message_json("GET", + "Get configuration templates", + url): + yield self.config_template_class( + self.rb_name, + self.rb_version, + template["template-name"], + template.get("description"), + template.get("chart-name"), + template.get("has-content") + ) + + def create_configuration_template(self, template_name: str, + description="") -> "ConfigurationTemplate": + """Create configuration template. + + Args: + template_name (str): Name of the template + description (str): Description + + Returns: + ConfigurationTemplate: Created object + + """ + url: str = f"{self.url}/config-template" + + self.send_message( + "POST", + "Create configuration template", + url, + data=jinja_env().get_template("multicloud_k8s_create_configuration_" + "template.json.j2").render( + template_name=template_name, + description=description + ) + ) + + return self.get_configuration_template_by_name(template_name) + + def get_configuration_template_by_name(self, template_name: str) -> "ConfigurationTemplate": + """Get configuration template. + + Args: + template_name (str): Name of the template + + Returns: + ConfigurationTemplate: object + + """ + url: str = f"{self.url}/config-template/{template_name}" + + template: dict = self.send_message_json( + "GET", + "Get Configuration template", + url + ) + return self.config_template_class( + self.rb_name, + self.rb_version, + template["template-name"], + template.get("description"), + template.get("chart-name"), + template.get("has-content") + ) diff --git a/src/onapsdk/k8s/instance.py b/src/onapsdk/k8s/instance.py new file mode 100644 index 0000000..4b93258 --- /dev/null +++ b/src/onapsdk/k8s/instance.py @@ -0,0 +1,621 @@ +"""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"]] + ) diff --git a/src/onapsdk/k8s/k8splugin_service.py b/src/onapsdk/k8s/k8splugin_service.py new file mode 100644 index 0000000..fd11ebb --- /dev/null +++ b/src/onapsdk/k8s/k8splugin_service.py @@ -0,0 +1,160 @@ +"""K8s package.""" +# Copyright 2023 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 dataclasses import dataclass +from typing import Any, Dict +from urllib.parse import urlsplit, parse_qs, urlencode, SplitResult + +from onapsdk.configuration import settings +from onapsdk.onap_service import OnapService + + +@dataclass +class GVK: + """Class to store GVK info of k8s resource. + + Contains group, version, and kind information + """ + + def __init__(self, gvk: dict) -> None: + """Gvk object initialization. + + Args: + Group (str): Group of resource + Version (str): Version of resource + Kind (str): Kind name + """ + super().__init__() + self.group: str = gvk["Group"] + self.version: str = gvk["Version"] + self.kind: str = gvk["Kind"] + + @classmethod + def to_list_of_gvk(cls, gvk_list: list) -> list: + """Convert list of dicts to list of GVK. + + Args: + gvk_list (list): list of gvk dicts + + Returns: + Converted list + + """ + final_list = [] + if gvk_list is not None: + for gvk in gvk_list: + final_list.append(cls(gvk)) + return final_list + + +@dataclass +class ResourceStatus: + """Class to store status of singular k8s resource.""" + + def __init__(self, status: dict) -> None: + """Status object initialization. + + Args: + GVK (str): GVK of resource + name (str): name of resource + status (str): full status of resource + """ + super().__init__() + self.name: str = status["name"] + self.gvk: GVK = GVK(status["GVK"]) + self.status: dict = status["status"] + + +class QueryResourceStatusMixin(ABC): # pylint: disable=too-few-public-methods + """Query resource status mixin class.""" + + def query_resource_status(self, # pylint: disable=too-many-arguments + query_url: str, + api_version: str, + kind: str, + namespace: str = None, + name: str = None, + labels: dict = None, + cloud_region: str = None) -> Dict[str, Any]: + """Call a query request. + + Args: + query_url (str): A query base url + kind (str): Kind of k8s resource + api_version (str): Api version of k8s resource + namespace (str): Namespace of k8s resource + name (str): Name of k8s resource + labels (dict): Lables of k8s resource + cloud_region (str): Cloud region ID + + Returns: + Query request response dictionary + + """ + splitted_url: SplitResult = urlsplit(query_url) + query: Dict[str, str] = parse_qs(splitted_url.query) + query["ApiVersion"] = api_version + query["Kind"] = kind + if cloud_region is not None: + query["CloudRegion"] = cloud_region + if name is not None: + query["Name"] = name + if namespace is not None: + query["Namespace"] = namespace + if labels is not None and len(labels) > 0: + query["Labels"] = ",".join([f"{key}={value}" for key, value in labels.items()]) + return self.send_message_json( + "GET", + "Query region status", + splitted_url._replace(query=urlencode(query)).geturl() + ) + + +class K8sPlugin(OnapService): + """K8sPlugin base class.""" + + base_url = settings.K8SPLUGIN_URL + api_version = "/v1" + headers = OnapService.headers + + @classmethod + def base_url_and_version(cls): + """Return base url with api version. + + Returns base url with api version + """ + return f"{K8sPlugin.base_url}{K8sPlugin.api_version}" + + +class RemovableK8sPlugin(K8sPlugin): + """K8S plugin resource which could be removed.""" + + @property + def url(self) -> str: + """Object url. + + Returns: + str: Object's url + + """ + raise NotImplementedError + + def delete(self) -> None: + """Delete k8s plugin object.""" + self.send_message( + "DELETE", + f"Delete {self.__class__.__name__}", + self.url + ) diff --git a/src/onapsdk/k8s/region.py b/src/onapsdk/k8s/region.py new file mode 100644 index 0000000..a5cf197 --- /dev/null +++ b/src/onapsdk/k8s/region.py @@ -0,0 +1,111 @@ +"""Query module.""" +# Copyright 2023 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 dataclasses import dataclass +from typing import Any, Dict + +from .k8splugin_service import K8sPlugin, QueryResourceStatusMixin, ResourceStatus +from .connectivity_info import ConnectivityInfo + + +@dataclass +class CloudRegionStatus: + """Class to store status of the Region.""" + + resource_count: str + resources_status: list + + +# pylint: disable=too-many-arguments +@dataclass +class CloudRegion(K8sPlugin, QueryResourceStatusMixin): + """Cloud region information.""" + + def __init__(self, + cloud_region_id: str, + info: ConnectivityInfo) -> None: + """Region object initialization. + + Args: + cloud_region_id (str): Cloud region ID + info (ConnectivityInfo): Connectivity Info + + """ + super().__init__() + self.cloud_region_id = cloud_region_id + self.connectivity_info = info + + @classmethod + def create(cls, + cloud_region_id: str, + cloud_owner: str = None, + kubeconfig: bytes = None) -> "CloudRegion": + """Create Cloud Region. + + Args: + cloud_region_id (str): Cloud region ID + cloud_owner (str): Cloud owner name + kubeconfig (str): kubernetes cluster kubeconfig + + Returns: + CloudRegion: Created region object + + """ + if cloud_owner is None: + cloud_owner = cloud_region_id + info = ConnectivityInfo.create(cloud_region_id, cloud_owner, kubeconfig) + return CloudRegion(cloud_region_id, info) + + def delete(self) -> None: + """Delete Region and associated Connectivity Information.""" + self.connectivity_info.delete() + + @classmethod + def get_by_region_id(cls, cloud_region_id: str) -> "CloudRegion": + """Get Region Information.""" + info = ConnectivityInfo.get_connectivity_info_by_region_id(cloud_region_id) + return CloudRegion(cloud_region_id, info) + + def query_resources(self, + kind: str, + api_version: str, + namespace: str = None, + name: str = None, + labels: dict = None) -> "CloudRegionStatus": + """Query for resources in the cloud region. + + Args: + kind (str): Kind of k8s resource + api_version (str): Api version of k8s resource + namespace (str): Namespace of k8s resource + name (str): Name of k8s resource + labels (dict): Lables of k8s resource + + Returns: + Filtered status of the cloud region + + """ + url = f"{self.base_url_and_version()}/query" + status: Dict[Any] = self.query_resource_status(url, + cloud_region=self.cloud_region_id, + api_version=api_version, + kind=kind, + namespace=namespace, + name=name, + labels=labels) + return CloudRegionStatus( + resource_count=int(status["resourceCount"]), + resources_status=[ResourceStatus(res_status) for + res_status in status["resourcesStatus"]] + ) diff --git a/src/onapsdk/k8s/templates/msb_esr_vim_registration.json.j2 b/src/onapsdk/k8s/templates/msb_esr_vim_registration.json.j2 new file mode 100644 index 0000000..ba19258 --- /dev/null +++ b/src/onapsdk/k8s/templates/msb_esr_vim_registration.json.j2 @@ -0,0 +1,31 @@ +{ + "cloudOwner": "{{ cloud_owner }}", + "cloudRegionId": "{{ cloud_region_id }}", + "cloudType": "{{ cloud_type }}", + "cloudRegionVersion": "{{ cloud_region_version }}" + {% if owner_defined_type %} + , "ownerDefinedType": "{{ owner_defined_type }}" + {% endif %} + {% if cloud_zone %} + , "cloudZone": "{{ cloud_zone }}" + {% endif %} + {% if complex_name %} + , "physicalLocationId": "{{ physical_location_id }}" + {% endif %} + {% if cloud_extra_info %} + , "cloudExtraInfo": "{{ cloud_extra_info }}" + {% endif %} + , "vimAuthInfos": + [{ + "userName": "{{ auth_info_username }}", + "password": "{{ auth_info_password }}", + "authUrl": "{{ auth_info_url }}", + "cloudDomain": "{{ auth_info_cloud_domain }}" + {% if auth_info_ssl_cacert %} + , "sslCacert": "{{ auth_info_ssl_cacert }}" + {% endif %} + {% if auth_info_ssl_insecure is not none %} + , "sslInsecure": {{ auth_info_ssl_insecure | tojson }} + {% endif %} + }] +} diff --git a/src/onapsdk/k8s/templates/multicloud_k8s_add_connectivity_info.json.j2 b/src/onapsdk/k8s/templates/multicloud_k8s_add_connectivity_info.json.j2 new file mode 100644 index 0000000..4a3dc2d --- /dev/null +++ b/src/onapsdk/k8s/templates/multicloud_k8s_add_connectivity_info.json.j2 @@ -0,0 +1,8 @@ +{ + "cloud-region" : "{{ cloud_region_id }}", + "cloud-owner" : "{{ cloud_owner }}", + "other-connectivity-list" : { + "connectivity-records" : [ + ] + } +} \ No newline at end of file diff --git a/src/onapsdk/k8s/templates/multicloud_k8s_add_definition.json.j2 b/src/onapsdk/k8s/templates/multicloud_k8s_add_definition.json.j2 new file mode 100644 index 0000000..866d577 --- /dev/null +++ b/src/onapsdk/k8s/templates/multicloud_k8s_add_definition.json.j2 @@ -0,0 +1,7 @@ +{ + "rb-name": "{{ rb_name }}", + "rb-version": "{{ rb_version }}", + "chart-name": "{{ chart_name }}", + "description": "{{ description }}", + "labels": {{ labels }} +} diff --git a/src/onapsdk/k8s/templates/multicloud_k8s_create_configuration_for_instance.json.j2 b/src/onapsdk/k8s/templates/multicloud_k8s_create_configuration_for_instance.json.j2 new file mode 100644 index 0000000..f20c452 --- /dev/null +++ b/src/onapsdk/k8s/templates/multicloud_k8s_create_configuration_for_instance.json.j2 @@ -0,0 +1,6 @@ +{ + "template-name": "{{ template_name }}", + "description": "{{ description }}", + "config-name": "{{ config_name }}", + "values": {{ override_values }} +} \ No newline at end of file diff --git a/src/onapsdk/k8s/templates/multicloud_k8s_create_configuration_template.json.j2 b/src/onapsdk/k8s/templates/multicloud_k8s_create_configuration_template.json.j2 new file mode 100644 index 0000000..61e6d2b --- /dev/null +++ b/src/onapsdk/k8s/templates/multicloud_k8s_create_configuration_template.json.j2 @@ -0,0 +1,4 @@ +{ + "template-name": "{{ template_name }}", + "description": "{{ description }}" +} \ No newline at end of file diff --git a/src/onapsdk/k8s/templates/multicloud_k8s_create_profile_for_definition.json.j2 b/src/onapsdk/k8s/templates/multicloud_k8s_create_profile_for_definition.json.j2 new file mode 100644 index 0000000..e7459b7 --- /dev/null +++ b/src/onapsdk/k8s/templates/multicloud_k8s_create_profile_for_definition.json.j2 @@ -0,0 +1,10 @@ +{ + "rb-name": "{{ rb_name }}", + "rb-version": "{{ rb_version }}", + "profile-name": "{{ profile_name }}", + "release-name": "{{ release_name }}", + "namespace": "{{ namespace }}", + "kubernetes-version": "{{ kubernetes_version }}", + "labels": {{ labels }}, + "extra-resource-types": {{ extra_types }} +} \ No newline at end of file diff --git a/src/onapsdk/k8s/templates/multicloud_k8s_instantiate.json.j2 b/src/onapsdk/k8s/templates/multicloud_k8s_instantiate.json.j2 new file mode 100644 index 0000000..fa5ef66 --- /dev/null +++ b/src/onapsdk/k8s/templates/multicloud_k8s_instantiate.json.j2 @@ -0,0 +1,18 @@ +{ + "cloud-region": "{{ cloud_region_id }}", + "profile-name": "{{ profile_name }}", + "rb-name": "{{ rb_name }}", + "rb-version": "{{ rb_version }}", + "override-values": + { + {% for override_value in override_values %} + "{{ override_value.name }}": "{{ override_value.value }}"{% if not loop.last %},{% endif %} + {% endfor %} + }, + "labels": + { + {% for label in labels %} + "{{ label.name }}": "{{ label.value }}"{% if not loop.last %},{% endif %} + {% endfor %} + } +} \ No newline at end of file diff --git a/src/onapsdk/msb/k8s/__init__.py b/src/onapsdk/msb/k8s/__init__.py index 655502d..91f4206 100644 --- a/src/onapsdk/msb/k8s/__init__.py +++ b/src/onapsdk/msb/k8s/__init__.py @@ -1,4 +1,4 @@ -"""K8s package.""" +"""K8s MSB package.""" # Copyright 2022 Orange, Deutsche Telekom AG # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,4 +14,5 @@ # limitations under the License. from .definition import Definition, Profile, ConfigurationTemplate from .connectivity_info import ConnectivityInfo -from .instance import InstantiationParameter, InstantiationRequest, Instance +from .instance import InstantiationParameter, InstantiationRequest, Instance, Configuration +from .k8splugin_msb_service import K8sPlugin diff --git a/src/onapsdk/msb/k8s/connectivity_info.py b/src/onapsdk/msb/k8s/connectivity_info.py index 71a43c1..5faf8db 100644 --- a/src/onapsdk/msb/k8s/connectivity_info.py +++ b/src/onapsdk/msb/k8s/connectivity_info.py @@ -12,94 +12,8 @@ # 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 onapsdk.utils.jinja import jinja_env -from ..msb_service import MSB +import warnings +from onapsdk.k8s.connectivity_info import ConnectivityInfo # pylint: disable=unused-import - -class ConnectivityInfo(MSB): - """Connectivity-Info class.""" - - api_version = "/api/multicloud-k8s/v1/v1" - url = f"{MSB.base_url}{api_version}/connectivity-info" - - def __init__(self, cloud_region_id: str, - cloud_owner: str, - other_connectivity_list: dict, - kubeconfig: str) -> None: - """Connectivity-info object initialization. - - Args: - cloud_region_id (str): Cloud region ID - cloud_owner (str): Cloud owner name - other_connectivity_list (dict): Optional other connectivity list - kubeconfig (str): kubernetes cluster kubeconfig - """ - super().__init__() - self.cloud_region_id: str = cloud_region_id - self.cloud_owner: str = cloud_owner - self.other_connectivity_list: dict = other_connectivity_list - self.kubeconfig: str = kubeconfig - - @classmethod - def get_connectivity_info_by_region_id(cls, cloud_region_id: str) -> "ConnectivityInfo": - """Get connectivity-info by its name (cloud region id). - - Args: - cloud_region_id (str): Cloud region ID - - Returns: - ConnectivityInfo: Connectivity-Info object - - """ - url: str = f"{cls.url}/{cloud_region_id}" - connectivity_info: dict = cls.send_message_json( - "GET", - "Get Connectivity Info", - url - ) - return cls( - connectivity_info["cloud-region"], - connectivity_info["cloud-owner"], - connectivity_info.get("other-connectivity-list"), - connectivity_info["kubeconfig"] - ) - - def delete(self) -> None: - """Delete connectivity info.""" - url: str = f"{self.url}/{self.cloud_region_id}" - self.send_message( - "DELETE", - "Delete Connectivity Info", - url - ) - - @classmethod - def create(cls, - cloud_region_id: str, - cloud_owner: str, - kubeconfig: bytes = None) -> "ConnectivityInfo": - """Create Connectivity Info. - - Args: - cloud_region_id (str): Cloud region ID - cloud_owner (str): Cloud owner name - kubeconfig (bytes): kubernetes cluster kubeconfig file - - Returns: - ConnectivityInfo: Created object - - """ - json_file = jinja_env().get_template("multicloud_k8s_add_connectivity_info.json.j2").render( - cloud_region_id=cloud_region_id, - cloud_owner=cloud_owner - ) - url: str = f"{cls.url}" - cls.send_message( - "POST", - "Create Connectivity Info", - url, - files={"file": kubeconfig, - "metadata": (None, json_file)}, - headers={} - ) - return cls.get_connectivity_info_by_region_id(cloud_region_id) +warnings.warn("onapsdk.msb.k8s.connectivity_info module is deprecated and will be removed with " + "the next version of ONAP SDK. Use onapsdk.k8s.connectivity_info") diff --git a/src/onapsdk/msb/k8s/definition.py b/src/onapsdk/msb/k8s/definition.py index 6c0def2..a8c5d6d 100644 --- a/src/onapsdk/msb/k8s/definition.py +++ b/src/onapsdk/msb/k8s/definition.py @@ -12,413 +12,8 @@ # 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 Iterator -from dataclasses import dataclass +import warnings +from onapsdk.k8s.definition import Definition, Profile, ConfigurationTemplate # pylint: disable=unused-import -from onapsdk.utils.jinja import jinja_env -from ..msb_service import MSB - - -# pylint: disable=too-many-arguments, too-few-public-methods -class DefinitionBase(MSB): - """DefinitionBase class.""" - - base_url = f"{MSB.base_url}/api/multicloud-k8s/v1/v1/rb/definition" - - def __init__(self, rb_name: str, - rb_version: str) -> None: - """Definition-Base object initialization. - - Args: - rb_name (str): Definition name - rb_version (str): Definition version - """ - super().__init__() - self.rb_name: str = rb_name - self.rb_version: str = rb_version - - @property - def url(self) -> str: - """URL address for Definition Based calls. - - Returns: - str: URL to RB Definition - - """ - return f"{self.base_url}/{self.rb_name}/{self.rb_version}" - - def delete(self) -> None: - """Delete Definition Based object.""" - self.send_message( - "DELETE", - f"Delete {self.__class__.__name__}", - self.url - ) - - def upload_artifact(self, package: bytes = None): - """Upload artifact. - - Args: - package (bytes): Artifact to be uploaded to multicloud-k8s plugin - - """ - url: str = f"{self.url}/content" - self.send_message( - "POST", - "Upload Artifact content", - url, - data=package, - headers={} - ) - - -class Definition(DefinitionBase): - """Definition class.""" - - def __init__(self, rb_name: str, - rb_version: str, - chart_name: str, - description: str, - labels: dict) -> None: - """Definition object initialization. - - Args: - rb_name (str): Definition name - rb_version (str): Definition version - chart_name (str): Chart name, optional field, will be detected if it is not provided - description (str): Definition description - labels (str): Labels - """ - super().__init__(rb_name, rb_version) - self.rb_name: str = rb_name - self.rb_version: str = rb_version - self.chart_name: str = chart_name - self.description: str = description - self.labels: dict = labels - - @classmethod - def get_all(cls): - """Get all definitions. - - Yields: - Definition: Definition object - - """ - for definition in cls.send_message_json("GET", - "Get definitions", - cls.base_url): - yield cls( - definition["rb-name"], - definition["rb-version"], - definition.get("chart-name"), - definition.get("description"), - definition.get("labels") - ) - - @classmethod - def get_definition_by_name_version(cls, rb_name: str, rb_version: str) -> "Definition": - """Get definition by it's name and version. - - Args: - rb_name (str): definition name - rb_version (str): definition version - - Returns: - Definition: Definition object - - """ - url: str = f"{cls.base_url}/{rb_name}/{rb_version}" - definition: dict = cls.send_message_json( - "GET", - "Get definition", - url - ) - return cls( - definition["rb-name"], - definition["rb-version"], - definition.get("chart-name"), - definition.get("description"), - definition.get("labels") - ) - - @classmethod - def create(cls, rb_name: str, - rb_version: str, - chart_name: str = "", - description: str = "", - labels=None) -> "Definition": - """Create Definition. - - Args: - rb_name (str): Definition name - rb_version (str): Definition version - chart_name (str): Chart name, optional field, will be detected if it is not provided - description (str): Definition description - labels (str): Labels - - Returns: - Definition: Created object - - """ - if labels is None: - labels = {} - url: str = f"{cls.base_url}" - cls.send_message( - "POST", - "Create definition", - url, - data=jinja_env().get_template("multicloud_k8s_add_definition.json.j2").render( - rb_name=rb_name, - rb_version=rb_version, - chart_name=chart_name, - description=description, - labels=labels - ) - ) - return cls.get_definition_by_name_version(rb_name, rb_version) - - def create_profile(self, profile_name: str, - namespace: str, - kubernetes_version: str, - release_name=None) -> "Profile": - """Create Profile for Definition. - - Args: - profile_name (str): Name of profile - namespace (str): Namespace that service is created in - kubernetes_version (str): Required Kubernetes version - release_name (str): Release name - - Returns: - Profile: Created object - - """ - url: str = f"{self.url}/profile" - if release_name is None: - release_name = profile_name - self.send_message( - "POST", - "Create profile for definition", - url, - data=jinja_env().get_template("multicloud_k8s_create_profile_" - "for_definition.json.j2").render( - rb_name=self.rb_name, - rb_version=self.rb_version, - profile_name=profile_name, - release_name=release_name, - namespace=namespace, - kubernetes_version=kubernetes_version - ) - ) - return self.get_profile_by_name(profile_name) - - def get_all_profiles(self) -> Iterator["Profile"]: - """Get all profiles. - - Yields: - Profile: Profile object - - """ - url: str = f"{self.url}/profile" - - for profile in self.send_message_json("GET", - "Get profiles", - url): - yield Profile( - profile["rb-name"], - profile["rb-version"], - profile["profile-name"], - profile["namespace"], - profile.get("kubernetes-version"), - profile.get("labels"), - profile.get("release-name") - ) - - def get_profile_by_name(self, profile_name: str) -> "Profile": - """Get profile by it's name. - - Args: - profile_name (str): profile name - - Returns: - Profile: Profile object - - """ - url: str = f"{self.url}/profile/{profile_name}" - - profile: dict = self.send_message_json( - "GET", - "Get profile", - url - ) - return Profile( - profile["rb-name"], - profile["rb-version"], - profile["profile-name"], - profile["namespace"], - profile.get("kubernetes-version"), - profile.get("labels"), - profile.get("release-name") - ) - - def get_all_configuration_templates(self): - """Get all configuration templates. - - Yields: - ConfigurationTemplate: ConfigurationTemplate object - - """ - url: str = f"{self.url}/config-template" - - for template in self.send_message_json("GET", - "Get configuration templates", - url): - yield ConfigurationTemplate( - self.rb_name, - self.rb_version, - template["template-name"], - template.get("description") - ) - - def create_configuration_template(self, template_name: str, - description="") -> "ConfigurationTemplate": - """Create configuration template. - - Args: - template_name (str): Name of the template - description (str): Description - - Returns: - ConfigurationTemplate: Created object - - """ - url: str = f"{self.url}/config-template" - - self.send_message( - "POST", - "Create configuration template", - url, - data=jinja_env().get_template("multicloud_k8s_create_configuration_" - "template.json.j2").render( - template_name=template_name, - description=description - ) - ) - - return self.get_configuration_template_by_name(template_name) - - def get_configuration_template_by_name(self, template_name: str) -> "ConfigurationTemplate": - """Get configuration template. - - Args: - template_name (str): Name of the template - - Returns: - ConfigurationTemplate: object - - """ - url: str = f"{self.url}/config-template/{template_name}" - - template: dict = self.send_message_json( - "GET", - "Get Configuration template", - url - ) - return ConfigurationTemplate( - self.rb_name, - self.rb_version, - template["template-name"], - template.get("description") - ) - - -class ProfileBase(DefinitionBase): - """ProfileBase class.""" - - def __init__(self, rb_name: str, - rb_version: str, - profile_name: str) -> None: - """Profile-Base object initialization. - - Args: - rb_name (str): Definition name - rb_version (str): Definition version - profile_name (str): Name of profile - """ - super().__init__(rb_name, rb_version) - self.rb_name: str = rb_name - self.rb_version: str = rb_version - self.profile_name: str = profile_name - - @property - def url(self) -> str: - """URL address for Profile calls. - - Returns: - str: URL to RB Profile - - """ - return f"{super().url}/profile/{self.profile_name}" - - -@dataclass -class Profile(ProfileBase): - """Profile class.""" - - def __init__(self, rb_name: str, - rb_version: str, - profile_name: str, - namespace: str, - kubernetes_version: str, - labels=None, - release_name=None) -> None: - """Profile object initialization. - - Args: - rb_name (str): Definition name - rb_version (str): Definition version - profile_name (str): Name of profile - release_name (str): Release name, if release_name is not provided, - namespace (str): Namespace that service is created in - kubernetes_version (str): Required Kubernetes version - labels (dict): Labels - """ - super().__init__(rb_name, rb_version, profile_name) - if release_name is None: - release_name = profile_name - self.release_name: str = release_name - self.namespace: str = namespace - self.kubernetes_version: str = kubernetes_version - self.labels: dict = labels - if self.labels is None: - self.labels = dict() - - -class ConfigurationTemplate(DefinitionBase): - """ConfigurationTemplate class.""" - - @property - def url(self) -> str: - """URL address for ConfigurationTemplate calls. - - Returns: - str: URL to Configuration template in Multicloud-k8s API. - - """ - return f"{super().url}/config-template/{self.template_name}" - - def __init__(self, rb_name: str, - rb_version: str, - template_name: str, - description="") -> None: - """Configuration-Template object initialization. - - Args: - rb_name (str): Definition name - rb_version (str): Definition version - template_name (str): Configuration template name - description (str): Namespace that service is created in - """ - super().__init__(rb_name, rb_version) - self.template_name: str = template_name - self.description: str = description +warnings.warn("onapsdk.msb.k8s.definition module is deprecated and will be removed with " + "the next version of ONAP SDK. Use onapsdk.k8s.definition") diff --git a/src/onapsdk/msb/k8s/instance.py b/src/onapsdk/msb/k8s/instance.py index 196b9d2..d85e8c6 100644 --- a/src/onapsdk/msb/k8s/instance.py +++ b/src/onapsdk/msb/k8s/instance.py @@ -12,179 +12,14 @@ # 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 Iterator -from dataclasses import dataclass -from onapsdk.msb import MSB -from onapsdk.utils.jinja import jinja_env +import warnings +from onapsdk.k8s.instance import ( # pylint: disable=unused-import + InstantiationParameter, + InstantiationRequest, + Instance, + Configuration +) - -# 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 Instance(MSB): - """Instance class.""" - - base_url = f"{MSB.base_url}/api/multicloud-k8s/v1/v1/instance" - - 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__() - self.instance_id: str = instance_id - self.namespace: str = namespace - self.request: InstantiationRequest = request - self.resources: dict = resources - self.override_values: dict = override_values - - @property - def url(self) -> str: - """URL address. - - Returns: - str: URL to Instance - - """ - return f"{self.base_url}/{self.instance_id}" - - @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", - cls.base_url): - yield cls( - instance_id=resource["id"], - namespace=resource["namespace"], - request=InstantiationRequest(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}/{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=InstantiationRequest(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}" - 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=InstantiationRequest(response["request"]), - resources=response["resources"], - override_values=response.get("override-values") - ) - - def delete(self) -> None: - """Delete Instance object.""" - self.send_message( - "DELETE", - f"Delete {self.instance_id} instance", - self.url - ) +warnings.warn("onapsdk.msb.k8s.instance module is deprecated and will be removed with " + "the next version of ONAP SDK. Use onapsdk.k8s.instance") diff --git a/src/onapsdk/msb/k8s/k8splugin_msb_service.py b/src/onapsdk/msb/k8s/k8splugin_msb_service.py new file mode 100644 index 0000000..d1e38df --- /dev/null +++ b/src/onapsdk/msb/k8s/k8splugin_msb_service.py @@ -0,0 +1,20 @@ +"""K8s package.""" +# Copyright 2023 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 warnings +from onapsdk.k8s.k8splugin_service import K8sPlugin # pylint: disable=unused-import + +warnings.warn("onapsdk.msb.k8s.k8splugin_service module is deprecated and will be removed with " + "the next version of ONAP SDK. Use onapsdk.k8s.k8splugin_service") diff --git a/src/onapsdk/msb/templates/msb_esr_vim_registration.json.j2 b/src/onapsdk/msb/templates/msb_esr_vim_registration.json.j2 deleted file mode 100644 index ba19258..0000000 --- a/src/onapsdk/msb/templates/msb_esr_vim_registration.json.j2 +++ /dev/null @@ -1,31 +0,0 @@ -{ - "cloudOwner": "{{ cloud_owner }}", - "cloudRegionId": "{{ cloud_region_id }}", - "cloudType": "{{ cloud_type }}", - "cloudRegionVersion": "{{ cloud_region_version }}" - {% if owner_defined_type %} - , "ownerDefinedType": "{{ owner_defined_type }}" - {% endif %} - {% if cloud_zone %} - , "cloudZone": "{{ cloud_zone }}" - {% endif %} - {% if complex_name %} - , "physicalLocationId": "{{ physical_location_id }}" - {% endif %} - {% if cloud_extra_info %} - , "cloudExtraInfo": "{{ cloud_extra_info }}" - {% endif %} - , "vimAuthInfos": - [{ - "userName": "{{ auth_info_username }}", - "password": "{{ auth_info_password }}", - "authUrl": "{{ auth_info_url }}", - "cloudDomain": "{{ auth_info_cloud_domain }}" - {% if auth_info_ssl_cacert %} - , "sslCacert": "{{ auth_info_ssl_cacert }}" - {% endif %} - {% if auth_info_ssl_insecure is not none %} - , "sslInsecure": {{ auth_info_ssl_insecure | tojson }} - {% endif %} - }] -} diff --git a/src/onapsdk/msb/templates/multicloud_k8s_add_connectivity_info.json.j2 b/src/onapsdk/msb/templates/multicloud_k8s_add_connectivity_info.json.j2 deleted file mode 100644 index 4a3dc2d..0000000 --- a/src/onapsdk/msb/templates/multicloud_k8s_add_connectivity_info.json.j2 +++ /dev/null @@ -1,8 +0,0 @@ -{ - "cloud-region" : "{{ cloud_region_id }}", - "cloud-owner" : "{{ cloud_owner }}", - "other-connectivity-list" : { - "connectivity-records" : [ - ] - } -} \ No newline at end of file diff --git a/src/onapsdk/msb/templates/multicloud_k8s_add_definition.json.j2 b/src/onapsdk/msb/templates/multicloud_k8s_add_definition.json.j2 deleted file mode 100644 index 866d577..0000000 --- a/src/onapsdk/msb/templates/multicloud_k8s_add_definition.json.j2 +++ /dev/null @@ -1,7 +0,0 @@ -{ - "rb-name": "{{ rb_name }}", - "rb-version": "{{ rb_version }}", - "chart-name": "{{ chart_name }}", - "description": "{{ description }}", - "labels": {{ labels }} -} diff --git a/src/onapsdk/msb/templates/multicloud_k8s_create_configuration_template.json.j2 b/src/onapsdk/msb/templates/multicloud_k8s_create_configuration_template.json.j2 deleted file mode 100644 index 61e6d2b..0000000 --- a/src/onapsdk/msb/templates/multicloud_k8s_create_configuration_template.json.j2 +++ /dev/null @@ -1,4 +0,0 @@ -{ - "template-name": "{{ template_name }}", - "description": "{{ description }}" -} \ No newline at end of file diff --git a/src/onapsdk/msb/templates/multicloud_k8s_create_profile_for_definition.json.j2 b/src/onapsdk/msb/templates/multicloud_k8s_create_profile_for_definition.json.j2 deleted file mode 100644 index 5ea2de1..0000000 --- a/src/onapsdk/msb/templates/multicloud_k8s_create_profile_for_definition.json.j2 +++ /dev/null @@ -1,8 +0,0 @@ -{ - "rb-name": "{{ rb_name }}", - "rb-version": "{{ rb_version }}", - "profile-name": "{{ profile_name }}", - "release-name": "{{ release_name }}", - "namespace": "{{ namespace }}", - "kubernetes-version": "{{ kubernetes_version }}" -} \ No newline at end of file diff --git a/src/onapsdk/msb/templates/multicloud_k8s_instantiate.json.j2 b/src/onapsdk/msb/templates/multicloud_k8s_instantiate.json.j2 deleted file mode 100644 index fa5ef66..0000000 --- a/src/onapsdk/msb/templates/multicloud_k8s_instantiate.json.j2 +++ /dev/null @@ -1,18 +0,0 @@ -{ - "cloud-region": "{{ cloud_region_id }}", - "profile-name": "{{ profile_name }}", - "rb-name": "{{ rb_name }}", - "rb-version": "{{ rb_version }}", - "override-values": - { - {% for override_value in override_values %} - "{{ override_value.name }}": "{{ override_value.value }}"{% if not loop.last %},{% endif %} - {% endfor %} - }, - "labels": - { - {% for label in labels %} - "{{ label.name }}": "{{ label.value }}"{% if not loop.last %},{% endif %} - {% endfor %} - } -} \ No newline at end of file diff --git a/src/onapsdk/utils/headers_creator.py b/src/onapsdk/utils/headers_creator.py index ae03a38..0f1bc79 100644 --- a/src/onapsdk/utils/headers_creator.py +++ b/src/onapsdk/utils/headers_creator.py @@ -223,7 +223,8 @@ def headers_sdc_artifact_upload(base_header: Dict[str, str], data: str): headers["Accept"] = "application/json, text/plain, */*" headers["Accept-Encoding"] = "gzip, deflate, br" headers["Content-Type"] = "application/json; charset=UTF-8" - md5_content = hashlib.new('md5', data.encode('UTF-8'), usedforsecurity=False).hexdigest() + md5_content = hashlib.new('md5', data.encode('UTF-8'), + usedforsecurity=False).hexdigest() # nosec content = base64.b64encode(md5_content.encode('ascii')).decode('UTF-8') headers["Content-MD5"] = content return headers diff --git a/src/onapsdk/utils/jinja.py b/src/onapsdk/utils/jinja.py index fe59eae..8af130c 100644 --- a/src/onapsdk/utils/jinja.py +++ b/src/onapsdk/utils/jinja.py @@ -39,7 +39,7 @@ def jinja_env() -> Environment: PackageLoader("onapsdk.aai"), PackageLoader("onapsdk.cds"), PackageLoader("onapsdk.clamp"), - PackageLoader("onapsdk.msb"), + PackageLoader("onapsdk.k8s"), PackageLoader("onapsdk.nbi"), PackageLoader("onapsdk.sdc"), PackageLoader("onapsdk.sdnc"), -- cgit 1.2.3-korg