Skip to content

Commit c0b4f5a

Browse files
Add machine reconcile delete on termination (#336)
* Add support for machine deletion after instance termination
1 parent 6135fa9 commit c0b4f5a

File tree

3 files changed

+107
-21
lines changed

3 files changed

+107
-21
lines changed

api/v1beta2/ocimachine_types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import (
2828
const (
2929
// MachineFinalizer allows ReconcileMachine to clean up OCI resources associated with OCIMachine before
3030
// removing it from the apiserver.
31-
MachineFinalizer = "ocimachine.infrastructure.cluster.x-k8s.io"
31+
MachineFinalizer = "ocimachine.infrastructure.cluster.x-k8s.io"
32+
DeleteMachineOnInstanceTermination = "ociclusters.x-k8s.io/delete-machine-on-instance-termination"
3233
)
3334

3435
// OCIMachineSpec defines the desired state of OCIMachine

controllers/ocimachine_controller.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,13 @@ func (r *OCIMachineReconciler) OCIClusterToOCIMachines(ctx context.Context) hand
234234
func (r *OCIMachineReconciler) reconcileNormal(ctx context.Context, logger logr.Logger, machineScope *scope.MachineScope) (ctrl.Result, error) {
235235
controllerutil.AddFinalizer(machineScope.OCIMachine, infrastructurev1beta2.MachineFinalizer)
236236
machine := machineScope.OCIMachine
237+
infraMachine := machineScope.Machine
238+
239+
annotations := infraMachine.GetAnnotations()
240+
deleteMachineOnTermination := false
241+
if annotations != nil {
242+
_, deleteMachineOnTermination = annotations[infrastructurev1beta2.DeleteMachineOnInstanceTermination]
243+
}
237244
// Make sure bootstrap data is available and populated.
238245
if machineScope.Machine.Spec.Bootstrap.DataSecretName == nil {
239246
r.Recorder.Event(machine, corev1.EventTypeNormal, infrastructurev1beta2.WaitingForBootstrapDataReason, "Bootstrap data secret reference is not yet available")
@@ -304,7 +311,21 @@ func (r *OCIMachineReconciler) reconcileNormal(ctx context.Context, logger logr.
304311
"Instance is in ready state")
305312
conditions.MarkTrue(machineScope.OCIMachine, infrastructurev1beta2.InstanceReadyCondition)
306313
machineScope.SetReady()
307-
return reconcile.Result{}, nil
314+
if deleteMachineOnTermination {
315+
// typically, if the VM is terminated, we should get machine events, so ideally, the 300 seconds
316+
// requeue time is not required, but in case, the event is missed, adding the requeue time
317+
return reconcile.Result{RequeueAfter: 300 * time.Second}, nil
318+
} else {
319+
return reconcile.Result{}, nil
320+
}
321+
case core.InstanceLifecycleStateTerminated:
322+
if deleteMachineOnTermination && infraMachine.DeletionTimestamp == nil {
323+
logger.Info("Deleting underlying machine as instance is terminated")
324+
if err := machineScope.Client.Delete(ctx, infraMachine); err != nil {
325+
return reconcile.Result{}, errors.Wrapf(err, "failed to delete machine %s/%s", infraMachine.Namespace, infraMachine.Name)
326+
}
327+
}
328+
fallthrough
308329
default:
309330
conditions.MarkFalse(machineScope.OCIMachine, infrastructurev1beta2.InstanceReadyCondition, infrastructurev1beta2.InstanceProvisionFailedReason, clusterv1.ConditionSeverityError, "")
310331
machineScope.SetFailureReason(capierrors.CreateMachineError)

controllers/ocimachine_controller_test.go

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,26 @@ import (
2222
"testing"
2323
"time"
2424

25-
"github.com/oracle/cluster-api-provider-oci/cloud/ociutil"
26-
"github.com/oracle/cluster-api-provider-oci/cloud/services/networkloadbalancer/mock_nlb"
27-
"github.com/oracle/cluster-api-provider-oci/cloud/services/vcn/mock_vcn"
28-
"github.com/oracle/oci-go-sdk/v65/networkloadbalancer"
29-
"sigs.k8s.io/controller-runtime/pkg/client"
30-
3125
"github.com/golang/mock/gomock"
3226
. "github.com/onsi/gomega"
3327
infrastructurev1beta2 "github.com/oracle/cluster-api-provider-oci/api/v1beta2"
28+
"github.com/oracle/cluster-api-provider-oci/cloud/ociutil"
3429
"github.com/oracle/cluster-api-provider-oci/cloud/scope"
3530
"github.com/oracle/cluster-api-provider-oci/cloud/services/compute/mock_compute"
31+
"github.com/oracle/cluster-api-provider-oci/cloud/services/networkloadbalancer/mock_nlb"
32+
"github.com/oracle/cluster-api-provider-oci/cloud/services/vcn/mock_vcn"
3633
"github.com/oracle/oci-go-sdk/v65/common"
3734
"github.com/oracle/oci-go-sdk/v65/core"
35+
"github.com/oracle/oci-go-sdk/v65/networkloadbalancer"
3836
corev1 "k8s.io/api/core/v1"
3937
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4038
"k8s.io/apimachinery/pkg/runtime"
4139
"k8s.io/apimachinery/pkg/types"
4240
"k8s.io/client-go/tools/record"
4341
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
4442
"sigs.k8s.io/cluster-api/util/conditions"
43+
ctrl "sigs.k8s.io/controller-runtime"
44+
"sigs.k8s.io/controller-runtime/pkg/client"
4545
"sigs.k8s.io/controller-runtime/pkg/client/fake"
4646
"sigs.k8s.io/controller-runtime/pkg/log"
4747
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -194,19 +194,22 @@ func TestNormalReconciliationFunction(t *testing.T) {
194194
teardown := func(t *testing.T, g *WithT) {
195195
mockCtrl.Finish()
196196
}
197-
tests := []struct {
197+
type test struct {
198198
name string
199199
errorExpected bool
200200
expectedEvent string
201201
eventNotExpected string
202202
conditionAssertion []conditionAssertion
203-
testSpecificSetup func(machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbclient *mock_nlb.MockNetworkLoadBalancerClient)
204-
}{
203+
deleteMachines []clusterv1.Machine
204+
testSpecificSetup func(t *test, machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbclient *mock_nlb.MockNetworkLoadBalancerClient)
205+
validate func(g *WithT, t *test, result ctrl.Result)
206+
}
207+
tests := []test{
205208
{
206209
name: "instance in provisioning state",
207210
errorExpected: false,
208211
conditionAssertion: []conditionAssertion{{infrastructurev1beta2.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityInfo, infrastructurev1beta2.InstanceNotReadyReason}},
209-
testSpecificSetup: func(machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbclient *mock_nlb.MockNetworkLoadBalancerClient) {
212+
testSpecificSetup: func(t *test, machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbclient *mock_nlb.MockNetworkLoadBalancerClient) {
210213
computeClient.EXPECT().GetInstance(gomock.Any(), gomock.Eq(core.GetInstanceRequest{
211214
InstanceId: common.String("test"),
212215
})).
@@ -222,7 +225,7 @@ func TestNormalReconciliationFunction(t *testing.T) {
222225
name: "instance in running state",
223226
errorExpected: false,
224227
conditionAssertion: []conditionAssertion{{infrastructurev1beta2.InstanceReadyCondition, corev1.ConditionTrue, "", ""}},
225-
testSpecificSetup: func(machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbclient *mock_nlb.MockNetworkLoadBalancerClient) {
228+
testSpecificSetup: func(t *test, machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbclient *mock_nlb.MockNetworkLoadBalancerClient) {
226229
machineScope.OCIMachine.Status.Addresses = []clusterv1.MachineAddress{
227230
{
228231
Type: clusterv1.MachineInternalIP,
@@ -240,12 +243,67 @@ func TestNormalReconciliationFunction(t *testing.T) {
240243
}, nil)
241244
},
242245
},
246+
{
247+
name: "instance in running state, reconcile every 5 minutes",
248+
errorExpected: false,
249+
conditionAssertion: []conditionAssertion{{infrastructurev1beta2.InstanceReadyCondition, corev1.ConditionTrue, "", ""}},
250+
testSpecificSetup: func(t *test, machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbclient *mock_nlb.MockNetworkLoadBalancerClient) {
251+
if machineScope.Machine.ObjectMeta.Annotations == nil {
252+
machineScope.Machine.ObjectMeta.Annotations = make(map[string]string)
253+
}
254+
machineScope.Machine.ObjectMeta.Annotations[infrastructurev1beta2.DeleteMachineOnInstanceTermination] = ""
255+
machineScope.OCIMachine.Status.Addresses = []clusterv1.MachineAddress{
256+
{
257+
Type: clusterv1.MachineInternalIP,
258+
Address: "1.1.1.1",
259+
},
260+
}
261+
computeClient.EXPECT().GetInstance(gomock.Any(), gomock.Eq(core.GetInstanceRequest{
262+
InstanceId: common.String("test"),
263+
})).
264+
Return(core.GetInstanceResponse{
265+
Instance: core.Instance{
266+
Id: common.String("test"),
267+
LifecycleState: core.InstanceLifecycleStateRunning,
268+
},
269+
}, nil)
270+
},
271+
},
272+
{
273+
name: "instance in running state, reconcile every 5 minutes",
274+
errorExpected: false,
275+
conditionAssertion: []conditionAssertion{{infrastructurev1beta2.InstanceReadyCondition, corev1.ConditionTrue, "", ""}},
276+
testSpecificSetup: func(t *test, machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbclient *mock_nlb.MockNetworkLoadBalancerClient) {
277+
if machineScope.Machine.ObjectMeta.Annotations == nil {
278+
machineScope.Machine.ObjectMeta.Annotations = make(map[string]string)
279+
}
280+
machineScope.Machine.ObjectMeta.Annotations[infrastructurev1beta2.DeleteMachineOnInstanceTermination] = ""
281+
machineScope.OCIMachine.Status.Addresses = []clusterv1.MachineAddress{
282+
{
283+
Type: clusterv1.MachineInternalIP,
284+
Address: "1.1.1.1",
285+
},
286+
}
287+
computeClient.EXPECT().GetInstance(gomock.Any(), gomock.Eq(core.GetInstanceRequest{
288+
InstanceId: common.String("test"),
289+
})).
290+
Return(core.GetInstanceResponse{
291+
Instance: core.Instance{
292+
Id: common.String("test"),
293+
LifecycleState: core.InstanceLifecycleStateRunning,
294+
},
295+
}, nil)
296+
},
297+
validate: func(g *WithT, t *test, result ctrl.Result) {
298+
g.Expect(result.RequeueAfter).To(Equal(300 * time.Second))
299+
},
300+
},
243301
{
244302
name: "instance in terminated state",
245303
errorExpected: true,
246304
expectedEvent: "invalid lifecycle state TERMINATED",
247305
conditionAssertion: []conditionAssertion{{infrastructurev1beta2.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityError, infrastructurev1beta2.InstanceProvisionFailedReason}},
248-
testSpecificSetup: func(machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbclient *mock_nlb.MockNetworkLoadBalancerClient) {
306+
testSpecificSetup: func(t *test, machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbclient *mock_nlb.MockNetworkLoadBalancerClient) {
249307
computeClient.EXPECT().GetInstance(gomock.Any(), gomock.Eq(core.GetInstanceRequest{
250308
InstanceId: common.String("test"),
251309
})).
@@ -256,13 +314,16 @@ func TestNormalReconciliationFunction(t *testing.T) {
256314
},
257315
}, nil)
258316
},
317+
validate: func(g *WithT, t *test, result ctrl.Result) {
318+
g.Expect(result.RequeueAfter).To(Equal(0 * time.Second))
319+
},
259320
},
260321
{
261322
name: "control plane backend vnic attachments call error",
262323
errorExpected: true,
263324
expectedEvent: "server error",
264325
conditionAssertion: []conditionAssertion{{infrastructurev1beta2.InstanceReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityError, infrastructurev1beta2.InstanceIPAddressNotFound}},
265-
testSpecificSetup: func(machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbclient *mock_nlb.MockNetworkLoadBalancerClient) {
326+
testSpecificSetup: func(t *test, machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbclient *mock_nlb.MockNetworkLoadBalancerClient) {
266327
machineScope.Machine.ObjectMeta.Labels = make(map[string]string)
267328
machineScope.Machine.ObjectMeta.Labels[clusterv1.MachineControlPlaneLabel] = "true"
268329
computeClient.EXPECT().GetInstance(gomock.Any(), gomock.Eq(core.GetInstanceRequest{
@@ -285,7 +346,7 @@ func TestNormalReconciliationFunction(t *testing.T) {
285346
{
286347
name: "control plane backend backend exists",
287348
errorExpected: false,
288-
testSpecificSetup: func(machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbClient *mock_nlb.MockNetworkLoadBalancerClient) {
349+
testSpecificSetup: func(t *test, machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbClient *mock_nlb.MockNetworkLoadBalancerClient) {
289350
machineScope.Machine.ObjectMeta.Labels = make(map[string]string)
290351
machineScope.Machine.ObjectMeta.Labels[clusterv1.MachineControlPlaneLabel] = "true"
291352
computeClient.EXPECT().GetInstance(gomock.Any(), gomock.Eq(core.GetInstanceRequest{
@@ -342,7 +403,7 @@ func TestNormalReconciliationFunction(t *testing.T) {
342403
{
343404
name: "backend creation",
344405
errorExpected: false,
345-
testSpecificSetup: func(machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbClient *mock_nlb.MockNetworkLoadBalancerClient) {
406+
testSpecificSetup: func(t *test, machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbClient *mock_nlb.MockNetworkLoadBalancerClient) {
346407
machineScope.Machine.ObjectMeta.Labels = make(map[string]string)
347408
machineScope.Machine.ObjectMeta.Labels[clusterv1.MachineControlPlaneLabel] = "true"
348409
computeClient.EXPECT().GetInstance(gomock.Any(), gomock.Eq(core.GetInstanceRequest{
@@ -417,7 +478,7 @@ func TestNormalReconciliationFunction(t *testing.T) {
417478
{
418479
name: "ip address exists",
419480
errorExpected: false,
420-
testSpecificSetup: func(machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbClient *mock_nlb.MockNetworkLoadBalancerClient) {
481+
testSpecificSetup: func(t *test, machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbClient *mock_nlb.MockNetworkLoadBalancerClient) {
421482
machineScope.Machine.ObjectMeta.Labels = make(map[string]string)
422483
machineScope.OCIMachine.Status.Addresses = []clusterv1.MachineAddress{
423484
{
@@ -476,7 +537,7 @@ func TestNormalReconciliationFunction(t *testing.T) {
476537
{
477538
name: "backend creation fails",
478539
errorExpected: true,
479-
testSpecificSetup: func(machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbClient *mock_nlb.MockNetworkLoadBalancerClient) {
540+
testSpecificSetup: func(t *test, machineScope *scope.MachineScope, computeClient *mock_compute.MockComputeClient, vcnClient *mock_vcn.MockClient, nlbClient *mock_nlb.MockNetworkLoadBalancerClient) {
480541
machineScope.Machine.ObjectMeta.Labels = make(map[string]string)
481542
machineScope.Machine.ObjectMeta.Labels[clusterv1.MachineControlPlaneLabel] = "true"
482543
computeClient.EXPECT().GetInstance(gomock.Any(), gomock.Eq(core.GetInstanceRequest{
@@ -555,9 +616,9 @@ func TestNormalReconciliationFunction(t *testing.T) {
555616
g := NewWithT(t)
556617
defer teardown(t, g)
557618
setup(t, g)
558-
tc.testSpecificSetup(ms, computeClient, vcnClient, nlbClient)
619+
tc.testSpecificSetup(&tc, ms, computeClient, vcnClient, nlbClient)
559620
ctx := context.Background()
560-
_, err := r.reconcileNormal(ctx, log.FromContext(ctx), ms)
621+
result, err := r.reconcileNormal(ctx, log.FromContext(ctx), ms)
561622
if len(tc.conditionAssertion) > 0 {
562623
expectConditions(g, ociMachine, tc.conditionAssertion)
563624
}
@@ -569,6 +630,9 @@ func TestNormalReconciliationFunction(t *testing.T) {
569630
if tc.expectedEvent != "" {
570631
g.Eventually(recorder.Events).Should(Receive(ContainSubstring(tc.expectedEvent)))
571632
}
633+
if tc.validate != nil {
634+
tc.validate(g, &tc, result)
635+
}
572636
})
573637
}
574638
}

0 commit comments

Comments
 (0)