sigs.k8s.io/cluster-api@v1.7.1/internal/runtime/registry/registry.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 registry 18 19 import ( 20 "sync" 21 22 "github.com/pkg/errors" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/apimachinery/pkg/labels" 25 "k8s.io/apimachinery/pkg/runtime/schema" 26 kerrors "k8s.io/apimachinery/pkg/util/errors" 27 28 runtimev1 "sigs.k8s.io/cluster-api/exp/runtime/api/v1alpha1" 29 runtimecatalog "sigs.k8s.io/cluster-api/exp/runtime/catalog" 30 ) 31 32 // ExtensionRegistry defines the funcs of a RuntimeExtension registry. 33 type ExtensionRegistry interface { 34 // WarmUp can be used to initialize a "cold" RuntimeExtension registry with all 35 // known runtimev1.ExtensionConfigs at a given time. 36 // After WarmUp completes the RuntimeExtension registry is considered ready. 37 WarmUp(extensionConfigList *runtimev1.ExtensionConfigList) error 38 39 // IsReady returns true if the RuntimeExtension registry is ready for usage. 40 // This happens after WarmUp is completed. 41 IsReady() bool 42 43 // Add adds all RuntimeExtensions of the given ExtensionConfig. 44 // Please note that if the ExtensionConfig has been added before, the 45 // corresponding registry entries will get updated/replaced with the 46 // one from the newly provided ExtensionConfig. 47 Add(extensionConfig *runtimev1.ExtensionConfig) error 48 49 // Remove removes all RuntimeExtensions corresponding to the provided ExtensionConfig. 50 Remove(extensionConfig *runtimev1.ExtensionConfig) error 51 52 // List lists all registered RuntimeExtensions for a given catalog.GroupHook. 53 List(gh runtimecatalog.GroupHook) ([]*ExtensionRegistration, error) 54 55 // Get gets the RuntimeExtensions with the given name. 56 Get(name string) (*ExtensionRegistration, error) 57 } 58 59 // ExtensionRegistration contains information about a registered RuntimeExtension. 60 type ExtensionRegistration struct { 61 // Name is the unique name of the RuntimeExtension. 62 Name string 63 64 // ExtensionConfigName is the name of the corresponding ExtensionConfig. 65 ExtensionConfigName string 66 67 // GroupVersionHook is the GroupVersionHook that the RuntimeExtension implements. 68 GroupVersionHook runtimecatalog.GroupVersionHook 69 70 // NamespaceSelector limits the objects by namespace for which a Runtime Extension is called. 71 NamespaceSelector labels.Selector 72 73 // ClientConfig is the ClientConfig to communicate with the RuntimeExtension. 74 ClientConfig runtimev1.ClientConfig 75 76 // TimeoutSeconds is the timeout duration used for calls to the RuntimeExtension. 77 TimeoutSeconds *int32 78 79 // FailurePolicy defines how failures in calls to the RuntimeExtension should be handled by a client. 80 FailurePolicy *runtimev1.FailurePolicy 81 82 // Settings captures additional information sent in call to the RuntimeExtensions. 83 Settings map[string]string 84 } 85 86 // extensionRegistry is an implementation of ExtensionRegistry. 87 type extensionRegistry struct { 88 // ready represents if the registry has been warmed up. 89 ready bool 90 // items contains the registry entries. 91 items map[string]*ExtensionRegistration 92 // lock is used to synchronize access to fields of the extensionRegistry. 93 lock sync.RWMutex 94 } 95 96 // New returns a new ExtensionRegistry. 97 func New() ExtensionRegistry { 98 return &extensionRegistry{ 99 items: map[string]*ExtensionRegistration{}, 100 } 101 } 102 103 // WarmUp can be used to initialize a "cold" RuntimeExtension registry with all 104 // known runtimev1.ExtensionConfigs at a given time. 105 // After WarmUp completes the RuntimeExtension registry is considered ready. 106 func (r *extensionRegistry) WarmUp(extensionConfigList *runtimev1.ExtensionConfigList) error { 107 if extensionConfigList == nil { 108 return errors.New("failed to warm up registry: invalid argument: when calling WarmUp ExtensionConfigList must not be nil") 109 } 110 111 r.lock.Lock() 112 defer r.lock.Unlock() 113 114 if r.ready { 115 return errors.New("failed to warm up registry: invalid operation: WarmUp cannot be called on a registry which has already been warmed up") 116 } 117 118 var allErrs []error 119 for i := range extensionConfigList.Items { 120 if err := r.add(&extensionConfigList.Items[i]); err != nil { 121 allErrs = append(allErrs, err) 122 } 123 } 124 if len(allErrs) > 0 { 125 // Reset the map, so that the next WarmUp can start with an empty map 126 // and doesn't inherit entries from this failed WarmUp. 127 r.items = map[string]*ExtensionRegistration{} 128 return errors.Wrapf(kerrors.NewAggregate(allErrs), "failed to warm up registry") 129 } 130 131 r.ready = true 132 return nil 133 } 134 135 // IsReady returns true if the RuntimeExtension registry is ready for usage. 136 // This happens after WarmUp is completed. 137 func (r *extensionRegistry) IsReady() bool { 138 r.lock.RLock() 139 defer r.lock.RUnlock() 140 141 return r.ready 142 } 143 144 // Add adds all RuntimeExtensions of the given ExtensionConfig. 145 // Please note that if the ExtensionConfig has been added before, the 146 // corresponding registry entries will get updated/replaced with the 147 // one from the newly provided ExtensionConfig. 148 func (r *extensionRegistry) Add(extensionConfig *runtimev1.ExtensionConfig) error { 149 if extensionConfig == nil { 150 return errors.New("failed to add ExtensionConfig to registry: invalid argument: when calling Add extensionConfig must not be nil") 151 } 152 153 r.lock.Lock() 154 defer r.lock.Unlock() 155 156 if !r.ready { 157 return errors.Errorf("failed to add ExtensionConfig %q to registry: invalid operation: Add cannot be called on a registry which has not been warmed up", extensionConfig.Name) 158 } 159 160 return r.add(extensionConfig) 161 } 162 163 // Remove removes all RuntimeExtensions corresponding to the provided ExtensionConfig. 164 func (r *extensionRegistry) Remove(extensionConfig *runtimev1.ExtensionConfig) error { 165 if extensionConfig == nil { 166 return errors.New("failed to remove ExtensionConfig from registry: invalid argument: when calling Remove ExtensionConfig must not be nil") 167 } 168 169 r.lock.Lock() 170 defer r.lock.Unlock() 171 172 if !r.ready { 173 return errors.Errorf("failed to remove ExtensionConfig %q from registry: invalid operation: Remove cannot be called on a registry which has not been warmed up", extensionConfig.Name) 174 } 175 176 r.remove(extensionConfig) 177 return nil 178 } 179 180 func (r *extensionRegistry) remove(extensionConfig *runtimev1.ExtensionConfig) { 181 for _, e := range r.items { 182 if e.ExtensionConfigName == extensionConfig.Name { 183 delete(r.items, e.Name) 184 } 185 } 186 } 187 188 // List lists all registered RuntimeExtensions for a given catalog.GroupHook. 189 func (r *extensionRegistry) List(gh runtimecatalog.GroupHook) ([]*ExtensionRegistration, error) { 190 if gh.Group == "" { 191 return nil, errors.New("failed to list extension handlers: invalid argument: when calling List gh.Group must not be empty") 192 } 193 if gh.Hook == "" { 194 return nil, errors.New("failed to list extension handlers: invalid argument: when calling List gh.Hook must not be empty") 195 } 196 197 r.lock.RLock() 198 defer r.lock.RUnlock() 199 200 if !r.ready { 201 return nil, errors.Errorf("failed to list extension handlers for GroupHook %q: invalid operation: List cannot be called on a registry which has not been warmed up", gh.String()) 202 } 203 204 l := []*ExtensionRegistration{} 205 for _, registration := range r.items { 206 if registration.GroupVersionHook.Group == gh.Group && registration.GroupVersionHook.Hook == gh.Hook { 207 l = append(l, registration) 208 } 209 } 210 return l, nil 211 } 212 213 // Get gets the RuntimeExtensions with the given name. 214 func (r *extensionRegistry) Get(name string) (*ExtensionRegistration, error) { 215 r.lock.RLock() 216 defer r.lock.RUnlock() 217 218 if !r.ready { 219 return nil, errors.Errorf("failed to get extension handler %q from registry: invalid operation: Get cannot be called on a registry not yet ready", name) 220 } 221 222 registration, ok := r.items[name] 223 if !ok { 224 return nil, errors.Errorf("failed to get extension handler %q from registry: handler with name %q has not been registered", name, name) 225 } 226 227 return registration, nil 228 } 229 230 func (r *extensionRegistry) add(extensionConfig *runtimev1.ExtensionConfig) error { 231 r.remove(extensionConfig) 232 233 // Create a selector from the NamespaceSelector defined in the extensionConfig spec. 234 selector, err := metav1.LabelSelectorAsSelector(extensionConfig.Spec.NamespaceSelector) 235 if err != nil { 236 return errors.Wrapf(err, "failed to add ExtensionConfig %q to registry: failed to create namespaceSelector", extensionConfig.Name) 237 } 238 239 var allErrs []error 240 registrations := []*ExtensionRegistration{} 241 for _, e := range extensionConfig.Status.Handlers { 242 gv, err := schema.ParseGroupVersion(e.RequestHook.APIVersion) 243 if err != nil { 244 allErrs = append(allErrs, errors.Wrapf(err, "failed to add extension handler %q to registry: failed to parse GroupVersion %q of handler %q", e.Name, e.RequestHook.APIVersion, e.Name)) 245 continue 246 } 247 248 // Registrations will only be added to the registry if no errors occur (all or nothing). 249 registrations = append(registrations, &ExtensionRegistration{ 250 ExtensionConfigName: extensionConfig.Name, 251 Name: e.Name, 252 GroupVersionHook: runtimecatalog.GroupVersionHook{ 253 Group: gv.Group, 254 Version: gv.Version, 255 Hook: e.RequestHook.Hook, 256 }, 257 NamespaceSelector: selector, 258 ClientConfig: extensionConfig.Spec.ClientConfig, 259 TimeoutSeconds: e.TimeoutSeconds, 260 FailurePolicy: e.FailurePolicy, 261 Settings: extensionConfig.Spec.Settings, 262 }) 263 } 264 265 if len(allErrs) > 0 { 266 return errors.Wrapf(kerrors.NewAggregate(allErrs), "failed to add ExtensionConfig %q to registry", extensionConfig.Name) 267 } 268 269 for _, registration := range registrations { 270 r.items[registration.Name] = registration 271 } 272 273 return nil 274 }