Skip to content

Commit bc01b64

Browse files
notandyfwiesel
authored andcommitted
[hypervisor-controller] one-time transport aggregate and custom traits
Previous commit changed the aggregate- and custom-traits handling to the hypervisor-crd. This removed the persistence over new gardener rollouts. This PR reintroduces the persistence by transporting the aggregates/traits from server labels/annotationts to the new hypervisor CRO. Also changes the existing code from using `controllerutil.CreateOrPatch` to explictly checking if the objects is already there (and only updating status in this case). Manual overrides to the CRO would have been overwritten else. controllerutil.CreateOrPatch behaves unpredicted in a way that it won't update status for newly created CRO, only for existing ones.
1 parent 5067c2d commit bc01b64

2 files changed

Lines changed: 80 additions & 40 deletions

File tree

internal/controller/hypervisor_controller.go

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ package controller
2020
import (
2121
"context"
2222
"fmt"
23+
"slices"
24+
"strings"
2325

2426
corev1 "k8s.io/api/core/v1"
27+
"k8s.io/apimachinery/pkg/api/errors"
2528
"k8s.io/apimachinery/pkg/api/meta"
2629
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2730
"k8s.io/apimachinery/pkg/labels"
@@ -36,7 +39,9 @@ import (
3639
)
3740

3841
const (
39-
labelLifecycleMode = "cobaltcore.cloud.sap/node-hypervisor-lifecycle"
42+
labelLifecycleMode = "cobaltcore.cloud.sap/node-hypervisor-lifecycle"
43+
annotationAggregates = "nova.openstack.cloud.sap/aggregates"
44+
annotationCustomTraits = "nova.openstack.cloud.sap/custom-traits"
4045
)
4146

4247
var transferLabels = []string{
@@ -80,24 +85,14 @@ func (hv *HypervisorController) Reconcile(ctx context.Context, req ctrl.Request)
8085
},
8186
}
8287

83-
// Ensure corresponding hypervisor exists or update it
84-
op, err := controllerutil.CreateOrPatch(ctx, hv.Client, hypervisor, func() error {
85-
// Transfer Labels
86-
for _, label := range transferLabels {
87-
if nodeLabels.Has(label) {
88-
hypervisor.Labels[label] = nodeLabels.Get(label)
89-
}
90-
}
91-
92-
if nodeLabels.Has(labelLifecycleMode) {
93-
hypervisor.Spec.LifecycleEnabled = true
94-
hypervisor.Spec.SkipTests = nodeLabels.Get(labelLifecycleMode) == "skip-tests"
88+
// Check if hypervisor already exists
89+
if err := hv.Get(ctx, k8sclient.ObjectKeyFromObject(hypervisor), hypervisor); err != nil {
90+
if !errors.IsNotFound(err) {
91+
return ctrl.Result{}, fmt.Errorf("failed to get hypervisor: %w", err)
9592
}
96-
97-
if err := controllerutil.SetControllerReference(node, hypervisor, hv.Scheme); err != nil {
98-
return fmt.Errorf("failed setting controller reference: %w", err)
99-
}
100-
93+
// continue with creation
94+
} else {
95+
// update Status if needed
10196
nodeTerminationCondition := FindNodeStatusCondition(node.Status.Conditions, "Terminating")
10297
if nodeTerminationCondition != nil && nodeTerminationCondition.Status == corev1.ConditionTrue {
10398
// Node might be terminating, propagate condition to hypervisor
@@ -113,16 +108,60 @@ func (hv *HypervisorController) Reconcile(ctx context.Context, req ctrl.Request)
113108
Reason: nodeTerminationCondition.Reason,
114109
Message: nodeTerminationCondition.Message,
115110
})
111+
return ctrl.Result{}, hv.Status().Update(ctx, hypervisor)
116112
}
117113

118-
return nil
119-
})
120-
if err != nil {
121-
log.Error(err, "Hypervisor reconcile failed")
122-
} else if op != controllerutil.OperationResultNone {
123-
log.Info("Hypervisor successfully reconciled", "operation", op)
114+
return ctrl.Result{}, nil
115+
}
116+
117+
// transfer labels
118+
for _, label := range transferLabels {
119+
if nodeLabels.Has(label) {
120+
hypervisor.Labels[label] = nodeLabels.Get(label)
121+
}
122+
}
123+
124+
// transport lifecycle label to hypervisor spec
125+
if nodeLabels.Has(labelLifecycleMode) {
126+
hypervisor.Spec.LifecycleEnabled = true
127+
hypervisor.Spec.SkipTests = nodeLabels.Get(labelLifecycleMode) == "skip-tests"
128+
}
129+
130+
// transport aggregates annotation to hypervisor spec
131+
if aggregates, found := node.Annotations[annotationAggregates]; found {
132+
// split aggregates string
133+
hypervisor.Spec.Aggregates = slices.Collect(func(yield func(string) bool) {
134+
for _, agg := range strings.Split(aggregates, ",") {
135+
trimmed := strings.TrimSpace(agg)
136+
if trimmed != "" && yield(trimmed) {
137+
return
138+
}
139+
}
140+
})
141+
}
142+
143+
// transport custom traits annotation to hypervisor spec
144+
if customTraits, found := node.Annotations[annotationCustomTraits]; found {
145+
// split custom traits string
146+
hypervisor.Spec.CustomTraits = slices.Collect(func(yield func(string) bool) {
147+
for _, trait := range strings.Split(customTraits, ",") {
148+
trimmed := strings.TrimSpace(trait)
149+
if trimmed != "" && yield(trimmed) {
150+
return
151+
}
152+
}
153+
})
154+
}
155+
156+
if err := controllerutil.SetControllerReference(node, hypervisor, hv.Scheme); err != nil {
157+
return ctrl.Result{}, fmt.Errorf("failed setting controller reference: %w", err)
158+
}
159+
160+
if err := hv.Create(ctx, hypervisor); err != nil {
161+
return ctrl.Result{}, err
124162
}
125163

