Skip to content

Commit 73a7f8e

Browse files
Add cache on provider scope
1 parent 9d43fd6 commit 73a7f8e

File tree

4 files changed

+86
-11
lines changed

4 files changed

+86
-11
lines changed

main.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package main
1717

1818
import (
1919
"context"
20+
"errors"
2021
"flag"
2122
"fmt"
2223
"net/http"
@@ -71,6 +72,7 @@ var (
7172
lbProvider string
7273
caCertsPath string
7374
showVersion bool
75+
scopeCacheMaxSize int
7476
logOptions = logs.NewOptions()
7577
)
7678

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

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

142+
fs.IntVar(&scopeCacheMaxSize, "scope-cache-max-size", 10, "The maximum credentials count the operator should keep in cache")
143+
140144
fs.BoolVar(&showVersion, "version", false, "Show current version and exit.")
141145
}
142146

@@ -156,6 +160,12 @@ func main() {
156160
os.Exit(1)
157161
}
158162

163+
if scopeCacheMaxSize <= 0 {
164+
err := errors.New("--scope-cache-max-size should be > 0")
165+
setupLog.Error(err, "unable to start manager")
166+
os.Exit(1)
167+
}
168+
159169
// klog.Background will automatically use the right logger.
160170
ctrl.SetLogger(klog.Background())
161171

@@ -209,8 +219,10 @@ func main() {
209219
// Initialize event recorder.
210220
record.InitFromRecorder(mgr.GetEventRecorderFor("openstack-controller"))
211221

222+
scopeFactory := scope.NewScopeFactory(scopeCacheMaxSize)
223+
212224
setupChecks(mgr)
213-
setupReconcilers(ctx, mgr, caCerts)
225+
setupReconcilers(ctx, mgr, caCerts, scopeFactory)
214226
setupWebhooks(mgr)
215227

216228
// +kubebuilder:scaffold:builder
@@ -233,12 +245,12 @@ func setupChecks(mgr ctrl.Manager) {
233245
}
234246
}
235247

236-
func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte) {
248+
func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte, scopeFactory scope.Factory) {
237249
if err := (&controllers.OpenStackClusterReconciler{
238250
Client: mgr.GetClient(),
239251
Recorder: mgr.GetEventRecorderFor("openstackcluster-controller"),
240252
WatchFilterValue: watchFilterValue,
241-
ScopeFactory: scope.ScopeFactory,
253+
ScopeFactory: scopeFactory,
242254
CaCertificates: caCerts,
243255
}).SetupWithManager(ctx, mgr, concurrency(openStackClusterConcurrency)); err != nil {
244256
setupLog.Error(err, "unable to create controller", "controller", "OpenStackCluster")
@@ -248,7 +260,7 @@ func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte) {
248260
Client: mgr.GetClient(),
249261
Recorder: mgr.GetEventRecorderFor("openstackmachine-controller"),
250262
WatchFilterValue: watchFilterValue,
251-
ScopeFactory: scope.ScopeFactory,
263+
ScopeFactory: scopeFactory,
252264
CaCertificates: caCerts,
253265
}).SetupWithManager(ctx, mgr, concurrency(openStackMachineConcurrency)); err != nil {
254266
setupLog.Error(err, "unable to create controller", "controller", "OpenStackMachine")

pkg/scope/mock.go

Lines changed: 6 additions & 0 deletions
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

Lines changed: 55 additions & 5 deletions
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"
@@ -35,8 +36,10 @@ import (
3536
"sigs.k8s.io/controller-runtime/pkg/client"
3637
"sigs.k8s.io/yaml"
3738

39+
"k8s.io/apimachinery/pkg/util/cache"
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+
cache *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,10 @@ func (providerScopeFactory) NewClientScopeFromMachine(ctx context.Context, ctrlC
6368
caCert = defaultCACert
6469
}
6570

66-
return NewProviderScope(cloud, caCert, logger)
71+
return NewCachedProviderScope(f.cache, cloud, caCert, logger)
6772
}
6873

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

@@ -82,7 +87,16 @@ func (providerScopeFactory) NewClientScopeFromCluster(ctx context.Context, ctrlC
8287
caCert = defaultCACert
8388
}
8489

85-
return NewProviderScope(cloud, caCert, logger)
90+
return NewCachedProviderScope(f.cache, cloud, caCert, logger)
91+
}
92+
93+
func getScopeCacheKey(cloud clientconfig.Cloud) (string, error) {
94+
key, err := hash.ComputeSpewHash(cloud)
95+
if err != nil {
96+
return "", err
97+
}
98+
99+
return fmt.Sprintf("%d", key), nil
86100
}
87101

88102
type providerScope struct {
@@ -106,6 +120,34 @@ func NewProviderScope(cloud clientconfig.Cloud, caCert []byte, logger logr.Logge
106120
}, nil
107121
}
108122

123+
func NewCachedProviderScope(cache *cache.LRUExpireCache, cloud clientconfig.Cloud, caCert []byte, logger logr.Logger) (Scope, error) {
124+
key, err := getScopeCacheKey(cloud)
125+
if err != nil {
126+
return nil, fmt.Errorf("can't compute cloud config cache key: %w", err)
127+
}
128+
129+
if scope, found := cache.Get(key); found {
130+
logger.V(6).Info("Using scope from cache")
131+
return scope.(Scope), nil
132+
}
133+
134+
scope, err := NewProviderScope(cloud, caCert, logger)
135+
if err != nil {
136+
return nil, err
137+
}
138+
139+
token, err := scope.ExtractToken()
140+
if err != nil {
141+
return nil, err
142+
}
143+
144+
// compute the token expiration time
145+
expiry := time.Until(token.ExpiresAt) / 2
146+
147+
cache.Add(key, scope, expiry)
148+
return scope, nil
149+
}
150+
109151
func (s *providerScope) Logger() logr.Logger {
110152
return s.logger
111153
}
@@ -134,6 +176,14 @@ func (s *providerScope) NewLbClient() (clients.LbClient, error) {
134176
return clients.NewLbClient(s.providerClient, s.providerClientOpts)
135177
}
136178

179+
func (s *providerScope) ExtractToken() (*tokens.Token, error) {
180+
client, err := openstack.NewIdentityV3(s.providerClient, gophercloud.EndpointOpts{})
181+
if err != nil {
182+
return nil, fmt.Errorf("can't create a new identity service client: %w", err)
183+
}
184+
return tokens.Get(client, s.providerClient.Token()).ExtractToken()
185+
}
186+
137187
func NewProviderClient(cloud clientconfig.Cloud, caCert []byte, logger logr.Logger) (*gophercloud.ProviderClient, *clientconfig.ClientOpts, string, error) {
138188
clientOpts := new(clientconfig.ClientOpts)
139189
if cloud.AuthInfo != nil {

pkg/scope/scope.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,20 @@ 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+
// NewScopeFactory creates the default scope factory. It generates service clients which make OpenStack API calls against a running cloud.
32+
func NewScopeFactory(maxCacheSize int) Factory {
33+
return &providerScopeFactory{
34+
cache: cache.NewLRUExpireCache(maxCacheSize),
35+
}
36+
}
3137

3238
// Factory instantiates a new Scope using credentials from either a cluster or a machine.
3339
type Factory interface {
@@ -44,4 +50,5 @@ type Scope interface {
4450
NewLbClient() (clients.LbClient, error)
4551
Logger() logr.Logger
4652
ProjectID() string
53+
ExtractToken() (*tokens.Token, error)
4754
}

0 commit comments

Comments
 (0)