11"""Module for converting TES tasks to Kubernetes jobs."""
22
3+ import base64
34import gzip
45import json
56import logging
7+ from decimal import Decimal
68from io import BytesIO
9+ from typing import Any
710
811from kubernetes .client import (
912 V1ConfigMap ,
1013 V1ConfigMapVolumeSource ,
14+ V1Container ,
15+ V1EnvVar ,
1116 V1ObjectMeta ,
17+ V1ResourceRequirements ,
1218 V1Volume ,
1319)
1420from kubernetes .client .models import V1Job
21+ from kubernetes .utils .quantity import parse_quantity # type: ignore
1522
1623from tesk .api .ga4gh .tes .models import TesExecutor , TesResources , TesTask
1724from tesk .api .kubernetes .constants import Constants , K8sConstants
18- from tesk .api .kubernetes .template import KubernetesTemplateSupplier
25+ from tesk .api .kubernetes .convert .data .job import Job
26+ from tesk .api .kubernetes .convert .data .task import Task
27+ from tesk .api .kubernetes .convert .executor_command_wrapper import ExecutorCommandWrapper
28+ from tesk .api .kubernetes .convert .template import KubernetesTemplateSupplier
1929from tesk .constants import TeskConstants
2030from tesk .custom_config import TaskmasterEnvProperties
21- from tesk .utils import get_taskmaster_env_property , get_taskmaster_template
31+ from tesk .utils import get_taskmaster_env_property , pydantic_model_list_json
2232
2333logger = logging .getLogger (__name__ )
2434
2535
2636class TesKubernetesConverter :
37+ """Convert TES requests to Kubernetes resources."""
38+
2739 def __init__ (self , namespace = TeskConstants .tesk_namespace ):
2840 """Initialize the converter."""
2941 self .taskmaster_env_properties : TaskmasterEnvProperties = (
3042 get_taskmaster_env_property ()
3143 )
44+ self .template_supplier = KubernetesTemplateSupplier (
45+ namespace = namespace
46+ # security_context=security_context
47+ )
3248 self .constants = Constants ()
3349 self .k8s_constants = K8sConstants ()
3450 self .namespace = namespace
3551
36- # TODO: Add user to the mmethod when auth implemented in FOCA
52+ # TODO: Add user to the method when auth implemented in FOCA
3753 def from_tes_task_to_k8s_job (self , task : TesTask ):
54+ """Convert TES task to Kubernetes job."""
3855 taskmsater_job : V1Job = KubernetesTemplateSupplier (
3956 self .namespace
4057 ).task_master_template ()
@@ -48,6 +65,8 @@ def from_tes_task_to_k8s_job(self, task: TesTask):
4865 if taskmsater_job .metadata .labels is None :
4966 taskmsater_job .metadata .labels = {}
5067
68+ # taskmsater_job.metadata.name = task.name
69+
5170 taskmsater_job .metadata .annotations [self .constants .ann_testask_name_key ] = (
5271 task .name
5372 )
@@ -84,21 +103,30 @@ def from_tes_task_to_k8s_job(self, task: TesTask):
84103 def from_tes_task_to_k8s_config_map (
85104 self ,
86105 task : TesTask ,
106+ job : V1Job ,
87107 # user,
88- job ,
89- ):
108+ ) -> V1ConfigMap :
109+ """Create a Kubernetes ConfigMap from a TES task."""
90110 task_master_config_map = V1ConfigMap (
91111 metadata = V1ObjectMeta (name = job .metadata .name )
92112 )
113+
114+ task_master_config_map .metadata .labels = (
115+ task_master_config_map .metadata .labels or {}
116+ )
117+ task_master_config_map .metadata .annotations = (
118+ task_master_config_map .metadata .annotations or {}
119+ )
120+
93121 task_master_config_map .metadata .annotations [
94122 self .constants .ann_testask_name_key
95- ] = task [ " name" ]
123+ ] = task . name
96124 # task_master_config_map.metadata.labels[self.constants.label_userid_key] = user["username"]
97125
98- if "tags" in task and "GROUP_NAME" in task [ " tags" ] :
126+ if "tags" in task and "GROUP_NAME" in task . tags :
99127 task_master_config_map .metadata .labels [
100128 self .constants .label_groupname_key
101- ] = task [ " tags" ] ["GROUP_NAME" ]
129+ ] = task . tags ["GROUP_NAME" ]
102130 # elif user["is_member"]:
103131 # task_master_config_map.metadata.labels[self.constants.label_groupname_key] = user[
104132 # "any_group"
@@ -107,30 +135,42 @@ def from_tes_task_to_k8s_config_map(
107135 executors_as_jobs = [
108136 self .from_tes_executor_to_k8s_job (
109137 task_master_config_map .metadata .name ,
110- task [ " name" ] ,
138+ task . name ,
111139 executor ,
112140 idx ,
113- task [ " resources" ] ,
141+ task . resources ,
114142 # user,
115143 )
116- for idx , executor in enumerate (task [ " executors" ] )
144+ for idx , executor in enumerate (task . executors )
117145 ]
118146
119- task_master_input = {
120- "inputs" : task .inputs or [],
121- "outputs" : task .outputs or [],
147+ task_master_input : dict [ str , Any ] = {
148+ "inputs" : pydantic_model_list_json ( task .inputs ) or [],
149+ "outputs" : pydantic_model_list_json ( task .outputs ) or [],
122150 "volumes" : task .volumes or [],
123- "resources" : {"disk_gb" : task .resources .disk_gb or 10.0 },
151+ "resources" : {"disk_gb" : float ( task .resources .disk_gb ) or 10.0 },
124152 }
125- task_master_input [self .constants .taskmaster_input_exec_key ] = executors_as_jobs
153+ task_master_input [self .constants .taskmaster_input_exec_key ] = [
154+ exec_job .to_dict () for exec_job in executors_as_jobs
155+ ]
156+
157+ def decimal_to_float (obj ):
158+ if isinstance (obj , Decimal ):
159+ return float (obj )
160+ raise TypeError
126161
127- task_master_input_as_json = json .dumps (task_master_input )
162+ taskmaster_input_as_json = json .loads (
163+ json .dumps (task_master_input , default = decimal_to_float )
164+ )
128165 try :
129166 with BytesIO () as obj :
130167 with gzip .GzipFile (fileobj = obj , mode = "wb" ) as gzip_file :
131- gzip_file .write (task_master_input_as_json .encode ("utf-8" ))
168+ json_data = json .dumps (taskmaster_input_as_json )
169+ gzip_file .write (json_data .encode ("utf-8" ))
132170 task_master_config_map .binary_data = {
133- f"{ self .constants .taskmaster_input } .gz" : obj .getvalue ()
171+ f"{ self .constants .taskmaster_input } .gz" : base64 .b64encode (
172+ obj .getvalue ()
173+ ).decode ("utf-8" )
134174 }
135175 except Exception as e :
136176 logger .info (
@@ -140,7 +180,6 @@ def from_tes_task_to_k8s_config_map(
140180
141181 return task_master_config_map
142182
143-
144183 def from_tes_executor_to_k8s_job (
145184 self ,
146185 generated_task_id : str ,
@@ -151,38 +190,58 @@ def from_tes_executor_to_k8s_job(
151190 # user: User
152191 ) -> V1Job :
153192 # Get new template executor Job object
154- job = self .executor_template_supplier ()
155-
193+ job : V1Job = self .template_supplier . executor_template ()
194+
156195 # Set executors name based on taskmaster's job name
157- Job (job ).change_job_name (Task (generated_task_id ).get_executor_name (executor_index ))
158-
196+ Job (job ).change_job_name (
197+ # Task(job, generated_task_id).get_executor_name(executor_index)
198+ "newname"
199+ )
200+
159201 # Put arbitrary labels and annotations
160202 job .metadata .labels = job .metadata .labels or {}
161- job .metadata .labels ["taskId" ] = generated_task_id
162- job .metadata .labels ["execNo" ] = str (executor_index )
163- job .metadata .labels ["userId" ] = user .username
164-
203+ job .metadata .labels [self . constants . label_testask_id_key ] = generated_task_id
204+ job .metadata .labels [self . constants . label_execno_key ] = str (executor_index )
205+ # job.metadata.labels[self.constants.label_userid_key ] = user.username
206+
165207 job .metadata .annotations = job .metadata .annotations or {}
166- job .metadata .annotations ["tesTaskName" ] = tes_task_name
167-
168- container = job .spec .template .spec .containers [0 ]
169-
208+ job .metadata .annotations [self .constants .ann_testask_name_key ] = tes_task_name
209+
210+ container : V1Container = job .spec .template .spec .containers [0 ]
211+
212+ # TODO: Not sure what to do with this
170213 # Convert potential TRS URI into docker image
171- container .image = self .trs_client .get_docker_image_for_tool_version_uri (executor .image )
172-
214+ # container.image = self.trs_client.get_docker_image_for_tool_version_uri(
215+ # executor.image
216+ # )
217+
218+ if not container .command :
219+ container .command = []
173220 # Map executor's command to job container's command
174- for command in ExecutorCommandWrapper (executor ).get_commands_with_stream_redirects ():
175- container .add_command_item (command )
176-
221+ for command in ExecutorCommandWrapper (
222+ executor
223+ ).get_commands_with_stream_redirects ():
224+ container .command .append (command )
225+
177226 if executor .env :
178- container .env = [V1EnvVar (name = key , value = value ) for key , value in executor .env .items ()]
179-
227+ container .env = [
228+ V1EnvVar (name = key , value = value ) for key , value in executor .env .items ()
229+ ]
230+ else :
231+ container .env = []
232+
180233 container .working_dir = executor .workdir
181-
234+
235+ container .resources = V1ResourceRequirements (requests = {})
236+
182237 if resources .cpu_cores :
183- container .resources .requests ['cpu' ] = QuantityFormatter ().parse (str (resources .cpu_cores ))
184-
238+ container .resources .requests ["cpu" ] = parse_quantity (
239+ str (resources .cpu_cores )
240+ )
241+
185242 if resources .ram_gb :
186- container .resources .requests ['memory' ] = QuantityFormatter ().parse (f"{ resources .ram_gb :.6f} Gi" )
187-
243+ container .resources .requests ["memory" ] = parse_quantity (
244+ f"{ resources .ram_gb :.6f} Gi"
245+ )
246+
188247 return job
0 commit comments