@@ -52,6 +52,7 @@ import (
5252 runtimemetrics "sigs.k8s.io/cluster-api/internal/runtime/metrics"
5353 runtimeregistry "sigs.k8s.io/cluster-api/internal/runtime/registry"
5454 "sigs.k8s.io/cluster-api/util"
55+ "sigs.k8s.io/cluster-api/util/cache"
5556)
5657
5758type errCallingExtensionHandler error
@@ -70,22 +71,52 @@ type Options struct {
7071// New returns a new Client.
7172func New (options Options ) runtimeclient.Client {
7273 return & client {
73- certFile : options .CertFile ,
74- keyFile : options .KeyFile ,
75- catalog : options .Catalog ,
76- registry : options .Registry ,
77- client : options .Client ,
74+ certFile : options .CertFile ,
75+ keyFile : options .KeyFile ,
76+ catalog : options .Catalog ,
77+ registry : options .Registry ,
78+ client : options .Client ,
79+ httpClientsCache : cache.New [httpClientEntry ](24 * time .Hour ),
7880 }
7981}
8082
8183var _ runtimeclient.Client = & client {}
8284
8385type client struct {
84- certFile string
85- keyFile string
86- catalog * runtimecatalog.Catalog
87- registry runtimeregistry.ExtensionRegistry
88- client ctrlclient.Client
86+ certFile string
87+ keyFile string
88+ catalog * runtimecatalog.Catalog
89+ registry runtimeregistry.ExtensionRegistry
90+ client ctrlclient.Client
91+ httpClientsCache cache.Cache [httpClientEntry ]
92+ }
93+
94+ type httpClientEntry struct {
95+ // Note: caData and hostName are the variable parts in the TLSConfig
96+ // for an http.Client that is used to call runtime extensions.
97+ caData []byte
98+ hostName string
99+
100+ client * http.Client
101+ }
102+
103+ func newHTTPClientEntry (hostName string , caData []byte , client * http.Client ) httpClientEntry {
104+ return httpClientEntry {
105+ hostName : hostName ,
106+ caData : caData ,
107+ client : client ,
108+ }
109+ }
110+
111+ func newHTTPClientEntryKey (hostName string , caData []byte ) string {
112+ return httpClientEntry {
113+ hostName : hostName ,
114+ caData : caData ,
115+ }.Key ()
116+ }
117+
118+ func (r httpClientEntry ) Key () string {
119+ return fmt .Sprintf ("%s/%s" , r .hostName , string (r .caData ))
89120}
90121
91122func (c * client ) WarmUp (extensionConfigList * runtimev1.ExtensionConfigList ) error {
@@ -105,16 +136,20 @@ func (c *client) Discover(ctx context.Context, extensionConfig *runtimev1.Extens
105136 return nil , errors .Wrapf (err , "failed to discover extension %q: failed to compute GVH of hook" , extensionConfig .Name )
106137 }
107138
139+ httpClient , err := c .getHTTPClient (extensionConfig .Spec .ClientConfig )
140+ if err != nil {
141+ return nil , errors .Wrapf (err , "failed to discover extension %q: failed to get http client" , extensionConfig .Name )
142+ }
143+
108144 request := & runtimehooksv1.DiscoveryRequest {}
109145 response := & runtimehooksv1.DiscoveryResponse {}
110146 opts := & httpCallOptions {
111- certFile : c .certFile ,
112- keyFile : c .keyFile ,
113147 catalog : c .catalog ,
114148 config : extensionConfig .Spec .ClientConfig ,
115149 registrationGVH : hookGVH ,
116150 hookGVH : hookGVH ,
117151 timeout : defaultDiscoveryTimeout ,
152+ httpClient : httpClient ,
118153 }
119154 if err := httpCall (ctx , request , response , opts ); err != nil {
120155 return nil , errors .Wrapf (err , "failed to discover extension %q" , extensionConfig .Name )
@@ -336,15 +371,19 @@ func (c *client) CallExtension(ctx context.Context, hook runtimecatalog.Hook, fo
336371 }
337372 }
338373
374+ httpClient , err := c .getHTTPClient (registration .ClientConfig )
375+ if err != nil {
376+ return errors .Wrapf (err , "failed to call extension handler %q: failed to get http client" , name )
377+ }
378+
339379 httpOpts := & httpCallOptions {
340- certFile : c .certFile ,
341- keyFile : c .keyFile ,
342380 catalog : c .catalog ,
343381 config : registration .ClientConfig ,
344382 registrationGVH : registration .GroupVersionHook ,
345383 hookGVH : hookGVH ,
346384 name : strings .TrimSuffix (registration .Name , "." + registration .ExtensionConfigName ),
347385 timeout : timeoutDuration ,
386+ httpClient : httpClient ,
348387 }
349388 err = httpCall (ctx , request , response , httpOpts )
350389 if err != nil {
@@ -388,6 +427,48 @@ func (c *client) CallExtension(ctx context.Context, hook runtimecatalog.Hook, fo
388427 return nil
389428}
390429
430+ func (c * client ) getHTTPClient (config runtimev1.ClientConfig ) (* http.Client , error ) {
431+ // Note: we are passing an empty gvh and "" as name because the only relevant part of the url
432+ // for this function is the Hostname, which derives from config (ghv and name are appended to the path).
433+ extensionURL , err := urlForExtension (config , runtimecatalog.GroupVersionHook {}, "" )
434+ if err != nil {
435+ return nil , err
436+ }
437+
438+ if cacheEntry , ok := c .httpClientsCache .Has (newHTTPClientEntryKey (extensionURL .Hostname (), config .CABundle )); ok {
439+ return cacheEntry .client , nil
440+ }
441+
442+ httpClient , err := createHTTPClient (c .certFile , c .keyFile , config .CABundle , extensionURL .Hostname ())
443+ if err != nil {
444+ return nil , err
445+ }
446+
447+ c .httpClientsCache .Add (newHTTPClientEntry (extensionURL .Hostname (), config .CABundle , httpClient ))
448+ return httpClient , nil
449+ }
450+
451+ func createHTTPClient (certFile , keyFile string , caData []byte , hostName string ) (* http.Client , error ) {
452+ httpClient := & http.Client {}
453+ tlsConfig , err := transport .TLSConfigFor (& transport.Config {
454+ TLS : transport.TLSConfig {
455+ CertFile : certFile ,
456+ KeyFile : keyFile ,
457+ CAData : caData ,
458+ ServerName : hostName ,
459+ },
460+ })
461+ if err != nil {
462+ return nil , errors .Wrap (err , "failed to create tls config" )
463+ }
464+
465+ // This also adds http2
466+ httpClient .Transport = utilnet .SetTransportDefaults (& http.Transport {
467+ TLSClientConfig : tlsConfig ,
468+ })
469+ return httpClient , nil
470+ }
471+
391472// cloneAndAddSettings creates a new request object and adds settings to it.
392473func cloneAndAddSettings (request runtimehooksv1.RequestObject , registrationSettings map [string ]string ) runtimehooksv1.RequestObject {
393474 // Merge the settings from registration with the settings in the request.
@@ -406,14 +487,13 @@ func cloneAndAddSettings(request runtimehooksv1.RequestObject, registrationSetti
406487}
407488
408489type httpCallOptions struct {
409- certFile string
410- keyFile string
411490 catalog * runtimecatalog.Catalog
412491 config runtimev1.ClientConfig
413492 registrationGVH runtimecatalog.GroupVersionHook
414493 hookGVH runtimecatalog.GroupVersionHook
415494 name string
416495 timeout time.Duration
496+ httpClient * http.Client
417497}
418498
419499func httpCall (ctx context.Context , request , response runtime.Object , opts * httpCallOptions ) error {
@@ -492,27 +572,8 @@ func httpCall(ctx context.Context, request, response runtime.Object, opts *httpC
492572 return errors .Wrap (err , "http call failed: failed to create http request" )
493573 }
494574
495- // Use client-go's transport.TLSConfigureFor to ensure good defaults for tls
496- client := & http.Client {}
497- defer client .CloseIdleConnections ()
498-
499- tlsConfig , err := transport .TLSConfigFor (& transport.Config {
500- TLS : transport.TLSConfig {
501- CertFile : opts .certFile ,
502- KeyFile : opts .keyFile ,
503- CAData : opts .config .CABundle ,
504- ServerName : extensionURL .Hostname (),
505- },
506- })
507- if err != nil {
508- return errors .Wrap (err , "http call failed: failed to create tls config" )
509- }
510- // This also adds http2
511- client .Transport = utilnet .SetTransportDefaults (& http.Transport {
512- TLSClientConfig : tlsConfig ,
513- })
514-
515- resp , err := client .Do (httpRequest )
575+ // Call the extension.
576+ resp , err := opts .httpClient .Do (httpRequest )
516577
517578 // Create http request metric.
518579 defer func () {
0 commit comments