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

fix(controller): Move v4 client instantiation to reconcileDelete #465

Merged
merged 6 commits into from
Jul 31, 2024
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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ mocks: ## Generate mocks for the project
mockgen -destination=mocks/k8sclient/informer.go -package=mockk8sclient k8s.io/client-go/informers/core/v1 ConfigMapInformer,SecretInformer
mockgen -destination=mocks/k8sclient/lister.go -package=mockk8sclient k8s.io/client-go/listers/core/v1 SecretLister,SecretNamespaceLister
mockgen -destination=mocks/k8sapimachinery/interfaces.go -package=mockmeta k8s.io/apimachinery/pkg/api/meta RESTMapper,RESTScope
mockgen -destination=mocks/nutanix/v3.go -package=mocknutanixv3 github.com/nutanix-cloud-native/prism-go-client/v3 Service

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

Expand Down
16 changes: 0 additions & 16 deletions controllers/nutanixcluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,22 +201,6 @@ func (r *NutanixClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque
NutanixCluster: cluster,
NutanixClient: v3Client,
}

createV4Client, err := isPrismCentralV4Compatible(ctx, v3Client)
if err != nil {
log.Error(err, "error occurred while checking environment compatibility for Prism Central v4 APIs")
}

if createV4Client {
v4Client, err := getPrismCentralV4ClientForCluster(ctx, cluster, r.SecretInformer, r.ConfigMapInformer)
if err != nil {
log.Error(err, "error occurred while fetching Prism Central v4 client")
return reconcile.Result{}, err
}

rctx.NutanixClientV4 = v4Client
}

// Check for request action
if !cluster.DeletionTimestamp.IsZero() {
// NutanixCluster is being deleted
Expand Down
179 changes: 99 additions & 80 deletions controllers/nutanixmachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,22 +275,6 @@
NutanixClient: v3Client,
}

createV4Client, err := isPrismCentralV4Compatible(ctx, v3Client)
if err != nil {
log.Error(err, "error occurred while checking compatibility for Prism Central v4 APIs")
}

if createV4Client {
log.Info("Creating Prism Central v4 client for cluster", "cluster", ntxCluster.Name)
v4Client, err := getPrismCentralV4ClientForCluster(ctx, ntxCluster, r.SecretInformer, r.ConfigMapInformer)
if err != nil {
log.Error(err, "error occurred while fetching Prism Central v4 client")
return reconcile.Result{}, err
}

rctx.NutanixClientV4 = v4Client
}

defer func() {
if err == nil {
// Always attempt to Patch the NutanixMachine object and its status after each reconciliation.
Expand Down Expand Up @@ -327,81 +311,116 @@
log.Error(errorMsg, "failed to delete VM")
return reconcile.Result{}, errorMsg
}

// Check if VMUUID is absent
if vmUUID == "" {
log.Info(fmt.Sprintf("VMUUID was not found in spec for VM %s. Skipping delete", vmName))
} else {
// Search for VM by UUID
vm, err := FindVMByUUID(ctx, v3Client, vmUUID)
// Error while finding VM
log.Info(fmt.Sprintf("VM UUID was not found in spec for VM %s. Skipping delete", vmName))
log.Info(fmt.Sprintf("Removing finalizers for VM %s during delete reconciliation", vmName))
ctrlutil.RemoveFinalizer(rctx.NutanixMachine, infrav1.NutanixMachineFinalizer)
return reconcile.Result{}, nil

Check warning on line 320 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L317-L320

Added lines #L317 - L320 were not covered by tests
}

vm, err := FindVMByUUID(ctx, v3Client, vmUUID)
dkoshkin marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
errorMsg := fmt.Errorf("error finding VM %s with UUID %s: %v", vmName, vmUUID, err)
log.Error(errorMsg, "error finding VM")
conditions.MarkFalse(rctx.NutanixMachine, infrav1.VMProvisionedCondition, infrav1.DeletionFailed, capiv1.ConditionSeverityWarning, errorMsg.Error())
return reconcile.Result{}, errorMsg

Check warning on line 328 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L323-L328

Added lines #L323 - L328 were not covered by tests
}

