Skip to content

Commit

Permalink
Use a cached prism-go-client with session auth
Browse files Browse the repository at this point in the history
This will reduce the number of basic auth calls that will
be made to IAM stack.
  • Loading branch information
thunderboltsid committed Jul 9, 2024
1 parent e35ef24 commit 7578bd8
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 50 deletions.
11 changes: 6 additions & 5 deletions internal/testing/mock/mock_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,17 @@ package mock

import (
"k8s.io/client-go/informers"
coreinformers "k8s.io/client-go/informers/core/v1"

"github.com/nutanix-cloud-native/cloud-provider-nutanix/pkg/provider/interfaces"
)

// MockClient is a mock implementation of the interfaces.Client interface
type MockClient struct {
mockPrism MockPrism
sharedInformers informers.SharedInformerFactory
secretInformer coreinformers.SecretInformer
configMapInformer coreinformers.ConfigMapInformer
mockPrism MockPrism
sharedInformers informers.SharedInformerFactory
}

// CreateMockClient creates a new MockClient
func CreateMockClient(mockEnvironment MockEnvironment) *MockClient {
return &MockClient{
mockPrism: MockPrism{
Expand All @@ -38,10 +37,12 @@ func CreateMockClient(mockEnvironment MockEnvironment) *MockClient {
}
}

// Get returns the mockPrism
func (mc *MockClient) Get() (interfaces.Prism, error) {
return &mc.mockPrism, nil
}

// SetInformers sets the sharedInformers
func (mc *MockClient) SetInformers(sharedInformers informers.SharedInformerFactory) {
mc.sharedInformers = sharedInformers
}
70 changes: 39 additions & 31 deletions pkg/provider/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,90 +20,98 @@ import (
"context"
"fmt"

prismgoclient "github.com/nutanix-cloud-native/prism-go-client"
"github.com/nutanix-cloud-native/prism-go-client/environment"
credentialTypes "github.com/nutanix-cloud-native/prism-go-client/environment/credentials"
kubernetesEnv "github.com/nutanix-cloud-native/prism-go-client/environment/providers/kubernetes"
envTypes "github.com/nutanix-cloud-native/prism-go-client/environment/types"
prismClientV3 "github.com/nutanix-cloud-native/prism-go-client/v3"
credentialtypes "github.com/nutanix-cloud-native/prism-go-client/environment/credentials"
kubernetesenv "github.com/nutanix-cloud-native/prism-go-client/environment/providers/kubernetes"
envtypes "github.com/nutanix-cloud-native/prism-go-client/environment/types"
prismclientv3 "github.com/nutanix-cloud-native/prism-go-client/v3"
"k8s.io/client-go/informers"
coreinformers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"

"github.com/nutanix-cloud-native/cloud-provider-nutanix/internal/constants"
"github.com/nutanix-cloud-native/cloud-provider-nutanix/pkg/provider/config"
"github.com/nutanix-cloud-native/cloud-provider-nutanix/pkg/provider/interfaces"
)

const errEnvironmentNotReady = "environment not initialized or ready yet"

type nutanixClient struct {
env *envTypes.Environment
env envtypes.Environment
config config.Config
secretInformer coreinformers.SecretInformer
sharedInformers informers.SharedInformerFactory
configMapInformer coreinformers.ConfigMapInformer
clientCache *prismclientv3.ClientCache
}

func (n *nutanixClient) Get() (interfaces.Prism, error) {
if err := n.setupEnvironment(); err != nil {
return nil, fmt.Errorf("%s: %v", errEnvironmentNotReady, err)
// Key returns the constant client name
// This implements the CachedClientParams interface of prism-go-client
func (n *nutanixClient) Key() string {
return constants.ClientName
}

// ManagementEndpoint returns the management endpoint of the Nutanix cluster
// This implements the CachedClientParams interface of prism-go-client
func (n *nutanixClient) ManagementEndpoint() envtypes.ManagementEndpoint {
if n.env == nil {
klog.Error("environment not initialized")
return envtypes.ManagementEndpoint{}
}
env := *n.env
me, err := env.GetManagementEndpoint(envTypes.Topology{})

mgmtEndpoint, err := n.env.GetManagementEndpoint(envtypes.Topology{})
if err != nil {
return nil, err
}
creds := &prismgoclient.Credentials{
URL: me.Address.Host, // Not really an URL
Endpoint: me.Address.Host,
Insecure: me.Insecure,
Username: me.ApiCredentials.Username,
Password: me.ApiCredentials.Password,
klog.Errorf("failed to get management endpoint: %s", err.Error())
return envtypes.ManagementEndpoint{}
}

clientOpts := make([]prismClientV3.ClientOption, 0)
if me.AdditionalTrustBundle != "" {
clientOpts = append(clientOpts, prismClientV3.WithPEMEncodedCertBundle([]byte(me.AdditionalTrustBundle)))
return *mgmtEndpoint
}

func (n *nutanixClient) Get() (interfaces.Prism, error) {
if err := n.setupEnvironment(); err != nil {
return nil, fmt.Errorf("%s: %w", errEnvironmentNotReady, err)
}

nutanixClient, err := prismClientV3.NewV3Client(*creds, clientOpts...)
if err != nil {
return nil, err
if n.clientCache == nil {
return nil, fmt.Errorf("%s: client cache not initialized", errEnvironmentNotReady)
}

_, err = nutanixClient.V3.GetCurrentLoggedInUser(context.Background())
client, err := n.clientCache.GetOrCreate(n)
if err != nil {
return nil, err
}

return nutanixClient.V3, nil
return client.V3, nil
}

func (n *nutanixClient) setupEnvironment() error {
if n.env != nil {
return nil
}

ccmNamespace, err := GetCCMNamespace()
if err != nil {
return err
}

pc := n.config.PrismCentral
if pc.CredentialRef != nil {
if pc.CredentialRef.Namespace == "" {
pc.CredentialRef.Namespace = ccmNamespace
}
}

additionalTrustBundleRef := pc.AdditionalTrustBundle
if additionalTrustBundleRef != nil &&
additionalTrustBundleRef.Kind == credentialTypes.NutanixTrustBundleKindConfigMap &&
additionalTrustBundleRef.Kind == credentialtypes.NutanixTrustBundleKindConfigMap &&
additionalTrustBundleRef.Namespace == "" {
additionalTrustBundleRef.Namespace = ccmNamespace
}

env := environment.NewEnvironment(kubernetesEnv.NewProvider(pc,
n.secretInformer, n.configMapInformer))
n.env = &env
n.env = environment.NewEnvironment(kubernetesenv.NewProvider(pc, n.secretInformer, n.configMapInformer))

return nil
}

Expand Down
146 changes: 146 additions & 0 deletions pkg/provider/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@ limitations under the License.
package provider

import (
"os"
"time"

"github.com/nutanix-cloud-native/prism-go-client/environment/credentials"
"github.com/nutanix-cloud-native/prism-go-client/environment/providers/local"
prismclientv3 "github.com/nutanix-cloud-native/prism-go-client/v3"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"

"github.com/nutanix-cloud-native/cloud-provider-nutanix/internal/constants"
"github.com/nutanix-cloud-native/cloud-provider-nutanix/internal/testing/mock"
"github.com/nutanix-cloud-native/cloud-provider-nutanix/pkg/provider/config"
)
Expand Down Expand Up @@ -51,4 +56,145 @@ var _ = Describe("Test Client", func() {
Expect(nClient.sharedInformers).ToNot(BeNil())
})
})

Context("Test Key", func() {
It("should return the client name", func() {
Expect(nClient.Key()).To(Equal(constants.ClientName))
})
})

Context("Test ManagementEndpoint", func() {
BeforeEach(func() {
nClient = nutanixClient{
config: config,
}
})

It("should return the empty management endpoint if env is uninitialized", func() {
Expect(nClient.ManagementEndpoint()).To(BeZero())
})

It("should return the empty management endpoint if env isn't properly initialized", func() {
p := local.NewProvider()
nClient.env = p
Expect(nClient.ManagementEndpoint()).To(BeZero())
})

It("should return the management endpoint", func() {
p := local.NewProvider()
err := os.Setenv("NUTANIX_ENDPOINT", "prism.nutanix.com")
defer os.Unsetenv("NUTANIX_ENDPOINT")
Expect(err).To(BeNil())
nClient.env = p
Expect(nClient.ManagementEndpoint()).ToNot(BeZero())
})
})

Context("Test Get", func() {
BeforeEach(func() {
nClient = nutanixClient{
config: config,
}
})

It("should return error if env is uninitialized", func() {
client, err := nClient.Get()
Expect(err).ToNot(BeNil())
Expect(client).To(BeNil())
})

It("should return error if clientCache is uninitialized", func() {
p := local.NewProvider()
err := os.Setenv("NUTANIX_ENDPOINT", "prism.nutanix.com")
Expect(err).To(BeNil())
defer os.Unsetenv("NUTANIX_ENDPOINT")
nClient.env = p
client, err := nClient.Get()
Expect(err).ToNot(BeNil())
Expect(client).To(BeNil())
})

It("should return an error when client creation fails", func() {
p := local.NewProvider()
err := os.Setenv("NUTANIX_ENDPOINT", "prism.nutanix.com")
Expect(err).To(BeNil())
defer os.Unsetenv("NUTANIX_ENDPOINT")
nClient.env = p
nClient.clientCache = prismclientv3.NewClientCache(prismclientv3.WithSessionAuth(false))
client, err := nClient.Get()
Expect(err).ToNot(BeNil())
Expect(client).To(BeNil())
})

It("should return a client when client creation succeeds", func() {
p := local.NewProvider()
err := os.Setenv("NUTANIX_ENDPOINT", "prism.nutanix.com")
Expect(err).To(BeNil())
defer os.Unsetenv("NUTANIX_ENDPOINT")

err = os.Setenv("NUTANIX_USERNAME", "username")
Expect(err).To(BeNil())
defer os.Unsetenv("NUTANIX_USERNAME")

err = os.Setenv("NUTANIX_PASSWORD", "password")
Expect(err).To(BeNil())
defer os.Unsetenv("NUTANIX_PASSWORD")

nClient.env = p
nClient.clientCache = prismclientv3.NewClientCache(prismclientv3.WithSessionAuth(false))
client, err := nClient.Get()
Expect(err).To(BeNil())
Expect(client).ToNot(BeNil())
})
})

Context("Test setupEnvironment", func() {
It("should return nil if env is already initialized", func() {
nClient.env = local.NewProvider()
Expect(nClient.setupEnvironment()).To(BeNil())
})

It("should return error if CCM namespace is not set", func() {
err := os.Setenv(constants.CCMNamespaceKey, "")
Expect(err).To(BeNil())
defer os.Unsetenv(constants.CCMNamespaceKey)

Expect(nClient.setupEnvironment()).ToNot(BeNil())
})

It("should set the namespace for credential ref if not set", func() {
err := os.Setenv(constants.CCMNamespaceKey, "kube-system")
Expect(err).To(BeNil())
defer os.Unsetenv(constants.CCMNamespaceKey)

nClient.config.PrismCentral.CredentialRef.Namespace = ""
defer func() {
nClient.config = mock.GenerateMockConfig()
}()

err = nClient.setupEnvironment()
Expect(err).To(BeNil())
Expect(nClient.config.PrismCentral.CredentialRef.Namespace).To(Equal("kube-system"))
})

It("should set the namespace for additional trust bundle if not set", func() {
err := os.Setenv(constants.CCMNamespaceKey, "kube-system")
Expect(err).To(BeNil())
defer os.Unsetenv(constants.CCMNamespaceKey)

nClient.config = mock.GenerateMockConfig()
nClient.config.PrismCentral.AdditionalTrustBundle = &credentials.NutanixTrustBundleReference{
Kind: credentials.NutanixTrustBundleKindConfigMap,
Name: "nutanix-trust-bundle",
}

defer func() {
nClient.config = mock.GenerateMockConfig()
}()

err = nClient.setupEnvironment()
Expect(err).To(BeNil())
Expect(nClient.config.PrismCentral.AdditionalTrustBundle.Namespace).To(Equal("kube-system"))
})
})
})
Loading

0 comments on commit 7578bd8

Please sign in to comment.