164+
log.Info("Created Hypervisor resource", "hypervisor", hypervisor.Name)
126165
return ctrl.Result{}, nil
127166
}
128167

internal/controller/hypervisor_controller_test.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ var _ = Describe("Hypervisor Controller", func() {
4747
// pregenerate the resource
4848
resource = &corev1.Node{
4949
ObjectMeta: metav1.ObjectMeta{
50-
Name: "node-test",
50+
Name: "other-node",
5151
Labels: map[string]string{corev1.LabelTopologyZone: "test-zone"}, //nolint:goconst
5252
},
5353
}
@@ -57,22 +57,24 @@ var _ = Describe("Hypervisor Controller", func() {
5757
node := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: resource.Name}}
5858
By("Cleanup the specific node")
5959
Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, node))).To(Succeed())
60+
61+
hypervisor := &kvmv1.Hypervisor{ObjectMeta: metav1.ObjectMeta{Name: resource.Name}}
62+
By("Cleanup the specific hypervisor")
63+
Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, hypervisor))).To(Succeed())
6064
})
6165

6266
Context("When reconciling a node", func() {
63-
BeforeEach(func() {
64-
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
65-
})
6667

6768
It("should successfully reconcile the node", func(ctx SpecContext) {
69+
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
70+
6871
By("Reconciling the created resource")
6972
_, err := hypervisorController.Reconcile(ctx, ctrl.Request{
7073
NamespacedName: types.NamespacedName{Name: resource.Name},
7174
})
7275
Expect(err).NotTo(HaveOccurred())
73-
})
7476

75-
It("should have created the Hypervisor resource", func(ctx SpecContext) {
77+
By("should have created the Hypervisor resource")
7678
// Get the Hypervisor resource
7779
hypervisor := &kvmv1.Hypervisor{}
7880
hypervisorName := types.NamespacedName{Name: resource.Name}
@@ -84,7 +86,7 @@ var _ = Describe("Hypervisor Controller", func() {
8486
})
8587

8688
Context("When reconciling a terminating node", func() {
87-
BeforeEach(func(ctx SpecContext) {
89+
It("should successfully reconcile the terminating node", func(ctx SpecContext) {
8890
// Mark the node as terminating
8991
resource.DeletionTimestamp = &metav1.Time{}
9092
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
@@ -97,17 +99,16 @@ var _ = Describe("Hypervisor Controller", func() {
9799
Message: "Node is terminating",
98100
})
99101
Expect(k8sClient.Status().Update(ctx, resource)).To(Succeed())
100-
})
101102

102-
It("should successfully reconcile the terminating node", func(ctx SpecContext) {
103103
By("Reconciling the created resource")
104-
_, err := hypervisorController.Reconcile(ctx, ctrl.Request{
105-
NamespacedName: types.NamespacedName{Name: resource.Name},
106-
})
107-
Expect(err).NotTo(HaveOccurred())
108-
})
109-
110-
It("should have set the terminating condition on the Hypervisor resource", func(ctx SpecContext) {
104+
for i := 0; i < 2; i++ {
105+
_, err := hypervisorController.Reconcile(ctx, ctrl.Request{
106+
NamespacedName: types.NamespacedName{Name: resource.Name},
107+
})
108+
Expect(err).NotTo(HaveOccurred())
109+
}
110+
111+
By("should have set the terminating condition on the Hypervisor resource")
111112
// Get the Hypervisor resource
112113
hypervisor := &kvmv1.Hypervisor{}
113114
hypervisorName := types.NamespacedName{Name: resource.Name}

0 commit comments

Comments
 (0)