if vm == nil {
log.Info(fmt.Sprintf("no VM found with UUID %s: assuming it is already deleted; skipping delete", vmUUID))
log.Info(fmt.Sprintf("removing finalizers for VM %s during delete reconciliation", vmName))
ctrlutil.RemoveFinalizer(rctx.NutanixMachine, infrav1.NutanixMachineFinalizer)
return reconcile.Result{}, nil

Check warning on line 335 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L331-L335

Added lines #L331 - L335 were not covered by tests
}

// Check if the VM name matches the Machine name or the NutanixMachine name.
// Earlier, we were creating VMs with the same name as the NutanixMachine name.
// Now, we create VMs with the same name as the Machine name in line with other CAPI providers.
// This check is to ensure that we are deleting the correct VM for both cases as older CAPX VMs
// will have the NutanixMachine name as the VM name.
if *vm.Spec.Name != vmName && *vm.Spec.Name != rctx.NutanixMachine.Name {
return reconcile.Result{}, fmt.Errorf("found VM with UUID %s but name %s did not match Machine name %s or NutanixMachineName %s", vmUUID, *vm.Spec.Name, vmName, rctx.NutanixMachine.Name)

Check warning on line 344 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L343-L344

Added lines #L343 - L344 were not covered by tests
}

log.V(1).Info(fmt.Sprintf("VM %s with UUID %s was found.", *vm.Spec.Name, vmUUID))
lastTaskUUID, err := GetTaskUUIDFromVM(vm)
if err != nil {
errorMsg := fmt.Errorf("error occurred fetching task UUID from VM: %v", err)
log.Error(errorMsg, "error fetching task UUID")
conditions.MarkFalse(rctx.NutanixMachine, infrav1.VMProvisionedCondition, infrav1.DeletionFailed, capiv1.ConditionSeverityWarning, errorMsg.Error())
return reconcile.Result{}, errorMsg

Check warning on line 353 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L347-L353

Added lines #L347 - L353 were not covered by tests
}

