sigs.k8s.io/cluster-api@v1.7.1/exp/runtime/internal/controllers/warmup.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 controllers 18 19 import ( 20 "context" 21 "time" 22 23 "github.com/pkg/errors" 24 kerrors "k8s.io/apimachinery/pkg/util/errors" 25 "k8s.io/apimachinery/pkg/util/wait" 26 "k8s.io/klog/v2" 27 ctrl "sigs.k8s.io/controller-runtime" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 "sigs.k8s.io/controller-runtime/pkg/manager" 30 31 runtimev1 "sigs.k8s.io/cluster-api/exp/runtime/api/v1alpha1" 32 runtimeclient "sigs.k8s.io/cluster-api/internal/runtime/client" 33 ) 34 35 const ( 36 defaultWarmupTimeout = 60 * time.Second 37 defaultWarmupInterval = 2 * time.Second 38 ) 39 40 var _ manager.LeaderElectionRunnable = &warmupRunnable{} 41 42 // warmupRunnable is a controller runtime LeaderElectionRunnable. It warms up the registry on controller start. 43 type warmupRunnable struct { 44 Client client.Client 45 APIReader client.Reader 46 RuntimeClient runtimeclient.Client 47 warmupTimeout time.Duration 48 warmupInterval time.Duration 49 } 50 51 // NeedLeaderElection satisfies the controller runtime LeaderElectionRunnable interface. 52 // This ensures we warm up the RuntimeSDK registry only after the controller became leader. 53 // Note: Only after the warmupRunnable is completed the registry becomes ready and thus 54 // all controllers using the runtime client or registry will wait until warmup is completed. 55 func (r *warmupRunnable) NeedLeaderElection() bool { 56 return true 57 } 58 59 // Start attempts to warm up the registry. It will retry for 60 seconds before returning an error. An error on Start will 60 // cause the CAPI controller manager to fail. 61 // We are retrying for 60 seconds to mitigate failures when the CAPI controller manager and RuntimeExtensions 62 // are started at the same time. After 60 seconds we crash the entire controller to surface the 63 // issue to users in a timely fashion as it would block reconciliation of all Clusters. 64 func (r *warmupRunnable) Start(ctx context.Context) error { 65 log := ctrl.LoggerFrom(ctx) 66 if r.warmupInterval == 0 { 67 r.warmupInterval = defaultWarmupInterval 68 } 69 if r.warmupTimeout == 0 { 70 r.warmupTimeout = defaultWarmupTimeout 71 } 72 ctx, cancel := context.WithTimeout(ctx, r.warmupTimeout) 73 defer cancel() 74 75 err := wait.PollUntilContextTimeout(ctx, r.warmupInterval, r.warmupTimeout, true, func(ctx context.Context) (done bool, err error) { 76 if err = warmupRegistry(ctx, r.Client, r.APIReader, r.RuntimeClient); err != nil { 77 log.Error(err, "ExtensionConfig registry warmup failed") 78 return false, nil 79 } 80 return true, nil 81 }) 82 83 if err != nil { 84 return errors.Wrapf(err, "ExtensionConfig registry warmup timed out after %s", r.warmupTimeout.String()) 85 } 86 87 return nil 88 } 89 90 // warmupRegistry attempts to discover all existing ExtensionConfigs and patch their status with discovered Handlers. 91 // It warms up the registry by passing it the up-to-date list of ExtensionConfigs. 92 func warmupRegistry(ctx context.Context, client client.Client, reader client.Reader, runtimeClient runtimeclient.Client) error { 93 log := ctrl.LoggerFrom(ctx) 94 95 var errs []error 96 97 extensionConfigList := runtimev1.ExtensionConfigList{} 98 if err := reader.List(ctx, &extensionConfigList); err != nil { 99 return errors.Wrapf(err, "failed to list ExtensionConfigs") 100 } 101 102 for i := range extensionConfigList.Items { 103 extensionConfig := &extensionConfigList.Items[i] 104 original := extensionConfig.DeepCopy() 105 106 log := log.WithValues("ExtensionConfig", klog.KObj(extensionConfig)) 107 ctx := ctrl.LoggerInto(ctx, log) 108 109 // Inject CABundle from secret if annotation is set. Otherwise https calls may fail. 110 if err := reconcileCABundle(ctx, client, extensionConfig); err != nil { 111 errs = append(errs, err) 112 // Note: we continue here because if reconcileCABundle doesn't work discovery will fail as well. 113 continue 114 } 115 116 extensionConfig, err := discoverExtensionConfig(ctx, runtimeClient, extensionConfig) 117 if err != nil { 118 errs = append(errs, err) 119 } 120 121 // Always patch the ExtensionConfig as it may contain updates in conditions or clientConfig.caBundle. 122 if err = patchExtensionConfig(ctx, client, original, extensionConfig); err != nil { 123 errs = append(errs, err) 124 } 125 extensionConfigList.Items[i] = *extensionConfig 126 } 127 128 // If there was some error in discovery or patching return before committing to the Registry. 129 if len(errs) != 0 { 130 return kerrors.NewAggregate(errs) 131 } 132 133 if err := runtimeClient.WarmUp(&extensionConfigList); err != nil { 134 return err 135 } 136 137 log.Info("The extension registry is warmed up") 138 139 return nil 140 }