From 920aa926a2a78dc7f1ba63e648d124a405962017 Mon Sep 17 00:00:00 2001 From: Eli Halych Date: Thu, 28 Jan 2021 16:11:39 +0000 Subject: Wrapper for simulators Implemented using Avionix. Supports Helm 3 only. The local directory path was defined relative to the package. Remote charts that are described locally are used. Starting the simulator is provided as a regular HTTP or HTTPS request. Issue-ID: INT-1829 Signed-off-by: Eli Halych Change-Id: Ia17c4043bedd853bf2c068e53d51cd2808a3c0db --- requirements.txt | 3 +- src/onaptests/configuration/settings.py | 1 + src/onaptests/steps/wrapper/__init__.py | 0 src/onaptests/steps/wrapper/helm_charts.py | 93 +++++++++++++++++++++++++++ src/onaptests/steps/wrapper/start.py | 64 ++++++++++++++++++ src/onaptests/templates/helm_charts/README.md | 31 +++++++++ src/onaptests/utils/exceptions.py | 4 ++ src/onaptests/utils/simulators.py | 13 ++++ 8 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 src/onaptests/steps/wrapper/__init__.py create mode 100644 src/onaptests/steps/wrapper/helm_charts.py create mode 100644 src/onaptests/steps/wrapper/start.py create mode 100755 src/onaptests/templates/helm_charts/README.md create mode 100644 src/onaptests/utils/simulators.py diff --git a/requirements.txt b/requirements.txt index fec6816..945b8ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ openstacksdk onapsdk==7.4.0 jinja2 kubernetes -docker \ No newline at end of file +docker +avionix==0.4.4 \ No newline at end of file diff --git a/src/onaptests/configuration/settings.py b/src/onaptests/configuration/settings.py index ba4e163..609ca66 100644 --- a/src/onaptests/configuration/settings.py +++ b/src/onaptests/configuration/settings.py @@ -41,6 +41,7 @@ CLEANUP_FLAG = False REPORTING_FILE_PATH = "/tmp/reporting.html" K8S_REGION_TYPE = "k8s" +TILLER_HOST = "localhost" K8S_CONFIG = None # None means it will use default config (~/.kube/config) K8S_NAMESPACE = "onap" # Kubernetes namespace #SOCK_HTTP = "socks5h://127.0.0.1:8091" diff --git a/src/onaptests/steps/wrapper/__init__.py b/src/onaptests/steps/wrapper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/onaptests/steps/wrapper/helm_charts.py b/src/onaptests/steps/wrapper/helm_charts.py new file mode 100644 index 0000000..4482683 --- /dev/null +++ b/src/onaptests/steps/wrapper/helm_charts.py @@ -0,0 +1,93 @@ +"""Basic container commands to Docker.""" +import yaml +from avionix import ChartBuilder, ChartDependency, ChartInfo +from avionix.errors import HelmError +from onaptests.steps.base import BaseStep +from onaptests.utils.simulators import get_local_dir +from onaptests.utils.exceptions import ( + EnvironmentPreparationException, + EnvironmentCleanupException) + + + +class HelmChartStep(BaseStep): + """Basic operations on a docker container.""" + + def __init__(self, + cleanup: bool = False, + chart_info_file: str = None) -> None: + """Setup Helm chart details. + + Arguments: + cleanup (bool): cleanup after execution. Defaults to False. + chart_info_file (str): description file of a chart. Default to None. + """ + chart_info = None + dependencies = [] + + super().__init__(cleanup=cleanup) + + chart_info_path = get_local_dir() / chart_info_file + + try: + with open(chart_info_path, 'r') as stream: + chart_info = yaml.safe_load(stream) + except IOError as err: + msg = f"{chart_info_file} not found." + raise EnvironmentPreparationException(msg) from err + + + try: + for dependency in chart_info["dependencies"]: + dep = ChartDependency( + name=dependency["name"], + version=dependency["version"], + repository=dependency["repository"], + local_repo_name=dependency["local_repo_name"], + values=dependency["values"]) + dependencies.append(dep) + + self.builder = ChartBuilder( + chart_info=ChartInfo( + api_version=chart_info["api_version"], + name=chart_info["chart_name"], + version=chart_info["version"], # SemVer 2 version + app_version=chart_info["app_version"], + dependencies=dependencies + ), + kubernetes_objects=[], + keep_chart=False + ) + except KeyError as err: + msg = f"{chart_info_file} does not contain required keys." + raise EnvironmentPreparationException(msg) from err + + @property + def description(self) -> str: + """Step description.""" + return "Execute Helm charts." + + @property + def component(self) -> str: + """Component name.""" + return "Environment" + + @BaseStep.store_state + def execute(self) -> None: + """Install helm release.""" + super().execute() + try: + self.builder.install_chart({"dependency-update": None}) + except HelmError as err: + msg = "Error during helm release installation." + raise EnvironmentPreparationException(msg) from err + + + def cleanup(self) -> None: + """Uninstall helm release.""" + try: + self.builder.uninstall_chart() + except HelmError as err: + msg = "Error during helm release deletion." + raise EnvironmentCleanupException(msg) from err + super().cleanup() diff --git a/src/onaptests/steps/wrapper/start.py b/src/onaptests/steps/wrapper/start.py new file mode 100644 index 0000000..18ba5df --- /dev/null +++ b/src/onaptests/steps/wrapper/start.py @@ -0,0 +1,64 @@ +"""Start simulators via simulators' API.""" +from typing import Union, Optional, Dict +import requests +from onaptests.steps.base import BaseStep +from onaptests.utils.exceptions import TestConfigurationException + +class SimulatorStartStep(BaseStep): + """Basic operations on a docker container.""" + + def __init__(self, # pylint: disable=R0913 + cleanup: bool = False, + https: bool = False, + host: str = None, + port: Union[int, str] = None, + endpoint: Optional[str] = "", + method: str = "GET", + data: Dict = None) -> None: + """Prepare request data and details. + + Arguments: + cleanup (bool): + determines if cleanup action should be called. + Defaults to False. + https (bool): use https or http. Defaults to False. + host (str): IP or hostname. Defaults to None. + port (Union[int, str]): port number. Defaults to None. + endpoint (str): + additional endpoint if applicable. + Defautls to "". + method (str): + GET or POST strings, case insensitive. + Defaults tp GET. + data (Dict): + parameters, that request's post() or get() takes, besides url. + For example, {"json": {}, ...}. Defaults to None. + """ + if not host and not port: + raise TestConfigurationException("Provide host and/or port.") + + super().__init__(cleanup=cleanup) + + default_port = "443" if https else "80" + protocol = "https" if https else "http" + endpoint = endpoint[1:] if endpoint.startswith("/") else endpoint + + self.method = method + self.data = data if data else {} + self.url = f"{protocol}://{host}:{port or default_port}/{endpoint}" + + @property + def description(self) -> str: + """Step description.""" + return "Send commands to the simulator application." + + @property + def component(self) -> str: + """Component name.""" + return "Environment" + + @BaseStep.store_state + def execute(self) -> None: + """Send a start command to the simulator application.""" + super().execute() + requests.request(self.method.upper(), self.url, **self.data) diff --git a/src/onaptests/templates/helm_charts/README.md b/src/onaptests/templates/helm_charts/README.md new file mode 100755 index 0000000..bf76ee2 --- /dev/null +++ b/src/onaptests/templates/helm_charts/README.md @@ -0,0 +1,31 @@ +# Local helm chart directory + +It is adviced that a remote repository is used for simulators, to reduce local +complexity and avoid mistakes related to the duplicate code, submodules etc. + +Place a .yaml file in this folder and mention it during HelmChartStep +initialization. + +How a chart info .yaml file would look like: + +```yaml +api_version: "v1" +app_version: "3.11.9" +chart_name: "mychart" +version: "0.1.0" +dependencies: +- name: "cassandra" + version: "0.1.4" + repository: "https://charts.kube-ops.io" + local_repo_name: "kube-ops" + values: {} +- name: "generate" + repository: https://charts.kube-ops.io + version: "~0.2.3" + local_repo_name: "kube-ops" + values: {} +``` + +All fields in the sample .yaml file above are required by the avionix library. +For more details, refer to the +[documentation](https://avionix.readthedocs.io/en/latest/reference/index.html). diff --git a/src/onaptests/utils/exceptions.py b/src/onaptests/utils/exceptions.py index e453d67..c12ee2f 100644 --- a/src/onaptests/utils/exceptions.py +++ b/src/onaptests/utils/exceptions.py @@ -74,3 +74,7 @@ class EnvironmentPreparationException(OnapTestException): class SubstepExecutionException(OnapTestException): """Exception raised if substep execution fails.""" + +class EnvironmentCleanupException(OnapTestException): + """Test environment cleanup exception.""" + error_message="Test couldn't finish a cleanup" diff --git a/src/onaptests/utils/simulators.py b/src/onaptests/utils/simulators.py new file mode 100644 index 0000000..08acdbd --- /dev/null +++ b/src/onaptests/utils/simulators.py @@ -0,0 +1,13 @@ +"""Standard functions for the simulator wrapper.""" +from importlib.resources import path + +def get_local_dir(): + """Get the default path for helm charts. + + Returns: + chart_directory (Path): + local helm chart folder relative to the package. + """ + with path('onaptests', 'templates') as templates: + chart_directory = templates / 'helm_charts' + return chart_directory -- cgit 1.2.3-korg