mirror of
https://github.com/kubevirt/containerized-data-importer.git
synced 2025-06-03 06:30:22 +00:00

* unique cert per clone source pod Signed-off-by: Michael Henriksen <mhenriks@redhat.com> * upload controller shouldn't cache certs Signed-off-by: Michael Henriksen <mhenriks@redhat.com> * fix test variable names Signed-off-by: Michael Henriksen <mhenriks@redhat.com>
257 lines
7.8 KiB
Go
257 lines
7.8 KiB
Go
package controller
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/runtime"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
coreinformers "k8s.io/client-go/informers/core/v1"
|
|
"k8s.io/client-go/kubernetes"
|
|
corelisters "k8s.io/client-go/listers/core/v1"
|
|
"k8s.io/client-go/tools/cache"
|
|
"k8s.io/client-go/util/workqueue"
|
|
"k8s.io/klog"
|
|
|
|
"kubevirt.io/containerized-data-importer/pkg/expectations"
|
|
)
|
|
|
|
const (
|
|
// AnnAPIGroup is the APIGroup for CDI
|
|
AnnAPIGroup = "cdi.kubevirt.io"
|
|
// AnnCreatedBy is a pod annotation indicating if the pod was created by the PVC
|
|
AnnCreatedBy = AnnAPIGroup + "/storage.createdByController"
|
|
// AnnPodPhase is a PVC annotation indicating the related pod progress (phase)
|
|
AnnPodPhase = AnnAPIGroup + "/storage.pod.phase"
|
|
// AnnPodReady tells whether the pod is ready
|
|
AnnPodReady = AnnAPIGroup + "/storage.pod.ready"
|
|
// AnnOwnerRef is used when owner is in a different namespace
|
|
AnnOwnerRef = AnnAPIGroup + "/storage.ownerRef"
|
|
)
|
|
|
|
//Controller is a struct that contains common information and functionality used by all CDI controllers.
|
|
type Controller struct {
|
|
clientset kubernetes.Interface
|
|
queue workqueue.RateLimitingInterface
|
|
pvcInformer, podInformer cache.SharedIndexInformer
|
|
pvcLister corelisters.PersistentVolumeClaimLister
|
|
podLister corelisters.PodLister
|
|
pvcsSynced cache.InformerSynced
|
|
podsSynced cache.InformerSynced
|
|
image string
|
|
pullPolicy string // Options: IfNotPresent, Always, or Never
|
|
verbose string // verbose levels: 1, 2, ...
|
|
podExpectations *expectations.UIDTrackingControllerExpectations
|
|
}
|
|
|
|
//NewController is called when we instantiate any CDI controller.
|
|
func NewController(client kubernetes.Interface,
|
|
pvcInformer coreinformers.PersistentVolumeClaimInformer,
|
|
podInformer coreinformers.PodInformer,
|
|
image string,
|
|
pullPolicy string,
|
|
verbose string) *Controller {
|
|
c := &Controller{
|
|
clientset: client,
|
|
queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
|
|
pvcInformer: pvcInformer.Informer(),
|
|
podInformer: podInformer.Informer(),
|
|
pvcLister: pvcInformer.Lister(),
|
|
podLister: podInformer.Lister(),
|
|
pvcsSynced: pvcInformer.Informer().HasSynced,
|
|
podsSynced: podInformer.Informer().HasSynced,
|
|
image: image,
|
|
pullPolicy: pullPolicy,
|
|
verbose: verbose,
|
|
podExpectations: expectations.NewUIDTrackingControllerExpectations(expectations.NewControllerExpectations()),
|
|
}
|
|
|
|
// Bind the pvc SharedIndexInformer to the pvc queue
|
|
c.pvcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
|
AddFunc: c.enqueuePVC,
|
|
UpdateFunc: func(old, new interface{}) {
|
|
c.enqueuePVC(new)
|
|
},
|
|
DeleteFunc: c.enqueuePVC,
|
|
})
|
|
|
|
// Bind the pod SharedIndexInformer to the pod queue
|
|
c.podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
|
AddFunc: c.handlePodAdd,
|
|
UpdateFunc: func(old, new interface{}) {
|
|
newDepl := new.(*v1.Pod)
|
|
oldDepl := old.(*v1.Pod)
|
|
if newDepl.ResourceVersion == oldDepl.ResourceVersion {
|
|
// Periodic resync will send update events for all known PVCs.
|
|
// Two different versions of the same PVCs will always have different RVs.
|
|
return
|
|
}
|
|
c.handlePodUpdate(new)
|
|
},
|
|
DeleteFunc: c.handlePodDelete,
|
|
})
|
|
|
|
return c
|
|
}
|
|
|
|
func (c *Controller) handlePodAdd(obj interface{}) {
|
|
c.handlePodObject(obj, "add")
|
|
}
|
|
func (c *Controller) handlePodUpdate(obj interface{}) {
|
|
c.handlePodObject(obj, "update")
|
|
}
|
|
func (c *Controller) handlePodDelete(obj interface{}) {
|
|
c.handlePodObject(obj, "delete")
|
|
}
|
|
|
|
func (c *Controller) observePodCreate(pvcKey string) {
|
|
c.podExpectations.CreationObserved(pvcKey)
|
|
}
|
|
|
|
func (c *Controller) handlePodObject(obj interface{}, verb string) {
|
|
var object metav1.Object
|
|
var ok bool
|
|
|
|
if object, ok = obj.(metav1.Object); !ok {
|
|
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
|
if !ok {
|
|
runtime.HandleError(errors.Errorf("error decoding object, invalid type"))
|
|
return
|
|
}
|
|
object, ok = tombstone.Obj.(metav1.Object)
|
|
if !ok {
|
|
runtime.HandleError(errors.Errorf("error decoding object tombstone, invalid type"))
|
|
return
|
|
}
|
|
klog.V(3).Infof("Recovered deleted object '%s' from tombstone", object.GetName())
|
|
}
|
|
|
|
_, createdByUs := object.GetAnnotations()[AnnCreatedBy]
|
|
if !createdByUs {
|
|
klog.V(3).Infof("Ignoring pod %s/%s, as it's not created by us", object.GetNamespace(), object.GetName())
|
|
return
|
|
}
|
|
|
|
klog.V(3).Infof("Processing object: %s/%s", object.GetNamespace(), object.GetName())
|
|
|
|
var pvc *v1.PersistentVolumeClaim
|
|
var err error
|
|
|
|
if ownerRefObj := metav1.GetControllerOf(object); ownerRefObj != nil {
|
|
if ownerRefObj.Kind == "PersistentVolumeClaim" {
|
|
pvc, err = c.pvcLister.PersistentVolumeClaims(object.GetNamespace()).Get(ownerRefObj.Name)
|
|
if err != nil {
|
|
klog.V(3).Infof("ignoring orphaned object '%s' of pvc '%s'", object.GetSelfLink(), ownerRefObj.Name)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
if pvc == nil {
|
|
ownerRefAnno, ok := object.GetAnnotations()[AnnOwnerRef]
|
|
if ok {
|
|
pvc, ok, err = c.pvcFromKey(ownerRefAnno)
|
|
if err != nil {
|
|
runtime.HandleError(errors.Wrapf(err, "error getting PVC %s", ownerRefAnno))
|
|
return
|
|
} else if !ok {
|
|
runtime.HandleError(errors.Errorf("error getting PVC %s from ownerref", ownerRefAnno))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
if pvc == nil {
|
|
klog.V(3).Infof("Object: %s/%s has unexpected owner and no ownerRef annotation", object.GetNamespace(), object.GetName())
|
|
return
|
|
}
|
|
|
|
klog.V(3).Infof("Will queue PVC %s/%s in response to %s %s/%s", pvc.Namespace, pvc.Name, verb, object.GetNamespace(), object.GetName())
|
|
|
|
if verb == "add" {
|
|
pvcKey, err := cache.MetaNamespaceKeyFunc(pvc)
|
|
if err != nil {
|
|
runtime.HandleError(err)
|
|
return
|
|
}
|
|
c.observePodCreate(pvcKey)
|
|
}
|
|
|
|
c.enqueuePVC(pvc)
|
|
}
|
|
|
|
func (c *Controller) enqueuePVC(obj interface{}) {
|
|
var key string
|
|
var err error
|
|
if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {
|
|
runtime.HandleError(err)
|
|
return
|
|
}
|
|
c.queue.AddRateLimited(key)
|
|
}
|
|
|
|
//Run is being called from cdi controllers
|
|
func (c *Controller) run(threadiness int, stopCh <-chan struct{}, f func()) error { //*CloneContorler
|
|
defer func() {
|
|
c.queue.ShutDown()
|
|
}()
|
|
klog.V(3).Infoln("Starting cdi controller Run loop")
|
|
if threadiness < 1 {
|
|
return errors.Errorf("expected >0 threads, got %d", threadiness)
|
|
}
|
|
|
|
if !cache.WaitForCacheSync(stopCh, c.pvcInformer.HasSynced) {
|
|
return errors.New("Timeout waiting for pvc cache sync")
|
|
}
|
|
if !cache.WaitForCacheSync(stopCh, c.podInformer.HasSynced) {
|
|
return errors.New("Timeout waiting for pod cache sync")
|
|
}
|
|
|
|
klog.V(3).Infoln("Controller cache has synced")
|
|
for i := 0; i < threadiness; i++ {
|
|
go wait.Until(f, time.Second, stopCh)
|
|
}
|
|
<-stopCh
|
|
return nil
|
|
}
|
|
|
|
// forget the passed-in key for this event and optionally log a message.
|
|
func (c *Controller) forgetKey(key interface{}, msg string) bool {
|
|
if len(msg) > 0 {
|
|
klog.V(3).Info(msg)
|
|
}
|
|
c.queue.Forget(key)
|
|
return true
|
|
}
|
|
|
|
// return a pvc pointer based on the passed-in work queue key.
|
|
func (c *Controller) pvcFromKey(key string) (*v1.PersistentVolumeClaim, bool, error) {
|
|
obj, exists, err := c.objFromKey(c.pvcInformer, key)
|
|
if err != nil {
|
|
return nil, false, errors.Wrap(err, "could not get pvc object from key")
|
|
} else if !exists {
|
|
return nil, false, nil
|
|
}
|
|
|
|
pvc, ok := obj.(*v1.PersistentVolumeClaim)
|
|
if !ok {
|
|
return nil, false, errors.New("Object not of type *v1.PersistentVolumeClaim")
|
|
}
|
|
return pvc, true, nil
|
|
}
|
|
|
|
func (c *Controller) objFromKey(informer cache.SharedIndexInformer, key string) (interface{}, bool, error) {
|
|
obj, ok, err := informer.GetIndexer().GetByKey(key)
|
|
if err != nil {
|
|
return nil, false, errors.Wrap(err, "error getting interface obj from store")
|
|
}
|
|
|
|
if !ok {
|
|
return nil, false, nil
|
|
}
|
|
return obj, true, nil
|
|
}
|