if lastTaskUUID != "" {
log.Info(fmt.Sprintf("checking if VM %s with UUID %s has in progress tasks", vmName, vmUUID))
taskInProgress, err := HasTaskInProgress(ctx, rctx.NutanixClient, lastTaskUUID)

Check warning on line 358 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L356-L358

Added lines #L356 - L358 were not covered by tests
if err != nil {
errorMsg := fmt.Errorf("error finding vm %s with uuid %s: %v", vmName, vmUUID, err)
log.Error(errorMsg, "error finding vm")
conditions.MarkFalse(rctx.NutanixMachine, infrav1.VMProvisionedCondition, infrav1.DeletionFailed, capiv1.ConditionSeverityWarning, errorMsg.Error())
return reconcile.Result{}, errorMsg
log.Error(err, fmt.Sprintf("error occurred while checking task %s for VM %s. Trying to delete VM", lastTaskUUID, vmName))

Check warning on line 360 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L360

Added line #L360 was not covered by tests
}
// Vm not found
if vm == nil {
log.V(1).Info(fmt.Sprintf("no vm found with UUID %s ... Already deleted? Skipping delete", vmUUID))
} else {
// Check if the VM name matches the Machine name or the NutanixMachine name.
// Earlier, we were creating VMs with the same name as the NutanixMachine name.
// Now, we create VMs with the same name as the Machine name in line with other CAPI providers.
// This check is to ensure that we are deleting the correct VM for both cases as older CAPX VMs
// will have the NutanixMachine name as the VM name.
if *vm.Spec.Name != vmName && *vm.Spec.Name != rctx.NutanixMachine.Name {
return reconcile.Result{}, fmt.Errorf("found VM with UUID %s but name %s did not match Machine name %s or NutanixMachineName %s", vmUUID, *vm.Spec.Name, vmName, rctx.NutanixMachine.Name)
}
log.V(1).Info(fmt.Sprintf("VM %s with UUID %s was found.", *vm.Spec.Name, vmUUID))
lastTaskUUID, err := GetTaskUUIDFromVM(vm)
if err != nil {
errorMsg := fmt.Errorf("error occurred fetching task UUID from vm: %v", err)
log.Error(errorMsg, "error fetching task UUID")
conditions.MarkFalse(rctx.NutanixMachine, infrav1.VMProvisionedCondition, infrav1.DeletionFailed, capiv1.ConditionSeverityWarning, errorMsg.Error())
return reconcile.Result{}, errorMsg
}
if lastTaskUUID != "" {
log.Info(fmt.Sprintf("checking if VM %s with UUID %s has in progress tasks", vmName, vmUUID))
taskInProgress, err := HasTaskInProgress(ctx, rctx.NutanixClient, lastTaskUUID)
if err != nil {
log.Error(err, fmt.Sprintf("error occurred while checking task %s for VM %s. Trying to delete VM", lastTaskUUID, vmName))
}
if taskInProgress {
log.Info(fmt.Sprintf("VM %s task with UUID %s still in progress. Requeuing", vmName, vmUUID))
return reconcile.Result{RequeueAfter: 5 * time.Second}, nil
}
log.V(1).Info(fmt.Sprintf("No running tasks anymore... Initiating delete for vm %s with UUID %s", vmName, vmUUID))
} else {
log.V(1).Info(fmt.Sprintf("no task UUID found on VM %s. Starting delete.", *vm.Spec.Name))
}
if taskInProgress {
log.Info(fmt.Sprintf("VM %s task with UUID %s still in progress. Requeuing", vmName, vmUUID))
return reconcile.Result{RequeueAfter: 5 * time.Second}, nil

Check warning on line 364 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L362-L364

Added lines #L362 - L364 were not covered by tests
}
log.V(1).Info(fmt.Sprintf("no running tasks anymore... Initiating delete for VM %s with UUID %s", vmName, vmUUID))

Check warning on line 366 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L366

Added line #L366 was not covered by tests
} else {
log.V(1).Info(fmt.Sprintf("no task UUID found on VM %s. Starting delete.", *vm.Spec.Name))

Check warning on line 368 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L368

Added line #L368 was not covered by tests
}

if rctx.NutanixClientV4 != nil {
if err := detachVolumeGroupsFromVM(ctx, rctx.NutanixClientV4, vmUUID); err != nil {
errorMsg := fmt.Errorf("failed to detach volume groups from VM %s with UUID %s: %v", vmName, vmUUID, err)
conditions.MarkFalse(rctx.NutanixMachine, infrav1.VMProvisionedCondition, infrav1.VolumeGroupDetachFailed, capiv1.ConditionSeverityWarning, errorMsg.Error())
log.Error(errorMsg, "failed to detach volume groups", "cluster", rctx.NutanixCluster.Name)
return reconcile.Result{}, err
}
var vgDetachNeeded bool
if vm.Spec.Resources != nil && vm.Spec.Resources.DiskList != nil {
for _, disk := range vm.Spec.Resources.DiskList {
if disk.VolumeGroupReference != nil {
vgDetachNeeded = true
break

Check warning on line 376 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L371-L376

Added lines #L371 - L376 were not covered by tests
}
}
}

// Delete the VM since the VM was found (err was nil)
deleteTaskUUID, err := DeleteVM(ctx, v3Client, vmName, vmUUID)
if err != nil {
errorMsg := fmt.Errorf("failed to delete VM %s with UUID %s: %v", vmName, vmUUID, err)
conditions.MarkFalse(rctx.NutanixMachine, infrav1.VMProvisionedCondition, infrav1.DeletionFailed, capiv1.ConditionSeverityWarning, errorMsg.Error())
log.Error(errorMsg, "failed to delete VM")
return reconcile.Result{}, err
}
log.Info(fmt.Sprintf("Deletion task with UUID %s received for vm %s with UUID %s. Requeueing", deleteTaskUUID, vmName, vmUUID))
return reconcile.Result{RequeueAfter: 5 * time.Second}, nil
if vgDetachNeeded {
if err := r.detachVolumeGroups(rctx, vmUUID); err != nil {
err := fmt.Errorf("failed to detach volume groups from VM %s with UUID %s: %v", vmName, vmUUID, err)
log.Error(err, "failed to detach volume groups from VM")
conditions.MarkFalse(rctx.NutanixMachine, infrav1.VMProvisionedCondition, infrav1.VolumeGroupDetachFailed, capiv1.ConditionSeverityWarning, err.Error())

Check warning on line 385 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L381-L385

Added lines #L381 - L385 were not covered by tests

return reconcile.Result{}, err

Check warning on line 387 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L387

Added line #L387 was not covered by tests
}
}

// Remove the finalizer from the NutanixMachine object
log.Info(fmt.Sprintf("Removing finalizers for VM %s during delete reconciliation", vmName))
ctrlutil.RemoveFinalizer(rctx.NutanixMachine, infrav1.NutanixMachineFinalizer)
// Delete the VM since the VM was found (err was nil)
deleteTaskUUID, err := DeleteVM(ctx, v3Client, vmName, vmUUID)
if err != nil {
err := fmt.Errorf("failed to delete VM %s with UUID %s: %v", vmName, vmUUID, err)
log.Error(err, "failed to delete VM")
conditions.MarkFalse(rctx.NutanixMachine, infrav1.VMProvisionedCondition, infrav1.DeletionFailed, capiv1.ConditionSeverityWarning, err.Error())

Check warning on line 396 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L392-L396

Added lines #L392 - L396 were not covered by tests

return reconcile.Result{}, nil
return reconcile.Result{}, err

Check warning on line 398 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L398

Added line #L398 was not covered by tests
}
log.Info(fmt.Sprintf("Deletion task with UUID %s received for vm %s with UUID %s. Requeueing", deleteTaskUUID, vmName, vmUUID))
return reconcile.Result{RequeueAfter: 5 * time.Second}, nil

Check warning on line 401 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L400-L401

Added lines #L400 - L401 were not covered by tests
}

func (r *NutanixMachineReconciler) detachVolumeGroups(rctx *nctx.MachineContext, vmUUID string) error {
createV4Client, err := isPrismCentralV4Compatible(rctx.Context, rctx.NutanixClient)
if err != nil {
return fmt.Errorf("error occurred while checking compatibility for Prism Central v4 APIs: %w", err)
}

if !createV4Client {
return nil
}

v4Client, err := getPrismCentralV4ClientForCluster(rctx.Context, rctx.NutanixCluster, r.SecretInformer, r.ConfigMapInformer)
if err != nil {
return fmt.Errorf("error occurred while fetching Prism Central v4 client: %w", err)

Check warning on line 416 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L414-L416

Added lines #L414 - L416 were not covered by tests
}

if err := detachVolumeGroupsFromVM(rctx.Context, v4Client, vmUUID); err != nil {
return fmt.Errorf("failed to detach volume groups from VM %s with UUID %s: %w", rctx.Machine.Name, vmUUID, err)

Check warning on line 420 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L419-L420

Added lines #L419 - L420 were not covered by tests
}

return nil

Check warning on line 423 in controllers/nutanixmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/nutanixmachine_controller.go#L423

Added line #L423 was not covered by tests
}

func (r *NutanixMachineReconciler) reconcileNormal(rctx *nctx.MachineContext) (reconcile.Result, error) {
Expand Down
85 changes: 85 additions & 0 deletions controllers/nutanixmachine_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ import (

"github.com/golang/mock/gomock"
credentialTypes "github.com/nutanix-cloud-native/prism-go-client/environment/credentials"
v3 "github.com/nutanix-cloud-native/prism-go-client/v3"
"github.com/nutanix-cloud-native/prism-go-client/v3/models"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/utils/ptr"
capiv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/util"
ctrl "sigs.k8s.io/controller-runtime"
Expand All @@ -39,6 +42,7 @@ import (
infrav1 "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1"
mockctlclient "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/mocks/ctlclient"
mockmeta "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/mocks/k8sapimachinery"
mocknutanixv3 "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/mocks/nutanix"
nctx "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/pkg/context"
)

Expand Down Expand Up @@ -108,6 +112,7 @@ func TestNutanixMachineReconciler(t *testing.T) {
g.Expect(result.Requeue).To(BeFalse())
})
})

