Skip to content

Commit b5ba35a

Browse files
committed
Revert fix: let deletion proceed when an owning object is already gone
1 parent 472953c commit b5ba35a

6 files changed

Lines changed: 34 additions & 205 deletions

‎controller/stackitcluster_controller.go‎

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,12 @@ func (r *StackitClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque
6969
return ctrl.Result{}, err
7070
}
7171

72-
deleting := !stackitCluster.DeletionTimestamp.IsZero()
73-
cluster, ownerErr := clusterutil.GetOwnerCluster(ctx, r.Client, stackitCluster.ObjectMeta)
74-
switch {
75-
case ownerGone(ownerErr) && deleting:
76-
// The owning Cluster is already gone and can never come back, so
77-
// returning the error here would retry forever without this object ever
78-
// reaching reconcileDelete. Deletion must not be blocked by a
79-
// precondition only the normal path needs.
80-
log.Info("Owning Cluster is gone, continuing deletion without it")
81-
case ownerErr != nil:
82-
return ctrl.Result{}, fmt.Errorf("get owner cluster: %w", ownerErr)
83-
case cluster == nil && !deleting:
84-
log.Info("StackitCluster has no owning Cluster yet, waiting")
72+
cluster, err := clusterutil.GetOwnerCluster(ctx, r.Client, stackitCluster.ObjectMeta)
73+
if err != nil {
74+
return ctrl.Result{}, fmt.Errorf("get owner cluster: %w", err)
75+
}
76+
if cluster == nil {
77+
log.Info("StackitCluster has no owning Cluster yet, requeueing")
8578
return ctrl.Result{}, nil
8679
}
8780

‎controller/stackitcluster_controller_test.go‎

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -665,56 +665,6 @@ var _ = Describe("StackitCluster Controller", func() {
665665
}).Should(BeTrue())
666666
})
667667

