1212from kubernetes .client .rest import ApiException
1313from airflow .decorators import task
1414from airflow .exceptions import AirflowFailException
15+ from http import HTTPStatus
1516
1617from xlml .utils import gke
1718
@@ -27,7 +28,6 @@ class CheckpointConfiguration:
2728 machine_type : str
2829 ramdisk_memory : str
2930 toleration_key : str
30- cpc_yaml_template : str
3131
3232 def __init__ (
3333 self ,
@@ -50,7 +50,7 @@ def __init__(
5050 machine_type (str): The machine type for the instance.
5151 ram_disk_memory_in_mi (str): The size of the RAM disk in mebibytes (Mi).
5252 The unit is in mebibytes (Mi) but the value should be passed as a
53- string with the unit, e.g., "2G " or "2048M". Defaults to "100G" ".
53+ string with the unit, e.g., "1G " or "1024Mi ".
5454 toleration_key (str): The toleration key for the Kubernetes pod.
5555 Defaults to "google.com/tpu".
5656 """
@@ -61,21 +61,49 @@ def __init__(
6161 self .machine_type = machine_type
6262 self .ramdisk_memory = ramdisk_memory_in_mi
6363 self .toleration_key = toleration_key
64- self .cpc_yaml_template = f"""
65- apiVersion: checkpointing.gke.io/v1
66- kind: CheckpointConfiguration
67- metadata:
68- name: my-checkpointconfiguration # This name will be used for deletion
69- spec:
70- cloudStorageBucketName: { self .gcs_bucket }
71- nodeSelector:
72- node.kubernetes.io/instance-type: { self .machine_type }
73- tolerations:
74- - key: { self .toleration_key }
75- operator: Exists
76- effect: NoSchedule
77- inMemoryVolumeSize: { self .ramdisk_memory }
78- """
64+
65+ def load_yaml_and_parse_body (
66+ self ,
67+ ) -> tuple [str , str , str , str , dict [str , any ]]:
68+ """
69+ Loads a YAML string template, populates it with class attributes, and parses the resulting body.
70+
71+ This method constructs a CheckpointConfiguration YAML manifest as a string,
72+ using class attributes such as `self.gcs_bucket`, `self.machine_type`,
73+ `self.toleration_key`, and `self.ramdisk_memory` to fill in the
74+ placeholders. It then uses `yaml.safe_load` to convert this YAML string
75+ into a Python dictionary.
76+
77+ Finally, it extracts key fields—group, version, plural, and name—from the
78+ loaded dictionary for use in API requests or other operations.
79+
80+ Returns:
81+ tuple[str, str, str, str, dict[str, any]]: A tuple containing the
82+ extracted API group, API version, plural name, resource name, and the
83+ full parsed YAML body as a dictionary.
84+ """
85+
86+ cpc_yaml_template = f"""
87+ apiVersion: checkpointing.gke.io/v1
88+ kind: CheckpointConfiguration
89+ metadata:
90+ name: my-checkpointconfiguration # This name will be used for deletion
91+ spec:
92+ cloudStorageBucketName: { self .gcs_bucket }
93+ nodeSelector:
94+ node.kubernetes.io/instance-type: { self .machine_type }
95+ tolerations:
96+ - key: { self .toleration_key }
97+ operator: Exists
98+ effect: NoSchedule
99+ inMemoryVolumeSize: { self .ramdisk_memory }
100+ """
101+ cpc_body = yaml .safe_load (cpc_yaml_template )
102+ group , version = cpc_body .get ("apiVersion" ).split ("/" , 1 )
103+ plural = cpc_body .get ("kind" ).lower () + "s"
104+ name = cpc_body .get ("metadata" , {}).get ("name" )
105+
106+ return group , version , plural , name , cpc_body
79107
80108 def create_custom_objects_api_client (self ) -> k8s_client .CustomObjectsApi :
81109 """Create a CustomObjectsApi client for the given cluster."""
@@ -89,14 +117,9 @@ def create_custom_objects_api_client(self) -> k8s_client.CustomObjectsApi:
89117@task
90118def apply_cpc (cpc : CheckpointConfiguration ) -> None :
91119 """Applies the CheckpointConfiguration to the cluster (create or replace)."""
92- custom_api = cpc .create_custom_objects_api_client ()
93- cpc_body = yaml .safe_load (cpc .cpc_yaml_template )
94- api_version = cpc_body .get ("apiVersion" )
95- kind = cpc_body .get ("kind" )
96- name = cpc_body .get ("metadata" , {}).get ("name" )
97120
98- group , version = api_version . split ( "/" , 1 )
99- plural = f" { kind . lower () } s"
121+ custom_api = cpc . create_custom_objects_api_client ( )
122+ group , version , plural , name , cpc_body = cpc . load_yaml_and_parse_body ()
100123
101124 try :
102125 # Here we first try to create a reasource
@@ -108,8 +131,7 @@ def apply_cpc(cpc: CheckpointConfiguration) -> None:
108131
109132 except ApiException as api_error :
110133 # If it already exists (409 Conflict), then try to replace it
111- # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409
112- if api_error .status == 409 :
134+ if api_error .status == HTTPStatus .CONFLICT :
113135 logging .info (
114136 f"CheckpointConfiguration '{ name } ' already exists. Attempting to replace..."
115137 )
@@ -142,43 +164,31 @@ def initiate_cpc_deletion(cpc: CheckpointConfiguration) -> None:
142164 Sends the delete request for the CheckpointConfiguration.
143165 """
144166 custom_api = cpc .create_custom_objects_api_client ()
145- cpc_body = yaml .safe_load (cpc .cpc_yaml_template )
146- name_to_delete = cpc_body .get ("metadata" , {}).get ("name" )
167+ group , version , plural , name , _ = cpc .load_yaml_and_parse_body ()
147168
148- if not name_to_delete :
169+ if not name :
149170 logging .error (
150171 "Could not determine CheckpointConfiguration name for deletion."
151172 )
152173 raise AirflowFailException ("Failed to determine CPC name for deletion." )
153174
154- api_version = cpc_body .get ("apiVersion" )
155- kind = cpc_body .get ("kind" )
156- group , version = api_version .split ("/" , 1 )
157- plural = f"{ kind .lower ()} s"
158-
159175 delete_options = k8s_client .V1DeleteOptions (propagation_policy = "Foreground" )
160-
161176 try :
162- logging .info (
163- f"Attempting to delete CheckpointConfiguration: { name_to_delete } "
164- )
177+ logging .info (f"Attempting to delete CheckpointConfiguration: { name } " )
165178 custom_api .delete_cluster_custom_object (
166179 group = group ,
167180 version = version ,
168181 plural = plural ,
169- name = name_to_delete ,
182+ name = name ,
170183 body = delete_options ,
171184 )
172- logging .info (
173- f"Delete request sent for CheckpointConfiguration '{ name_to_delete } '."
174- )
185+ logging .info (f"Delete request sent for CheckpointConfiguration '{ name } '." )
175186
176187 except ApiException as e :
177188 # The resource is already gone (404), so we can exit successfully
178- # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409
179- if e .status == 404 :
189+ if e .status == HTTPStatus .NOT_FOUND :
180190 logging .info (
181- f"CheckpointConfiguration '{ name_to_delete } ' not found. Already deleted or never existed."
191+ f"CheckpointConfiguration '{ name } ' not found. Already deleted or never existed."
182192 )
183193 return
184194 else :
@@ -193,36 +203,28 @@ def wait_for_cpc_deletion(cpc: CheckpointConfiguration) -> bool:
193203 A sensor that waits for the CheckpointConfiguration to be completely deleted.
194204 """
195205 custom_api = cpc .create_custom_objects_api_client ()
196- cpc_body = yaml .safe_load (cpc .cpc_yaml_template )
197- name_to_delete = cpc_body .get ("metadata" , {}).get ("name" )
206+ group , version , plural , name , _ = cpc .load_yaml_and_parse_body ()
198207
199- if not name_to_delete :
208+ if not name :
200209 logging .error (
201210 "Could not determine CheckpointConfiguration name for deletion."
202211 )
203212 raise AirflowFailException ("Failed to determine CPC name for deletion." )
204213
205- api_version = cpc_body .get ("apiVersion" )
206- kind = cpc_body .get ("kind" )
207- group , version = api_version .split ("/" , 1 )
208- plural = f"{ kind .lower ()} s"
209-
210214 try :
211215 custom_api .get_cluster_custom_object (
212- group = group , version = version , plural = plural , name = name_to_delete
216+ group = group , version = version , plural = plural , name = name
213217 )
214218 logging .info (
215- f"CheckpointConfiguration '{ name_to_delete } ' still exists. "
219+ f"CheckpointConfiguration '{ name } ' still exists. "
216220 f"Polling again in 10s..."
217221 )
218222 return False
219223 except ApiException as e :
220224 # The resource is already gone (404), so we can exit successfully
221225 # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409
222226 if e .status == 404 :
223- logging .info (
224- f"CheckpointConfiguration: { name_to_delete } successfully deleted"
225- )
227+ logging .info (f"CheckpointConfiguration: { name } successfully deleted" )
226228 return True # Return True to tell the sensor to succeed
227229 else :
228230 logging .error (
0 commit comments