Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ var (
lbProvider string
caCertsPath string
showVersion bool
scopeCacheMaxSize int
logOptions = logs.NewOptions()
)

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

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

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.")

fs.BoolVar(&showVersion, "version", false, "Show current version and exit.")
}

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

scopeFactory := scope.NewFactory(scopeCacheMaxSize)

setupChecks(mgr)
setupReconcilers(ctx, mgr, caCerts)
setupReconcilers(ctx, mgr, caCerts, scopeFactory)
setupWebhooks(mgr)

// +kubebuilder:scaffold:builder
Expand All @@ -233,12 +238,12 @@ func setupChecks(mgr ctrl.Manager) {
}
}

func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte) {
func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte, scopeFactory scope.Factory) {
if err := (&controllers.OpenStackClusterReconciler{
Client: mgr.GetClient(),
Recorder: mgr.GetEventRecorderFor("openstackcluster-controller"),
WatchFilterValue: watchFilterValue,
ScopeFactory: scope.ScopeFactory,
ScopeFactory: scopeFactory,
CaCertificates: caCerts,
}).SetupWithManager(ctx, mgr, concurrency(openStackClusterConcurrency)); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "OpenStackCluster")
Expand All @@ -248,7 +253,7 @@ func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte) {
Client: mgr.GetClient(),
Recorder: mgr.GetEventRecorderFor("openstackmachine-controller"),
WatchFilterValue: watchFilterValue,
ScopeFactory: scope.ScopeFactory,
ScopeFactory: scopeFactory,
CaCertificates: caCerts,
}).SetupWithManager(ctx, mgr, concurrency(openStackMachineConcurrency)); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "OpenStackMachine")
Expand Down
6 changes: 6 additions & 0 deletions pkg/scope/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ package scope

import (
"context"
"time"

"github.com/go-logr/logr"
"github.com/golang/mock/gomock"
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
"sigs.k8s.io/controller-runtime/pkg/client"

infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7"
Expand Down Expand Up @@ -105,3 +107,7 @@ func (f *MockScopeFactory) Logger() logr.Logger {
func (f *MockScopeFactory) ProjectID() string {
return f.projectID
}

func (f *MockScopeFactory) ExtractToken() (*tokens.Token, error) {
return &tokens.Token{ExpiresAt: time.Now().Add(24 * time.Hour)}, nil
}
68 changes: 63 additions & 5 deletions pkg/scope/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"crypto/x509"
"fmt"
"net/http"
"time"

"github.com/go-logr/logr"
"github.com/gophercloud/gophercloud"
Expand All @@ -31,12 +32,14 @@ import (
"github.com/gophercloud/utils/openstack/clientconfig"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/cache"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"

infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7"
"sigs.k8s.io/cluster-api-provider-openstack/pkg/clients"
"sigs.k8s.io/cluster-api-provider-openstack/pkg/utils/hash"
"sigs.k8s.io/cluster-api-provider-openstack/version"
)

Expand All @@ -45,9 +48,11 @@ const (
caSecretKey = "cacert"
)

type providerScopeFactory struct{}
type providerScopeFactory struct {
clientCache *cache.LRUExpireCache
}

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

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

return NewProviderScope(cloud, caCert, logger)
if f.clientCache == nil {
return NewProviderScope(cloud, caCert, logger)
}

return NewCachedProviderScope(f.clientCache, cloud, caCert, logger)
}

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

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

return NewProviderScope(cloud, caCert, logger)
if f.clientCache == nil {
return NewProviderScope(cloud, caCert, logger)
}

return NewCachedProviderScope(f.clientCache, cloud, caCert, logger)
}

func getScopeCacheKey(cloud clientconfig.Cloud) (string, error) {
key, err := hash.ComputeSpewHash(cloud)
if err != nil {
return "", err
}

return fmt.Sprintf("%d", key), nil
}

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

func NewCachedProviderScope(cache *cache.LRUExpireCache, cloud clientconfig.Cloud, caCert []byte, logger logr.Logger) (Scope, error) {
key, err := getScopeCacheKey(cloud)
if err != nil {
return nil, fmt.Errorf("compute cloud config cache key: %w", err)
}

if scope, found := cache.Get(key); found {
logger.V(6).Info("Using scope from cache")
return scope.(Scope), nil
}

scope, err := NewProviderScope(cloud, caCert, logger)
if err != nil {
return nil, err
}

token, err := scope.ExtractToken()
if err != nil {
return nil, err
}

// compute the token expiration time
expiry := time.Until(token.ExpiresAt) / 2

cache.Add(key, scope, expiry)
return scope, nil
}

func (s *providerScope) Logger() logr.Logger {
return s.logger
}
Expand Down Expand Up @@ -134,6 +184,14 @@ func (s *providerScope) NewLbClient() (clients.LbClient, error) {
return clients.NewLbClient(s.providerClient, s.providerClientOpts)
}

func (s *providerScope) ExtractToken() (*tokens.Token, error) {
client, err := openstack.NewIdentityV3(s.providerClient, gophercloud.EndpointOpts{})
if err != nil {
return nil, fmt.Errorf("create new identity service client: %w", err)
}
return tokens.Get(client, s.providerClient.Token()).ExtractToken()
}

func NewProviderClient(cloud clientconfig.Cloud, caCert []byte, logger logr.Logger) (*gophercloud.ProviderClient, *clientconfig.ClientOpts, string, error) {
clientOpts := new(clientconfig.ClientOpts)
if cloud.AuthInfo != nil {
Expand Down
15 changes: 13 additions & 2 deletions pkg/scope/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,24 @@ import (
"context"

"github.com/go-logr/logr"
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
"k8s.io/apimachinery/pkg/util/cache"
"sigs.k8s.io/controller-runtime/pkg/client"

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

// ScopeFactory is the default scope factory. It generates service clients which make OpenStack API calls against a running cloud.
var ScopeFactory Factory = providerScopeFactory{}
// NewFactory creates the default scope factory. It generates service clients which make OpenStack API calls against a running cloud.
func NewFactory(maxCacheSize int) Factory {
var c *cache.LRUExpireCache
if maxCacheSize > 0 {
c = cache.NewLRUExpireCache(maxCacheSize)
}
return &providerScopeFactory{
clientCache: c,
}
}

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