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