@@ -86,58 +86,6 @@ def verify_backend(self) -> None:
8686 )
8787 return
8888
89- def _select_best_pod_for_role (
90- self , pods : list [models .IoK8sApiCoreV1Pod ]
91- ) -> Optional [models .IoK8sApiCoreV1Pod ]:
92- """
93- Select the best Pod for a role based on status priority and creation timestamp.
94-
95- Priority order:
96- 1. Running or Succeeded Pods (prefer most recent)
97- 2. Failed Pods (prefer most recent)
98- 3. Pending Pods (prefer most recent)
99- 4. Unknown Pods (prefer most recent)
100- """
101- if not pods :
102- return None
103-
104- # Pod status priority (higher number = higher priority)
105- status_priority = {
106- constants .POD_RUNNING : 4 , # Highest priority
107- constants .POD_SUCCEEDED : 3 , # Second highest
108- constants .POD_FAILED : 2 , # Third priority
109- constants .POD_PENDING : 1 , # Low priority
110- constants .POD_UNKNOWN : 0 , # Lowest priority
111- }
112-
113- # Group Pods by status priority
114- pods_by_status = {}
115- for pod in pods :
116- status = pod .status .phase if pod .status else constants .POD_UNKNOWN
117- priority = status_priority .get (status , 0 )
118-
119- if priority not in pods_by_status :
120- pods_by_status [priority ] = []
121- pods_by_status [priority ].append (pod )
122-
123- # Find the highest priority status that has Pods
124- highest_priority = max (pods_by_status .keys ()) if pods_by_status else 0
125- candidate_pods = pods_by_status [highest_priority ]
126-
127- # Among Pods with the same status, select the most recent one
128- if len (candidate_pods ) == 1 :
129- return candidate_pods [0 ]
130-
131- # Sort by creation timestamp (most recent first)
132- candidate_pods .sort (
133- key = lambda p : (
134- p .metadata .creation_timestamp or datetime .datetime .min .replace (tzinfo = timezone .utc )
135- ),
136- reverse = True ,
137- )
138-
139- return candidate_pods [0 ]
140-
14189 def list_runtimes (self ) -> list [types .Runtime ]:
14290 """List available runtimes, preferring namespaced over cluster-scoped for duplicates.
14391
@@ -656,62 +604,6 @@ def __get_runtime_from_cr(
656604 ),
657605 )
658606
659- def _select_best_pod_for_role (
660- self , pods : list [models .IoK8sApiCoreV1Pod ]
661- ) -> Optional [models .IoK8sApiCoreV1Pod ]:
662- """
663- Select the best Pod for a role based on status priority and creation timestamp.
664-
665- Priority order (higher priority = preferred):
666- 1. Running or Succeeded Pods (equal priority, prefer most recent)
667- 2. Failed Pods (prefer most recent)
668- 3. Pending Pods (prefer most recent)
669- 4. Unknown Pods (prefer most recent)
670-
671- Both Running and Succeeded are considered healthy states with equal priority.
672- When multiple pods share the same priority, the most recently created pod is selected.
673- """
674- if not pods :
675- return None
676-
677- # Pod status priority (higher number = higher priority)
678- # Running and Succeeded have equal priority as both are healthy states
679- status_priority = {
680- constants .POD_RUNNING : 4 , # Highest priority (healthy)
681- constants .POD_SUCCEEDED : 4 , # Highest priority (healthy)
682- constants .POD_FAILED : 2 , # Lower priority
683- constants .POD_PENDING : 1 , # Low priority
684- constants .POD_UNKNOWN : 0 , # Lowest priority
685- }
686-
687- # Group Pods by status priority
688- pods_by_status = {}
689- for pod in pods :
690- status = pod .status .phase if pod .status else constants .POD_UNKNOWN
691- priority = status_priority .get (status , 0 )
692-
693- if priority not in pods_by_status :
694- pods_by_status [priority ] = []
695- pods_by_status [priority ].append (pod )
696-
697- # Find the highest priority status that has Pods
698- highest_priority = max (pods_by_status .keys ()) if pods_by_status else 0
699- candidate_pods = pods_by_status [highest_priority ]
700-
701- # Among Pods with the same priority, select the most recent one
702- if len (candidate_pods ) == 1 :
703- return candidate_pods [0 ]
704-
705- # Sort by creation timestamp (most recent first)
706- candidate_pods .sort (
707- key = lambda p : (
708- p .metadata .creation_timestamp or datetime .datetime .min .replace (tzinfo = timezone .utc )
709- ),
710- reverse = True ,
711- )
712-
713- return candidate_pods [0 ]
714-
715607 def _read_pod_logs (self , pod_name : str , container_name : str , follow : bool ) -> Iterator [str ]:
716608 """Read logs from a pod container."""
717609 try :
@@ -787,9 +679,21 @@ def __get_trainjob_from_cr(
787679 if not pod_list :
788680 return trainjob
789681
790- # Collect all Pods for this role
791- pods_by_role : dict [str , list [models .IoK8sApiCoreV1Pod ]] = {}
792- for pod in pod_list .items :
682+ # Sort Pods by creation timestamp (newest first) to ensure we process
683+ # the most recent Pod for each role when duplicates exist (e.g., restarts)
684+ pods = sorted (
685+ pod_list .items ,
686+ key = lambda p : (
687+ p .metadata .creation_timestamp
688+ or datetime .datetime .min .replace (tzinfo = timezone .utc )
689+ ),
690+ reverse = True ,
691+ )
692+
693+ # Track seen role keys to skip duplicate Pods (older ones)
694+ seen_roles : set [str ] = set ()
695+
696+ for pod in pods :
793697 # Pod must have labels to detect the TrainJob step.
794698 # Every Pod always has a single TrainJob step.
795699 if not (pod .metadata and pod .metadata .name and pod .metadata .labels and pod .spec ):
@@ -805,24 +709,13 @@ def __get_trainjob_from_cr(
805709 else :
806710 key = role
807711
808- if key not in pods_by_role :
809- pods_by_role [key ] = []
810- pods_by_role [key ].append (pod )
811-
812- # Select the best Pod for each role using status-priority logic
813- selected_pods : dict [str , models .IoK8sApiCoreV1Pod ] = {}
814- for role_key , pods in pods_by_role .items ():
815- best_pod = self ._select_best_pod_for_role (pods )
816- if best_pod :
817- selected_pods [role_key ] = best_pod
712+ # Skip if we've already processed a Pod for this role (newer one)
713+ if key in seen_roles :
714+ continue
715+ seen_roles .add (key )
818716
819- # Process only the selected Pod for each role
820- for _role_key , pod in selected_pods .items ():
821717 # Get the Initializer step.
822- if pod .metadata .labels [constants .JOBSET_RJOB_NAME_LABEL ] in {
823- constants .DATASET_INITIALIZER ,
824- constants .MODEL_INITIALIZER ,
825- }:
718+ if role in {constants .DATASET_INITIALIZER , constants .MODEL_INITIALIZER }:
826719 trainjob .steps .append (
827720 utils .get_trainjob_initializer_step (
828721 pod .metadata .name ,
@@ -831,17 +724,14 @@ def __get_trainjob_from_cr(
831724 )
832725 )
833726 # Get the Node step.
834- elif pod .metadata .labels [constants .JOBSET_RJOB_NAME_LABEL ] in {
835- constants .LAUNCHER ,
836- constants .NODE ,
837- }:
727+ elif role in {constants .LAUNCHER , constants .NODE }:
838728 trainjob .steps .append (
839729 utils .get_trainjob_node_step (
840730 pod .metadata .name ,
841731 pod .spec ,
842732 pod .status ,
843733 trainjob .runtime ,
844- pod . metadata . labels [ constants . JOBSET_RJOB_NAME_LABEL ] ,
734+ role ,
845735 int (pod .metadata .labels [constants .JOB_INDEX_LABEL ]),
846736 )
847737 )
0 commit comments