diff --git a/api/v1alpha1/condition_consts.go b/api/v1alpha1/condition_consts.go index bbf96b8..508e0e9 100644 --- a/api/v1alpha1/condition_consts.go +++ b/api/v1alpha1/condition_consts.go @@ -51,4 +51,10 @@ const ( // MicrovmDeploymentProvisionFailedReason indicates that the microvm deployment failed to provision. MicrovmDeploymentProvisionFailedReason = "MicrovmDeploymentProvisionFailed" + + // MicrovmDeploymentUpdatingReason indicates the microvm deployment is in a pending state. + MicrovmDeploymentUpdatingReason = "MicrovmDeploymentUpdating" + + // MicrovmDeploymentUpdateFailed indicates the microvm deployment is in a pending state. + MicrovmDeploymentUpdateFailedReason = "MicrovmDeploymentUpdateFailed" ) diff --git a/controllers/helpers_test.go b/controllers/helpers_test.go index 68af332..00c681d 100644 --- a/controllers/helpers_test.go +++ b/controllers/helpers_test.go @@ -114,6 +114,18 @@ func reconcileMicrovmDeployment(client client.Client) (ctrl.Result, error) { return mvmDepController.Reconcile(context.TODO(), request) } +func reconcileMicrovmDeploymentNTimes(g *WithT, client client.Client, count int, r, rr int32) error { + for count > 0 { + ensureMicrovmReplicaSetState(g, client, r, rr) + if _, err := reconcileMicrovmDeployment(client); err != nil { + return err + } + count-- + } + + return nil +} + func getMicrovm(c client.Client, name, namespace string) (*infrav1.Microvm, error) { key := client.ObjectKey{ Name: name, diff --git a/controllers/microvmdeployment_controller.go b/controllers/microvmdeployment_controller.go index 828416b..f7b5590 100644 --- a/controllers/microvmdeployment_controller.go +++ b/controllers/microvmdeployment_controller.go @@ -170,6 +170,25 @@ func (r *MicrovmDeploymentReconciler) reconcileNormal( } mvmDeploymentScope.SetNotReady(infrav1.MicrovmDeploymentIncompleteReason, "Info", "") + // if we are here then a scale down has been requested. + // we delete the first found until the numbers balance out. + // TODO the way this works is very naive and often ends up deleting everything + // if the timing is wrong/right, find a better way https://github.com/weaveworks-liquidmetal/microvm-operator/issues/17 + case createdSets > mvmDeploymentScope.RequiredSets(): + mvmDeploymentScope.Info("MicrovmDeployment updating: delete microvmreplicaset") + mvmDeploymentScope.SetNotReady(infrav1.MicrovmDeploymentUpdatingReason, "Info", "") + + rs := rsList[0] + if !rs.DeletionTimestamp.IsZero() { + return ctrl.Result{}, nil + } + + if err := r.Delete(ctx, &rs); err != nil { + mvmDeploymentScope.Error(err, "failed deleting microvmreplicaset") + mvmDeploymentScope.SetNotReady(infrav1.MicrovmDeploymentUpdateFailedReason, "Error", "") + + return ctrl.Result{}, err + } // if all desired objects have been created, but are not quite ready yet, // set the condition and requeue default: diff --git a/controllers/microvmdeployment_controller_test.go b/controllers/microvmdeployment_controller_test.go index 0743e94..0d104d5 100644 --- a/controllers/microvmdeployment_controller_test.go +++ b/controllers/microvmdeployment_controller_test.go @@ -1,9 +1,11 @@ package controllers_test import ( + "context" "testing" . "github.com/onsi/gomega" + "github.com/weaveworks-liquidmetal/controller-pkg/types/microvm" infrav1 "github.com/weaveworks-liquidmetal/microvm-operator/api/v1alpha1" "k8s.io/apimachinery/pkg/runtime" ) @@ -79,3 +81,64 @@ func TestMicrovmDep_ReconcileNormal_CreateSucceeds(t *testing.T) { g.Expect(microvmReplicaSetsCreated(g, client)).To(Equal(expectedReplicaSets), "Expected all Microvms to have been created after two reconciliations") assertOneSetPerHost(g, reconciled, client) } + +func TestMicrovmDep_ReconcileNormal_UpdateSucceeds(t *testing.T) { + g := NewWithT(t) + + // updating a replicaset with 2 replicas + var ( + initialReplicaSetCount int = 2 + scaledReplicaSetCount int32 = 1 + expectedReplicas int32 = 2 + initialReplicaCount int32 = 4 + scaledReplicaCount int32 = 2 + ) + + mvmD := createMicrovmDeployment(expectedReplicas, initialReplicaSetCount) + objects := []runtime.Object{mvmD} + client := createFakeClient(g, objects) + + // create + g.Expect(reconcileMicrovmDeploymentNTimes(g, client, initialReplicaSetCount+4, expectedReplicas, expectedReplicas)).To(Succeed()) + + reconciled, err := getMicrovmDeployment(client, testMicrovmDeploymentName, testNamespace) + g.Expect(err).NotTo(HaveOccurred(), "Getting microvm should not fail") + + assertMDFinalizer(g, reconciled) + assertConditionTrue(g, reconciled, infrav1.MicrovmDeploymentReadyCondition) + g.Expect(reconciled.Status.Ready).To(BeTrue(), "MicrovmDeployment should be ready now") + g.Expect(reconciled.Status.Replicas).To(Equal(initialReplicaCount), "Expected the record to contain 4 replicas") + g.Expect(reconciled.Status.ReadyReplicas).To(Equal(initialReplicaCount), "Expected all replicas to be ready") + g.Expect(microvmReplicaSetsCreated(g, client)).To(Equal(initialReplicaSetCount), "Expected 2 replicasets to exist") + + // update, scale down to 1 + reconciled.Spec.Hosts = []microvm.Host{{Endpoint: "1.2.3.4:9090"}} + g.Expect(client.Update(context.TODO(), reconciled)).To(Succeed()) + + // first reconciliation + result, err := reconcileMicrovmDeployment(client) + g.Expect(err).NotTo(HaveOccurred(), "Reconciling microvmdeployment the first time should not error") + g.Expect(result.IsZero()).To(BeFalse(), "Expect requeue to be requested after update") + + reconciled, err = getMicrovmDeployment(client, testMicrovmDeploymentName, testNamespace) + g.Expect(err).NotTo(HaveOccurred(), "Getting microvmdeployment should not fail") + + assertConditionFalse(g, reconciled, infrav1.MicrovmDeploymentReadyCondition, infrav1.MicrovmDeploymentUpdatingReason) + g.Expect(reconciled.Status.Ready).To(BeFalse(), "MicrovmDeployment should not be ready") + g.Expect(reconciled.Status.Replicas).To(Equal(initialReplicaCount), "Expected the record to contain 4 replicas") + g.Expect(reconciled.Status.ReadyReplicas).To(Equal(initialReplicaCount), "Expected all replicas to be ready") + + // second reconciliation + result, err = reconcileMicrovmDeployment(client) + g.Expect(err).NotTo(HaveOccurred(), "Reconciling microvmdeployment the second time should not error") + g.Expect(result.IsZero()).To(BeTrue(), "Expect requeue to not be requested after reconcile") + + reconciled, err = getMicrovmDeployment(client, testMicrovmDeploymentName, testNamespace) + g.Expect(err).NotTo(HaveOccurred(), "Getting microvmdeployment should not fail") + + assertConditionTrue(g, reconciled, infrav1.MicrovmDeploymentReadyCondition) + g.Expect(reconciled.Status.Ready).To(BeTrue(), "MicrovmDeployment should be ready again") + g.Expect(reconciled.Status.Replicas).To(Equal(scaledReplicaCount), "Expected the record to contain 2 replicas") + g.Expect(reconciled.Status.ReadyReplicas).To(Equal(scaledReplicaCount), "Expected all replicas to be ready") + g.Expect(microvmReplicaSetsCreated(g, client)).To(Equal(int(scaledReplicaSetCount)), "Expected replicasets to have been scaled down after two reconciliations") +}