Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#239: Changed return type for deploy API method #240

Merged
merged 5 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 30 additions & 8 deletions exasol/slc/api/deploy.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import getpass
from typing import Optional, Tuple
from typing import Dict, Optional, Tuple

import luigi
from exasol_integration_test_docker_environment.lib.api.common import (
cli_function,
generate_root_task,
Expand All @@ -14,7 +13,9 @@
DependencyLoggerBaseTask,
)

from exasol.slc.internal.tasks.upload.upload_containers import UploadContainers
from exasol.slc.internal.tasks.upload.deploy_containers import DeployContainers
from exasol.slc.internal.tasks.upload.deploy_info import toDeployResult
from exasol.slc.models.deploy_result import DeployResult


@cli_function
Expand Down Expand Up @@ -52,13 +53,14 @@ def deploy(
task_dependencies_dot_file: Optional[str] = None,
log_level: Optional[str] = None,
use_job_specific_log_file: bool = True,
) -> luigi.LocalTarget:
) -> Dict[str, Dict[str, DeployResult]]:
"""
This command uploads the whole script-language-container package of the flavor to the database.
If the stages or the packaged container do not exists locally, the system will build, pull or
If the stages or the packaged container do not exist locally, the system will build, pull or
export them before the upload.
:raises api_errors.TaskFailureError: if operation is not successful.
:return: Path to resulting report file.
:return: A dictionary with an instance of class DeployResult for each release for each deployed flavor.
For example { "flavors/standard-flavor" : {"release" : DeployResult(...) } }
"""
import_build_steps(flavor_path)
set_build_config(
Expand Down Expand Up @@ -94,7 +96,7 @@ def deploy(

def root_task_generator() -> DependencyLoggerBaseTask:
return generate_root_task(
task_class=UploadContainers,
task_class=DeployContainers,
flavor_paths=list(flavor_path),
release_goals=list(release_goal),
database_host=bucketfs_host,
Expand All @@ -110,10 +112,30 @@ def root_task_generator() -> DependencyLoggerBaseTask:
use_ssl_cert_validation=use_ssl_cert_validation,
)

return run_task(
deploy_infos = run_task(
root_task_generator,
workers=workers,
task_dependencies_dot_file=task_dependencies_dot_file,
log_level=log_level,
use_job_specific_log_file=use_job_specific_log_file,
)

return {
flavor: {
release: toDeployResult(
deploy_info=deploy_info,
bucketfs_use_https=bucketfs_use_https,
bucketfs_host=bucketfs_host,
bucketfs_port=bucketfs_port,
bucket_name=bucket,
bucketfs_name=bucketfs_name,
bucketfs_username=bucketfs_user,
bucketfs_password=bucketfs_password,
ssl_cert_path=ssl_cert_path,
use_ssl_cert_validation=use_ssl_cert_validation,
path_in_bucket=path_in_bucket,
)
for release, deploy_info in deploy_info_per_release.items()
}
for flavor, deploy_info_per_release in deploy_infos.items()
}
130 changes: 130 additions & 0 deletions exasol/slc/internal/tasks/upload/deploy_container_base_task.py
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from pathlib import Path

import exasol.bucketfs as bfs # type: ignore
import luigi
from exasol_integration_test_docker_environment.abstract_method_exception import (
AbstractMethodException,
)
from exasol_integration_test_docker_environment.lib.base.flavor_task import (
FlavorBaseTask,
)

from exasol.slc.internal.tasks.upload.deploy_info import DeployInfo
from exasol.slc.internal.tasks.upload.language_def_parser import (
parse_language_definition,
)
from exasol.slc.internal.tasks.upload.language_definition import LanguageDefinition
from exasol.slc.internal.tasks.upload.upload_container_parameter import (
UploadContainerParameter,
)
from exasol.slc.models.export_info import ExportInfo
from exasol.slc.models.language_definition_components import (
LanguageDefinitionComponents,
)
from exasol.slc.models.language_definitions_builder import LanguageDefinitionsBuilder


class DeployContainerBaseTask(FlavorBaseTask, UploadContainerParameter):
# TODO check if upload was successfull by requesting the file
# TODO add error checks and propose reasons for the error
# TODO extract bucketfs interaction into own module

release_goal = luigi.Parameter()

def __init__(self, *args, **kwargs):
self.export_info_future = None
super().__init__(*args, **kwargs)

def register_required(self):
task = self.get_export_task()
self.export_info_future = self.register_dependency(task)

def get_export_task(self):
raise AbstractMethodException()

def run_task(self):
export_info = self.get_values_from_future(self.export_info_future)
path_in_bucket = self._upload_container(export_info)
language_definition = LanguageDefinition(
release_name=self._get_complete_release_name(export_info),
flavor_path=self.flavor_path,
bucketfs_name=self.bucketfs_name,
bucket_name=self.bucket_name,
path_in_bucket=self.path_in_bucket,
)
language_definitions = language_definition.generate_definition().split(" ")
language_def_components_list = list()
for lang_def in language_definitions:
alias, url = parse_language_definition(lang_def)
language_def_components_list.append(
LanguageDefinitionComponents(alias=alias, url=url)
)

lang_def_builder = LanguageDefinitionsBuilder(language_def_components_list)
try:
release_path = Path(export_info.cache_file).relative_to(Path("").absolute())
except ValueError:
release_path = Path(export_info.cache_file)

result = DeployInfo(
release_path=str(release_path),
complete_release_name=self._get_complete_release_name(export_info),
human_readable_location=self._complete_url(export_info),
language_definition_builder=lang_def_builder,
)
self.return_object(result)

def build_file_path_in_bucket(self, release_info: ExportInfo) -> bfs.path.PathLike:
backend = bfs.path.StorageBackend.onprem

complete_release_name = self._get_complete_release_name(release_info)
verify = self.ssl_cert_path or self.use_ssl_cert_validation
path_in_bucket_to_upload_path = bfs.path.build_path(
backend=backend,
url=self._url,
bucket_name=self.bucket_name,
service_name=self.bucketfs_name,
username=self.bucketfs_username,
password=self.bucketfs_password,
verify=verify,
path=self.path_in_bucket or "",
)
return path_in_bucket_to_upload_path / f"{complete_release_name}.tar.gz"

@property
def _url(self) -> str:
return f"{self._get_url_prefix()}{self.database_host}:{self.bucketfs_port}"

def _complete_url(self, export_info: ExportInfo):
path_in_bucket = (
f"{self.path_in_bucket}/" if self.path_in_bucket not in [None, ""] else ""
)
return f"{self._url}/{self.bucket_name}/{path_in_bucket}{self._get_complete_release_name(export_info)}.tar.gz"

def _upload_container(self, release_info: ExportInfo) -> bfs.path.PathLike:
bucket_path = self.build_file_path_in_bucket(release_info)
self.logger.info(
f"Upload {release_info.cache_file} to {self._complete_url(release_info)}"
)
with open(release_info.cache_file, "rb") as file:
bucket_path.write(file)
return bucket_path

def _get_complete_release_name(self, release_info: ExportInfo):
complete_release_name = f"""{release_info.name}-{release_info.release_goal}-{self._get_release_name(
release_info)}"""
return complete_release_name

def _get_release_name(self, release_info: ExportInfo):
if self.release_name is None:
release_name = release_info.hash
else:
release_name = self.release_name
return release_name

def _get_url_prefix(self):
if self.bucketfs_https:
url_prefix = "https://"
else:
url_prefix = "http://"
return url_prefix
38 changes: 38 additions & 0 deletions exasol/slc/internal/tasks/upload/deploy_container_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import importlib

import luigi
from exasol_integration_test_docker_environment.lib.base.json_pickle_parameter import (
JsonPickleParameter,
)
from exasol_integration_test_docker_environment.lib.docker.images.required_task_info import (
RequiredTaskInfo,
)

from exasol.slc.internal.tasks.upload.deploy_container_base_task import (
DeployContainerBaseTask,
)


class DeployContainerTask(DeployContainerBaseTask):
# We need to create the ExportContainerTask for UploadContainerTask dynamically,
# because we want to push as soon as possible after an image was build and
# don't want to wait for the push finishing before starting the build of depended images,
# but we also need to create a UploadContainerTask for each ExportContainerTask of a goal

required_task_info = JsonPickleParameter(
RequiredTaskInfo,
visibility=luigi.parameter.ParameterVisibility.HIDDEN,
significant=True,
) # type: RequiredTaskInfo

def get_export_task(self):
module = importlib.import_module(
self.required_task_info.module_name # pylint: disable=no-member
)
class_ = getattr(
module, self.required_task_info.class_name # pylint: disable=no-member
)
instance = self.create_child_task(
class_, **self.required_task_info.params # pylint: disable=no-member
)
return instance
42 changes: 42 additions & 0 deletions exasol/slc/internal/tasks/upload/deploy_container_tasks_creator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from typing import Dict

from exasol_integration_test_docker_environment.lib.docker.images.create.docker_image_create_task import (
DockerCreateImageTask,
)
from exasol_integration_test_docker_environment.lib.docker.images.required_task_info import (
RequiredTaskInfo,
)

from exasol.slc.internal.tasks.upload.deploy_container_task import DeployContainerTask
from exasol.slc.internal.tasks.upload.upload_containers_parameter import (
UploadContainersParameter,
)


class DeployContainerTasksCreator:

def __init__(self, task: UploadContainersParameter):
self.task = task

def create_deploy_tasks(self, build_tasks: Dict[str, DockerCreateImageTask]):
return {
release_goal: self._create_deploy_task(release_goal, build_task)
for release_goal, build_task in build_tasks.items()
}

def _create_deploy_task(self, release_goal: str, build_task: DockerCreateImageTask):
required_task_info = self._create_required_task_info(build_task)
return self.task.create_child_task_with_common_params( # type: ignore
DeployContainerTask,
required_task_info=required_task_info,
release_goal=release_goal,
)

@staticmethod
def _create_required_task_info(build_task):
required_task_info = RequiredTaskInfo(
module_name=build_task.__module__,
class_name=build_task.__class__.__name__,
params=build_task.param_kwargs,
)
return required_task_info
63 changes: 63 additions & 0 deletions exasol/slc/internal/tasks/upload/deploy_containers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from typing import Dict

from exasol_integration_test_docker_environment.lib.base.flavor_task import (
FlavorsBaseTask,
)

from exasol.slc.internal.tasks.build.docker_flavor_build_base import (
DockerFlavorBuildBase,
)
from exasol.slc.internal.tasks.export.export_container_tasks_creator import (
ExportContainerTasksCreator,
)
from exasol.slc.internal.tasks.upload.deploy_container_tasks_creator import (
DeployContainerTasksCreator,
)
from exasol.slc.internal.tasks.upload.upload_containers_parameter import (
UploadContainersParameter,
)


class DeployContainers(FlavorsBaseTask, UploadContainersParameter):

def __init__(self, *args, **kwargs):
self.lang_def_builders_futures = None
super().__init__(*args, **kwargs)

def register_required(self):
tasks = self.create_tasks_for_flavors_with_common_params( # type: ignore
DeployFlavorContainers
) # type: Dict[str,DeployFlavorContainers]
self.lang_def_builders_futures = self.register_dependencies(tasks)

def run_task(self):
lang_definitionbuilders = self.get_values_from_futures(
self.lang_def_builders_futures
)
self.return_object(lang_definitionbuilders)


class DeployFlavorContainers(DockerFlavorBuildBase, UploadContainersParameter):

def get_goals(self):
return set(self.release_goals)

def run_task(self):
build_tasks = self.create_build_tasks()

export_tasks = self.create_export_tasks(build_tasks)
deploy_tasks = self.create_deploy_tasks(export_tasks)

lang_definitions_futures = yield from self.run_dependencies(deploy_tasks)
language_definition = self.get_values_from_futures(lang_definitions_futures)
self.return_object(language_definition)

def create_deploy_tasks(self, export_tasks):
deploy_tasks_creator = DeployContainerTasksCreator(self)
deploy_tasks = deploy_tasks_creator.create_deploy_tasks(export_tasks)
return deploy_tasks

def create_export_tasks(self, build_tasks):
export_tasks_creator = ExportContainerTasksCreator(self, export_path=None)
export_tasks = export_tasks_creator.create_export_tasks(build_tasks)
return export_tasks
Loading
Loading