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

     1  /*
     2  Copyright 2024 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 storageversionmigrator
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"k8s.io/apimachinery/pkg/api/meta"
    25  	"k8s.io/apimachinery/pkg/runtime/schema"
    26  	"k8s.io/apimachinery/pkg/util/wait"
    27  	"k8s.io/client-go/discovery"
    28  	"k8s.io/client-go/metadata"
    29  	"k8s.io/client-go/tools/cache"
    30  	"k8s.io/client-go/util/workqueue"
    31  	"k8s.io/klog/v2"
    32  	"k8s.io/kubernetes/pkg/controller"
    33  
    34  	svmv1alpha1 "k8s.io/api/storagemigration/v1alpha1"
    35  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    36  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    37  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    38  	svminformers "k8s.io/client-go/informers/storagemigration/v1alpha1"
    39  	clientset "k8s.io/client-go/kubernetes"
    40  	svmlisters "k8s.io/client-go/listers/storagemigration/v1alpha1"
    41  )
    42  
    43  const (
    44  	// this name is guaranteed to be not present in the cluster as it not a valid namespace name
    45  	fakeSVMNamespaceName          string = "@fake:svm_ns!"
    46  	ResourceVersionControllerName string = "resource-version-controller"
    47  )
    48  
    49  // ResourceVersionController adds the resource version obtained from a randomly nonexistent namespace
    50  // to the SVM status before the migration is initiated. This resource version is utilized for checking
    51  // freshness of GC cache before the migration is initiated.
    52  type ResourceVersionController struct {
    53  	discoveryClient *discovery.DiscoveryClient
    54  	metadataClient  metadata.Interface
    55  	svmListers      svmlisters.StorageVersionMigrationLister
    56  	svmSynced       cache.InformerSynced
    57  	queue           workqueue.TypedRateLimitingInterface[string]
    58  	kubeClient      clientset.Interface
    59  	mapper          meta.ResettableRESTMapper
    60  }
    61  
    62  func NewResourceVersionController(
    63  	ctx context.Context,
    64  	kubeClient clientset.Interface,
    65  	discoveryClient *discovery.DiscoveryClient,
    66  	metadataClient metadata.Interface,
    67  	svmInformer svminformers.StorageVersionMigrationInformer,
    68  	mapper meta.ResettableRESTMapper,
    69  ) *ResourceVersionController {
    70  	logger := klog.FromContext(ctx)
    71  
    72  	rvController := &ResourceVersionController{
    73  		kubeClient:      kubeClient,
    74  		discoveryClient: discoveryClient,
    75  		metadataClient:  metadataClient,
    76  		svmListers:      svmInformer.Lister(),
    77  		svmSynced:       svmInformer.Informer().HasSynced,
    78  		mapper:          mapper,
    79  		queue: workqueue.NewTypedRateLimitingQueueWithConfig(
    80  			workqueue.DefaultTypedControllerRateLimiter[string](),
    81  			workqueue.TypedRateLimitingQueueConfig[string]{Name: ResourceVersionControllerName},
    82  		),
    83  	}
    84  
    85  	_, _ = svmInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
    86  		AddFunc: func(obj interface{}) {
    87  			rvController.addSVM(logger, obj)
    88  		},
    89  		UpdateFunc: func(oldObj, newObj interface{}) {
    90  			rvController.updateSVM(logger, oldObj, newObj)
    91  		},
    92  	})
    93  
    94  	return rvController
    95  }
    96  
    97  func (rv *ResourceVersionController) addSVM(logger klog.Logger, obj interface{}) {
    98  	svm := obj.(*svmv1alpha1.StorageVersionMigration)
    99  	logger.V(4).Info("Adding", "svm", klog.KObj(svm))
   100  	rv.enqueue(svm)
   101  }
   102  
   103  func (rv *ResourceVersionController) updateSVM(logger klog.Logger, oldObj, newObj interface{}) {
   104  	oldSVM := oldObj.(*svmv1alpha1.StorageVersionMigration)
   105  	newSVM := newObj.(*svmv1alpha1.StorageVersionMigration)
   106  	logger.V(4).Info("Updating", "svm", klog.KObj(oldSVM))
   107  	rv.enqueue(newSVM)
   108  }
   109  
   110  func (rv *ResourceVersionController) enqueue(svm *svmv1alpha1.StorageVersionMigration) {
   111  	key, err := controller.KeyFunc(svm)
   112  	if err != nil {
   113  		utilruntime.HandleError(fmt.Errorf("couldn't get key for object %#v: %w", svm, err))
   114  		return
   115  	}
   116  
   117  	rv.queue.Add(key)
   118  }
   119  
   120  func (rv *ResourceVersionController) Run(ctx context.Context) {
   121  	defer utilruntime.HandleCrash()
   122  	defer rv.queue.ShutDown()
   123  
   124  	logger := klog.FromContext(ctx)
   125  	logger.Info("Starting", "controller", ResourceVersionControllerName)
   126  	defer logger.Info("Shutting down", "controller", ResourceVersionControllerName)
   127  
   128  	if !cache.WaitForNamedCacheSync(ResourceVersionControllerName, ctx.Done(), rv.svmSynced) {
   129  		return
   130  	}
   131  
   132  	go wait.UntilWithContext(ctx, rv.worker, time.Second)
   133  
   134  	<-ctx.Done()
   135  }
   136  
   137  func (rv *ResourceVersionController) worker(ctx context.Context) {
   138  	for rv.processNext(ctx) {
   139  	}
   140  }
   141  
   142  func (rv *ResourceVersionController) processNext(ctx context.Context) bool {
   143  	key, quit := rv.queue.Get()
   144  	if quit {
   145  		return false
   146  	}
   147  	defer rv.queue.Done(key)
   148  
   149  	err := rv.sync(ctx, key)
   150  	if err == nil {
   151  		rv.queue.Forget(key)
   152  		return true
   153  	}
   154  
   155  	klog.FromContext(ctx).V(2).Info("Error syncing SVM resource, retrying", "svm", key, "err", err)
   156  	rv.queue.AddRateLimited(key)
   157  
   158  	return true
   159  }
   160  
   161  func (rv *ResourceVersionController) sync(ctx context.Context, key string) error {
   162  	logger := klog.FromContext(ctx)
   163  	startTime := time.Now()
   164  
   165  	// SVM is a cluster scoped resource so we don't care about the namespace
   166  	_, name, err := cache.SplitMetaNamespaceKey(key)
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	svm, err := rv.svmListers.Get(name)
   172  	if apierrors.IsNotFound(err) {
   173  		// no work to do, don't fail and requeue
   174  		return nil
   175  	}
   176  	if err != nil {
   177  		return err
   178  	}
   179  	// working with copy to avoid race condition between this and migration controller
   180  	toBeProcessedSVM := svm.DeepCopy()
   181  	gvr := getGVRFromResource(toBeProcessedSVM)
   182  
   183  	if IsConditionTrue(toBeProcessedSVM, svmv1alpha1.MigrationSucceeded) || IsConditionTrue(toBeProcessedSVM, svmv1alpha1.MigrationFailed) {
   184  		logger.V(4).Info("Migration has already succeeded or failed previously, skipping", "svm", name)
   185  		return nil
   186  	}
   187  
   188  	if len(toBeProcessedSVM.Status.ResourceVersion) != 0 {
   189  		logger.V(4).Info("Resource version is already set", "svm", name)
   190  		return nil
   191  	}
   192  
   193  	exists, err := rv.resourceExists(gvr)
   194  	if err != nil {
   195  		return err
   196  	}
   197  	if !exists {
   198  		_, err = rv.kubeClient.StoragemigrationV1alpha1().
   199  			StorageVersionMigrations().
   200  			UpdateStatus(
   201  				ctx,
   202  				setStatusConditions(toBeProcessedSVM, svmv1alpha1.MigrationFailed, migrationFailedStatusReason),
   203  				metav1.UpdateOptions{},
   204  			)
   205  		if err != nil {
   206  			return err
   207  		}
   208  
   209  		return nil
   210  	}
   211  
   212  	toBeProcessedSVM.Status.ResourceVersion, err = rv.getLatestResourceVersion(gvr, ctx)
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	_, err = rv.kubeClient.StoragemigrationV1alpha1().
   218  		StorageVersionMigrations().
   219  		UpdateStatus(ctx, toBeProcessedSVM, metav1.UpdateOptions{})
   220  	if err != nil {
   221  		return fmt.Errorf("error updating status for %s: %w", toBeProcessedSVM.Name, err)
   222  	}
   223  
   224  	logger.V(4).Info("Resource version has been successfully added", "svm", key, "elapsed", time.Since(startTime))
   225  	return nil
   226  }
   227  
   228  func (rv *ResourceVersionController) getLatestResourceVersion(gvr schema.GroupVersionResource, ctx context.Context) (string, error) {
   229  	isResourceNamespaceScoped, err := rv.isResourceNamespaceScoped(gvr)
   230  	if err != nil {
   231  		return "", err
   232  	}
   233  
   234  	var randomList *metav1.PartialObjectMetadataList
   235  	if isResourceNamespaceScoped {
   236  		// get list resourceVersion from random non-existent namesapce for the given GVR
   237  		randomList, err = rv.metadataClient.Resource(gvr).
   238  			Namespace(fakeSVMNamespaceName).
   239  			List(ctx, metav1.ListOptions{
   240  				Limit: 1,
   241  			})
   242  	} else {
   243  		randomList, err = rv.metadataClient.Resource(gvr).
   244  			List(ctx, metav1.ListOptions{
   245  				Limit: 1,
   246  			})
   247  	}
   248  	if err != nil {
   249  		// error here is very abstract. adding additional context for better debugging
   250  		return "", fmt.Errorf("error getting latest resourceVersion for %s: %w", gvr.String(), err)
   251  	}
   252  
   253  	return randomList.GetResourceVersion(), err
   254  }
   255  
   256  func (rv *ResourceVersionController) resourceExists(gvr schema.GroupVersionResource) (bool, error) {
   257  	mapperGVRs, err := rv.mapper.ResourcesFor(gvr)
   258  	if err != nil {
   259  		return false, err
   260  	}
   261  
   262  	for _, mapperGVR := range mapperGVRs {
   263  		if mapperGVR.Group == gvr.Group &&
   264  			mapperGVR.Version == gvr.Version &&
   265  			mapperGVR.Resource == gvr.Resource {
   266  			return true, nil
   267  		}
   268  	}
   269  
   270  	return false, nil
   271  }
   272  
   273  func (rv *ResourceVersionController) isResourceNamespaceScoped(gvr schema.GroupVersionResource) (bool, error) {
   274  	resourceList, err := rv.discoveryClient.ServerResourcesForGroupVersion(gvr.GroupVersion().String())
   275  	if err != nil {
   276  		return false, err
   277  	}
   278  
   279  	for _, resource := range resourceList.APIResources {
   280  		if resource.Name == gvr.Resource {
   281  			return resource.Namespaced, nil
   282  		}
   283  	}
   284  
   285  	return false, fmt.Errorf("resource %q not found", gvr.String())
   286  }