Skip to content

Commit f39b475

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 103055b commit f39b475

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
@@ -25,7 +25,6 @@ import (
2525
"github.com/gophercloud/gophercloud/v2/testhelper/client"
2626
. "github.com/onsi/ginkgo/v2"
2727
. "github.com/onsi/gomega"
28-
corev1 "k8s.io/api/core/v1"
2928
"k8s.io/apimachinery/pkg/api/meta"
3029
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3130
"k8s.io/apimachinery/pkg/types"
@@ -67,9 +66,9 @@ const (
6766
var _ = Describe("Decommission Controller", func() {
6867
var (
6968
r *NodeDecommissionReconciler
70-
nodeName = types.NamespacedName{Name: hypervisorName}
69+
name = types.NamespacedName{Name: hypervisorName}
7170
reconcileReq = ctrl.Request{
72-
NamespacedName: nodeName,
71+
NamespacedName: name,
7372
}
7473
fakeServer testhelper.FakeServer
7574
)
@@ -90,90 +89,70 @@ var _ = Describe("Decommission Controller", func() {
9089
placementClient: client.ServiceClient(fakeServer),
9190
}
9291

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

136-
Context("When reconciling a node", func() {
115+
Context("When reconciling a hypervisor", func() {
137116
It("should set the finalizer", func(ctx SpecContext) {
138117
By("reconciling the created resource")
139118
_, err := r.Reconcile(ctx, reconcileReq)
140119
Expect(err).NotTo(HaveOccurred())
141-
node := &corev1.Node{}
120+
hypervisor := &kvmv1.Hypervisor{}
142121

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

148-
Context("When terminating a node", func() {
127+
Context("When terminating a hypervisor", func() {
149128
JustBeforeEach(func(ctx SpecContext) {
150129
By("reconciling first reconciling the to add the finalizer")
151130
_, err := r.Reconcile(ctx, reconcileReq)
152131
Expect(err).NotTo(HaveOccurred())
153-
node := &corev1.Node{}
132+
hypervisor := &kvmv1.Hypervisor{}
154133

155-
Expect(k8sClient.Get(ctx, nodeName, node)).To(Succeed())
156-
Expect(node.Finalizers).To(ContainElement(decommissionFinalizerName))
134+
Expect(k8sClient.Get(ctx, name, hypervisor)).To(Succeed())
135+
Expect(hypervisor.Finalizers).To(ContainElement(decommissionFinalizerName))
157136

158-
By("and then terminating then node")
159-
node.Status.Conditions = append(node.Status.Conditions, corev1.NodeCondition{
137+
By("and then terminating then hypervisor")
138+
meta.SetStatusCondition(&hypervisor.Status.Conditions, metav1.Condition{
160139
Type: "Terminating",
161-
Status: corev1.ConditionTrue,
140+
Status: metav1.ConditionTrue,
162141
Reason: "dontcare",
163142
Message: "dontcare",
164143
})
165-
Expect(k8sClient.Status().Update(ctx, node)).To(Succeed())
166-
Expect(k8sClient.Delete(ctx, node)).To(Succeed())
167-
nodelist := &corev1.NodeList{}
168-
Expect(k8sClient.List(ctx, nodelist)).To(Succeed())
169-
Expect(nodelist.Items).NotTo(BeEmpty())
144+
Expect(k8sClient.Status().Update(ctx, hypervisor)).To(Succeed())
145+
Expect(k8sClient.Delete(ctx, hypervisor)).To(Succeed())
146+
hypervisorList := &kvmv1.HypervisorList{}
147+
Expect(k8sClient.List(ctx, hypervisorList)).To(Succeed())
148+
Expect(hypervisorList.Items).NotTo(BeEmpty())
170149
})
171150

172151
When("the hypervisor was set to ready", func() {
173152
getHypervisorsCalled := 0
174153
BeforeEach(func(ctx SpecContext) {
175154
hv := &kvmv1.Hypervisor{}
176-
Expect(k8sClient.Get(ctx, nodeName, hv)).To(Succeed())
155+
Expect(k8sClient.Get(ctx, name, hv)).To(Succeed())
177156
meta.SetStatusCondition(&hv.Status.Conditions,
178157
metav1.Condition{
179158
Type: kvmv1.ConditionTypeReady,
@@ -243,7 +222,7 @@ var _ = Describe("Decommission Controller", func() {
243222
_, err := r.Reconcile(ctx, reconcileReq)
244223
Expect(err).NotTo(HaveOccurred())
245224
hypervisor := &kvmv1.Hypervisor{}
246-
Expect(k8sClient.Get(ctx, nodeName, hypervisor)).To(Succeed())
225+
Expect(k8sClient.Get(ctx, name, hypervisor)).To(Succeed())
247226
Expect(hypervisor.Status.Conditions).To(ContainElement(
248227
SatisfyAll(
249228
HaveField("Type", kvmv1.ConditionTypeReady),
@@ -261,8 +240,8 @@ var _ = Describe("Decommission Controller", func() {
261240
}
262241
Expect(getHypervisorsCalled).To(BeNumerically(">", 0))
263242

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

0 commit comments

Comments
 (0)