Context("Validates machine config", func() {
It("should error if no failure domain is present on machine and no subnets are passed", func() {
err := reconciler.validateMachineConfig(&nctx.MachineContext{
Expand Down Expand Up @@ -234,6 +239,86 @@ func TestNutanixMachineReconciler(t *testing.T) {
g.Expect(err).To(HaveOccurred())
})
})

Context("Detaches a volume group on deletion", func() {
It("should error if get prism central returns error", func() {
mockctrl := gomock.NewController(t)
mockv3Service := mocknutanixv3.NewMockService(mockctrl)
mockv3Service.EXPECT().GetPrismCentral(gomock.Any()).Return(nil, errors.New("error"))

v3Client := &v3.Client{
V3: mockv3Service,
}
err := reconciler.detachVolumeGroups(&nctx.MachineContext{
NutanixClient: v3Client,
}, ntnxMachine.Status.VmUUID)
g.Expect(err).To(HaveOccurred())
})

It("should error if prism central version is empty", func() {
mockctrl := gomock.NewController(t)
mockv3Service := mocknutanixv3.NewMockService(mockctrl)
mockv3Service.EXPECT().GetPrismCentral(gomock.Any()).Return(&models.PrismCentral{Resources: &models.PrismCentralResources{
Version: ptr.To(""),
}}, nil)

v3Client := &v3.Client{
V3: mockv3Service,
}
err := reconciler.detachVolumeGroups(&nctx.MachineContext{
NutanixClient: v3Client,
}, ntnxMachine.Status.VmUUID)
g.Expect(err).To(HaveOccurred())
})

It("should error if prism central version value is absent", func() {
mockctrl := gomock.NewController(t)
mockv3Service := mocknutanixv3.NewMockService(mockctrl)
mockv3Service.EXPECT().GetPrismCentral(gomock.Any()).Return(&models.PrismCentral{Resources: &models.PrismCentralResources{
Version: ptr.To("pc."),
}}, nil)

v3Client := &v3.Client{
V3: mockv3Service,
}
err := reconciler.detachVolumeGroups(&nctx.MachineContext{
NutanixClient: v3Client,
}, ntnxMachine.Status.VmUUID)
g.Expect(err).To(HaveOccurred())
})

It("should error if prism central version is invalid", func() {
mockctrl := gomock.NewController(t)
mockv3Service := mocknutanixv3.NewMockService(mockctrl)
mockv3Service.EXPECT().GetPrismCentral(gomock.Any()).Return(&models.PrismCentral{Resources: &models.PrismCentralResources{
Version: ptr.To("not.a.valid.version"),
}}, nil)

v3Client := &v3.Client{
V3: mockv3Service,
}
err := reconciler.detachVolumeGroups(&nctx.MachineContext{
NutanixClient: v3Client,
}, ntnxMachine.Status.VmUUID)
g.Expect(err).To(HaveOccurred())
})

It("should not error if prism central is not v4 compatible", func() {
mockctrl := gomock.NewController(t)
mockv3Service := mocknutanixv3.NewMockService(mockctrl)
mockv3Service.EXPECT().GetPrismCentral(gomock.Any()).Return(&models.PrismCentral{Resources: &models.PrismCentralResources{
Version: ptr.To("pc.2023.4.0.1"),
}}, nil)

v3Client := &v3.Client{
V3: mockv3Service,
}
err := reconciler.detachVolumeGroups(&nctx.MachineContext{
NutanixClient: v3Client,
}, ntnxMachine.Status.VmUUID)
g.Expect(err).To(Not(HaveOccurred()))
})
})
})
}

Expand Down
Loading
Loading