Skip to content

Commit 35f8cad

Browse files
committed
Traits: Only reconcile after onboarding and before decomissioning
As with 6bb6c6 for the aggregates: Traits set by the operator may conflict with the testing, and decomissioning may make the resource-provider disappear. To avoid these controllers fighting, let's limit this controller to the actual active lifetime of the hypervisor. Unfortunately, that means we have to reconcile on every status update.
1 parent 7f23138 commit 35f8cad

File tree

2 files changed

+112
-32
lines changed

2 files changed

+112
-32
lines changed

internal/controller/traits_controller.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import (
3030
ctrl "sigs.k8s.io/controller-runtime"
3131
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
3232
logger "sigs.k8s.io/controller-runtime/pkg/log"
33-
"sigs.k8s.io/controller-runtime/pkg/predicate"
3433

3534
"github.com/gophercloud/gophercloud/v2"
3635
"github.com/gophercloud/gophercloud/v2/openstack/placement/v1/resourceproviders"
@@ -73,6 +72,11 @@ func (tc *TraitsController) Reconcile(ctx context.Context, req ctrl.Request) (ct
7372
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
7473
}
7574

75+
if !meta.IsStatusConditionFalse(hv.Status.Conditions, ConditionTypeOnboarding) ||
76+
meta.IsStatusConditionTrue(hv.Status.Conditions, kvmv1.ConditionTypeTerminating) {
77+
return ctrl.Result{}, nil
78+
}
79+
7680
customTraitsApplied := slices.Collect(func(yield func(string) bool) {
7781
for _, trait := range hv.Status.Traits {
7882
if strings.HasPrefix(trait, customPrefix) && !yield(trait) {
@@ -178,6 +182,5 @@ func (tc *TraitsController) SetupWithManager(mgr ctrl.Manager) error {
178182
return ctrl.NewControllerManagedBy(mgr).
179183
Named(TraitsControllerName).
180184
For(&kvmv1.Hypervisor{}).
181-
WithEventFilter(predicate.GenerationChangedPredicate{}).
182185
Complete(tc)
183186
}

internal/controller/traits_controller_test.go

Lines changed: 107 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,16 @@ import (
3535
)
3636

3737
var _ = Describe("TraitsController", func() {
38-
var (
39-
tc *TraitsController
40-
fakeServer testhelper.FakeServer
41-
)
42-
43-
const TraitsBody = `
38+
const (
39+
TraitsBody = `
4440
{
4541
"resource_provider_generation": 1,
4642
"traits": [
4743
"CUSTOM_FOO",
4844
"HW_CPU_X86_VMX"
4945
]
5046
}`
51-
const TraitsBodyUpdated = `
47+
TraitsBodyUpdated = `
5248
{
5349
"resource_provider_generation": 2,
5450
"traits": [
@@ -57,12 +53,20 @@ var _ = Describe("TraitsController", func() {
5753
"HW_CPU_X86_VMX"
5854
]
5955
}`
56+
)
57+
58+
var (
59+
tc *TraitsController
60+
fakeServer testhelper.FakeServer
61+
hypervisorName = types.NamespacedName{Name: "hv-test"}
62+
)
6063

6164
// Setup and teardown
6265

6366
BeforeEach(func(ctx context.Context) {
6467
By("Setting up the OpenStack http mock server")
6568
fakeServer = testhelper.SetupHTTP()
69+
DeferCleanup(fakeServer.Teardown)
6670

6771
By("Creating the TraitsController")
6872
tc = &TraitsController{
@@ -74,39 +78,26 @@ var _ = Describe("TraitsController", func() {
7478
By("Creating a Hypervisor resource")
7579
hypervisor := &kvmv1.Hypervisor{
7680
ObjectMeta: v1.ObjectMeta{
77-
Name: "hv-test",
78-
Namespace: "default",
81+
Name: hypervisorName.Name,
7982
},
8083
Spec: kvmv1.HypervisorSpec{
8184
LifecycleEnabled: true,
8285
CustomTraits: []string{"CUSTOM_FOO", "CUSTOM_BAR"},
8386
},
8487
}
8588
Expect(k8sClient.Create(ctx, hypervisor)).To(Succeed())
86-
// Ensure the resource status is being updated
87-
meta.SetStatusCondition(&hypervisor.Status.Conditions, v1.Condition{
88-
Type: kvmv1.ConditionTypeReady,
89-
Status: v1.ConditionTrue,
90-
Reason: "UnitTest",
91-
})
92-
hypervisor.Status.Traits = []string{"CUSTOM_FOO", "HW_CPU_X86_VMX"}
93-
hypervisor.Status.HypervisorID = "1234"
94-
Expect(k8sClient.Status().Update(ctx, hypervisor)).To(Succeed())
95-
})
96-
97-
AfterEach(func() {
98-
By("Deleting the Hypervisor resource")
99-
hypervisor := &kvmv1.Hypervisor{}
100-
Expect(tc.Client.Get(ctx, types.NamespacedName{Name: "hv-test", Namespace: "default"}, hypervisor)).To(Succeed())
101-
Expect(tc.Client.Delete(ctx, hypervisor)).To(Succeed())
10289

103-
By("Tearing down the OpenStack http mock server")
104-
fakeServer.Teardown()
90+
DeferCleanup(func(ctx context.Context) {
91+
By("Deleting the Hypervisor resource")
92+
hypervisor := &kvmv1.Hypervisor{}
93+
Expect(tc.Client.Get(ctx, hypervisorName, hypervisor)).To(Succeed())
94+
Expect(tc.Client.Delete(ctx, hypervisor)).To(Succeed())
95+
})
10596
})
10697

10798
// Tests
10899

109-
Context("Reconcile", func() {
100+
Context("Reconcile after onboarding before decomissioning", func() {
110101
BeforeEach(func() {
111102
// Mock resourceproviders.GetTraits
112103
fakeServer.Mux.HandleFunc("GET /resource_providers/1234/traits", func(w http.ResponseWriter, r *http.Request) {
@@ -142,17 +133,103 @@ var _ = Describe("TraitsController", func() {
142133
_, err = fmt.Fprint(w, TraitsBodyUpdated)
143134
Expect(err).NotTo(HaveOccurred())
144135
})
136+
137+
hypervisor := &kvmv1.Hypervisor{}
138+
Expect(k8sClient.Get(ctx, hypervisorName, hypervisor)).To(Succeed())
139+
meta.SetStatusCondition(&hypervisor.Status.Conditions, v1.Condition{
140+
Type: ConditionTypeOnboarding,
141+
Status: v1.ConditionFalse,
142+
Reason: "UnitTest",
143+
})
144+
hypervisor.Status.HypervisorID = "1234"
145+
hypervisor.Status.Traits = []string{"CUSTOM_FOO", "HW_CPU_X86_VMX"}
146+
Expect(k8sClient.Status().Update(ctx, hypervisor)).To(Succeed())
145147
})
146148

147149
It("should update traits and set status condition when traits differ", func() {
148-
req := ctrl.Request{NamespacedName: types.NamespacedName{Name: "hv-test", Namespace: "default"}}
150+
req := ctrl.Request{NamespacedName: hypervisorName}
149151
_, err := tc.Reconcile(ctx, req)
150152
Expect(err).NotTo(HaveOccurred())
151153

152154
updated := &kvmv1.Hypervisor{}
153-
Expect(tc.Client.Get(ctx, req.NamespacedName, updated)).To(Succeed())
155+
Expect(tc.Client.Get(ctx, hypervisorName, updated)).To(Succeed())
154156
Expect(updated.Status.Traits).To(ContainElements("CUSTOM_FOO", "CUSTOM_BAR", "HW_CPU_X86_VMX"))
155157
Expect(meta.IsStatusConditionTrue(updated.Status.Conditions, ConditionTypeTraitsUpdated)).To(BeTrue())
156158
})
157159
})
160+
161+
Context("Reconcile before onboarding", func() {
162+
BeforeEach(func() {
163+
// Mock resourceproviders.GetTraits
164+
fakeServer.Mux.HandleFunc("GET /resource_providers/1234/traits", func(w http.ResponseWriter, r *http.Request) {
165+
defer GinkgoRecover()
166+
Fail("should not be called")
167+
})
168+
// Mock resourceproviders.UpdateTraits
169+
fakeServer.Mux.HandleFunc("PUT /resource_providers/1234/traits", func(w http.ResponseWriter, r *http.Request) {
170+
defer GinkgoRecover()
171+
Fail("should not be called")
172+
})
173+
174+
hypervisor := &kvmv1.Hypervisor{}
175+
Expect(k8sClient.Get(ctx, hypervisorName, hypervisor)).To(Succeed())
176+
hypervisor.Status.HypervisorID = "1234"
177+
hypervisor.Status.Traits = []string{"CUSTOM_FOO", "HW_CPU_X86_VMX"}
178+
Expect(k8sClient.Status().Update(ctx, hypervisor)).To(Succeed())
179+
})
180+
181+
It("should not update traits", func() {
182+
req := ctrl.Request{NamespacedName: hypervisorName}
183+
_, err := tc.Reconcile(ctx, req)
184+
Expect(err).NotTo(HaveOccurred())
185+
186+
updated := &kvmv1.Hypervisor{}
187+
Expect(tc.Client.Get(ctx, hypervisorName, updated)).To(Succeed())
188+
Expect(updated.Status.Traits).NotTo(ContainElements("CUSTOM_FOO", "CUSTOM_BAR", "HW_CPU_X86_VMX"))
189+
Expect(meta.IsStatusConditionTrue(updated.Status.Conditions, ConditionTypeTraitsUpdated)).To(BeFalse())
190+
})
191+
})
192+
193+
Context("Reconcile when terminating", func() {
194+
BeforeEach(func() {
195+
// Mock resourceproviders.GetTraits
196+
fakeServer.Mux.HandleFunc("GET /resource_providers/1234/traits", func(w http.ResponseWriter, r *http.Request) {
197+
defer GinkgoRecover()
198+
Fail("should not be called")
199+
})
200+
// Mock resourceproviders.UpdateTraits
201+
fakeServer.Mux.HandleFunc("PUT /resource_providers/1234/traits", func(w http.ResponseWriter, r *http.Request) {
202+
defer GinkgoRecover()
203+
Fail("should not be called")
204+
})
205+
206+
hypervisor := &kvmv1.Hypervisor{}
207+
Expect(k8sClient.Get(ctx, hypervisorName, hypervisor)).To(Succeed())
208+
meta.SetStatusCondition(&hypervisor.Status.Conditions, v1.Condition{
209+
Type: ConditionTypeOnboarding,
210+
Status: v1.ConditionFalse,
211+
Reason: "UnitTest",
212+
})
213+
meta.SetStatusCondition(&hypervisor.Status.Conditions, v1.Condition{
214+
Type: kvmv1.ConditionTypeTerminating,
215+
Status: v1.ConditionTrue,
216+
Reason: "UnitTest",
217+
})
218+
hypervisor.Status.Traits = []string{"CUSTOM_FOO", "HW_CPU_X86_VMX"}
219+
hypervisor.Status.HypervisorID = "1234"
220+
Expect(k8sClient.Status().Update(ctx, hypervisor)).To(Succeed())
221+
222+
})
223+
224+
It("should not update traits", func() {
225+
req := ctrl.Request{NamespacedName: hypervisorName}
226+
_, err := tc.Reconcile(ctx, req)
227+
Expect(err).NotTo(HaveOccurred())
228+
229+
updated := &kvmv1.Hypervisor{}
230+
Expect(tc.Client.Get(ctx, hypervisorName, updated)).To(Succeed())
231+
Expect(updated.Status.Traits).NotTo(ContainElements("CUSTOM_FOO", "CUSTOM_BAR", "HW_CPU_X86_VMX"))
232+
Expect(meta.IsStatusConditionTrue(updated.Status.Conditions, ConditionTypeTraitsUpdated)).To(BeFalse())
233+
})
234+
})
158235
})

0 commit comments

Comments
 (0)