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

AzureMachinePoolMachine remove finalizer and avoid recreation if VMSS is deleting #4959

Merged
merged 1 commit into from
Jul 30, 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
4 changes: 4 additions & 0 deletions azure/scope/machinepool.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,10 @@ func (m *MachinePoolScope) applyAzureMachinePoolMachines(ctx context.Context) er
// determine which machines need to be created to reflect the current state in Azure
azureMachinesByProviderID := m.vmssState.InstancesByProviderID(m.AzureMachinePool.Spec.OrchestrationMode)
for key, val := range azureMachinesByProviderID {
if val.State == infrav1.Deleting || val.State == infrav1.Deleted {
log.V(4).Info("not recreating AzureMachinePoolMachine because VMSS VM is deleting", "providerID", key)
continue
}
if _, ok := existingMachinesByProviderID[key]; !ok {
log.V(4).Info("creating AzureMachinePoolMachine", "providerID", key)
if err := m.createMachine(ctx, val); err != nil {
Expand Down
20 changes: 20 additions & 0 deletions azure/scope/machinepool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1449,6 +1449,26 @@ func TestMachinePoolScope_applyAzureMachinePoolMachines(t *testing.T) {
g.Expect(err).To(HaveOccurred())
},
},
{
Name: "if existing MachinePool is present but in deleting state, do not recreate AzureMachinePoolMachines",
Setup: func(mp *expv1.MachinePool, amp *infrav1exp.AzureMachinePool, vmssState *azure.VMSS, cb *fake.ClientBuilder) {
mp.Spec.Replicas = ptr.To[int32](1)

vmssState.Instances = []azure.VMSSVM{
{
ID: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/vm",
Name: "vm",
State: infrav1.Deleting,
},
}
},
Verify: func(g *WithT, amp *infrav1exp.AzureMachinePool, c client.Client, err error) {
g.Expect(err).NotTo(HaveOccurred())
list := infrav1exp.AzureMachinePoolMachineList{}
g.Expect(c.List(ctx, &list)).NotTo(HaveOccurred())
g.Expect(list.Items).Should(BeEmpty())
},
},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
Expand Down
9 changes: 7 additions & 2 deletions exp/controllers/azuremachinepoolmachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,14 @@
return reconcile.Result{}, err
}

if machine != nil {
switch {
case machine != nil:
logger = logger.WithValues("machine", machine.Name)
} else {
case !azureMachinePool.ObjectMeta.DeletionTimestamp.IsZero():
logger.Info("AzureMachinePool is being deleted, removing finalizer")
controllerutil.RemoveFinalizer(azureMachine, infrav1exp.AzureMachinePoolMachineFinalizer)
return reconcile.Result{}, ampmr.Client.Update(ctx, azureMachine)
default:

Check warning on line 224 in exp/controllers/azuremachinepoolmachine_controller.go

View check run for this annotation

Codecov / codecov/patch

exp/controllers/azuremachinepoolmachine_controller.go#L224

Added line #L224 was not covered by tests
logger.Info("Waiting for Machine Controller to set OwnerRef on AzureMachinePoolMachine")
return reconcile.Result{}, nil
}
Expand Down
146 changes: 146 additions & 0 deletions exp/controllers/azuremachinepoolmachine_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,24 @@ func TestAzureMachinePoolMachineReconciler_Reconcile(t *testing.T) {
g.Expect(err.Error()).Should(ContainSubstring("not found"))
},
},
{
Name: "should remove finalizer if Machine is not found and AzureMachinePool has deletionTimestamp set",
Setup: func(cb *fake.ClientBuilder, reconciler *mock_azure.MockReconcilerMockRecorder) {
objects := getDeletingMachinePoolObjects()
cb.WithObjects(objects...)
},
Verify: func(g *WithT, c client.Client, result ctrl.Result, err error) {
g.Expect(err).NotTo(HaveOccurred())

ampm := &infrav1exp.AzureMachinePoolMachine{}
err = c.Get(context.Background(), types.NamespacedName{
Name: "ampm1",
Namespace: "default",
}, ampm)
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).Should(ContainSubstring("not found"))
},
},
}

