Skip to content

Commit 9bb91b2

Browse files
committed
Decomission: Base it on the hypervisor
This also needs some manual migration work: The finalizer in the node needs to get manually removed, and the name change of the controller-name requires a reset of the field owners.
1 parent bbaf78c commit 9bb91b2

File tree

2 files changed

+52
-81
lines changed

2 files changed

+52
-81
lines changed

internal/controller/decomission_controller.go

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/aggregates"
2929
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/services"
3030
"github.com/gophercloud/gophercloud/v2/openstack/placement/v1/resourceproviders"
31-
corev1 "k8s.io/api/core/v1"
3231
"k8s.io/apimachinery/pkg/api/meta"
3332
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3433
"k8s.io/apimachinery/pkg/runtime"
@@ -44,7 +43,7 @@ import (
4443

4544
const (
4645
decommissionFinalizerName = "cobaltcore.cloud.sap/decommission-hypervisor"
47-
DecommissionControllerName = "nodeDecommission"
46+
DecommissionControllerName = "decommission"
4847
)
4948

5049
type NodeDecommissionReconciler struct {
@@ -57,20 +56,13 @@ type NodeDecommissionReconciler struct {
5756
// The counter-side in gardener is here:
5857
// https://github.com/gardener/machine-controller-manager/blob/rel-v0.56/pkg/util/provider/machinecontroller/machine.go#L646
5958

60-
// +kubebuilder:rbac:groups="",resources=nodes,verbs=get;list;watch;patch;update
61-
// +kubebuilder:rbac:groups="",resources=nodes/finalizers,verbs=update
6259
// +kubebuilder:rbac:groups=kvm.cloud.sap,resources=hypervisors,verbs=get;list;watch
6360
// +kubebuilder:rbac:groups=kvm.cloud.sap,resources=hypervisors/status,verbs=get;list;watch;update;patch
6461
func (r *NodeDecommissionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
6562
hostname := req.Name
6663
log := logger.FromContext(ctx).WithName(req.Name).WithValues("hostname", hostname)
6764
ctx = logger.IntoContext(ctx, log)
6865

69-
node := &corev1.Node{}
70-
if err := r.Get(ctx, req.NamespacedName, node); err != nil {
71-
return ctrl.Result{}, k8sclient.IgnoreNotFound(err)
72-
}
73-
7466
// Fetch HV to check if lifecycle management is enabled
7567
hv := &kvmv1.Hypervisor{}
7668
if err := r.Get(ctx, k8sclient.ObjectKey{Name: hostname}, hv); err != nil {
@@ -79,14 +71,14 @@ func (r *NodeDecommissionReconciler) Reconcile(ctx context.Context, req ctrl.Req
7971
}
8072
if !hv.Spec.LifecycleEnabled {
8173
// Get out of the way
82-
return r.removeFinalizer(ctx, node)
74+
return r.removeFinalizer(ctx, hv)
8375
}
8476

85-
if !controllerutil.ContainsFinalizer(node, decommissionFinalizerName) {
77+
if !controllerutil.ContainsFinalizer(hv, decommissionFinalizerName) {
8678
return ctrl.Result{}, retry.RetryOnConflict(retry.DefaultRetry, func() error {
87-
patch := k8sclient.MergeFrom(node.DeepCopy())
88-
controllerutil.AddFinalizer(node, decommissionFinalizerName)
89-
if err := r.Patch(ctx, node, patch); err != nil {
79+
patch := k8sclient.MergeFrom(hv.DeepCopy())
80+
controllerutil.AddFinalizer(hv, decommissionFinalizerName)
81+
if err := r.Patch(ctx, hv, patch); err != nil {
9082
return fmt.Errorf("failed to add finalizer due to %w", err)
9183
}
9284
log.Info("Added finalizer")
@@ -95,16 +87,16 @@ func (r *NodeDecommissionReconciler) Reconcile(ctx context.Context, req ctrl.Req
9587
}
9688

9789
// Not yet deleting hv, nothing more to do
98-
if node.DeletionTimestamp.IsZero() {
90+
if hv.DeletionTimestamp.IsZero() {
9991
return ctrl.Result{}, nil
10092
}
10193

10294
// Someone is just deleting the hv, without going through termination
10395
// See: https://github.com/gardener/machine-controller-manager/blob/rel-v0.56/pkg/util/provider/machinecontroller/machine.go#L658-L659
104-
if !IsNodeConditionTrue(node.Status.Conditions, "Terminating") {
96+
if !meta.IsStatusConditionTrue(hv.Status.Conditions, "Terminating") {
10597
log.Info("removing finalizer since not terminating")
10698
// So we just get out of the way for now
107-
return r.removeFinalizer(ctx, node)
99+
return r.removeFinalizer(ctx, hv)
108100
}
109101

110102
if meta.IsStatusConditionTrue(hv.Status.Conditions, kvmv1.ConditionTypeReady) {
@@ -116,7 +108,7 @@ func (r *NodeDecommissionReconciler) Reconcile(ctx context.Context, req ctrl.Req
116108
hypervisor, err := openstack.GetHypervisorByName(ctx, r.computeClient, hostname, true)
117109
if errors.Is(err, openstack.ErrNoHypervisor) {
118110
// We are (hopefully) done
119-
return r.removeFinalizer(ctx, node)
111+
return r.removeFinalizer(ctx, hv)
120112
}
121113

122114
// TODO: remove since RunningVMs is only available until micro-version 2.87, and also is updated asynchronously
@@ -141,7 +133,7 @@ func (r *NodeDecommissionReconciler) Reconcile(ctx context.Context, req ctrl.Req
141133
return r.setDecommissioningCondition(ctx, hv, fmt.Sprintf("cannot list aggregates due to %v", err))
142134
}
143135

144-
host := node.Name
136+
host := hv.Name
145137
for name, aggregate := range aggs {
146138
if slices.Contains(aggregate.Hosts, host) {
147139
opts := aggregates.RemoveHostOpts{Host: host}
@@ -168,17 +160,17 @@ func (r *NodeDecommissionReconciler) Reconcile(ctx context.Context, req ctrl.Req
168160
return r.setDecommissioningCondition(ctx, hv, fmt.Sprintf("cannot clean up resource provider: %v", err))
169161
}
170162

171-
return r.removeFinalizer(ctx, node)
163+
return r.removeFinalizer(ctx, hv)
172164
}
173165

174-
func (r *NodeDecommissionReconciler) removeFinalizer(ctx context.Context, node *corev1.Node) (ctrl.Result, error) {
175-
if !controllerutil.ContainsFinalizer(node, decommissionFinalizerName) {
166+
func (r *NodeDecommissionReconciler) removeFinalizer(ctx context.Context, hypervisor *kvmv1.Hypervisor) (ctrl.Result, error) {
167+
if !controllerutil.ContainsFinalizer(hypervisor, decommissionFinalizerName) {
176168
return ctrl.Result{}, nil
177169
}
178170

179-
nodeBase := node.DeepCopy()
180-
controllerutil.RemoveFinalizer(node, decommissionFinalizerName)
181-
err := r.Patch(ctx, node, k8sclient.MergeFromWithOptions(nodeBase,
171+
base := hypervisor.DeepCopy()
172+
controllerutil.RemoveFinalizer(hypervisor, decommissionFinalizerName)
173+
err := r.Patch(ctx, hypervisor, k8sclient.MergeFromWithOptions(base,
182174
k8sclient.MergeFromWithOptimisticLock{}), k8sclient.FieldOwner(DecommissionControllerName))
183175
return ctrl.Result{}, err
184176
}
@@ -195,7 +187,7 @@ func (r *NodeDecommissionReconciler) setDecommissioningCondition(ctx context.Con
195187
k8sclient.MergeFromWithOptimisticLock{}), k8sclient.FieldOwner(DecommissionControllerName)); err != nil {
196188
return ctrl.Result{}, fmt.Errorf("cannot update hypervisor status due to %w", err)
197189
}
198-
return ctrl.Result{RequeueAfter: shortRetryTime}, nil
190+
return ctrl.Result{}, nil
199191
}
200192

201193
// SetupWithManager sets up the controller with the Manager.
@@ -217,6 +209,6 @@ func (r *NodeDecommissionReconciler) SetupWithManager(mgr ctrl.Manager) error {
217209

218210
return ctrl.NewControllerManagedBy(mgr).
219211
Named(DecommissionControllerName).
220-
For(&corev1.Node{}).
212+
For(&kvmv1.Hypervisor{}).
221213
Complete(r)
222214
}

internal/controller/decomission_controller_test.go

Lines changed: 33 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"github.com/gophercloud/gophercloud/v2/testhelper/client"
2727
. "github.com/onsi/ginkgo/v2"
2828
. "github.com/onsi/gomega"
29-
corev1 "k8s.io/api/core/v1"
3029
"k8s.io/apimachinery/pkg/api/meta"
3130
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3231
"k8s.io/apimachinery/pkg/types"
@@ -68,9 +67,9 @@ const (
6867
var _ = Describe("Decommission Controller", func() {
6968
var (
7069
r *NodeDecommissionReconciler
71-
nodeName = types.NamespacedName{Name: hypervisorName}
70+
name = types.NamespacedName{Name: hypervisorName}
7271
reconcileReq = ctrl.Request{
73-
NamespacedName: nodeName,
72+
NamespacedName: name,
7473
}
7574
fakeServer testhelper.FakeServer
7675
)
@@ -91,90 +90,70 @@ var _ = Describe("Decommission Controller", func() {
9190
placementClient: client.ServiceClient(fakeServer),
9291
}
9392

94-
By("creating the namespace for the reconciler")
95-
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespaceName}}
96-
Expect(k8sclient.IgnoreAlreadyExists(k8sClient.Create(ctx, ns))).To(Succeed())
97-
98-
DeferCleanup(func(ctx SpecContext) {
99-
Expect(k8sClient.Delete(ctx, ns)).To(Succeed())
100-
})
101-
102-
By("creating the core resource for the Kind Node")
103-
node := &corev1.Node{
104-
ObjectMeta: metav1.ObjectMeta{
105-
Name: nodeName.Name,
106-
Labels: map[string]string{labelEvictionRequired: "true"},
107-
},
108-
}
109-
Expect(k8sClient.Create(ctx, node)).To(Succeed())
110-
DeferCleanup(func(ctx SpecContext) {
111-
node := &corev1.Node{}
112-
Expect(k8sclient.IgnoreNotFound(k8sClient.Get(ctx, nodeName, node))).To(Succeed())
113-
if len(node.Finalizers) > 0 {
114-
node.Finalizers = make([]string, 0)
115-
Expect(k8sClient.Update(ctx, node)).To(Succeed())
116-
}
117-
if node.Name != "" {
118-
Expect(k8sclient.IgnoreNotFound(k8sClient.Delete(ctx, node))).To(Succeed())
119-
}
120-
})
121-
122-
By("Create the hypervisor resource with lifecycle enabled")
93+
By("Create the hypervisor to be reconciled with lifecycle enabled")
12394
hypervisor := &kvmv1.Hypervisor{
12495
ObjectMeta: metav1.ObjectMeta{
125-
Name: nodeName.Name,
96+
Name: name.Name,
12697
},
12798
Spec: kvmv1.HypervisorSpec{
12899
LifecycleEnabled: true,
129100
},
130101
}
131102
Expect(k8sClient.Create(ctx, hypervisor)).To(Succeed())
132103
DeferCleanup(func(ctx SpecContext) {
133-
Expect(k8sClient.Delete(ctx, hypervisor)).To(Succeed())
104+
hypervisor := &kvmv1.Hypervisor{}
105+
Expect(k8sclient.IgnoreNotFound(k8sClient.Get(ctx, name, hypervisor))).To(Succeed())
106+
if len(hypervisor.Finalizers) > 0 {
107+
hypervisor.Finalizers = make([]string, 0)
108+
Expect(k8sClient.Update(ctx, hypervisor)).To(Succeed())
109+
}
110+
if hypervisor.Name != "" {
111+
Expect(k8sclient.IgnoreNotFound(k8sClient.Delete(ctx, hypervisor))).To(Succeed())
112+
}
134113
})
135114
})
136115

137-
Context("When reconciling a node", func() {
116+
Context("When reconciling a hypervisor", func() {
138117
It("should set the finalizer", func(ctx SpecContext) {
139118
By("reconciling the created resource")
140119
_, err := r.Reconcile(ctx, reconcileReq)
141120
Expect(err).NotTo(HaveOccurred())
142-
node := &corev1.Node{}
121+
hypervisor := &kvmv1.Hypervisor{}
143122

144-
Expect(k8sClient.Get(ctx, nodeName, node)).To(Succeed())
145-
Expect(node.Finalizers).To(ContainElement(decommissionFinalizerName))
123+
Expect(k8sClient.Get(ctx, name, hypervisor)).To(Succeed())
124+
Expect(hypervisor.Finalizers).To(ContainElement(decommissionFinalizerName))
146125
})
147126
})
148127

149-
Context("When terminating a node", func() {
128+
Context("When terminating a hypervisor", func() {
150129
JustBeforeEach(func(ctx SpecContext) {
151130
By("reconciling first reconciling the to add the finalizer")
152131
_, err := r.Reconcile(ctx, reconcileReq)
153132
Expect(err).NotTo(HaveOccurred())
154-
node := &corev1.Node{}
133+
hypervisor := &kvmv1.Hypervisor{}
155134

156-
Expect(k8sClient.Get(ctx, nodeName, node)).To(Succeed())
157-
Expect(node.Finalizers).To(ContainElement(decommissionFinalizerName))
135+
Expect(k8sClient.Get(ctx, name, hypervisor)).To(Succeed())
136+
Expect(hypervisor.Finalizers).To(ContainElement(decommissionFinalizerName))
158137

159-
By("and then terminating then node")
160-
node.Status.Conditions = append(node.Status.Conditions, corev1.NodeCondition{
138+
By("and then terminating then hypervisor")
139+
meta.SetStatusCondition(&hypervisor.Status.Conditions, metav1.Condition{
161140
Type: "Terminating",
162-
Status: corev1.ConditionTrue,
141+
Status: metav1.ConditionTrue,
163142
Reason: "dontcare",
164143
Message: "dontcare",
165144
})
166-
Expect(k8sClient.Status().Update(ctx, node)).To(Succeed())
167-
Expect(k8sClient.Delete(ctx, node)).To(Succeed())
168-
nodelist := &corev1.NodeList{}
169-
Expect(k8sClient.List(ctx, nodelist)).To(Succeed())
170-
Expect(nodelist.Items).NotTo(BeEmpty())
145+
Expect(k8sClient.Status().Update(ctx, hypervisor)).To(Succeed())
146+
Expect(k8sClient.Delete(ctx, hypervisor)).To(Succeed())
147+
hypervisorList := &kvmv1.HypervisorList{}
148+
Expect(k8sClient.List(ctx, hypervisorList)).To(Succeed())
149+
Expect(hypervisorList.Items).NotTo(BeEmpty())
171150
})
172151

173152
When("the hypervisor was set to ready", func() {
174153
getHypervisorsCalled := 0
175154
BeforeEach(func(ctx SpecContext) {
176155
hv := &kvmv1.Hypervisor{}
177-
Expect(k8sClient.Get(ctx, nodeName, hv)).To(Succeed())
156+
Expect(k8sClient.Get(ctx, name, hv)).To(Succeed())
178157
meta.SetStatusCondition(&hv.Status.Conditions,
179158
metav1.Condition{
180159
Type: kvmv1.ConditionTypeReady,
@@ -244,7 +223,7 @@ var _ = Describe("Decommission Controller", func() {
244223
_, err := r.Reconcile(ctx, reconcileReq)
245224
Expect(err).NotTo(HaveOccurred())
246225
hypervisor := &kvmv1.Hypervisor{}
247-
Expect(k8sClient.Get(ctx, nodeName, hypervisor)).To(Succeed())
226+
Expect(k8sClient.Get(ctx, name, hypervisor)).To(Succeed())
248227
Expect(hypervisor.Status.Conditions).To(ContainElement(
249228
SatisfyAll(
250229
HaveField("Type", kvmv1.ConditionTypeReady),
@@ -262,8 +241,8 @@ var _ = Describe("Decommission Controller", func() {
262241
}
263242
Expect(getHypervisorsCalled).To(BeNumerically(">", 0))
264243

265-
node := &corev1.Node{}
266-
err := k8sClient.Get(ctx, nodeName, node)
244+
hypervisor := &kvmv1.Hypervisor{}
245+
err := k8sClient.Get(ctx, name, hypervisor)
267246
Expect(err).To(HaveOccurred())
268247
Expect(k8sclient.IgnoreNotFound(err)).To(Succeed())
269248
})

0 commit comments

Comments
 (0)