Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a nutanix prism client cache #415

Merged
merged 2 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,10 @@ mocks: ## Generate mocks for the project
mockgen -destination=mocks/ctlclient/cache_mock.go -package=mockctlclient sigs.k8s.io/controller-runtime/pkg/cache Cache
mockgen -destination=mocks/k8sclient/cm_informer.go -package=mockk8sclient k8s.io/client-go/informers/core/v1 ConfigMapInformer
mockgen -destination=mocks/k8sclient/secret_informer.go -package=mockk8sclient k8s.io/client-go/informers/core/v1 SecretInformer
mockgen -destination=mocks/k8sclient/secret_lister.go -package=mockk8sclient k8s.io/client-go/listers/core/v1 SecretLister
mockgen -destination=mocks/k8sclient/secret_namespace_lister.go -package=mockk8sclient k8s.io/client-go/listers/core/v1 SecretNamespaceLister

GOTESTPKGS = $(shell go list ./... | grep -v /mocks | grep -v /templates)
GOTESTPKGS = $(shell go list ./... | grep -v /mocks | grep -v /templates | grep -v /v1alpha4)

.PHONY: unit-test
unit-test: mocks ## Run unit tests.
Expand Down
8 changes: 8 additions & 0 deletions api/v1beta1/nutanixcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ limitations under the License.
package v1beta1

import (
"cmp"
"fmt"

credentialTypes "github.com/nutanix-cloud-native/prism-go-client/environment/credentials"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
capiv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/errors"
Expand Down Expand Up @@ -162,6 +164,12 @@ func (ncl *NutanixCluster) GetPrismCentralCredentialRef() (*credentialTypes.Nuta
return prismCentralInfo.CredentialRef, nil
}

// GetNamespacedName returns the namespaced name of the NutanixCluster.
func (ncl *NutanixCluster) GetNamespacedName() string {
namespace := cmp.Or(ncl.Namespace, corev1.NamespaceDefault)
return fmt.Sprintf("%s/%s", namespace, ncl.Name)
}

// +kubebuilder:object:root=true

// NutanixClusterList contains a list of NutanixCluster
Expand Down
36 changes: 36 additions & 0 deletions api/v1beta1/nutanixcluster_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,39 @@ func TestGetCredentialRefForCluster(t *testing.T) {
})
}
}

