Skip to content

Commit 31236ea

Browse files
DanNiEShlarsksclaude
committed
Scope HostLease watch to target namespace via cache instead of predicate
Use controller-runtime cache configuration to restrict the HostLease watch to a single namespace (from HOSTLEASE_NAMESPACE env var) instead of using a namespace predicate. This reduces memory usage and event notifications since the controller no longer watches HostLease resources across all namespaces. Also adds RBAC for pods and configures the HOSTLEASE_NAMESPACE env var in the manager deployment via downward API. Co-Authored-By: Lars Kellogg-Stedman <lars@redhat.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e089d70 commit 31236ea

4 files changed

Lines changed: 65 additions & 30 deletions

File tree

cmd/main.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package main
1919
import (
2020
"crypto/tls"
2121
"flag"
22+
"fmt"
2223
"os"
2324
"path/filepath"
2425

@@ -30,7 +31,9 @@ import (
3031
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
3132
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
3233
ctrl "sigs.k8s.io/controller-runtime"
34+
"sigs.k8s.io/controller-runtime/pkg/cache"
3335
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
36+
"sigs.k8s.io/controller-runtime/pkg/client"
3437
"sigs.k8s.io/controller-runtime/pkg/healthz"
3538
"sigs.k8s.io/controller-runtime/pkg/log/zap"
3639
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
@@ -43,6 +46,8 @@ import (
4346
// +kubebuilder:scaffold:imports
4447
)
4548

49+
const envHostLeaseNamespace = "HOSTLEASE_NAMESPACE"
50+
4651
var (
4752
scheme = runtime.NewScheme()
4853
setupLog = ctrl.Log.WithName("setup")
@@ -178,13 +183,28 @@ func main() {
178183
})
179184
}
180185

186+
controllerNamespace := os.Getenv(envHostLeaseNamespace)
187+
if controllerNamespace == "" {
188+
setupLog.Error(nil, fmt.Sprintf("%s environment variable must be set", envHostLeaseNamespace))
189+
os.Exit(1)
190+
}
191+
181192
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
182193
Scheme: scheme,
183194
Metrics: metricsServerOptions,
184195
WebhookServer: webhookServer,
185196
HealthProbeBindAddress: probeAddr,
186197
LeaderElection: enableLeaderElection,
187198
LeaderElectionID: "6f134d35.osac.openshift.io",
199+
Cache: cache.Options{
200+
ByObject: map[client.Object]cache.ByObject{
201+
&v1alpha1.HostLease{}: {
202+
Namespaces: map[string]cache.Config{
203+
controllerNamespace: {},
204+
},
205+
},
206+
},
207+
},
188208
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
189209
// when the Manager ends. This requires the binary to immediately end when the
190210
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly

config/manager/manager.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ spec:
6565
- --health-probe-bind-address=:8081
6666
image: controller:latest
6767
name: manager
68+
env:
69+
- name: HOSTLEASE_NAMESPACE
70+
valueFrom:
71+
fieldRef:
72+
fieldPath: metadata.namespace
6873
ports: []
6974
securityContext:
7075
allowPrivilegeEscalation: false

config/rbac/role.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ rules:
1111
verbs: ["get", "list", "watch"]
1212
- apiGroups: ["osac.openshift.io"]
1313
resources: ["hostleases"]
14-
verbs: ["get", "list", "watch"]
14+
verbs: ["get", "list", "watch", "update", "patch"]
1515
- apiGroups: ["osac.openshift.io"]
1616
resources: ["hostleases/status"]
1717
verbs: ["get", "update", "patch"]
18+
- apiGroups: ["osac.openshift.io"]
19+
resources: ["hostleases/finalizers"]
20+
verbs: ["update", "patch"]

internal/controller/hostlease_controller_test.go

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ import (
4242
logf "sigs.k8s.io/controller-runtime/pkg/log"
4343
)
4444

45+
const (
46+
testPowerOff = "power off"
47+
testPowerOn = "power on"
48+
testNodeID = "node-1"
49+
testNamespace = "default"
50+
)
51+
4552
// mockIronicClient implements ironic.NodeClient for testing.
4653
type mockIronicClient struct {
4754
getNodeFunc func(ctx context.Context, nodeID string) (*nodes.Node, error)
@@ -53,7 +60,7 @@ func (m *mockIronicClient) GetNode(ctx context.Context, nodeID string) (*nodes.N
5360
if m.getNodeFunc != nil {
5461
return m.getNodeFunc(ctx, nodeID)
5562
}
56-
return &nodes.Node{PowerState: "power off"}, nil
63+
return &nodes.Node{PowerState: testPowerOff}, nil
5764
}
5865

5966
func (m *mockIronicClient) SetPowerState(ctx context.Context, nodeID string, target ironic.TargetPowerState) error {
@@ -112,7 +119,7 @@ var _ = Describe("HostLeaseReconciler", func() {
112119
hostLease := &v1alpha1.HostLease{
113120
Spec: v1alpha1.HostLeaseSpec{
114121
ExternalHostID: "",
115-
HostClass: "openstack",
122+
HostClass: hostClass,
116123
},
117124
}
118125
Expect(reconciler.validateOpenStackHost(hostLease, log)).To(BeFalse())
@@ -121,7 +128,7 @@ var _ = Describe("HostLeaseReconciler", func() {
121128
It("should return false when HostClass does not match", func() {
122129
hostLease := &v1alpha1.HostLease{
123130
Spec: v1alpha1.HostLeaseSpec{
124-
ExternalHostID: "node-1",
131+
ExternalHostID: testNodeID,
125132
HostClass: "other",
126133
},
127134
}
@@ -131,8 +138,8 @@ var _ = Describe("HostLeaseReconciler", func() {
131138
It("should return true when ExternalHostID and HostClass are valid", func() {
132139
hostLease := &v1alpha1.HostLease{
133140
Spec: v1alpha1.HostLeaseSpec{
134-
ExternalHostID: "node-1",
135-
HostClass: "openstack",
141+
ExternalHostID: testNodeID,
142+
HostClass: hostClass,
136143
},
137144
}
138145
Expect(reconciler.validateOpenStackHost(hostLease, log)).To(BeTrue())
@@ -149,15 +156,15 @@ var _ = Describe("HostLeaseReconciler", func() {
149156
ctx = context.Background()
150157
hostLease = &v1alpha1.HostLease{
151158
Spec: v1alpha1.HostLeaseSpec{
152-
ExternalHostID: "node-1",
153-
HostClass: "openstack",
159+
ExternalHostID: testNodeID,
160+
HostClass: hostClass,
154161
},
155162
}
156163
})
157164

158165
It("should power on when desired on and currently off", func() {
159166
hostLease.Spec.PoweredOn = boolPtr(true)
160-
node := &nodes.Node{PowerState: "power off"}
167+
node := &nodes.Node{PowerState: testPowerOff}
161168

162169
var calledTarget ironic.TargetPowerState
163170
mockIronic.setPowerStateFunc = func(ctx context.Context, nodeID string, target ironic.TargetPowerState) error {
@@ -172,7 +179,7 @@ var _ = Describe("HostLeaseReconciler", func() {
172179

173180
It("should power off when desired off and currently on", func() {
174181
hostLease.Spec.PoweredOn = boolPtr(false)
175-
node := &nodes.Node{PowerState: "power on"}
182+
node := &nodes.Node{PowerState: testPowerOn}
176183

177184
var calledTarget ironic.TargetPowerState
178185
mockIronic.setPowerStateFunc = func(ctx context.Context, nodeID string, target ironic.TargetPowerState) error {
@@ -187,7 +194,7 @@ var _ = Describe("HostLeaseReconciler", func() {
187194

188195
It("should not call SetPowerState when power state already matches (on)", func() {
189196
hostLease.Spec.PoweredOn = boolPtr(true)
190-
node := &nodes.Node{PowerState: "power on"}
197+
node := &nodes.Node{PowerState: testPowerOn}
191198

192199
called := false
193200
mockIronic.setPowerStateFunc = func(ctx context.Context, nodeID string, target ironic.TargetPowerState) error {
@@ -202,7 +209,7 @@ var _ = Describe("HostLeaseReconciler", func() {
202209

203210
It("should not call SetPowerState when power state already matches (off)", func() {
204211
hostLease.Spec.PoweredOn = boolPtr(false)
205-
node := &nodes.Node{PowerState: "power off"}
212+
node := &nodes.Node{PowerState: testPowerOff}
206213

207214
called := false
208215
mockIronic.setPowerStateFunc = func(ctx context.Context, nodeID string, target ironic.TargetPowerState) error {
@@ -217,15 +224,15 @@ var _ = Describe("HostLeaseReconciler", func() {
217224

218225
It("should not be called when PoweredOn is nil (guarded by Reconcile)", func() {
219226
hostLease.Spec.PoweredOn = boolPtr(true)
220-
node := &nodes.Node{PowerState: "power on"}
227+
node := &nodes.Node{PowerState: testPowerOn}
221228

222229
err := reconciler.reconcilePower(ctx, hostLease, node, log)
223230
Expect(err).NotTo(HaveOccurred())
224231
})
225232

226233
It("should skip SetPowerState when node is transitioning", func() {
227234
hostLease.Spec.PoweredOn = boolPtr(true)
228-
node := &nodes.Node{PowerState: "power off", TargetPowerState: "power on"}
235+
node := &nodes.Node{PowerState: testPowerOff, TargetPowerState: testPowerOn}
229236

230237
called := false
231238
mockIronic.setPowerStateFunc = func(ctx context.Context, nodeID string, target ironic.TargetPowerState) error {
@@ -240,7 +247,7 @@ var _ = Describe("HostLeaseReconciler", func() {
240247

241248
It("should return error when SetPowerState fails on power on", func() {
242249
hostLease.Spec.PoweredOn = boolPtr(true)
243-
node := &nodes.Node{PowerState: "power off"}
250+
node := &nodes.Node{PowerState: testPowerOff}
244251

245252
mockIronic.setPowerStateFunc = func(ctx context.Context, nodeID string, target ironic.TargetPowerState) error {
246253
return errors.New("ironic API error")
@@ -253,7 +260,7 @@ var _ = Describe("HostLeaseReconciler", func() {
253260

254261
It("should return error when SetPowerState fails on power off", func() {
255262
hostLease.Spec.PoweredOn = boolPtr(false)
256-
node := &nodes.Node{PowerState: "power on"}
263+
node := &nodes.Node{PowerState: testPowerOn}
257264

258265
mockIronic.setPowerStateFunc = func(ctx context.Context, nodeID string, target ironic.TargetPowerState) error {
259266
return errors.New("ironic API error")
@@ -271,7 +278,7 @@ var _ = Describe("HostLeaseReconciler", func() {
271278
hostLease := &v1alpha1.HostLease{
272279
ObjectMeta: metav1.ObjectMeta{
273280
Name: "hostlease-delete",
274-
Namespace: "default",
281+
Namespace: testNamespace,
275282
Finalizers: []string{hostLeaseFinalizer},
276283
DeletionTimestamp: &now,
277284
},
@@ -324,7 +331,7 @@ var _ = Describe("HostLeaseReconciler", func() {
324331
hostLease := &v1alpha1.HostLease{
325332
ObjectMeta: metav1.ObjectMeta{
326333
Name: "hostlease-delete-non-openstack",
327-
Namespace: "default",
334+
Namespace: testNamespace,
328335
Finalizers: []string{hostLeaseFinalizer},
329336
DeletionTimestamp: &now,
330337
},
@@ -360,7 +367,7 @@ var _ = Describe("HostLeaseReconciler", func() {
360367
hostLease := &v1alpha1.HostLease{
361368
ObjectMeta: metav1.ObjectMeta{
362369
Name: "hostlease-delete-openstack-no-externalid",
363-
Namespace: "default",
370+
Namespace: testNamespace,
364371
Finalizers: []string{hostLeaseFinalizer},
365372
DeletionTimestamp: &now,
366373
},
@@ -397,7 +404,7 @@ var _ = Describe("HostLeaseReconciler", func() {
397404
hostLease := &v1alpha1.HostLease{
398405
ObjectMeta: metav1.ObjectMeta{
399406
Name: "hostlease-add-finalizer",
400-
Namespace: "default",
407+
Namespace: testNamespace,
401408
},
402409
Spec: v1alpha1.HostLeaseSpec{
403410
ExternalHostID: "node-finalizer",
@@ -437,13 +444,13 @@ var _ = Describe("HostLeaseReconciler", func() {
437444
hostLease := &v1alpha1.HostLease{
438445
ObjectMeta: metav1.ObjectMeta{
439446
Name: "hostlease-sample",
440-
Namespace: "default",
447+
Namespace: testNamespace,
441448
Finalizers: []string{
442449
hostLeaseFinalizer,
443450
},
444451
},
445452
Spec: v1alpha1.HostLeaseSpec{
446-
ExternalHostID: "node-1",
453+
ExternalHostID: testNodeID,
447454
HostClass: hostClass,
448455
PoweredOn: nil,
449456
},
@@ -494,7 +501,7 @@ var _ = Describe("HostLeaseReconciler", func() {
494501
hostLease := &v1alpha1.HostLease{
495502
ObjectMeta: metav1.ObjectMeta{
496503
Name: "hostlease-managed",
497-
Namespace: "default",
504+
Namespace: testNamespace,
498505
Finalizers: []string{
499506
hostLeaseFinalizer,
500507
},
@@ -550,7 +557,7 @@ var _ = Describe("HostLeaseReconciler", func() {
550557
hostLease := &v1alpha1.HostLease{
551558
ObjectMeta: metav1.ObjectMeta{
552559
Name: "hostlease-requeue",
553-
Namespace: "default",
560+
Namespace: testNamespace,
554561
Finalizers: []string{
555562
hostLeaseFinalizer,
556563
},
@@ -679,11 +686,11 @@ var _ = Describe("HostLeaseReconciler", func() {
679686
hostLease = &v1alpha1.HostLease{
680687
ObjectMeta: metav1.ObjectMeta{
681688
Name: "hostlease-sync",
682-
Namespace: "default",
689+
Namespace: testNamespace,
683690
},
684691
Spec: v1alpha1.HostLeaseSpec{
685-
ExternalHostID: "node-1",
686-
HostClass: "openstack",
692+
ExternalHostID: testNodeID,
693+
HostClass: hostClass,
687694
},
688695
}
689696
reconciler.Client = fake.NewClientBuilder().
@@ -706,7 +713,7 @@ var _ = Describe("HostLeaseReconciler", func() {
706713
})
707714

708715
It("should set phase to Ready and PowerSynced to True when node is on", func() {
709-
node := &nodes.Node{PowerState: "power on"}
716+
node := &nodes.Node{PowerState: testPowerOn}
710717
err := reconciler.syncHostLeaseStatus(context.Background(), hostLease, node, nil, log)
711718
Expect(err).NotTo(HaveOccurred())
712719

@@ -721,7 +728,7 @@ var _ = Describe("HostLeaseReconciler", func() {
721728
})
722729

723730
It("should set phase to Ready and PowerSynced to True when node is off", func() {
724-
node := &nodes.Node{PowerState: "power off"}
731+
node := &nodes.Node{PowerState: testPowerOff}
725732
err := reconciler.syncHostLeaseStatus(context.Background(), hostLease, node, nil, log)
726733
Expect(err).NotTo(HaveOccurred())
727734

@@ -737,7 +744,7 @@ var _ = Describe("HostLeaseReconciler", func() {
737744

738745
It("should set phase to Progressing when power state does not match desired", func() {
739746
hostLease.Spec.PoweredOn = boolPtr(true)
740-
node := &nodes.Node{PowerState: "power off"}
747+
node := &nodes.Node{PowerState: testPowerOff}
741748
err := reconciler.syncHostLeaseStatus(context.Background(), hostLease, node, nil, log)
742749
Expect(err).NotTo(HaveOccurred())
743750

0 commit comments

Comments
 (0)