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 }