for _, c := range cases {
Expand Down Expand Up @@ -282,3 +300,131 @@ func getReadyMachinePoolMachineClusterObjects(ampmIsDeleting bool, ampmProvision

return []client.Object{cluster, azCluster, mp, amp, ma, ampm, fakeIdentity, fakeSecret}
}

func getDeletingMachinePoolObjects() []client.Object {
azCluster := &infrav1.AzureCluster{
TypeMeta: metav1.TypeMeta{
Kind: "AzureCluster",
},
ObjectMeta: metav1.ObjectMeta{
Name: "azCluster1",
Namespace: "default",
},
Spec: infrav1.AzureClusterSpec{
AzureClusterClassSpec: infrav1.AzureClusterClassSpec{
SubscriptionID: "subID",
IdentityRef: &corev1.ObjectReference{
Name: "fake-identity",
Namespace: "default",
Kind: "AzureClusterIdentity",
},
},
},
}

cluster := &clusterv1.Cluster{
TypeMeta: metav1.TypeMeta{
Kind: "Cluster",
},
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1",
Namespace: "default",
},
Spec: clusterv1.ClusterSpec{
InfrastructureRef: &corev1.ObjectReference{
Name: azCluster.Name,
Namespace: "default",
Kind: "AzureCluster",
},
},
Status: clusterv1.ClusterStatus{
InfrastructureReady: true,
},
}

mp := &expv1.MachinePool{
TypeMeta: metav1.TypeMeta{
Kind: "MachinePool",
},
ObjectMeta: metav1.ObjectMeta{
Name: "mp1",
Namespace: "default",
Finalizers: []string{"test"},
DeletionTimestamp: &metav1.Time{
Time: time.Now(),
},
Labels: map[string]string{
"cluster.x-k8s.io/cluster-name": cluster.Name,
},
},
}

amp := &infrav1exp.AzureMachinePool{
TypeMeta: metav1.TypeMeta{
Kind: "AzureMachinePool",
},
ObjectMeta: metav1.ObjectMeta{
Name: "amp1",
Namespace: "default",
Finalizers: []string{"test"},
DeletionTimestamp: &metav1.Time{
Time: time.Now(),
},
OwnerReferences: []metav1.OwnerReference{
{
Name: mp.Name,
Kind: "MachinePool",
APIVersion: expv1.GroupVersion.String(),
},
},
},
}

ampm := &infrav1exp.AzureMachinePoolMachine{
ObjectMeta: metav1.ObjectMeta{
Name: "ampm1",
Namespace: "default",
DeletionTimestamp: &metav1.Time{
Time: time.Now(),
},
Finalizers: []string{infrav1exp.AzureMachinePoolMachineFinalizer},
Labels: map[string]string{
clusterv1.ClusterNameLabel: cluster.Name,
},
OwnerReferences: []metav1.OwnerReference{
{
Name: amp.Name,
Kind: infrav1.AzureMachinePoolKind,
APIVersion: infrav1exp.GroupVersion.String(),
},
},
},
}

fakeIdentity := &infrav1.AzureClusterIdentity{
ObjectMeta: metav1.ObjectMeta{
Name: "fake-identity",
Namespace: "default",
},
Spec: infrav1.AzureClusterIdentitySpec{
Type: infrav1.ServicePrincipal,
ClientSecret: corev1.SecretReference{
Name: "fooSecret",
Namespace: "default",
},
TenantID: "fake-tenantid",
},
}

fakeSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "fooSecret",
Namespace: "default",
},
Data: map[string][]byte{
"clientSecret": []byte("fooSecret"),
},
}

return []client.Object{cluster, azCluster, mp, amp, ampm, fakeIdentity, fakeSecret}
}