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

     1  /*
     2  Copyright 2022 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 legacytokentracking
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"golang.org/x/time/rate"
    25  
    26  	corev1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/fields"
    30  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    31  	"k8s.io/apimachinery/pkg/util/wait"
    32  	corev1informers "k8s.io/client-go/informers/core/v1"
    33  	"k8s.io/client-go/kubernetes"
    34  	corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
    35  	"k8s.io/client-go/tools/cache"
    36  	"k8s.io/client-go/util/workqueue"
    37  	"k8s.io/klog/v2"
    38  	"k8s.io/utils/clock"
    39  )
    40  
    41  const (
    42  	ConfigMapName    = "kube-apiserver-legacy-service-account-token-tracking"
    43  	ConfigMapDataKey = "since"
    44  	dateFormat       = "2006-01-02"
    45  )
    46  
    47  var (
    48  	queueKey = metav1.NamespaceSystem + "/" + ConfigMapName
    49  )
    50  
    51  // Controller maintains a timestamp value configmap `kube-apiserver-legacy-service-account-token-tracking`
    52  // in `kube-system` to indicates if the tracking for legacy tokens is enabled in
    53  // the cluster. For HA clusters, the configmap will be eventually created after
    54  // all controller instances have enabled the feature. When disabling this
    55  // feature, existing configmap will be deleted.
    56  type Controller struct {
    57  	configMapClient   corev1client.ConfigMapsGetter
    58  	configMapInformer cache.SharedIndexInformer
    59  	configMapCache    cache.Indexer
    60  	configMapSynced   cache.InformerSynced
    61  	queue             workqueue.TypedRateLimitingInterface[string]
    62  
    63  	// rate limiter controls the rate limit of the creation of the configmap.
    64  	// this is useful in multi-apiserver cluster to prevent config existing in a
    65  	// cluster with mixed enabled/disabled controllers. otherwise, those
    66  	// apiservers will fight to create/delete until all apiservers are enabled
    67  	// or disabled.
    68  	creationRatelimiter *rate.Limiter
    69  	clock               clock.Clock
    70  }
    71  
    72  // NewController returns a Controller struct.
    73  func NewController(cs kubernetes.Interface) *Controller {
    74  	return newController(cs, clock.RealClock{}, rate.NewLimiter(rate.Every(30*time.Minute), 1))
    75  }
    76  
    77  func newController(cs kubernetes.Interface, cl clock.Clock, limiter *rate.Limiter) *Controller {
    78  	informer := corev1informers.NewFilteredConfigMapInformer(cs, metav1.NamespaceSystem, 12*time.Hour, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, func(options *metav1.ListOptions) {
    79  		options.FieldSelector = fields.OneTermEqualSelector("metadata.name", ConfigMapName).String()
    80  	})
    81  
    82  	c := &Controller{
    83  		configMapClient: cs.CoreV1(),
    84  		queue: workqueue.NewTypedRateLimitingQueueWithConfig(
    85  			workqueue.DefaultTypedControllerRateLimiter[string](),
    86  			workqueue.TypedRateLimitingQueueConfig[string]{Name: "legacy_token_tracking_controller"},
    87  		),
    88  		configMapInformer:   informer,
    89  		configMapCache:      informer.GetIndexer(),
    90  		configMapSynced:     informer.HasSynced,
    91  		creationRatelimiter: limiter,
    92  		clock:               cl,
    93  	}
    94  
    95  	informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
    96  		AddFunc: func(obj interface{}) {
    97  			c.enqueue()
    98  		},
    99  		UpdateFunc: func(oldObj, newObj interface{}) {
   100  			c.enqueue()
   101  		},
   102  		DeleteFunc: func(obj interface{}) {
   103  			c.enqueue()
   104  		},
   105  	})
   106  
   107  	return c
   108  }
   109  
   110  func (c *Controller) enqueue() {
   111  	c.queue.Add(queueKey)
   112  }
   113  
   114  // Run starts the controller sync loop.
   115  func (c *Controller) Run(stopCh <-chan struct{}) {
   116  	defer utilruntime.HandleCrash()
   117  	defer c.queue.ShutDown()
   118  
   119  	klog.Info("Starting legacy_token_tracking_controller")
   120  	defer klog.Infof("Shutting down legacy_token_tracking_controller")
   121  
   122  	go c.configMapInformer.Run(stopCh)
   123  	if !cache.WaitForNamedCacheSync("configmaps", stopCh, c.configMapSynced) {
   124  		return
   125  	}
   126  
   127  	go wait.Until(c.runWorker, time.Second, stopCh)
   128  
   129  	c.queue.Add(queueKey)
   130  
   131  	<-stopCh
   132  	klog.Info("Ending legacy_token_tracking_controller")
   133  }
   134  
   135  func (c *Controller) runWorker() {
   136  	for c.processNext() {
   137  	}
   138  }
   139  
   140  func (c *Controller) processNext() bool {
   141  	key, quit := c.queue.Get()
   142  	if quit {
   143  		return false
   144  	}
   145  	defer c.queue.Done(key)
   146  
   147  	if err := c.syncConfigMap(); err != nil {
   148  		utilruntime.HandleError(fmt.Errorf("while syncing ConfigMap %q, err: %w", key, err))
   149  		c.queue.AddRateLimited(key)
   150  		return true
   151  	}
   152  	c.queue.Forget(key)
   153  	return true
   154  }
   155  
   156  func (c *Controller) syncConfigMap() error {
   157  	obj, exists, err := c.configMapCache.GetByKey(queueKey)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	now := c.clock.Now()
   163  	if !exists {
   164  		r := c.creationRatelimiter.ReserveN(now, 1)
   165  		if delay := r.DelayFrom(now); delay > 0 {
   166  			c.queue.AddAfter(queueKey, delay)
   167  			r.CancelAt(now)
   168  			return nil
   169  		}
   170  
   171  		if _, err = c.configMapClient.ConfigMaps(metav1.NamespaceSystem).Create(context.TODO(), &corev1.ConfigMap{
   172  			ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: ConfigMapName},
   173  			Data:       map[string]string{ConfigMapDataKey: now.UTC().Format(dateFormat)},
   174  		}, metav1.CreateOptions{}); err != nil {
   175  			if apierrors.IsAlreadyExists(err) {
   176  				return nil
   177  			}
   178  			// don't consume the creationRatelimiter for an unsuccessful attempt
   179  			r.CancelAt(now)
   180  			return err
   181  		}
   182  	} else {
   183  		configMap := obj.(*corev1.ConfigMap)
   184  		if _, err = time.Parse(dateFormat, configMap.Data[ConfigMapDataKey]); err != nil {
   185  			configMap := configMap.DeepCopy()
   186  			if configMap.Data == nil {
   187  				configMap.Data = map[string]string{}
   188  			}
   189  			configMap.Data[ConfigMapDataKey] = now.UTC().Format(dateFormat)
   190  			if _, err = c.configMapClient.ConfigMaps(metav1.NamespaceSystem).Update(context.TODO(), configMap, metav1.UpdateOptions{}); err != nil {
   191  				if apierrors.IsNotFound(err) || apierrors.IsConflict(err) {
   192  					return nil
   193  				}
   194  				return err
   195  			}
   196  		}
   197  	}
   198  
   199  	return nil
   200  }