-
Notifications
You must be signed in to change notification settings - Fork 33
feat: create task #207
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
base: main
Are you sure you want to change the base?
feat: create task #207
Changes from 2 commits
06602d1
b5a917d
3fa2eca
ffecf22
0acd4b0
dd14159
2948508
7f21d1f
71d9be1
1a56253
41d8d19
0dfe256
e4993df
2ac6959
a4edd0e
8586b4d
0d7981a
3eaad7f
c45724b
b182a87
b4570a5
925d63a
11976e1
c6e3964
5607e62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Task API controller logic.""" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| """TESK API module for creating a task.""" | ||
|
|
||
| import logging | ||
|
|
||
| from tesk.api.ga4gh.tes.models import TesCreateTaskResponse, TesResources, TesTask | ||
| from tesk.api.ga4gh.tes.task.task_request import TesTaskRequest | ||
| from tesk.exceptions import KubernetesError | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class CreateTesTask(TesTaskRequest): | ||
| """Create TES task.""" | ||
JaeAeich marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| def __init__( | ||
| self, | ||
| task: TesTask, | ||
| ): | ||
| """Initialize the CreateTask class. | ||
| Args: | ||
| task: TES task to create. | ||
| """ | ||
| super().__init__() | ||
| self.task = task | ||
|
|
||
| def handle_request(self) -> TesCreateTaskResponse: | ||
| """Create TES task.""" | ||
| attempts_no = 0 | ||
JaeAeich marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| while ( | ||
| attempts_no < self.tesk_k8s_constants.job_constants.JOB_CREATE_ATTEMPTS_NO | ||
| ): | ||
| try: | ||
| attempts_no += 1 | ||
jemaltahir marked this conversation as resolved.
Show resolved
Hide resolved
JaeAeich marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| resources = self.task.resources | ||
| minimum_ram_gb = self.kubernetes_client_wrapper.minimum_ram_gb() | ||
|
|
||
| if not self.task.resources: | ||
JaeAeich marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| self.task.resources = TesResources(cpu_cores=int(minimum_ram_gb)) | ||
JaeAeich marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if resources and resources.ram_gb and resources.ram_gb < minimum_ram_gb: | ||
| self.task.resources.ram_gb = minimum_ram_gb | ||
JaeAeich marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| taskmaster_job = self.tes_kubernetes_converter.from_tes_task_to_k8s_job( | ||
| self.task, | ||
| ) | ||
| taskmaster_config_map = ( | ||
| self.tes_kubernetes_converter.from_tes_task_to_k8s_config_map( | ||
| self.task, | ||
| taskmaster_job, | ||
| ) | ||
| ) | ||
|
|
||
| _ = self.kubernetes_client_wrapper.create_config_map( | ||
| taskmaster_config_map | ||
| ) | ||
| created_job = self.kubernetes_client_wrapper.create_job(taskmaster_job) | ||
|
|
||
| assert created_job.metadata is not None | ||
| assert created_job.metadata.name is not None | ||
JaeAeich marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return TesCreateTaskResponse(id=created_job.metadata.name) | ||
|
|
||
| except KubernetesError as e: | ||
| if ( | ||
| not e.is_object_name_duplicated() | ||
|
||
| or attempts_no | ||
| >= self.tesk_k8s_constants.job_constants.JOB_CREATE_ATTEMPTS_NO | ||
| ): | ||
| raise e | ||
|
|
||
| except Exception as exc: | ||
| logging.error("ERROR: In createTask", exc_info=True) | ||
| raise exc | ||
JaeAeich marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| return TesCreateTaskResponse(id="") # To silence mypy, should never be reached | ||
JaeAeich marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it a base class for a TESK request (any request, including
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is the former, can you please suggest a name, I can't think of a reason or name to do so. I name it task request so that this base class can hold all the attr that might be common among endpoint that deal with "task request" and create a common interface to interacting with api programatically. If we extend more endpoint sooner or later (say create serviceInfo), then I would propose to even create a base class for this class named
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that second part is where I would see this going then. Maybe even ending up in FOCA. I mean, if we already have 21 changed files (not including tests) for the addition of a single controller, we might as well go all the way, right? 😛
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @JaeAeich: Why is it that with your PRs I often see the entire file changed, even though it's just an iteration over the last time I've viewed? I don't have this with other people, so I guess it's something with your editor or Git flow. Please have a look at this, because it's really quite annoying. Instead of just focusing on what changed since the last time, I have to look through the entire file again - which is not only not fun, but also holds up the reviews big time, especially when they tend to end up being huge. And even apart from that, it's also really not good practice in terms of provenance. If this were previously existing code (e.g., maybe some of the old TES code from TES-core still remains), you'd end up being listed as
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I agree, but I can assure you am not going it on purpose 😭, the thing is TESK code is complex and if I try to break the code base it makes no sense and is very hard to connect the dots. Also I don't think I have any issues in my git flow, my configs seem to be sound. I am trying not to step and cover up someones code but if you notice we most of the files are completely new and not a modification of prev (well there weren't prev files to speack of as I am mostly working on new module
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess I meant that it happens between reviews. If I have seen and checked a file in review #1, it shouldn't be entirely green again in review #2, unless the entire file changed between reviews #1 and #2 - which shouldn't really happen. Anyway... please rename it somehow. If it's generic to a degree that at some point we might put it in FOCA (which it certainly isn't in its current form), maybe just use |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| """Base class for tesk request.""" | ||
|
|
||
| import json | ||
| import logging | ||
| from abc import ABC, abstractmethod | ||
|
|
||
| from pydantic import BaseModel | ||
|
|
||
| from tesk.k8s.constants import tesk_k8s_constants | ||
| from tesk.k8s.converter.converter import TesKubernetesConverter | ||
| from tesk.k8s.wrapper import KubernetesClientWrapper | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class TesTaskRequest(ABC): | ||
| """Base class for tesk request ecapsulating common methods and members.""" | ||
JaeAeich marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| def __init__(self): | ||
| """Initialise base class for tesk request.""" | ||
| self.kubernetes_client_wrapper = KubernetesClientWrapper() | ||
| self.tes_kubernetes_converter = TesKubernetesConverter() | ||
| self.tesk_k8s_constants = tesk_k8s_constants | ||
|
|
||
| @abstractmethod | ||
| def handle_request(self) -> BaseModel: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be a more specific model that is returned? I mean the function is called |
||
| """Business logic for the request.""" | ||
| pass | ||
|
|
||
| def response(self) -> dict: | ||
| """Get response for the request.""" | ||
| response: BaseModel = self.handle_request() | ||
| try: | ||
| res: dict = json.loads(json.dumps(response)) | ||
| return res | ||
| except (TypeError, ValueError) as e: | ||
| logger.info(e) | ||
|
Comment on lines
+40
to
+44
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the significance of this? Why not going straight for the Pydantic way of marshalling the model?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried, for some reason I couldn't, IDK why! And this weird way only worked.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the error? Do you think it's a bug in Pydantic? If you really have good reason to use this code as it is, I think you should document inside the code exactly why you couldn't just validate the model with Pydantic, and do so by linking to an existing issue. Currently my assumption is that there's something wrong with your code and this workaround just masks that. |
||
| return response.dict() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,83 @@ | ||
| """Custom configuration model for the FOCA app.""" | ||
|
|
||
| from pydantic import BaseModel | ||
| from typing import Dict, Optional | ||
|
|
||
| from pydantic import BaseModel, Field | ||
|
|
||
| from tesk.api.ga4gh.tes.models import Service | ||
| from tesk.constants import tesk_constants | ||
|
|
||
|
|
||
| class FtpConfig(BaseModel): | ||
| """Ftp configuration model for the TESK.""" | ||
|
|
||
| secretName: Optional[str] = Field( | ||
| default=None, description="Name of the secret with FTP account credentials" | ||
| ) | ||
| enabled: bool = Field( | ||
| default=False, | ||
| description="If FTP account enabled (based on non-emptiness of secretName)", | ||
| ) | ||
|
|
||
|
|
||
| class ExecutorSecret(BaseModel): | ||
| """Executor secret configuration.""" | ||
|
|
||
| name: Optional[str] = Field( | ||
| default=None, | ||
| description=( | ||
| "Name of a secret that will be mounted as volume to each executor. The same" | ||
| " name will be used for the secret and the volume" | ||
| ), | ||
| ) | ||
| mountPath: Optional[str] = Field( | ||
| default=None, | ||
| alias="mountPath", | ||
| description="The path where the secret will be mounted to executors", | ||
| ) | ||
| enabled: bool = Field( | ||
| default=False, description="Indicates whether the secret is enabled" | ||
| ) | ||
|
|
||
|
|
||
| class Taskmaster(BaseModel): | ||
| """Taskmaster environment properties model for the TESK.""" | ||
|
|
||
| imageName: str = Field( | ||
| default=tesk_constants.TASKMASTER_IMAGE_NAME, | ||
| description="Taskmaster image name", | ||
| ) | ||
| imageVersion: str = Field( | ||
| default=tesk_constants.TASKMASTER_IMAGE_VERSION, | ||
| description="Taskmaster image version", | ||
| ) | ||
| filerImageName: str = Field( | ||
| default=tesk_constants.FILER_IMAGE_NAME, description="Filer image name" | ||
| ) | ||
| filerImageVersion: str = Field( | ||
| default=tesk_constants.FILER_IMAGE_VERSION, description="Filer image version" | ||
| ) | ||
| ftp: FtpConfig = Field(default=None, description="Test FTP account settings") | ||
| debug: bool = Field( | ||
| default=False, | ||
| description="If verbose (debug) mode of taskmaster is on (passes additional " | ||
| "flag to taskmaster and sets image pull policy to Always)", | ||
| ) | ||
| environment: Optional[Dict[str, str]] = Field( | ||
| default=None, | ||
| description="Environment variables, that will be passed to taskmaster", | ||
| ) | ||
| serviceAccountName: str = Field( | ||
| default="default", description="Service Account name for taskmaster" | ||
| ) | ||
| executorSecret: Optional[ExecutorSecret] = Field( | ||
| default=None, description="Executor secret configuration" | ||
| ) | ||
|
|
||
|
|
||
| class CustomConfig(BaseModel): | ||
| """Custom configuration model for the FOCA app.""" | ||
|
|
||
| # Define custom configuration fields here | ||
| service_info: Service | ||
| taskmaster: Taskmaster |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Module for converting Kubernetes objects to Task objects.""" |
Uh oh!
There was an error while loading. Please reload this page.