k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controlplane/controller/crdregistration/crdregistration_controller.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package crdregistration
    18  
    19  import (
    20  	"fmt"
    21  	"time"
    22  
    23  	"k8s.io/klog/v2"
    24  
    25  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    26  	crdinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1"
    27  	crdlisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/labels"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    32  	"k8s.io/apimachinery/pkg/util/wait"
    33  	"k8s.io/client-go/tools/cache"
    34  	"k8s.io/client-go/util/workqueue"
    35  	v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
    36  )
    37  
    38  // AutoAPIServiceRegistration is an interface which callers can re-declare locally and properly cast to for
    39  // adding and removing APIServices
    40  type AutoAPIServiceRegistration interface {
    41  	// AddAPIServiceToSync adds an API service to auto-register.
    42  	AddAPIServiceToSync(in *v1.APIService)
    43  	// RemoveAPIServiceToSync removes an API service to auto-register.
    44  	RemoveAPIServiceToSync(name string)
    45  }
    46  
    47  type crdRegistrationController struct {
    48  	crdLister crdlisters.CustomResourceDefinitionLister
    49  	crdSynced cache.InformerSynced
    50  
    51  	apiServiceRegistration AutoAPIServiceRegistration
    52  
    53  	syncHandler func(groupVersion schema.GroupVersion) error
    54  
    55  	syncedInitialSet chan struct{}
    56  
    57  	// queue is where incoming work is placed to de-dup and to allow "easy" rate limited requeues on errors
    58  	// this is actually keyed by a groupVersion
    59  	queue workqueue.TypedRateLimitingInterface[schema.GroupVersion]
    60  }
    61  
    62  // NewCRDRegistrationController returns a controller which will register CRD GroupVersions with the auto APIService registration
    63  // controller so they automatically stay in sync.
    64  func NewCRDRegistrationController(crdinformer crdinformers.CustomResourceDefinitionInformer, apiServiceRegistration AutoAPIServiceRegistration) *crdRegistrationController {
    65  	c := &crdRegistrationController{
    66  		crdLister:              crdinformer.Lister(),
    67  		crdSynced:              crdinformer.Informer().HasSynced,
    68  		apiServiceRegistration: apiServiceRegistration,
    69  		syncedInitialSet:       make(chan struct{}),
    70  		queue: workqueue.NewTypedRateLimitingQueueWithConfig(
    71  			workqueue.DefaultTypedControllerRateLimiter[schema.GroupVersion](),
    72  			workqueue.TypedRateLimitingQueueConfig[schema.GroupVersion]{Name: "crd_autoregistration_controller"},
    73  		),
    74  	}
    75  	c.syncHandler = c.handleVersionUpdate
    76  
    77  	crdinformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
    78  		AddFunc: func(obj interface{}) {
    79  			cast := obj.(*apiextensionsv1.CustomResourceDefinition)
    80  			c.enqueueCRD(cast)
    81  		},
    82  		UpdateFunc: func(oldObj, newObj interface{}) {
    83  			// Enqueue both old and new object to make sure we remove and add appropriate API services.
    84  			// The working queue will resolve any duplicates and only changes will stay in the queue.
    85  			c.enqueueCRD(oldObj.(*apiextensionsv1.CustomResourceDefinition))
    86  			c.enqueueCRD(newObj.(*apiextensionsv1.CustomResourceDefinition))
    87  		},
    88  		DeleteFunc: func(obj interface{}) {
    89  			cast, ok := obj.(*apiextensionsv1.CustomResourceDefinition)
    90  			if !ok {
    91  				tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
    92  				if !ok {
    93  					klog.V(2).Infof("Couldn't get object from tombstone %#v", obj)
    94  					return
    95  				}
    96  				cast, ok = tombstone.Obj.(*apiextensionsv1.CustomResourceDefinition)
    97  				if !ok {
    98  					klog.V(2).Infof("Tombstone contained unexpected object: %#v", obj)
    99  					return
   100  				}
   101  			}
   102  			c.enqueueCRD(cast)
   103  		},
   104  	})
   105  
   106  	return c
   107  }
   108  
   109  func (c *crdRegistrationController) Run(workers int, stopCh <-chan struct{}) {
   110  	defer utilruntime.HandleCrash()
   111  	// make sure the work queue is shutdown which will trigger workers to end
   112  	defer c.queue.ShutDown()
   113  
   114  	klog.Infof("Starting crd-autoregister controller")
   115  	defer klog.Infof("Shutting down crd-autoregister controller")
   116  
   117  	// wait for your secondary caches to fill before starting your work
   118  	if !cache.WaitForNamedCacheSync("crd-autoregister", stopCh, c.crdSynced) {
   119  		return
   120  	}
   121  
   122  	// process each item in the list once
   123  	if crds, err := c.crdLister.List(labels.Everything()); err != nil {
   124  		utilruntime.HandleError(err)
   125  	} else {
   126  		for _, crd := range crds {
   127  			for _, version := range crd.Spec.Versions {
   128  				if err := c.syncHandler(schema.GroupVersion{Group: crd.Spec.Group, Version: version.Name}); err != nil {
   129  					utilruntime.HandleError(err)
   130  				}
   131  			}
   132  		}
   133  	}
   134  	close(c.syncedInitialSet)
   135  
   136  	// start up your worker threads based on workers.  Some controllers have multiple kinds of workers
   137  	for i := 0; i < workers; i++ {
   138  		// runWorker will loop until "something bad" happens.  The .Until will then rekick the worker
   139  		// after one second
   140  		go wait.Until(c.runWorker, time.Second, stopCh)
   141  	}
   142  
   143  	// wait until we're told to stop
   144  	<-stopCh
   145  }
   146  
   147  // WaitForInitialSync blocks until the initial set of CRD resources has been processed
   148  func (c *crdRegistrationController) WaitForInitialSync() {
   149  	<-c.syncedInitialSet
   150  }
   151  
   152  func (c *crdRegistrationController) runWorker() {
   153  	// hot loop until we're told to stop.  processNextWorkItem will automatically wait until there's work
   154  	// available, so we don't worry about secondary waits
   155  	for c.processNextWorkItem() {
   156  	}
   157  }
   158  
   159  // processNextWorkItem deals with one key off the queue.  It returns false when it's time to quit.
   160  func (c *crdRegistrationController) processNextWorkItem() bool {
   161  	// pull the next work item from queue.  It should be a key we use to lookup something in a cache
   162  	key, quit := c.queue.Get()
   163  	if quit {
   164  		return false
   165  	}
   166  	// you always have to indicate to the queue that you've completed a piece of work
   167  	defer c.queue.Done(key)
   168  
   169  	// do your work on the key.  This method will contains your "do stuff" logic
   170  	err := c.syncHandler(key)
   171  	if err == nil {
   172  		// if you had no error, tell the queue to stop tracking history for your key.  This will
   173  		// reset things like failure counts for per-item rate limiting
   174  		c.queue.Forget(key)
   175  		return true
   176  	}
   177  
   178  	// there was a failure so be sure to report it.  This method allows for pluggable error handling
   179  	// which can be used for things like cluster-monitoring
   180  	utilruntime.HandleError(fmt.Errorf("%v failed with : %v", key, err))
   181  	// since we failed, we should requeue the item to work on later.  This method will add a backoff
   182  	// to avoid hotlooping on particular items (they're probably still not going to work right away)
   183  	// and overall controller protection (everything I've done is broken, this controller needs to
   184  	// calm down or it can starve other useful work) cases.
   185  	c.queue.AddRateLimited(key)
   186  
   187  	return true
   188  }
   189  
   190  func (c *crdRegistrationController) enqueueCRD(crd *apiextensionsv1.CustomResourceDefinition) {
   191  	for _, version := range crd.Spec.Versions {
   192  		c.queue.Add(schema.GroupVersion{Group: crd.Spec.Group, Version: version.Name})
   193  	}
   194  }
   195  
   196  func (c *crdRegistrationController) handleVersionUpdate(groupVersion schema.GroupVersion) error {
   197  	apiServiceName := groupVersion.Version + "." + groupVersion.Group
   198  
   199  	// check all CRDs.  There shouldn't that many, but if we have problems later we can index them
   200  	crds, err := c.crdLister.List(labels.Everything())
   201  	if err != nil {
   202  		return err
   203  	}
   204  	for _, crd := range crds {
   205  		if crd.Spec.Group != groupVersion.Group {
   206  			continue
   207  		}
   208  		for _, version := range crd.Spec.Versions {
   209  			if version.Name != groupVersion.Version || !version.Served {
   210  				continue
   211  			}
   212  
   213  			c.apiServiceRegistration.AddAPIServiceToSync(&v1.APIService{
   214  				ObjectMeta: metav1.ObjectMeta{Name: apiServiceName},
   215  				Spec: v1.APIServiceSpec{
   216  					Group:                groupVersion.Group,
   217  					Version:              groupVersion.Version,
   218  					GroupPriorityMinimum: 1000, // CRDs should have relatively low priority
   219  					VersionPriority:      100,  // CRDs will be sorted by kube-like versions like any other APIService with the same VersionPriority
   220  				},
   221  			})
   222  			return nil
   223  		}
   224  	}
   225  
   226  	c.apiServiceRegistration.RemoveAPIServiceToSync(apiServiceName)
   227  	return nil
   228  }