Skip to content

Commit 39b60ea

Browse files
RuntimeSDK: add registry
Signed-off-by: Stefan Büringer [email protected] Co-authored-by: fabriziopandini <[email protected]>
1 parent 3bff7da commit 39b60ea

File tree

4 files changed

+482
-0
lines changed

4 files changed

+482
-0
lines changed

internal/runtime/catalog/catalog.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,11 @@ func (gvh GroupVersionHook) String() string {
334334
var emptyGroupVersionHook = GroupVersionHook{}
335335

336336
var emptyGroupVersionKind = schema.GroupVersionKind{}
337+
338+
// GroupHook represents Group and Hook of a GroupVersionHook.
339+
// This can be used instead of GroupVersionHook when
340+
// Version should not be used.
341+
type GroupHook struct {
342+
Group string
343+
Hook string
344+
}

internal/runtime/registry/doc.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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 implements the RuntimeSDK registry.
18+
package registry

internal/runtime/registry/registry.go

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
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

Comments
 (0)