func TestGetNamespacedName(t *testing.T) {
t.Parallel()
tests := []struct {
name string
nutanixCluster *NutanixCluster
expectedFullName string
}{
{
name: "namespace and name are set",
nutanixCluster: &NutanixCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "test-namespace",
},
},
expectedFullName: "test-namespace/test",
},
{
name: "namespace is not set, should use default",
nutanixCluster: &NutanixCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
},
expectedFullName: "default/test",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fullName := tt.nutanixCluster.GetNamespacedName()
assert.Equal(t, tt.expectedFullName, fullName)
})
}
}
106 changes: 97 additions & 9 deletions controllers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,16 @@ import (
"github.com/google/uuid"
"github.com/nutanix-cloud-native/prism-go-client/utils"
nutanixClientV3 "github.com/nutanix-cloud-native/prism-go-client/v3"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
coreinformers "k8s.io/client-go/informers/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/client-go/informers/core/v1"
capiv1 "sigs.k8s.io/cluster-api/api/v1beta1"
capiutil "sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/conditions"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

infrav1 "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1"
nutanixClient "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/pkg/client"
Expand All @@ -44,14 +51,6 @@ const (
gpuUnused = "UNUSED"
)

// CreateNutanixClient creates a new Nutanix client from the environment
func CreateNutanixClient(ctx context.Context, secretInformer coreinformers.SecretInformer, cmInformer coreinformers.ConfigMapInformer, nutanixCluster *infrav1.NutanixCluster) (*nutanixClientV3.Client, error) {
log := ctrl.LoggerFrom(ctx)
log.V(1).Info("creating nutanix client")
helper := nutanixClient.NewHelper(secretInformer, cmInformer)
return helper.BuildClientForNutanixClusterWithFallback(ctx, nutanixCluster)
}

// DeleteVM deletes a VM and is invoked by the NutanixMachineReconciler
func DeleteVM(ctx context.Context, client *nutanixClientV3.Client, vmName, vmUUID string) (string, error) {
log := ctrl.LoggerFrom(ctx)
Expand Down Expand Up @@ -760,3 +759,92 @@ func GetFailureDomain(failureDomainName string, nutanixCluster *infrav1.NutanixC
}
return nil, fmt.Errorf("failed to find failure domain %s on nutanix cluster object", failureDomainName)
}

func reconcileCredentialRef(ctx context.Context, k8sClient client.Client, nutanixCluster *infrav1.NutanixCluster) error {
log := ctrl.LoggerFrom(ctx)
credentialRef, err := getPrismCentralCredentialRefForCluster(nutanixCluster)
if err != nil {
return err
}

secret := &corev1.Secret{}
if credentialRef == nil {
return nil
}

log.V(1).Info(fmt.Sprintf("credential ref is kind Secret for cluster %s", nutanixCluster.Name))
secretKey := client.ObjectKey{
Namespace: nutanixCluster.Namespace,
Name: credentialRef.Name,
}

if err := k8sClient.Get(ctx, secretKey, secret); err != nil {
errorMsg := fmt.Errorf("error occurred while fetching cluster %s secret for credential ref: %v", nutanixCluster.Name, err)
log.Error(errorMsg, "error occurred fetching cluster")
return errorMsg
}

// Check if ownerRef is already set on nutanixCluster object
if !capiutil.IsOwnedByObject(secret, nutanixCluster) {
// Check if another nutanixCluster already has set ownerRef. Secret can only be owned by one nutanixCluster object
if capiutil.HasOwner(secret.OwnerReferences, infrav1.GroupVersion.String(), []string{
nutanixCluster.Kind,
}) {
return fmt.Errorf("secret %s already owned by another nutanixCluster object", secret.Name)
}
// Set nutanixCluster ownerRef on the secret
secret.OwnerReferences = capiutil.EnsureOwnerRef(secret.OwnerReferences, metav1.OwnerReference{
APIVersion: infrav1.GroupVersion.String(),
Kind: nutanixCluster.Kind,
UID: nutanixCluster.UID,
Name: nutanixCluster.Name,
})
}

if !ctrlutil.ContainsFinalizer(secret, infrav1.NutanixClusterCredentialFinalizer) {
ctrlutil.AddFinalizer(secret, infrav1.NutanixClusterCredentialFinalizer)
}

err = k8sClient.Update(ctx, secret)
if err != nil {
errorMsg := fmt.Errorf("failed to update secret for cluster %s: %v", nutanixCluster.Name, err)
log.Error(errorMsg, "failed to update secret")
return errorMsg
}

return nil
}

func getPrismCentralClientForCluster(ctx context.Context, k8sClient client.Client, cluster *infrav1.NutanixCluster, capiCluster *capiv1.Cluster,
secretInformer v1.SecretInformer, mapInformer v1.ConfigMapInformer,
) (*nutanixClientV3.Client, error) {
log := ctrl.LoggerFrom(ctx)

if err := reconcileCredentialRef(ctx, k8sClient, cluster); err != nil {
thunderboltsid marked this conversation as resolved.
Show resolved Hide resolved
log.Error(err, fmt.Sprintf("error occurred while reconciling credential ref for cluster %s", capiCluster.Name))
conditions.MarkFalse(cluster, infrav1.CredentialRefSecretOwnerSetCondition, infrav1.CredentialRefSecretOwnerSetFailed, capiv1.ConditionSeverityError, err.Error())
return nil, err
}
conditions.MarkTrue(cluster, infrav1.CredentialRefSecretOwnerSetCondition)

clientHelper := nutanixClient.NewHelper(secretInformer, mapInformer)
mep, err := clientHelper.BuildManagementEndpoint(ctx, cluster)
thunderboltsid marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
log.Error(err, fmt.Sprintf("error occurred while getting management endpoint for cluster %q", cluster.GetNamespacedName()))
conditions.MarkFalse(cluster, infrav1.PrismCentralClientCondition, infrav1.PrismCentralClientInitializationFailed, capiv1.ConditionSeverityError, err.Error())
return nil, err
}

v3Client, err := nutanixClient.NutanixClientCache.GetOrCreate(&nutanixClient.CacheParams{
NutanixCluster: cluster,
PrismManagementEndpoint: mep,
})
if err != nil {
log.Error(err, "error occurred while getting nutanix prism client from cache")
conditions.MarkFalse(cluster, infrav1.PrismCentralClientCondition, infrav1.PrismCentralClientInitializationFailed, capiv1.ConditionSeverityError, err.Error())
return nil, fmt.Errorf("nutanix prism client error: %w", err)
}

conditions.MarkTrue(cluster, infrav1.PrismCentralClientCondition)
return v3Client, nil
}
Loading
Loading