@@ -58,58 +58,6 @@ def __init__(self, cfg: KubernetesBackendConfig):
5858
5959 self .namespace = cfg .namespace
6060
61- def _select_best_pod_for_role (
62- self , pods : list [models .IoK8sApiCoreV1Pod ]
63- ) -> Optional [models .IoK8sApiCoreV1Pod ]:
64- """
65- Select the best Pod for a role based on status priority and creation timestamp.
66-
67- Priority order:
68- 1. Running or Succeeded Pods (prefer most recent)
69- 2. Failed Pods (prefer most recent)
70- 3. Pending Pods (prefer most recent)
71- 4. Unknown Pods (prefer most recent)
72- """
73- if not pods :
74- return None
75-
76- # Pod status priority (higher number = higher priority)
77- status_priority = {
78- constants .POD_RUNNING : 4 , # Highest priority
79- constants .POD_SUCCEEDED : 3 , # Second highest
80- constants .POD_FAILED : 2 , # Third priority
81- constants .POD_PENDING : 1 , # Low priority
82- constants .POD_UNKNOWN : 0 , # Lowest priority
83- }
84-
85- # Group Pods by status priority
86- pods_by_status = {}
87- for pod in pods :
88- status = pod .status .phase if pod .status else constants .POD_UNKNOWN
89- priority = status_priority .get (status , 0 )
90-
91- if priority not in pods_by_status :
92- pods_by_status [priority ] = []
93- pods_by_status [priority ].append (pod )
94-
95- # Find the highest priority status that has Pods
96- highest_priority = max (pods_by_status .keys ()) if pods_by_status else 0
97- candidate_pods = pods_by_status [highest_priority ]
98-
99- # Among Pods with the same status, select the most recent one
100- if len (candidate_pods ) == 1 :
101- return candidate_pods [0 ]
102-
103- # Sort by creation timestamp (most recent first)
104- candidate_pods .sort (
105- key = lambda p : (
106- p .metadata .creation_timestamp or datetime .datetime .min .replace (tzinfo = timezone .utc )
107- ),
108- reverse = True ,
109- )
110-
111- return candidate_pods [0 ]
112-
11361 def list_runtimes (self ) -> list [types .Runtime ]:
11462 result = []
11563 try :
@@ -498,6 +446,62 @@ def __get_runtime_from_cr(
498446 ),
499447 )
500448
449+ def _select_best_pod_for_role (
450+ self , pods : list [models .IoK8sApiCoreV1Pod ]
451+ ) -> Optional [models .IoK8sApiCoreV1Pod ]:
452+ """
453+ Select the best Pod for a role based on status priority and creation timestamp.
454+
455+ Priority order (higher priority = preferred):
456+ 1. Running or Succeeded Pods (equal priority, prefer most recent)
457+ 2. Failed Pods (prefer most recent)
458+ 3. Pending Pods (prefer most recent)
459+ 4. Unknown Pods (prefer most recent)
460+
461+ Both Running and Succeeded are considered healthy states with equal priority.
462+ When multiple pods share the same priority, the most recently created pod is selected.
463+ """
464+ if not pods :
465+ return None
466+
467+ # Pod status priority (higher number = higher priority)
468+ # Running and Succeeded have equal priority as both are healthy states
469+ status_priority = {
470+ constants .POD_RUNNING : 4 , # Highest priority (healthy)
471+ constants .POD_SUCCEEDED : 4 , # Highest priority (healthy)
472+ constants .POD_FAILED : 2 , # Lower priority
473+ constants .POD_PENDING : 1 , # Low priority
474+ constants .POD_UNKNOWN : 0 , # Lowest priority
475+ }
476+
477+ # Group Pods by status priority
478+ pods_by_status = {}
479+ for pod in pods :
480+ status = pod .status .phase if pod .status else constants .POD_UNKNOWN
481+ priority = status_priority .get (status , 0 )
482+
483+ if priority not in pods_by_status :
484+ pods_by_status [priority ] = []
485+ pods_by_status [priority ].append (pod )
486+
487+ # Find the highest priority status that has Pods
488+ highest_priority = max (pods_by_status .keys ()) if pods_by_status else 0
489+ candidate_pods = pods_by_status [highest_priority ]
490+
491+ # Among Pods with the same priority, select the most recent one
492+ if len (candidate_pods ) == 1 :
493+ return candidate_pods [0 ]
494+
495+ # Sort by creation timestamp (most recent first)
496+ candidate_pods .sort (
497+ key = lambda p : (
498+ p .metadata .creation_timestamp or datetime .datetime .min .replace (tzinfo = timezone .utc )
499+ ),
500+ reverse = True ,
501+ )
502+
503+ return candidate_pods [0 ]
504+
501505 def _read_pod_logs (self , pod_name : str , container_name : str , follow : bool ) -> Iterator [str ]:
502506 """Read logs from a pod container."""
503507 try :
@@ -585,7 +589,7 @@ def __get_trainjob_from_cr(
585589 # - For training nodes: use role + job index (node-0, node-1, launcher-0, etc.)
586590 role = pod .metadata .labels [constants .JOBSET_RJOB_NAME_LABEL ]
587591 if role in {constants .LAUNCHER , constants .NODE }:
588- job_index = pod .metadata .labels . get ( constants .JOB_INDEX_LABEL , "0" )
592+ job_index = pod .metadata .labels [ constants .JOB_INDEX_LABEL ]
589593 key = f"{ role } -{ job_index } "
590594 else :
591595 key = role
@@ -627,7 +631,7 @@ def __get_trainjob_from_cr(
627631 pod .status ,
628632 trainjob .runtime ,
629633 pod .metadata .labels [constants .JOBSET_RJOB_NAME_LABEL ],
630- int (pod .metadata .labels . get ( constants .JOB_INDEX_LABEL , "0" ) ),
634+ int (pod .metadata .labels [ constants .JOB_INDEX_LABEL ] ),
631635 )
632636 )
633637 except multiprocessing .TimeoutError as e :
0 commit comments