668-
// GetOwnerCluster returns an error once the owning Cluster is gone, and that
669-
// error was returned from Reconcile. Since the Cluster can never come back,
670-
// the StackitCluster retried forever without ever reaching reconcileDelete
671-
// and stayed in Terminating for good.
672-
It("finalizes deletion when the owning Cluster is already gone", func() {
673-
_, err := reconciler.Reconcile(ctx, request)
674-
Expect(err).NotTo(HaveOccurred())
675-
Expect(fakeCloud.LoadBalancerCount()).To(Equal(1))
676-
677-
deleteIfExists(ctx, &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: clusterName, Namespace: namespace}})
678-
679-
got := &infrav1.StackitCluster{}
680-
Expect(k8sClient.Get(ctx, stackitKey, got)).To(Succeed())
681-
Expect(k8sClient.Delete(ctx, got)).To(Succeed())
682-
683-
_, err = reconciler.Reconcile(ctx, request)
684-
Expect(err).NotTo(HaveOccurred())
685-
Expect(fakeCloud.LoadBalancerCount()).To(Equal(0))
686-
Eventually(func() bool {
687-
err := k8sClient.Get(ctx, stackitKey, &infrav1.StackitCluster{})
688-
return apierrors.IsNotFound(err)
689-
}).Should(BeTrue())
690-
})
691-
692-
It("still waits for Machines when the owning Cluster is already gone", func() {
693-
_, err := reconciler.Reconcile(ctx, request)
694-
Expect(err).NotTo(HaveOccurred())
695-
696-
machineName := "machine-" + clusterName
697-
createOwnerMachine(ctx, machineName, clusterName, "stackit-"+machineName)
698-
DeferCleanup(func() {
699-
deleteIfExists(ctx, &clusterv1.Machine{ObjectMeta: metav1.ObjectMeta{Name: machineName, Namespace: namespace}})
700-
})
701-
702-
deleteIfExists(ctx, &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: clusterName, Namespace: namespace}})
703-
704-
got := &infrav1.StackitCluster{}
705-
Expect(k8sClient.Get(ctx, stackitKey, got)).To(Succeed())
706-
Expect(k8sClient.Delete(ctx, got)).To(Succeed())
707-
708-
result, err := reconciler.Reconcile(ctx, request)
709-
Expect(err).NotTo(HaveOccurred())
710-
Expect(result.RequeueAfter).To(Equal(deleteRequeueAfter),
711-
"the Machine name has to come from the ownerReference once the Cluster is gone")
712-
713-
Expect(k8sClient.Get(ctx, stackitKey, got)).To(Succeed())
714-
Expect(got.Finalizers).To(ContainElement(infrav1.ClusterFinalizer))
715-
Expect(fakeCloud.LoadBalancerCount()).To(Equal(1))
716-
})
717-
718668
It("keeps the finalizer when load balancer deletion returns a transient error", func() {
719669
_, err := reconciler.Reconcile(ctx, request)
720670
Expect(err).NotTo(HaveOccurred())

‎controller/stackitcluster_infrastructure.go‎

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -232,44 +232,19 @@ func bootstrapTargetIP(network *cloud.Network) string {
232232
return "10.0.0.1"
233233
}
234234

235-
// ownerClusterName reports the name of the Cluster this StackitCluster belongs
236-
// to, without needing that Cluster to still exist.
237-
func ownerClusterName(stackitCluster *infrav1.StackitCluster) string {
238-
for _, ref := range stackitCluster.OwnerReferences {
239-
if ref.Kind == "Cluster" {
240-
return ref.Name
241-
}
242-
}
243-
return ""
244-
}
245-
246235
func (r *StackitClusterReconciler) reconcileDelete(ctx context.Context, clusterScope *scope.ClusterScope) (ctrl.Result, error) {
247236
stackitCluster := clusterScope.StackitCluster
248237

249238
// Machines resolve their credentials and project context through this
250-
// StackitCluster, so removing the finalizer while any of them remain leaves
251-
// them unable to delete their own servers. Cluster API orders this correctly
252-
// when deletion starts at the Cluster, but a namespace teardown or a direct
253-
// delete of this object bypasses that ordering entirely.
254-
//
239+
// StackitCluster, so the finalizer has to stay while any of them remain.
255240
// Selects the same way util/collections.GetFilteredMachinesForCluster does,
256-
// inlined because that package pulls in the kubeadm bootstrap API for a
257-
// query this short. Cluster API sets the label on every Machine it owns.
258-
//
259-
// The Cluster itself may already be gone — a namespace teardown deletes it
260-
// in no particular order — so the name is taken from the ownerReference,
261-
// which outlives it. Owner references are always same-namespace, so the
262-
// StackitCluster's own namespace is the right one either way.
263-
clusterName := ownerClusterName(stackitCluster)
264-
if clusterScope.Cluster != nil {
265-
clusterName = clusterScope.Cluster.Name
266-
}
241+
// inlined because that package pulls in the kubeadm bootstrap API.
267242
machines := &clusterv1.MachineList{}
268243
if err := r.List(ctx, machines,
269-
client.InNamespace(stackitCluster.Namespace),
270-
client.MatchingLabels{clusterv1.ClusterNameLabel: clusterName},
244+
client.InNamespace(clusterScope.Cluster.Namespace),
245+
client.MatchingLabels{clusterv1.ClusterNameLabel: clusterScope.Cluster.Name},
271246
); err != nil {
272-
return ctrl.Result{}, fmt.Errorf("list Machines for cluster %s: %w", clusterName, err)
247+
return ctrl.Result{}, fmt.Errorf("list Machines for cluster %s: %w", clusterScope.Cluster.Name, err)
273248
}
274249
if len(machines.Items) > 0 {
275250
logf.FromContext(ctx).Info(

‎controller/stackitmachine_controller.go‎

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package controller
1818

1919
import (
2020
"context"
21-
"errors"
2221
"fmt"
2322

2423
corev1 "k8s.io/api/core/v1"
@@ -67,31 +66,31 @@ func (r *StackitMachineReconciler) Reconcile(ctx context.Context, req ctrl.Reque
6766
return ctrl.Result{}, err
6867
}
6968

70-
// The owning objects are resolved as far as they still exist. While the
71-
// object is being deleted an owner that is simply gone is tolerated, so the
72-
// checks that need one sit behind the deletion branch below rather than in
73-
// front of it. Every other error still fails, so a transient API problem
74-
// cannot be mistaken for a missing owner and drop the finalizer over a
75-
// server that is still running.
76-
deleting := !stackitMachine.DeletionTimestamp.IsZero()
77-
78-
machine, ownerErr := clusterutil.GetOwnerMachine(ctx, r.Client, stackitMachine.ObjectMeta)
79-
if ownerErr != nil && (!deleting || !ownerGone(ownerErr)) {
80-
return ctrl.Result{}, fmt.Errorf("get owner machine: %w", ownerErr)
69+
machine, err := clusterutil.GetOwnerMachine(ctx, r.Client, stackitMachine.ObjectMeta)
70+
if err != nil {
71+
return ctrl.Result{}, fmt.Errorf("get owner machine: %w", err)
8172
}
82-
var cluster *clusterv1.Cluster
83-
if machine != nil {
84-
cluster, ownerErr = clusterutil.GetClusterFromMetadata(ctx, r.Client, machine.ObjectMeta)
85-
if ownerErr != nil && (!deleting || !ownerGone(ownerErr)) {
86-
return ctrl.Result{}, fmt.Errorf("get cluster from machine metadata: %w", ownerErr)
87-
}
73+
if machine == nil {
74+
log.Info("StackitMachine has no owning Machine yet, requeueing")
75+
return ctrl.Result{}, nil
8876
}
89-
var stackitCluster *infrav1.StackitCluster
90-
if cluster != nil {
91-
stackitCluster, err = r.getStackitCluster(ctx, cluster)
92-
if err != nil {
93-
return ctrl.Result{}, err
94-
}
77+
78+
cluster, err := clusterutil.GetClusterFromMetadata(ctx, r.Client, machine.ObjectMeta)
79+
if err != nil {
80+
return ctrl.Result{}, fmt.Errorf("get cluster from machine metadata: %w", err)
81+
}
82+
if cluster == nil {
83+
log.Info("Machine has no owning Cluster yet, requeueing")
84+
return ctrl.Result{}, nil
85+
}
86+
87+
stackitCluster, err := r.getStackitCluster(ctx, cluster)
88+
if err != nil {
89+
return ctrl.Result{}, err
90+
}
91+
if stackitCluster == nil {
92+
log.Info("StackitCluster not found, requeueing")
93+
return ctrl.Result{}, nil
9594
}
9695

9796
machineScope, err := scope.NewMachineScope(r.Client, cluster, machine, stackitCluster, stackitMachine)
@@ -113,29 +112,9 @@ func (r *StackitMachineReconciler) Reconcile(ctx context.Context, req ctrl.Reque
113112
if !stackitMachine.DeletionTimestamp.IsZero() {
114113
return ctrl.Result{}, r.reconcileDelete(ctx, machineScope)
115114
}
116-
117-
switch {
118-
case machine == nil:
119-
log.Info("StackitMachine has no owning Machine yet, waiting")
120-
return ctrl.Result{}, nil
121-
case cluster == nil:
122-
log.Info("Machine has no owning Cluster yet, waiting")
123-
return ctrl.Result{}, nil
124-
case stackitCluster == nil:
125-
log.Info("StackitCluster not found, waiting")
126-
return ctrl.Result{}, nil
127-
}
128115
return r.reconcileNormal(ctx, machineScope)
129116
}
130117

131-
// ownerGone reports whether err means the owning object no longer exists, as
132-
// opposed to not being readable right now. GetOwnerMachine and GetOwnerCluster
133-
// surface a plain NotFound; GetClusterFromMetadata reports ErrNoCluster when the
134-
// Machine has lost the label naming its Cluster.
135-
func ownerGone(err error) bool {
136-
return apierrors.IsNotFound(err) || errors.Is(err, clusterutil.ErrNoCluster)
137-
}
138-
139118
// SetupWithManager registers the controller with the manager.
140119
func (r *StackitMachineReconciler) SetupWithManager(mgr ctrl.Manager) error {
141120
return ctrl.NewControllerManagedBy(mgr).

‎controller/stackitmachine_controller_test.go‎

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -448,44 +448,6 @@ var _ = Describe("StackitMachine Controller", func() {
448448
}).Should(BeTrue())
449449
})
450450

451-
// Every one of these owning objects used to be checked before the
452-
// DeletionTimestamp branch and returned without a requeue, so a
453-
// StackitMachine whose owner disappeared first — a namespace teardown
454-
// deletes in no particular order — could never be deleted again.
455-
DescribeTable("finalizes deletion when an owning object is already gone",
456-
func(deleteOwner func()) {
457-
updateMachineBootstrapSecret(ctx, machineName, bootstrapName)
458-
createBootstrapSecret(ctx, bootstrapName)
459-
_, err := reconciler.Reconcile(ctx, request)
460-
Expect(err).NotTo(HaveOccurred())
461-
Expect(fakeCloud.ServerCount()).To(Equal(1))
462-
463-
deleteOwner()
464-
465-
got := &infrav1.StackitMachine{}
466-
Expect(k8sClient.Get(ctx, stackitKey, got)).To(Succeed())
467-
Expect(k8sClient.Delete(ctx, got)).To(Succeed())
468-
469-
_, err = reconciler.Reconcile(ctx, request)
470-
Expect(err).NotTo(HaveOccurred())
471-
Expect(fakeCloud.ServerCount()).To(Equal(1),
472-
"without the owning objects there are no credentials and no tags, so the server cannot be reached")
473-
Eventually(func() bool {
474-
err := k8sClient.Get(ctx, stackitKey, &infrav1.StackitMachine{})
475-
return apierrors.IsNotFound(err)
476-
}).Should(BeTrue())
477-
},
478-
Entry("owning Machine", func() {
479-
deleteIfExists(ctx, &clusterv1.Machine{ObjectMeta: metav1.ObjectMeta{Name: machineName, Namespace: namespace}})
480-
}),
481-
Entry("owning Cluster", func() {
482-
deleteIfExists(ctx, &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: clusterName, Namespace: namespace}})
483-
}),
484-
Entry("owning StackitCluster", func() {
485-
deleteIfExists(ctx, &infrav1.StackitCluster{ObjectMeta: metav1.ObjectMeta{Name: clusterName, Namespace: namespace}})
486-
}),
487-
)
488-
489451
It("maps owning Machine events to StackitMachine reconcile requests", func() {
490452
machine := &clusterv1.Machine{}
491453
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: machineName, Namespace: namespace}, machine)).To(Succeed())

‎controller/stackitmachine_infrastructure.go‎

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -170,22 +170,6 @@ func validateMachineAvailabilityZone(machineScope *scope.MachineScope) error {
170170
func (r *StackitMachineReconciler) reconcileDelete(ctx context.Context, machineScope *scope.MachineScope) error {
171171
stackitMachine := machineScope.StackitMachine
172172

173-
// Without one of the owning objects the server can no longer be reached at
174-
// all: the StackitCluster carries the credentials, the project and the
175-
// region, and the Cluster and Machine names make up the tags that identify
176-
// the server. Blocking here would leave the StackitMachine in Terminating
177-
// forever, so finalize and make the possible leak loud instead — the same
178-
// trade the missing-credentials path below makes.
179-
if missing := missingOwner(machineScope); missing != "" {
180-
if r.Recorder != nil {
181-
r.Recorder.Eventf(stackitMachine, nil, corev1.EventTypeWarning, "CleanupSkipped", "Delete",
182-
"%s is gone; finalizing without cloud cleanup. "+
183-
"Any remaining STACKIT server for this machine must be removed manually.", missing)
184-
}
185-
controllerutil.RemoveFinalizer(stackitMachine, infrav1.MachineFinalizer)
186-
return nil
187-
}
188-
189173
// The cloud client is built unconditionally: an empty status.instanceID is
190174
// not proof that no server exists, so every deletion has to ask the cloud
191175
// before it may drop the finalizer.
@@ -236,20 +220,6 @@ func (r *StackitMachineReconciler) reconcileDelete(ctx context.Context, machineS
236220
return nil
237221
}
238222

239-
// missingOwner names the first owning object that no longer exists, or an empty
240-
// string once all of them are present.
241-
func missingOwner(machineScope *scope.MachineScope) string {
242-
switch {
243-
case machineScope.Machine == nil:
244-
return "Owning Machine"
245-
case machineScope.Cluster == nil:
246-
return "Owning Cluster"
247-
case machineScope.StackitCluster == nil:
248-
return "Owning StackitCluster"
249-
}
250-
return ""
251-
}
252-
253223
// resolveServerForDeletion reports the ID of the server backing this machine, or
254224
// an empty string once the cloud confirms none exists.
255225
//

0 commit comments

Comments
 (0)