Skip to content

Commit f28da18

Browse files
authored
Merge pull request kubernetes-sigs#1688 from alexandrevilain/feat/add-provider-scope-cache
✨ Add cache on provider scope
2 parents 4bba410 + f444006 commit f28da18

File tree

4 files changed

+91
-11
lines changed

4 files changed

+91
-11
lines changed

main.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ var (
7171
lbProvider string
7272
caCertsPath string
7373
showVersion bool
74+
scopeCacheMaxSize int
7475
logOptions = logs.NewOptions()
7576
)
7677

@@ -137,6 +138,8 @@ func InitFlags(fs *pflag.FlagSet) {
137138

138139
fs.StringVar(&caCertsPath, "ca-certs", "", "The path to a PEM-encoded CA Certificate file to supply as default for each request.")
139140

141+
fs.IntVar(&scopeCacheMaxSize, "scope-cache-max-size", 10, "The maximum credentials count the operator should keep in cache. Setting this value to 0 means no cache.")
142+
140143
fs.BoolVar(&showVersion, "version", false, "Show current version and exit.")
141144
}
142145

@@ -209,8 +212,10 @@ func main() {
209212
// Initialize event recorder.
210213
record.InitFromRecorder(mgr.GetEventRecorderFor("openstack-controller"))
211214

215+
scopeFactory := scope.NewFactory(scopeCacheMaxSize)
216+
212217
setupChecks(mgr)
213-
setupReconcilers(ctx, mgr, caCerts)
218+
setupReconcilers(ctx, mgr, caCerts, scopeFactory)
214219
setupWebhooks(mgr)
215220

216221
// +kubebuilder:scaffold:builder
@@ -233,12 +238,12 @@ func setupChecks(mgr ctrl.Manager) {
233238
}
234239
}
235240

