Skip to content

Commit cbc4042

Browse files
authored
feat: Delete machine (#40)
* feat: Microvm deletion * Remove finalizers from Cluster We do not currently create any supporting infrastructure for our machines. We can add this back if we need it.
1 parent 90f8216 commit cbc4042

File tree

8 files changed

+184
-35
lines changed

8 files changed

+184
-35
lines changed

api/v1alpha1/condition_consts.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ const (
3030
// MicrovmPendingReason indicates the microvm is in a pending state.
3131
MicrovmPendingReason = "MicrovmPending"
3232

33+
// MicrovmDeletingReason indicates the microvm is in a deleted state.
34+
MicrovmDeletingReason = "MicrovmDeleting"
35+
36+
// MicrovmDeletedFailedReason indicates the microvm failed to deleted cleanly.
37+
MicrovmDeleteFailedReason = "MicrovmDeleteFailed"
38+
3339
// MicrovmUnknownStateReason indicates that the microvm in in an unknown or unsupported state
3440
// for reconciliation.
3541
MicrovmUnknownStateReason = "MicrovmUnknownState"

api/v1alpha1/microvmcluster_types.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,6 @@ import (
88
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
99
)
1010

11-
const (
12-
// ClusterFinalizer allows ReconcileMicrovmCluster to clean up esources associated with MicrovmCluster before
13-
// removing it from the apiserver.
14-
ClusterFinalizer = "microvmcluster.infrastructure.cluster.x-k8s.io"
15-
)
16-
1711
// MicrovmClusterSpec defines the desired state of MicrovmCluster.
1812
type MicrovmClusterSpec struct {
1913
// ControlPlaneEndpoint represents the endpoint used to communicate with the control plane.

controllers/helpers_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,3 +356,7 @@ func hasMachineFinalizer(machine *infrav1.MicrovmMachine) bool {
356356

357357
return false
358358
}
359+
360+
func assertMachineNotReady(g *WithT, machine *infrav1.MicrovmMachine) {
361+
g.Expect(machine.Status.Ready).To(BeFalse())
362+
}

controllers/microvmcluster_controller.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"sigs.k8s.io/controller-runtime/pkg/builder"
2525
"sigs.k8s.io/controller-runtime/pkg/client"
2626
"sigs.k8s.io/controller-runtime/pkg/controller"
27-
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
2827
"sigs.k8s.io/controller-runtime/pkg/handler"
2928
"sigs.k8s.io/controller-runtime/pkg/log"
3029
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -120,9 +119,7 @@ func (r *MicrovmClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque
120119
func (r *MicrovmClusterReconciler) reconcileDelete(_ context.Context, clusterScope *scope.ClusterScope) (reconcile.Result, error) {
121120
clusterScope.Info("Reconciling MicrovmCluster delete")
122121

123-
// TODO: do any required deletion
124-
125-
controllerutil.RemoveFinalizer(clusterScope.MvmCluster, infrav1.ClusterFinalizer)
122+
// We currently do not do any Cluster creation so there is nothing to delete.
126123

127124
return reconcile.Result{}, nil
128125
}
@@ -134,8 +131,6 @@ func (r *MicrovmClusterReconciler) reconcileNormal(ctx context.Context, clusterS
134131
return reconcile.Result{}, errControlplaneEndpointRequired
135132
}
136133

137-
controllerutil.AddFinalizer(clusterScope.MvmCluster, infrav1.ClusterFinalizer)
138-
139134
clusterScope.MvmCluster.Status.Ready = true
140135

141136
available := r.isAPIServerAvailable(ctx, clusterScope)

controllers/microvmcluster_controller_test.go

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ func TestClusterReconciliationNoEndpoint(t *testing.T) {
4242

4343
c := conditions.Get(reconciled, infrav1.LoadBalancerAvailableCondition)
4444
g.Expect(c).To(BeNil())
45-
46-
g.Expect(reconciled.Finalizers).To(HaveLen(0))
4745
}
4846

4947
func TestClusterReconciliationWithClusterEndpoint(t *testing.T) {
@@ -89,8 +87,6 @@ func TestClusterReconciliationWithClusterEndpoint(t *testing.T) {
8987
c = conditions.Get(reconciled, clusterv1.ReadyCondition)
9088
g.Expect(c).ToNot(BeNil())
9189
g.Expect(c.Status).To(Equal(corev1.ConditionTrue))
92-
93-
g.Expect(reconciled.Finalizers).To(HaveLen(1))
9490
}
9591

9692
func TestClusterReconciliationWithMvmClusterEndpoint(t *testing.T) {
@@ -136,8 +132,6 @@ func TestClusterReconciliationWithMvmClusterEndpoint(t *testing.T) {
136132
c = conditions.Get(reconciled, clusterv1.ReadyCondition)
137133
g.Expect(c).ToNot(BeNil())
138134
g.Expect(c.Status).To(Equal(corev1.ConditionTrue))
139-
140-
g.Expect(reconciled.Finalizers).To(HaveLen(1))
141135
}
142136

143137
func TestClusterReconciliationWithClusterEndpointAPIServerNotReady(t *testing.T) {
@@ -177,8 +171,6 @@ func TestClusterReconciliationWithClusterEndpointAPIServerNotReady(t *testing.T)
177171
c = conditions.Get(reconciled, clusterv1.ReadyCondition)
178172
g.Expect(c).ToNot(BeNil())
179173
g.Expect(c.Status).To(Equal(corev1.ConditionFalse))
180-
181-
g.Expect(reconciled.Finalizers).To(HaveLen(1))
182174
}
183175

184176
func TestClusterReconciliationMicrovmAlreadyDeleted(t *testing.T) {
@@ -221,8 +213,6 @@ func TestClusterReconciliationNotOwner(t *testing.T) {
221213

222214
c := conditions.Get(reconciled, infrav1.LoadBalancerAvailableCondition)
223215
g.Expect(c).To(BeNil())
224-
225-
g.Expect(reconciled.Finalizers).To(HaveLen(0))
226216
}
227217

228218
func TestClusterReconciliationWhenPaused(t *testing.T) {
@@ -251,8 +241,6 @@ func TestClusterReconciliationWhenPaused(t *testing.T) {
251241

252242
c := conditions.Get(reconciled, infrav1.LoadBalancerAvailableCondition)
253243
g.Expect(c).To(BeNil())
254-
255-
g.Expect(reconciled.Finalizers).To(HaveLen(0))
256244
}
257245

258246
func TestClusterReconciliationDelete(t *testing.T) {
@@ -276,7 +264,6 @@ func TestClusterReconciliationDelete(t *testing.T) {
276264
g.Expect(result.RequeueAfter).To(Equal(time.Duration(0)))
277265

278266
// TODO: when we move to envtest this should return an NotFound error. #30
279-
reconciled, err := getMicrovmCluster(context.TODO(), client, testClusterName, testClusterNamespace)
267+
_, err = getMicrovmCluster(context.TODO(), client, testClusterName, testClusterNamespace)
280268
g.Expect(err).NotTo(HaveOccurred())
281-
g.Expect(reconciled.Finalizers).To(HaveLen(0))
282269
}

controllers/microvmmachine_controller.go

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,52 @@ func (r *MicrovmMachineReconciler) Reconcile(ctx context.Context, req ctrl.Reque
145145
return r.reconcileNormal(ctx, machineScope)
146146
}
147147

148-
func (r *MicrovmMachineReconciler) reconcileDelete(_ context.Context, machineScope *scope.MachineScope) (reconcile.Result, error) {
148+
func (r *MicrovmMachineReconciler) reconcileDelete(ctx context.Context, machineScope *scope.MachineScope) (reconcile.Result, error) {
149149
machineScope.Info("Reconciling MicrovmMachine delete")
150150

151-
// TODO: call flintlock to delete
151+
mvmSvc, err := r.getMicrovmService(machineScope)
152+
if err != nil {
153+
machineScope.Error(err, "failed to get microvm service")
154+
155+
return ctrl.Result{}, nil
156+
}
157+
158+
microvm, err := mvmSvc.Get(ctx)
159+
if err != nil && !isSpecNotFound(err) {
160+
machineScope.Error(err, "failed getting microvm")
161+
162+
return ctrl.Result{}, err
163+
}
164+
165+
if microvm != nil {
166+
machineScope.Info("deleting microvm")
167+
168+
// Mark the machine as no longer ready before we delete
169+
machineScope.SetNotReady(infrav1.MicrovmDeletingReason, clusterv1.ConditionSeverityInfo, "")
170+
if err := machineScope.Patch(); err != nil {
171+
machineScope.Error(err, "failed to patch object")
172+
173+
return ctrl.Result{}, err
174+
}
175+
176+
// TODO: we should check the state returned from the above Get and only
177+
// call Delete if not "deleting". Flintlock #310
178+
if _, err := mvmSvc.Delete(ctx); err != nil {
179+
machineScope.SetNotReady(infrav1.MicrovmDeleteFailedReason, clusterv1.ConditionSeverityError, "")
180+
181+
return ctrl.Result{}, err
182+
}
183+
184+
return ctrl.Result{RequeueAfter: requeuePeriod}, nil
185+
}
152186

187+
// By this point Flintlock has no record of the MvM, so we are good to clear
188+
// the finalizer
153189
controllerutil.RemoveFinalizer(machineScope.MvmMachine, infrav1.MachineFinalizer)
154190

155-
return reconcile.Result{}, nil
191+
machineScope.Info("microvm deleted")
192+
193+
return ctrl.Result{}, nil
156194
}
157195

158196
func (r *MicrovmMachineReconciler) reconcileNormal(ctx context.Context, machineScope *scope.MachineScope) (reconcile.Result, error) {
@@ -180,12 +218,10 @@ func (r *MicrovmMachineReconciler) reconcileNormal(ctx context.Context, machineS
180218
}
181219

182220
microvm, err := mvmSvc.Get(ctx)
183-
if err != nil {
184-
if !isSpecNotFound(err) {
185-
machineScope.Error(err, "failed checking if microvm exists")
221+
if err != nil && !isSpecNotFound(err) {
222+
machineScope.Error(err, "failed checking if microvm exists")
186223

187-
return ctrl.Result{}, err
188-
}
224+
return ctrl.Result{}, err
189225
}
190226

191227
controllerutil.AddFinalizer(machineScope.MvmMachine, infrav1.MachineFinalizer)
@@ -226,6 +262,7 @@ func (r *MicrovmMachineReconciler) reconcileNormal(ctx context.Context, machineS
226262
return ctrl.Result{RequeueAfter: requeuePeriod}, errMicrovmUnknownState
227263
}
228264

265+
machineScope.Info("microvm created")
229266
machineScope.MvmMachine.Spec.ProviderID = &microvm.Spec.Id
230267
machineScope.SetReady()
231268

controllers/microvmmachine_controller_test.go

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,20 @@ import (
77
"encoding/base64"
88
"errors"
99
"testing"
10+
"time"
1011

1112
. "github.com/onsi/gomega"
1213

14+
apierrors "k8s.io/apimachinery/pkg/api/errors"
15+
1316
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1417

1518
flintlocktypes "github.com/weaveworks/flintlock/api/types"
1619

20+
"github.com/weaveworks/cluster-api-provider-microvm/api/v1alpha1"
1721
infrav1 "github.com/weaveworks/cluster-api-provider-microvm/api/v1alpha1"
1822
"github.com/weaveworks/cluster-api-provider-microvm/internal/services/microvm/mock_client"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1924
)
2025

2126
func TestMachineReconcile(t *testing.T) {
@@ -57,6 +62,15 @@ func TestMachineReconcile(t *testing.T) {
5762
t.Run("and create microvm succeeds", machineReconcileNoVmCreateSucceeds)
5863
t.Run("and create microvm succeeds and reconciles again", machineReconcileNoVmCreateAdditionReconcile)
5964
})
65+
66+
t.Run("microvm_has_deletion_timestamp", func(t *testing.T) {
67+
t.Parallel()
68+
69+
t.Run("and delete microvm succeeds", machineReconcileDeleteVmSucceeds)
70+
t.Run("microvm get returns nil", machineReconcileDeleteGetReturnsNil)
71+
t.Run("microvm get returns error", machineReconcileDeleteGetErrors)
72+
t.Run("microvm delete returns error", machineReconcileDeleteDeleteErrors)
73+
})
6074
}
6175

6276
func machineReconcileMissingMvmMachine(t *testing.T) {
@@ -337,9 +351,109 @@ func machineReconcileNoVmCreateAdditionReconcile(t *testing.T) {
337351
g.Expect(result.IsZero()).To(BeFalse(), "Expect requeue to be requested after create")
338352

339353
withExistingMicrovm(&fakeAPIClient, flintlocktypes.MicroVMStatus_CREATED)
340-
result, err = reconcileMachine(client, &fakeAPIClient)
354+
_, err = reconcileMachine(client, &fakeAPIClient)
355+
g.Expect(err).NotTo(HaveOccurred(), "Reconciling should not return an error")
341356

342357
reconciled, err := getMicrovmMachine(client, testMachineName, testClusterNamespace)
343358
g.Expect(err).NotTo(HaveOccurred(), "Getting microvm machine should not fail")
344359
assertMachineReconciled(g, reconciled)
345360
}
361+
362+
func machineReconcileDeleteVmSucceeds(t *testing.T) {
363+
t.Parallel()
364+
g := NewWithT(t)
365+
366+
apiObjects := defaultClusterObjects()
367+
apiObjects.MvmMachine.DeletionTimestamp = &metav1.Time{
368+
Time: time.Now(),
369+
}
370+
apiObjects.MvmMachine.Finalizers = []string{v1alpha1.MachineFinalizer}
371+
372+
fakeAPIClient := mock_client.FakeClient{}
373+
withExistingMicrovm(&fakeAPIClient, flintlocktypes.MicroVMStatus_CREATED)
374+
375+
client := createFakeClient(g, apiObjects.AsRuntimeObjects())
376+
377+
result, err := reconcileMachine(client, &fakeAPIClient)
378+
g.Expect(err).NotTo(HaveOccurred(), "Reconciling when deleting microvm should not return error")
379+
g.Expect(result.Requeue).To(BeFalse())
380+
g.Expect(result.RequeueAfter).To(BeNumerically(">", time.Duration(0)))
381+
382+
g.Expect(fakeAPIClient.DeleteMicroVMCallCount()).To(Equal(1))
383+
_, deleteReq, _ := fakeAPIClient.DeleteMicroVMArgsForCall(0)
384+
g.Expect(deleteReq.Id).To(Equal(testMachineName))
385+
g.Expect(deleteReq.Namespace).To(Equal(testClusterNamespace))
386+
387+
_, err = getMicrovmMachine(client, testMachineName, testClusterNamespace)
388+
g.Expect(apierrors.IsNotFound(err)).To(BeFalse())
389+
}
390+
391+
func machineReconcileDeleteGetReturnsNil(t *testing.T) {
392+
t.Parallel()
393+
g := NewWithT(t)
394+
395+
apiObjects := defaultClusterObjects()
396+
apiObjects.MvmMachine.DeletionTimestamp = &metav1.Time{
397+
Time: time.Now(),
398+
}
399+
apiObjects.MvmMachine.Finalizers = []string{v1alpha1.MachineFinalizer}
400+
401+
fakeAPIClient := mock_client.FakeClient{}
402+
withMissingMicrovm(&fakeAPIClient)
403+
404+
client := createFakeClient(g, apiObjects.AsRuntimeObjects())
405+
406+
result, err := reconcileMachine(client, &fakeAPIClient)
407+
g.Expect(err).NotTo(HaveOccurred(), "Reconciling when deleting microvm should not return error")
408+
g.Expect(result.Requeue).To(BeFalse())
409+
g.Expect(result.RequeueAfter).To(Equal(time.Duration(0)))
410+
411+
g.Expect(fakeAPIClient.DeleteMicroVMCallCount()).To(Equal(0))
412+
413+
_, err = getMicrovmMachine(client, testMachineName, testClusterNamespace)
414+
g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
415+
}
416+
417+
func machineReconcileDeleteGetErrors(t *testing.T) {
418+
t.Parallel()
419+
g := NewWithT(t)
420+
421+
apiObjects := defaultClusterObjects()
422+
apiObjects.MvmMachine.DeletionTimestamp = &metav1.Time{
423+
Time: time.Now(),
424+
}
425+
apiObjects.MvmMachine.Finalizers = []string{v1alpha1.MachineFinalizer}
426+
427+
fakeAPIClient := mock_client.FakeClient{}
428+
withExistingMicrovm(&fakeAPIClient, flintlocktypes.MicroVMStatus_CREATED)
429+
fakeAPIClient.GetMicroVMReturns(nil, errors.New("something terrible happened"))
430+
431+
client := createFakeClient(g, apiObjects.AsRuntimeObjects())
432+
_, err := reconcileMachine(client, &fakeAPIClient)
433+
g.Expect(err).To(HaveOccurred(), "Reconciling when microvm service exists errors should return error")
434+
}
435+
436+
func machineReconcileDeleteDeleteErrors(t *testing.T) {
437+
t.Parallel()
438+
g := NewWithT(t)
439+
440+
apiObjects := defaultClusterObjects()
441+
apiObjects.MvmMachine.DeletionTimestamp = &metav1.Time{
442+
Time: time.Now(),
443+
}
444+
apiObjects.MvmMachine.Finalizers = []string{v1alpha1.MachineFinalizer}
445+
446+
fakeAPIClient := mock_client.FakeClient{}
447+
withExistingMicrovm(&fakeAPIClient, flintlocktypes.MicroVMStatus_CREATED)
448+
fakeAPIClient.DeleteMicroVMReturns(nil, errors.New("something terrible happened"))
449+
450+
client := createFakeClient(g, apiObjects.AsRuntimeObjects())
451+
_, err := reconcileMachine(client, &fakeAPIClient)
452+
g.Expect(err).To(HaveOccurred(), "Reconciling when deleting microvm errors should return error")
453+
454+
reconciled, err := getMicrovmMachine(client, testMachineName, testClusterNamespace)
455+
g.Expect(err).NotTo(HaveOccurred(), "Getting microvm machine should not fail")
456+
457+
assertConditionFalse(g, reconciled, infrav1.MicrovmReadyCondition, infrav1.MicrovmDeleteFailedReason)
458+
assertMachineNotReady(g, reconciled)
459+
}

internal/services/microvm/service.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/weaveworks/cluster-api-provider-microvm/internal/defaults"
1212
"github.com/weaveworks/cluster-api-provider-microvm/internal/scope"
1313
"github.com/yitsushi/macpot"
14+
"google.golang.org/protobuf/types/known/emptypb"
1415
"k8s.io/utils/pointer"
1516

1617
flintlockv1 "github.com/weaveworks/flintlock/api/services/microvm/v1alpha1"
@@ -89,3 +90,14 @@ func (s *Service) Get(ctx context.Context) (*flintlocktypes.MicroVM, error) {
8990

9091
return resp.Microvm, nil
9192
}
93+
94+
func (s *Service) Delete(ctx context.Context) (*emptypb.Empty, error) {
95+
s.scope.V(defaults.LogLevelDebug).Info("Deleting microvm for machine", "machine-name", s.scope.Name(), "cluster-name", s.scope.ClusterName())
96+
97+
input := &flintlockv1.DeleteMicroVMRequest{
98+
Id: s.scope.Name(),
99+
Namespace: s.scope.Namespace(),
100+
}
101+
102+
return s.client.DeleteMicroVM(ctx, input)
103+
}

0 commit comments

Comments
 (0)