Skip to content

Commit 3ec8093

Browse files
committed
refactor(trainer): address PR review feedback for pod selection logic
- Give Running and Succeeded pods equal priority (both are healthy states) - Update docstring to clearly explain equal priority and timestamp tiebreaker - Remove JOB_INDEX_LABEL .get() default, use direct access - Move _select_best_pod_for_role method after public methods per convention Addresses review comments from @kramaranya on PR #160 Signed-off-by: HKanoje <hrithik.kanoje@gmail.com>
1 parent f9df10e commit 3ec8093

1 file changed

Lines changed: 58 additions & 54 deletions

File tree

kubeflow/trainer/backends/kubernetes/backend.py

Lines changed: 58 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)