Skip to content

Commit 580b061

Browse files
committed
Changes
- add unit tests for creating suspended, suspending and resuming - use fake clock for unit tests - do not return from the syncHandler after worker pods cleanup on suspend - this allows to continue with the MPIJob update in the same sync
1 parent 3b8eb54 commit 580b061

File tree

2 files changed

+271
-26
lines changed

2 files changed

+271
-26
lines changed

pkg/controller/mpi_job_controller.go

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import (
5151
"k8s.io/client-go/tools/record"
5252
"k8s.io/client-go/util/workqueue"
5353
"k8s.io/klog"
54+
"k8s.io/utils/clock"
5455
"k8s.io/utils/pointer"
5556
podgroupv1beta1 "volcano.sh/apis/pkg/apis/scheduling/v1beta1"
5657
volcanoclient "volcano.sh/apis/pkg/client/clientset/versioned"
@@ -245,6 +246,9 @@ type MPIJobController struct {
245246

246247
// To allow injection of updateStatus for testing.
247248
updateStatusHandler func(mpijob *kubeflow.MPIJob) error
249+
250+
// Clock for internal use of unit-testing
251+
clock clock.WithTicker
248252
}
249253

250254
// NewMPIJobController returns a new MPIJob controller.
@@ -260,6 +264,26 @@ func NewMPIJobController(
260264
podgroupsInformer podgroupsinformer.PodGroupInformer,
261265
mpiJobInformer informers.MPIJobInformer,
262266
gangSchedulerName string) *MPIJobController {
267+
return NewMPIJobControllerWithClock(kubeClient, kubeflowClient, volcanoClientSet,
268+
configMapInformer, secretInformer, serviceInformer, jobInformer,
269+
podInformer, podgroupsInformer, mpiJobInformer, gangSchedulerName,
270+
&clock.RealClock{})
271+
}
272+
273+
// NewMPIJobController returns a new MPIJob controller.
274+
func NewMPIJobControllerWithClock(
275+
kubeClient kubernetes.Interface,
276+
kubeflowClient clientset.Interface,
277+
volcanoClientSet volcanoclient.Interface,
278+
configMapInformer coreinformers.ConfigMapInformer,
279+
secretInformer coreinformers.SecretInformer,
280+
serviceInformer coreinformers.ServiceInformer,
281+
jobInformer batchinformers.JobInformer,
282+
podInformer coreinformers.PodInformer,
283+
podgroupsInformer podgroupsinformer.PodGroupInformer,
284+
mpiJobInformer informers.MPIJobInformer,
285+
gangSchedulerName string,
286+
clock clock.WithTicker) *MPIJobController {
263287

264288
// Create event broadcaster.
265289
klog.V(4).Info("Creating event broadcaster")
@@ -296,6 +320,7 @@ func NewMPIJobController(
296320
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "MPIJobs"),
297321
recorder: recorder,
298322
gangSchedulerName: gangSchedulerName,
323+
clock: clock,
299324
}
300325

301326
controller.updateStatusHandler = controller.doUpdateJobStatus
@@ -451,9 +476,9 @@ func (c *MPIJobController) processNextWorkItem() bool {
451476
// converge the two. It then updates the Status block of the MPIJob resource
452477
// with the current status of the resource.
453478
func (c *MPIJobController) syncHandler(key string) error {
454-
startTime := time.Now()
479+
startTime := c.clock.Now()
455480
defer func() {
456-
klog.Infof("Finished syncing job %q (%v)", key, time.Since(startTime))
481+
klog.Infof("Finished syncing job %q (%v)", key, c.clock.Since(startTime))
457482
}()
458483

459484
// Convert the namespace/name string into a distinct namespace and name.
@@ -505,7 +530,10 @@ func (c *MPIJobController) syncHandler(key string) error {
505530
// cleanup and stop retrying the MPIJob.
506531
if isFinished(mpiJob.Status) && mpiJob.Status.CompletionTime != nil {
507532
if isCleanUpPods(mpiJob.Spec.RunPolicy.CleanPodPolicy) {
508-
return cleanUpWorkerPods(mpiJob, c)
533+
if err := cleanUpWorkerPods(mpiJob, c); err != nil {
534+
return err
535+
}
536+
return c.updateStatusHandler(mpiJob)
509537
}
510538
return nil
511539
}
@@ -570,13 +598,6 @@ func (c *MPIJobController) syncHandler(key string) error {
570598
}
571599
}
572600

573-
// Finally, we update the status block of the MPIJob resource to reflect the
574-
// current state of the world.
575-
err = c.updateMPIJobStatus(mpiJob, launcher, worker)
576-
if err != nil {
577-
return err
578-
}
579-
580601
if launcher != nil {
581602
if isMPIJobSuspended(mpiJob) != isJobSuspended(launcher) {
582603
// align the suspension state of launcher with the MPIJob
@@ -593,6 +614,14 @@ func (c *MPIJobController) syncHandler(key string) error {
593614
return err
594615
}
595616
}
617+
618+
// Finally, we update the status block of the MPIJob resource to reflect the
619+
// current state of the world.
620+
err = c.updateMPIJobStatus(mpiJob, launcher, worker)
621+
if err != nil {
622+
return err
623+
}
624+
596625
return nil
597626
}
598627

@@ -608,7 +637,7 @@ func cleanUpWorkerPods(mpiJob *kubeflow.MPIJob, c *MPIJobController) error {
608637
}
609638
}
610639
mpiJob.Status.ReplicaStatuses[common.ReplicaType(kubeflow.MPIReplicaTypeWorker)].Active = 0
611-
return c.updateStatusHandler(mpiJob)
640+
return nil
612641
}
613642

614643
// getLauncherJob gets the launcher Job controlled by this MPIJob.
@@ -932,10 +961,6 @@ func (c *MPIJobController) deleteWorkerPods(mpiJob *kubeflow.MPIJob) error {
932961

933962
func (c *MPIJobController) updateMPIJobStatus(mpiJob *kubeflow.MPIJob, launcher *batchv1.Job, worker []*corev1.Pod) error {
934963
oldStatus := mpiJob.Status.DeepCopy()
935-
launcherPods, err := c.jobPods(launcher)
936-
if err != nil {
937-
return fmt.Errorf("checking launcher pods running: %w", err)
938-
}
939964
if isMPIJobSuspended(mpiJob) {
940965
// it is suspended now
941966
if updateMPIJobConditions(mpiJob, kubeflow.JobSuspended, v1.ConditionTrue, "MPIJobSuspended", "MPIJob suspended") {
@@ -945,10 +970,14 @@ func (c *MPIJobController) updateMPIJobStatus(mpiJob *kubeflow.MPIJob, launcher
945970
// it is not suspended now, consider resumed if the condition was set before
946971
if updateMPIJobConditions(mpiJob, kubeflow.JobSuspended, v1.ConditionFalse, "MPIJobResumed", "MPIJob resumed") {
947972
c.recorder.Event(mpiJob, corev1.EventTypeNormal, "MPIJobResumed", "MPIJob resumed")
948-
now := metav1.NewTime(time.Now())
973+
now := metav1.NewTime(c.clock.Now())
949974
mpiJob.Status.StartTime = &now
950975
}
951976
}
977+
launcherPods, err := c.jobPods(launcher)
978+
if err != nil {
979+
return fmt.Errorf("checking launcher pods running: %w", err)
980+
}
952981
// Job.status.Active accounts for Pending and Running pods. Count running pods
953982
// from the lister instead.
954983
launcherPodsCnt := countRunningPods(launcherPods)
@@ -1001,13 +1030,13 @@ func (c *MPIJobController) updateMPIJobStatus(mpiJob *kubeflow.MPIJob, launcher
10011030
c.recorder.Event(mpiJob, corev1.EventTypeWarning, mpiJobEvict, msg)
10021031
}
10031032

1004-
if launcher != nil && launcherPodsCnt >= 1 && running == len(worker) {
1033+
if isMPIJobSuspended(mpiJob) {
1034+
msg := fmt.Sprintf("MPIJob %s/%s is suspended.", mpiJob.Namespace, mpiJob.Name)
1035+
updateMPIJobConditions(mpiJob, common.JobRunning, v1.ConditionFalse, mpiJobSuspendedReason, msg)
1036+
} else if launcher != nil && launcherPodsCnt >= 1 && running == len(worker) {
10051037
msg := fmt.Sprintf("MPIJob %s/%s is running.", mpiJob.Namespace, mpiJob.Name)
10061038
updateMPIJobConditions(mpiJob, common.JobRunning, v1.ConditionTrue, mpiJobRunningReason, msg)
10071039
c.recorder.Eventf(mpiJob, corev1.EventTypeNormal, "MPIJobRunning", "MPIJob %s/%s is running", mpiJob.Namespace, mpiJob.Name)
1008-
} else if isMPIJobSuspended(mpiJob) {
1009-
msg := fmt.Sprintf("MPIJob %s/%s is suspended.", mpiJob.Namespace, mpiJob.Name)
1010-
updateMPIJobConditions(mpiJob, common.JobRunning, v1.ConditionFalse, mpiJobSuspendedReason, msg)
10111040
}
10121041

10131042
// no need to update the mpijob if the status hasn't changed since last time.

0 commit comments

Comments
 (0)