3
3
4
4
import pytest
5
5
from airflow .exceptions import TaskDeferred
6
+ from airflow .providers .cncf .kubernetes .triggers .kubernetes_pod import ContainerState
6
7
from airflow .providers .cncf .kubernetes .utils .pod_manager import PodLoggingStatus
8
+ from kubernetes .client import models as k8s
7
9
8
10
from astronomer .providers .cncf .kubernetes .operators .kubernetes_pod import (
9
11
KubernetesPodOperatorAsync ,
17
19
KUBE_POD_MOD = "astronomer.providers.cncf.kubernetes.operators.kubernetes_pod"
18
20
19
21
22
+ def _build_mock_pod (state : k8s .V1ContainerState ) -> k8s .V1Pod :
23
+ return k8s .V1Pod (
24
+ metadata = k8s .V1ObjectMeta (name = "base" , namespace = "default" ),
25
+ status = k8s .V1PodStatus (
26
+ container_statuses = [
27
+ k8s .V1ContainerStatus (
28
+ name = "base" ,
29
+ image = "alpine" ,
30
+ image_id = "1" ,
31
+ ready = True ,
32
+ restart_count = 1 ,
33
+ state = state ,
34
+ )
35
+ ]
36
+ ),
37
+ )
38
+
39
+
20
40
class TestKubernetesPodOperatorAsync :
21
41
def test_raise_for_trigger_status_pending_timeout (self ):
22
42
"""Assert trigger raise exception in case of timeout"""
@@ -152,12 +172,79 @@ def test_defer_with_kwargs(self):
152
172
with pytest .raises (ValueError ):
153
173
op .defer (kwargs = {"timeout" : 10 })
154
174
175
+ @mock .patch (f"{ KUBE_POD_MOD } .KubernetesPodOperatorAsync.trigger_reentry" )
176
+ @mock .patch (
177
+ f"{ KUBE_POD_MOD } .KubernetesPodOperatorAsync.define_container_state" ,
178
+ return_value = ContainerState .FAILED ,
179
+ )
180
+ @mock .patch (f"{ KUBE_POD_MOD } .KubernetesPodOperatorAsync.build_pod_request_obj" )
181
+ @mock .patch (f"{ KUBE_POD_MOD } .KubernetesPodOperatorAsync.get_or_create_pod" )
182
+ @mock .patch (f"{ KUBE_POD_MOD } .KubernetesPodOperatorAsync.defer" )
183
+ def test_execute_failed_before_defer (
184
+ self ,
185
+ mock_defer ,
186
+ mock_get_or_create_pod ,
187
+ mock_build_pod_request_obj ,
188
+ mock_define_container_state ,
189
+ mock_trigger_reentry ,
190
+ ):
191
+ mock_get_or_create_pod .return_value = _build_mock_pod (
192
+ k8s .V1ContainerState ({"running" : k8s .V1ContainerStateTerminated (exit_code = 1 ), "waiting" : None })
193
+ )
194
+ mock_build_pod_request_obj .return_value = {}
195
+ mock_defer .return_value = {}
196
+ op = KubernetesPodOperatorAsync (task_id = "test_task" , name = "test-pod" , get_logs = True )
197
+
198
+ op .execute (context = create_context (op ))
199
+ assert mock_trigger_reentry .called
200
+ assert not mock_defer .called
201
+
202
+ @mock .patch (f"{ KUBE_POD_MOD } .KubernetesPodOperatorAsync.trigger_reentry" )
203
+ @mock .patch (
204
+ f"{ KUBE_POD_MOD } .KubernetesPodOperatorAsync.define_container_state" ,
205
+ return_value = ContainerState .TERMINATED ,
206
+ )
207
+ @mock .patch (f"{ KUBE_POD_MOD } .KubernetesPodOperatorAsync.build_pod_request_obj" )
208
+ @mock .patch (f"{ KUBE_POD_MOD } .KubernetesPodOperatorAsync.get_or_create_pod" )
209
+ @mock .patch (f"{ KUBE_POD_MOD } .KubernetesPodOperatorAsync.defer" )
210
+ def test_execute_succeeded_before_defer (
211
+ self ,
212
+ mock_defer ,
213
+ mock_get_or_create_pod ,
214
+ mock_build_pod_request_obj ,
215
+ mock_define_container_state ,
216
+ mock_trigger_reentry ,
217
+ ):
218
+ mock_get_or_create_pod .return_value = _build_mock_pod (
219
+ k8s .V1ContainerState ({"running" : k8s .V1ContainerStateTerminated (exit_code = 0 ), "waiting" : None })
220
+ )
221
+ mock_build_pod_request_obj .return_value = {}
222
+ mock_defer .return_value = {}
223
+ op = KubernetesPodOperatorAsync (task_id = "test_task" , name = "test-pod" , get_logs = True )
224
+ assert op .execute (context = create_context (op ))
225
+ assert mock_trigger_reentry .called
226
+ assert not mock_defer .called
227
+
228
+ @mock .patch (
229
+ "astronomer.providers.cncf.kubernetes.operators.kubernetes_pod.KubernetesPodOperatorAsync.define_container_state" ,
230
+ return_value = ContainerState .RUNNING ,
231
+ )
155
232
@mock .patch (f"{ KUBE_POD_MOD } .KubernetesPodOperatorAsync.build_pod_request_obj" )
156
233
@mock .patch (f"{ KUBE_POD_MOD } .KubernetesPodOperatorAsync.get_or_create_pod" )
157
234
@mock .patch (f"{ KUBE_POD_MOD } .KubernetesPodOperatorAsync.defer" )
158
- def test_execute (self , mock_defer , mock_get_or_create_pod , mock_build_pod_request_obj ):
235
+ def test_execute (
236
+ self ,
237
+ mock_defer ,
238
+ mock_get_or_create_pod ,
239
+ mock_build_pod_request_obj ,
240
+ mock_define_container_state ,
241
+ ):
159
242
"""Assert that execute succeeded"""
160
- mock_get_or_create_pod .return_value = {}
243
+ mock_get_or_create_pod .return_value = _build_mock_pod (
244
+ k8s .V1ContainerState (
245
+ {"running" : k8s .V1ContainerStateRunning (), "terminated" : None , "waiting" : None }
246
+ )
247
+ )
161
248
mock_build_pod_request_obj .return_value = {}
162
249
mock_defer .return_value = {}
163
250
op = KubernetesPodOperatorAsync (task_id = "test_task" , name = "test-pod" , get_logs = True )
@@ -169,3 +256,40 @@ def test_execute_complete(self, mock_trigger_reentry):
169
256
mock_trigger_reentry .return_value = {}
170
257
op = KubernetesPodOperatorAsync (task_id = "test_task" , name = "test-pod" , get_logs = True )
171
258
assert op .execute_complete (context = create_context (op ), event = {}) is None
259
+
260
+ @pytest .mark .parametrize (
261
+ "container_state, expected_state" ,
262
+ [
263
+ (
264
+ {"running" : k8s .V1ContainerStateRunning (), "terminated" : None , "waiting" : None },
265
+ ContainerState .RUNNING ,
266
+ ),
267
+ (
268
+ {"running" : None , "terminated" : k8s .V1ContainerStateTerminated (exit_code = 0 ), "waiting" : None },
269
+ ContainerState .TERMINATED ,
270
+ ),
271
+ (
272
+ {"running" : None , "terminated" : None , "waiting" : k8s .V1ContainerStateWaiting ()},
273
+ ContainerState .WAITING ,
274
+ ),
275
+ ],
276
+ )
277
+ def test_define_container_state_should_execute_successfully (self , container_state , expected_state ):
278
+ op = KubernetesPodOperatorAsync (task_id = "test_task" , name = "test-pod" , get_logs = True )
279
+ op .pod = _build_mock_pod (k8s .V1ContainerState (** container_state ))
280
+ assert expected_state == op .define_container_state ()
281
+
282
+ @pytest .mark .parametrize (
283
+ "pod" ,
284
+ (
285
+ _build_mock_pod (k8s .V1ContainerState (running = None , terminated = None , waiting = None )),
286
+ k8s .V1Pod (
287
+ metadata = k8s .V1ObjectMeta (name = "base" , namespace = "default" ),
288
+ status = k8s .V1PodStatus (container_statuses = []),
289
+ ),
290
+ ),
291
+ )
292
+ def test_define_container_state_with_undefined_state (self , pod ):
293
+ op = KubernetesPodOperatorAsync (task_id = "test_task" , name = "test-pod" , get_logs = True )
294
+ op .pod = pod
295
+ assert op .define_container_state () == ContainerState .UNDEFINED
0 commit comments