@@ -27,6 +27,7 @@ import (
27
27
"net/http"
28
28
"net/url"
29
29
"path"
30
+ "reflect"
30
31
"strconv"
31
32
"strings"
32
33
"time"
@@ -49,6 +50,7 @@ import (
49
50
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
50
51
runtimemetrics "sigs.k8s.io/cluster-api/internal/runtime/metrics"
51
52
runtimeregistry "sigs.k8s.io/cluster-api/internal/runtime/registry"
53
+ "sigs.k8s.io/cluster-api/internal/util/cache"
52
54
"sigs.k8s.io/cluster-api/util"
53
55
)
54
56
@@ -96,7 +98,7 @@ type Client interface {
96
98
CallAllExtensions (ctx context.Context , hook runtimecatalog.Hook , forObject metav1.Object , request runtimehooksv1.RequestObject , response runtimehooksv1.ResponseObject ) error
97
99
98
100
// CallExtension calls the ExtensionHandler with the given name.
99
- CallExtension (ctx context.Context , hook runtimecatalog.Hook , forObject metav1.Object , name string , request runtimehooksv1.RequestObject , response runtimehooksv1.ResponseObject ) error
101
+ CallExtension (ctx context.Context , hook runtimecatalog.Hook , forObject metav1.Object , name string , request runtimehooksv1.RequestObject , response runtimehooksv1.ResponseObject , opts ... CallExtensionOption ) error
100
102
}
101
103
102
104
var _ Client = & client {}
@@ -276,6 +278,44 @@ func aggregateSuccessfulResponses(aggregatedResponse runtimehooksv1.ResponseObje
276
278
aggregatedResponse .SetMessage (strings .Join (messages , ", " ))
277
279
}
278
280
281
+ // CallExtensionOption is the interface for configuration that modifies CallExtensionOptions for a CallExtension call.
282
+ type CallExtensionOption interface {
283
+ // ApplyToOptions applies this configuration to the given CallExtensionOptions.
284
+ ApplyToOptions (* CallExtensionOptions )
285
+ }
286
+
287
+ // CallExtensionCacheEntry is a cache entry for the cache that can be used with the CallExtension call via
288
+ // the WithCaching option.
289
+ type CallExtensionCacheEntry struct {
290
+ CacheKey string
291
+ Response runtimehooksv1.ResponseObject
292
+ }
293
+
294
+ // Key returns the cache key of a CallExtensionCacheEntry.
295
+ func (c CallExtensionCacheEntry ) Key () string {
296
+ return c .CacheKey
297
+ }
298
+
299
+ // WithCaching enables caching for the CallExtension call.
300
+ type WithCaching struct {
301
+ Cache cache.Cache [CallExtensionCacheEntry ]
302
+ CacheKeyFunc func (* runtimeregistry.ExtensionRegistration , runtimehooksv1.RequestObject ) string
303
+ }
304
+
305
+ // ApplyToOptions applies WithCaching to the given CallExtensionOptions.
306
+ func (w WithCaching ) ApplyToOptions (in * CallExtensionOptions ) {
307
+ in .WithCaching = true
308
+ in .Cache = w .Cache
309
+ in .CacheKeyFunc = w .CacheKeyFunc
310
+ }
311
+
312
+ // CallExtensionOptions contains the options for the CallExtension call.
313
+ type CallExtensionOptions struct {
314
+ WithCaching bool
315
+ Cache cache.Cache [CallExtensionCacheEntry ]
316
+ CacheKeyFunc func (* runtimeregistry.ExtensionRegistration , runtimehooksv1.RequestObject ) string
317
+ }
318
+
279
319
// CallExtension makes the call to the extension with the given name.
280
320
// The response object passed will be updated with the response of the call.
281
321
// An error is returned if the extension is not compatible with the hook.
@@ -288,7 +328,13 @@ func aggregateSuccessfulResponses(aggregatedResponse runtimehooksv1.ResponseObje
288
328
// Nb. FailurePolicy does not affect the following kinds of errors:
289
329
// - Internal errors. Examples: hooks is incompatible with ExtensionHandler, ExtensionHandler information is missing.
290
330
// - Error when ExtensionHandler returns a response with `Status` set to `Failure`.
291
- func (c * client ) CallExtension (ctx context.Context , hook runtimecatalog.Hook , forObject metav1.Object , name string , request runtimehooksv1.RequestObject , response runtimehooksv1.ResponseObject ) error {
331
+ func (c * client ) CallExtension (ctx context.Context , hook runtimecatalog.Hook , forObject metav1.Object , name string , request runtimehooksv1.RequestObject , response runtimehooksv1.ResponseObject , opts ... CallExtensionOption ) error {
332
+ // Calculate the options.
333
+ options := & CallExtensionOptions {}
334
+ for _ , opt := range opts {
335
+ opt .ApplyToOptions (options )
336
+ }
337
+
292
338
log := ctrl .LoggerFrom (ctx ).WithValues ("extensionHandler" , name , "hook" , runtimecatalog .HookName (hook ))
293
339
ctx = ctrl .LoggerInto (ctx , log )
294
340
hookGVH , err := c .catalog .GroupVersionHook (hook )
@@ -331,15 +377,31 @@ func (c *client) CallExtension(ctx context.Context, hook runtimecatalog.Hook, fo
331
377
// Prepare the request by merging the settings in the registration with the settings in the request.
332
378
request = cloneAndAddSettings (request , registration .Settings )
333
379
334
- opts := & httpCallOptions {
380
+ var cacheKey string
381
+ if options .WithCaching {
382
+ // Return a cached response if response is cached.
383
+ cacheKey = options .CacheKeyFunc (registration , request )
384
+ if cacheEntry , ok := options .Cache .Has (cacheKey ); ok {
385
+ // Set response to cacheEntry.Response.
386
+ outVal := reflect .ValueOf (response )
387
+ cacheVal := reflect .ValueOf (cacheEntry .Response )
388
+ if ! cacheVal .Type ().AssignableTo (outVal .Type ()) {
389
+ return fmt .Errorf ("failed to call extension handler %q: cached response of type %s instead of type %s" , name , cacheVal .Type (), outVal .Type ())
390
+ }
391
+ reflect .Indirect (outVal ).Set (reflect .Indirect (cacheVal ))
392
+ return nil
393
+ }
394
+ }
395
+
396
+ httpOpts := & httpCallOptions {
335
397
catalog : c .catalog ,
336
398
config : registration .ClientConfig ,
337
399
registrationGVH : registration .GroupVersionHook ,
338
400
hookGVH : hookGVH ,
339
401
name : strings .TrimSuffix (registration .Name , "." + registration .ExtensionConfigName ),
340
402
timeout : timeoutDuration ,
341
403
}
342
- err = httpCall (ctx , request , response , opts )
404
+ err = httpCall (ctx , request , response , httpOpts )
343
405
if err != nil {
344
406
// If the error is errCallingExtensionHandler then apply failure policy to calculate
345
407
// the effective result of the operation.
@@ -368,6 +430,14 @@ func (c *client) CallExtension(ctx context.Context, hook runtimecatalog.Hook, fo
368
430
log .V (4 ).Info ("Extension handler returned success response" )
369
431
}
370
432
433
+ if options .WithCaching {
434
+ // Add response to the cache.
435
+ options .Cache .Add (CallExtensionCacheEntry {
436
+ CacheKey : cacheKey ,
437
+ Response : response ,
438
+ })
439
+ }
440
+
371
441
// Received a successful response from the extension handler. The `response` object
372
442
// has been populated with the result. Return no error.
373
443
return nil
0 commit comments