236-
func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte) {
241+
func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte, scopeFactory scope.Factory) {
237242
if err := (&controllers.OpenStackClusterReconciler{
238243
Client: mgr.GetClient(),
239244
Recorder: mgr.GetEventRecorderFor("openstackcluster-controller"),
240245
WatchFilterValue: watchFilterValue,
241-
ScopeFactory: scope.ScopeFactory,
246+
ScopeFactory: scopeFactory,
242247
CaCertificates: caCerts,
243248
}).SetupWithManager(ctx, mgr, concurrency(openStackClusterConcurrency)); err != nil {
244249
setupLog.Error(err, "unable to create controller", "controller", "OpenStackCluster")
@@ -248,7 +253,7 @@ func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte) {
248253
Client: mgr.GetClient(),
249254
Recorder: mgr.GetEventRecorderFor("openstackmachine-controller"),
250255
WatchFilterValue: watchFilterValue,
251-
ScopeFactory: scope.ScopeFactory,
256+
ScopeFactory: scopeFactory,
252257
CaCertificates: caCerts,
253258
}).SetupWithManager(ctx, mgr, concurrency(openStackMachineConcurrency)); err != nil {
254259
setupLog.Error(err, "unable to create controller", "controller", "OpenStackMachine")

pkg/scope/mock.go

+6
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ package scope
1818

1919
import (
2020
"context"
21+
"time"
2122

2223
"github.com/go-logr/logr"
2324
"github.com/golang/mock/gomock"
25+
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
2426
"sigs.k8s.io/controller-runtime/pkg/client"
2527

2628
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7"
@@ -105,3 +107,7 @@ func (f *MockScopeFactory) Logger() logr.Logger {
105107
func (f *MockScopeFactory) ProjectID() string {
106108
return f.projectID
107109
}
110+
111+
func (f *MockScopeFactory) ExtractToken() (*tokens.Token, error) {
112+
return &tokens.Token{ExpiresAt: time.Now().Add(24 * time.Hour)}, nil
113+
}

pkg/scope/provider.go

+63-5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"crypto/x509"
2323
"fmt"
2424
"net/http"
25+
"time"
2526

2627
"github.com/go-logr/logr"
2728
"github.com/gophercloud/gophercloud"
@@ -31,12 +32,14 @@ import (
3132
"github.com/gophercloud/utils/openstack/clientconfig"
3233
corev1 "k8s.io/api/core/v1"
3334
"k8s.io/apimachinery/pkg/types"
35+
"k8s.io/apimachinery/pkg/util/cache"
3436
"k8s.io/klog/v2"
3537
"sigs.k8s.io/controller-runtime/pkg/client"
3638
"sigs.k8s.io/yaml"
3739

3840
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7"
3941
"sigs.k8s.io/cluster-api-provider-openstack/pkg/clients"
42+
"sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/hash"
4043
"sigs.k8s.io/cluster-api-provider-openstack/version"
4144
)
4245

@@ -45,9 +48,11 @@ const (
4548
caSecretKey = "cacert"
4649
)
4750

48-
type providerScopeFactory struct{}
51+
type providerScopeFactory struct {
52+
clientCache *cache.LRUExpireCache
53+
}
4954

50-
func (providerScopeFactory) NewClientScopeFromMachine(ctx context.Context, ctrlClient client.Client, openStackMachine *infrav1.OpenStackMachine, defaultCACert []byte, logger logr.Logger) (Scope, error) {
55+
func (f *providerScopeFactory) NewClientScopeFromMachine(ctx context.Context, ctrlClient client.Client, openStackMachine *infrav1.OpenStackMachine, defaultCACert []byte, logger logr.Logger) (Scope, error) {
5156
var cloud clientconfig.Cloud
5257
var caCert []byte
5358

@@ -63,10 +68,14 @@ func (providerScopeFactory) NewClientScopeFromMachine(ctx context.Context, ctrlC
6368
caCert = defaultCACert
6469
}
6570

66-
return NewProviderScope(cloud, caCert, logger)
71+
if f.clientCache == nil {
72+
return NewProviderScope(cloud, caCert, logger)
73+
}
74+
75+
return NewCachedProviderScope(f.clientCache, cloud, caCert, logger)
6776
}
6877

69-
func (providerScopeFactory) NewClientScopeFromCluster(ctx context.Context, ctrlClient client.Client, openStackCluster *infrav1.OpenStackCluster, defaultCACert []byte, logger logr.Logger) (Scope, error) {
78+
func (f *providerScopeFactory) NewClientScopeFromCluster(ctx context.Context, ctrlClient client.Client, openStackCluster *infrav1.OpenStackCluster, defaultCACert []byte, logger logr.Logger) (Scope, error) {
7079
var cloud clientconfig.Cloud
7180
var caCert []byte
7281

@@ -82,7 +91,20 @@ func (providerScopeFactory) NewClientScopeFromCluster(ctx context.Context, ctrlC
8291
caCert = defaultCACert
8392
}
8493

85-
return NewProviderScope(cloud, caCert, logger)
94+
if f.clientCache == nil {
95+
return NewProviderScope(cloud, caCert, logger)
96+
}
97+
98+
return NewCachedProviderScope(f.clientCache, cloud, caCert, logger)
99+
}
100+
101+
func getScopeCacheKey(cloud clientconfig.Cloud) (string, error) {
102+
key, err := hash.ComputeSpewHash(cloud)
103+
if err != nil {
104+
return "", err
105+
}
106+
107+
return fmt.Sprintf("%d", key), nil
86108
}
87109

88110
type providerScope struct {
@@ -106,6 +128,34 @@ func NewProviderScope(cloud clientconfig.Cloud, caCert []byte, logger logr.Logge
106128
}, nil
107129
}
108130

131+
func NewCachedProviderScope(cache *cache.LRUExpireCache, cloud clientconfig.Cloud, caCert []byte, logger logr.Logger) (Scope, error) {
132+
key, err := getScopeCacheKey(cloud)
133+
if err != nil {
134+
return nil, fmt.Errorf("compute cloud config cache key: %w", err)
135+
}
136+
137+
if scope, found := cache.Get(key); found {
138+
logger.V(6).Info("Using scope from cache")
139+
return scope.(Scope), nil
140+
}
141+
142+
scope, err := NewProviderScope(cloud, caCert, logger)
143+
if err != nil {
144+
return nil, err
145+
}
146+
147+
token, err := scope.ExtractToken()
148+
if err != nil {
149+
return nil, err
150+
}
151+
152+
// compute the token expiration time
153+
expiry := time.Until(token.ExpiresAt) / 2
154+
155+
cache.Add(key, scope, expiry)
156+
return scope, nil
157+
}
158+
109159
func (s *providerScope) Logger() logr.Logger {
110160
return s.logger
111161
}
@@ -134,6 +184,14 @@ func (s *providerScope) NewLbClient() (clients.LbClient, error) {
134184
return clients.NewLbClient(s.providerClient, s.providerClientOpts)
135185
}
136186

187+
func (s *providerScope) ExtractToken() (*tokens.Token, error) {
188+
client, err := openstack.NewIdentityV3(s.providerClient, gophercloud.EndpointOpts{})
189+
if err != nil {
190+
return nil, fmt.Errorf("create new identity service client: %w", err)
191+
}
192+
return tokens.Get(client, s.providerClient.Token()).ExtractToken()
193+
}
194+
137195
func NewProviderClient(cloud clientconfig.Cloud, caCert []byte, logger logr.Logger) (*gophercloud.ProviderClient, *clientconfig.ClientOpts, string, error) {
138196
clientOpts := new(clientconfig.ClientOpts)
139197
if cloud.AuthInfo != nil {

pkg/scope/scope.go

+13-2
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,24 @@ import (
2020
"context"
2121

2222
"github.com/go-logr/logr"
23+
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
24+
"k8s.io/apimachinery/pkg/util/cache"
2325
"sigs.k8s.io/controller-runtime/pkg/client"
2426

2527
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7"
2628
"sigs.k8s.io/cluster-api-provider-openstack/pkg/clients"
2729
)
2830

29-
// ScopeFactory is the default scope factory. It generates service clients which make OpenStack API calls against a running cloud.
30-
var ScopeFactory Factory = providerScopeFactory{}
31+
// NewFactory creates the default scope factory. It generates service clients which make OpenStack API calls against a running cloud.
32+
func NewFactory(maxCacheSize int) Factory {
33+
var c *cache.LRUExpireCache
34+
if maxCacheSize > 0 {
35+
c = cache.NewLRUExpireCache(maxCacheSize)
36+
}
37+
return &providerScopeFactory{
38+
clientCache: c,
39+
}
40+
}
3141

3242
// Factory instantiates a new Scope using credentials from either a cluster or a machine.
3343
type Factory interface {
@@ -44,4 +54,5 @@ type Scope interface {
4454
NewLbClient() (clients.LbClient, error)
4555
Logger() logr.Logger
4656
ProjectID() string
57+
ExtractToken() (*tokens.Token, error)
4758
}

0 commit comments

Comments
 (0)