@@ -55,6 +55,7 @@ import (
5555 runtimemetrics "sigs.k8s.io/cluster-api/internal/runtime/metrics"
5656 runtimeregistry "sigs.k8s.io/cluster-api/internal/runtime/registry"
5757 "sigs.k8s.io/cluster-api/util"
58+ "sigs.k8s.io/cluster-api/util/cache"
5859)
5960
6061type errCallingExtensionHandler error
@@ -73,22 +74,52 @@ type Options struct {
7374// New returns a new Client.
7475func New (options Options ) runtimeclient.Client {
7576 return & client {
76- certFile : options .CertFile ,
77- keyFile : options .KeyFile ,
78- catalog : options .Catalog ,
79- registry : options .Registry ,
80- client : options .Client ,
77+ certFile : options .CertFile ,
78+ keyFile : options .KeyFile ,
79+ catalog : options .Catalog ,
80+ registry : options .Registry ,
81+ client : options .Client ,
82+ httpClientsCache : cache.New [httpClientEntry ](24 * time .Hour ),
8183 }
8284}
8385
8486var _ runtimeclient.Client = & client {}
8587
8688type client struct {
87- certFile string
88- keyFile string
89- catalog * runtimecatalog.Catalog
90- registry runtimeregistry.ExtensionRegistry
91- client ctrlclient.Client
89+ certFile string
90+ keyFile string
91+ catalog * runtimecatalog.Catalog
92+ registry runtimeregistry.ExtensionRegistry
93+ client ctrlclient.Client
94+ httpClientsCache cache.Cache [httpClientEntry ]
95+ }
96+
97+ type httpClientEntry struct {
98+ // Note: caData and hostName are the variable parts in the TLSConfig
99+ // for an http.Client that is used to call runtime extensions.
100+ caData []byte
101+ hostName string
102+
103+ client * http.Client
104+ }
105+
106+ func newHTTPClientEntry (hostName string , caData []byte , client * http.Client ) httpClientEntry {
107+ return httpClientEntry {
108+ hostName : hostName ,
109+ caData : caData ,
110+ client : client ,
111+ }
112+ }
113+
114+ func newHTTPClientEntryKey (hostName string , caData []byte ) string {
115+ return httpClientEntry {
116+ hostName : hostName ,
117+ caData : caData ,
118+ }.Key ()
119+ }
120+
121+ func (r httpClientEntry ) Key () string {
122+ return fmt .Sprintf ("%s/%s" , r .hostName , string (r .caData ))
92123}
93124
94125func (c * client ) WarmUp (extensionConfigList * runtimev1.ExtensionConfigList ) error {
@@ -108,16 +139,20 @@ func (c *client) Discover(ctx context.Context, extensionConfig *runtimev1.Extens
108139 return nil , errors .Wrapf (err , "failed to discover extension %q: failed to compute GVH of hook" , extensionConfig .Name )
109140 }
110141
142+ httpClient , err := c .getHTTPClient (extensionConfig .Spec .ClientConfig )
143+ if err != nil {
144+ return nil , errors .Wrapf (err , "failed to discover extension %q: failed to get http client" , extensionConfig .Name )
145+ }
146+
111147 request := & runtimehooksv1.DiscoveryRequest {}
112148 response := & runtimehooksv1.DiscoveryResponse {}
113149 opts := & httpCallOptions {
114- certFile : c .certFile ,
115- keyFile : c .keyFile ,
116150 catalog : c .catalog ,
117151 config : extensionConfig .Spec .ClientConfig ,
118152 registrationGVH : hookGVH ,
119153 hookGVH : hookGVH ,
120154 timeout : defaultDiscoveryTimeout ,
155+ httpClient : httpClient ,
121156 }
122157 if err := httpCall (ctx , request , response , opts ); err != nil {
123158 return nil , errors .Wrapf (err , "failed to discover extension %q" , extensionConfig .Name )
@@ -363,15 +398,19 @@ func (c *client) CallExtension(ctx context.Context, hook runtimecatalog.Hook, fo
363398 }
364399 }
365400
401+ httpClient , err := c .getHTTPClient (registration .ClientConfig )
402+ if err != nil {
403+ return errors .Wrapf (err , "failed to call extension handler %q: failed to get http client" , name )
404+ }
405+
366406 httpOpts := & httpCallOptions {
367- certFile : c .certFile ,
368- keyFile : c .keyFile ,
369407 catalog : c .catalog ,
370408 config : registration .ClientConfig ,
371409 registrationGVH : registration .GroupVersionHook ,
372410 hookGVH : hookGVH ,
373411 name : strings .TrimSuffix (registration .Name , "." + registration .ExtensionConfigName ),
374412 timeout : timeoutDuration ,
413+ httpClient : httpClient ,
375414 }
376415 err = httpCall (ctx , request , response , httpOpts )
377416 if err != nil {
@@ -413,6 +452,48 @@ func (c *client) CallExtension(ctx context.Context, hook runtimecatalog.Hook, fo
413452 return nil
414453}
415454
455+ func (c * client ) getHTTPClient (config runtimev1.ClientConfig ) (* http.Client , error ) {
456+ // Note: we are passing an empty gvh and "" as name because the only relevant part of the url
457+ // for this function is the Hostname, which derives from config (ghv and name are appended to the path).
458+ extensionURL , err := urlForExtension (config , runtimecatalog.GroupVersionHook {}, "" )
459+ if err != nil {
460+ return nil , err
461+ }
462+
463+ if cacheEntry , ok := c .httpClientsCache .Has (newHTTPClientEntryKey (extensionURL .Hostname (), config .CABundle )); ok {
464+ return cacheEntry .client , nil
465+ }
466+
467+ httpClient , err := createHTTPClient (c .certFile , c .keyFile , config .CABundle , extensionURL .Hostname ())
468+ if err != nil {
469+ return nil , err
470+ }
471+
472+ c .httpClientsCache .Add (newHTTPClientEntry (extensionURL .Hostname (), config .CABundle , httpClient ))
473+ return httpClient , nil
474+ }
475+
476+ func createHTTPClient (certFile , keyFile string , caData []byte , hostName string ) (* http.Client , error ) {
477+ httpClient := & http.Client {}
478+ tlsConfig , err := transport .TLSConfigFor (& transport.Config {
479+ TLS : transport.TLSConfig {
480+ CertFile : certFile ,
481+ KeyFile : keyFile ,
482+ CAData : caData ,
483+ ServerName : hostName ,
484+ },
485+ })
486+ if err != nil {
487+ return nil , errors .Wrap (err , "failed to create tls config" )
488+ }
489+
490+ // This also adds http2
491+ httpClient .Transport = utilnet .SetTransportDefaults (& http.Transport {
492+ TLSClientConfig : tlsConfig ,
493+ })
494+ return httpClient , nil
495+ }
496+
416497// cloneAndAddSettings creates a new request object and adds settings to it.
417498func cloneAndAddSettings (request runtimehooksv1.RequestObject , registrationSettings map [string ]string ) runtimehooksv1.RequestObject {
418499 // Merge the settings from registration with the settings in the request.
@@ -431,14 +512,13 @@ func cloneAndAddSettings(request runtimehooksv1.RequestObject, registrationSetti
431512}
432513
433514type httpCallOptions struct {
434- certFile string
435- keyFile string
436515 catalog * runtimecatalog.Catalog
437516 config runtimev1.ClientConfig
438517 registrationGVH runtimecatalog.GroupVersionHook
439518 hookGVH runtimecatalog.GroupVersionHook
440519 name string
441520 timeout time.Duration
521+ httpClient * http.Client
442522}
443523
444524func httpCall (ctx context.Context , request , response runtime.Object , opts * httpCallOptions ) error {
@@ -517,27 +597,8 @@ func httpCall(ctx context.Context, request, response runtime.Object, opts *httpC
517597 return errors .Wrap (err , "http call failed: failed to create http request" )
518598 }
519599
520- // Use client-go's transport.TLSConfigureFor to ensure good defaults for tls
521- client := & http.Client {}
522- defer client .CloseIdleConnections ()
523-
524- tlsConfig , err := transport .TLSConfigFor (& transport.Config {
525- TLS : transport.TLSConfig {
526- CertFile : opts .certFile ,
527- KeyFile : opts .keyFile ,
528- CAData : opts .config .CABundle ,
529- ServerName : extensionURL .Hostname (),
530- },
531- })
532- if err != nil {
533- return errors .Wrap (err , "http call failed: failed to create tls config" )
534- }
535- // This also adds http2
536- client .Transport = utilnet .SetTransportDefaults (& http.Transport {
537- TLSClientConfig : tlsConfig ,
538- })
539-
540- resp , err := client .Do (httpRequest )
600+ // Call the extension.
601+ resp , err := opts .httpClient .Do (httpRequest )
541602
542603 // Create http request metric.
543604 defer func () {
0 commit comments