pkg/controller/podgroup.go (242 lines of code) (raw):

/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package controller import ( "context" "fmt" "reflect" "sort" "strings" "time" v1 "k8s.io/api/core/v1" apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" coreinformer "k8s.io/client-go/informers/core/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" corelister "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" schedv1alpha1 "sigs.k8s.io/scheduler-plugins/pkg/apis/scheduling/v1alpha1" schedclientset "sigs.k8s.io/scheduler-plugins/pkg/generated/clientset/versioned" schedinformer "sigs.k8s.io/scheduler-plugins/pkg/generated/informers/externalversions/scheduling/v1alpha1" schedlister "sigs.k8s.io/scheduler-plugins/pkg/generated/listers/scheduling/v1alpha1" "sigs.k8s.io/scheduler-plugins/pkg/util" ) // PodGroupController is a controller that process pod groups using provided Handler interface type PodGroupController struct { eventRecorder record.EventRecorder pgQueue workqueue.RateLimitingInterface pgLister schedlister.PodGroupLister podLister corelister.PodLister pgListerSynced cache.InformerSynced podListerSynced cache.InformerSynced pgClient schedclientset.Interface } // NewPodGroupController returns a new *PodGroupController func NewPodGroupController(client kubernetes.Interface, pgInformer schedinformer.PodGroupInformer, podInformer coreinformer.PodInformer, pgClient schedclientset.Interface) *PodGroupController { broadcaster := record.NewBroadcaster() broadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: client.CoreV1().Events(v1.NamespaceAll)}) ctrl := &PodGroupController{ eventRecorder: broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "PodGroupController"}), pgQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "PodGroup"), } pgInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: ctrl.pgAdded, UpdateFunc: ctrl.pgUpdated, }) podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: ctrl.podAdded, UpdateFunc: ctrl.podUpdated, }) ctrl.pgLister = pgInformer.Lister() ctrl.podLister = podInformer.Lister() ctrl.pgListerSynced = pgInformer.Informer().HasSynced ctrl.podListerSynced = podInformer.Informer().HasSynced ctrl.pgClient = pgClient return ctrl } // Run starts listening on channel events func (ctrl *PodGroupController) Run(workers int, stopCh <-chan struct{}) { defer ctrl.pgQueue.ShutDown() klog.Info("Starting Pod Group controller") defer klog.Info("Shutting Pod Group controller") if !cache.WaitForCacheSync(stopCh, ctrl.pgListerSynced, ctrl.podListerSynced) { klog.Error("Cannot sync caches") return } klog.Info("Pod Group sync finished") for i := 0; i < workers; i++ { go wait.Until(ctrl.worker, time.Second, stopCh) } <-stopCh } // pgAdded reacts to a PG creation func (ctrl *PodGroupController) pgAdded(obj interface{}) { key, err := cache.MetaNamespaceKeyFunc(obj) if err != nil { runtime.HandleError(err) return } pg := obj.(*schedv1alpha1.PodGroup) if pg.Status.Phase == schedv1alpha1.PodGroupFinished || pg.Status.Phase == schedv1alpha1.PodGroupFailed { return } // If startScheduleTime - createTime > 2days, do not enqueue again because pod may have been GCed if pg.Status.Scheduled == pg.Spec.MinMember && pg.Status.Running == 0 && pg.Status.ScheduleStartTime.Sub(pg.CreationTimestamp.Time) > 48*time.Hour { return } klog.Infof("Enqueue key %v", key) ctrl.pgQueue.Add(key) } // pgUpdated reacts to a PG update func (ctrl *PodGroupController) pgUpdated(old, new interface{}) { ctrl.pgAdded(new) } // podAdded reacts to a PG creation func (ctrl *PodGroupController) podAdded(obj interface{}) { pod := obj.(*v1.Pod) pgName := util.GetPodGroupLabel(pod) if len(pgName) == 0 { return } pg, err := ctrl.pgLister.PodGroups(pod.Namespace).Get(pgName) if err != nil { klog.Error(err) return } klog.V(5).Infof("Add pg %v when pod %v add", pg.Name, pod.Name) ctrl.pgAdded(pg) } // pgUpdated reacts to a PG update func (ctrl *PodGroupController) podUpdated(old, new interface{}) { ctrl.podAdded(new) } func (ctrl *PodGroupController) worker() { for ctrl.processNextWorkItem() { } } // processNextWorkItem deals with one key off the queue. It returns false when it's time to quit. func (ctrl *PodGroupController) processNextWorkItem() bool { keyObj, quit := ctrl.pgQueue.Get() if quit { return false } defer ctrl.pgQueue.Done(keyObj) key, ok := keyObj.(string) if !ok { ctrl.pgQueue.Forget(keyObj) runtime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", keyObj)) return true } if err := ctrl.syncHandler(key); err != nil { runtime.HandleError(err) klog.Errorf("error syncing pod group %q: %s", key, err.Error()) return true } return true } // syncHandle syncs pod group and convert status func (ctrl *PodGroupController) syncHandler(key string) error { // Convert the namespace/name string into a distinct namespace and name namespace, name, err := cache.SplitMetaNamespaceKey(key) if err != nil { runtime.HandleError(fmt.Errorf("invalid resource key: %s", key)) return nil } defer func() { if err != nil { ctrl.pgQueue.AddRateLimited(key) return } }() pg, err := ctrl.pgLister.PodGroups(namespace).Get(name) if apierrs.IsNotFound(err) { klog.V(5).Infof("Pod group %q has been deleted ", key) return nil } if err != nil { klog.V(3).Infof("Unable to retrieve pod group %q from store: %v", key, err) return err } pgCopy := pg.DeepCopy() selector := labels.Set(map[string]string{util.PodGroupLabel: pgCopy.Name}).AsSelector() pods, err := ctrl.podLister.List(selector) if err != nil { klog.Errorf("List pods for group %v failed: %v", pgCopy.Name, err) return err } switch pgCopy.Status.Phase { case "": pgCopy.Status.Phase = schedv1alpha1.PodGroupPending case schedv1alpha1.PodGroupPending: if len(pods) >= int(pg.Spec.MinMember) { pgCopy.Status.Phase = schedv1alpha1.PodGroupPreScheduling fillOccupiedObj(pg, pods[0]) } default: var ( running int32 = 0 succeeded int32 = 0 failed int32 = 0 ) if len(pods) != 0 { for _, pod := range pods { switch pod.Status.Phase { case v1.PodRunning: running++ case v1.PodSucceeded: succeeded++ case v1.PodFailed: failed++ } } } pgCopy.Status.Failed = failed pgCopy.Status.Succeeded = succeeded pgCopy.Status.Running = running if pgCopy.Status.Scheduled >= pgCopy.Spec.MinMember && pgCopy.Status.Phase == schedv1alpha1.PodGroupScheduling { pgCopy.Status.Phase = schedv1alpha1.PodGroupScheduled } if pgCopy.Status.Succeeded+pgCopy.Status.Running >= pg.Spec.MinMember && pgCopy.Status.Phase == schedv1alpha1.PodGroupScheduled { pgCopy.Status.Phase = schedv1alpha1.PodGroupRunning } // Final state of pod group if pgCopy.Status.Failed != 0 && pgCopy.Status.Failed+pgCopy.Status.Running+pgCopy.Status.Succeeded >= pg.Spec. MinMember { pgCopy.Status.Phase = schedv1alpha1.PodGroupFailed } if pgCopy.Status.Succeeded >= pg.Spec.MinMember { pgCopy.Status.Phase = schedv1alpha1.PodGroupFinished } } err = ctrl.patchPodGroup(pg, pgCopy) if err == nil { ctrl.pgQueue.Forget(pg) } return err } func (ctrl *PodGroupController) patchPodGroup(old, new *schedv1alpha1.PodGroup) error { if !reflect.DeepEqual(old, new) { patch, err := util.CreateMergePatch(old, new) if err != nil { return err } _, err = ctrl.pgClient.SchedulingV1alpha1().PodGroups(old.Namespace).Patch(context.TODO(), old.Name, types.MergePatchType, patch, metav1.PatchOptions{}) if err != nil { return err } } return nil } func fillOccupiedObj(pg *schedv1alpha1.PodGroup, pod *v1.Pod) { var refs []string for _, ownerRef := range pod.OwnerReferences { refs = append(refs, fmt.Sprintf("%s/%s", pod.Namespace, ownerRef.Name)) } if len(pg.Status.OccupiedBy) == 0 { return } if len(refs) != 0 { sort.Strings(refs) pg.Status.OccupiedBy = strings.Join(refs, ",") } }