• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubevirt / hyperconverged-cluster-operator / 24628067360

19 Apr 2026 11:31AM UTC coverage: 80.49% (-0.02%) from 80.512%
24628067360

push

github

web-flow
Fix bugs in the perses controller (#4171)

The perses controller is not working properly, and fails to recognise
that the perses CRDs are installed.

This PR fixes the issue and adds an e2e test to verify.

Signed-off-by: Nahshon Unna Tsameret <nunnatsa@redhat.com>

18 of 28 new or added lines in 2 files covered. (64.29%)

10673 of 13260 relevant lines covered (80.49%)

2.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

35.29
/controllers/crd/crd_controller.go
1
package crd
2

3
import (
4
        "context"
5

6
        "github.com/go-logr/logr"
7
        operatorhandler "github.com/operator-framework/operator-lib/handler"
8
        corev1 "k8s.io/api/core/v1"
9
        apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
10
        "sigs.k8s.io/controller-runtime/pkg/client"
11
        "sigs.k8s.io/controller-runtime/pkg/controller"
12
        logf "sigs.k8s.io/controller-runtime/pkg/log"
13
        "sigs.k8s.io/controller-runtime/pkg/manager"
14
        "sigs.k8s.io/controller-runtime/pkg/predicate"
15
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
16
        "sigs.k8s.io/controller-runtime/pkg/source"
17

18
        hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
19
)
20

21
var (
22
        log = logf.Log.WithName("controller_crd")
23
)
24

25
// RegisterReconciler creates a new CRD Reconciler and registers it into manager.
26
// persesAvailableOnBoot should be computed by main at startup.
27
func RegisterReconciler(mgr manager.Manager, restartCh chan<- struct{}, persesAvailableOnBoot bool) error {
×
28
        return add(mgr, newReconciler(mgr, restartCh, persesAvailableOnBoot))
×
29
}
×
30

31
// newReconciler returns a new reconcile.Reconciler
32
func newReconciler(mgr manager.Manager, restartCh chan<- struct{}, persesAvailableOnBoot bool) reconcile.Reconciler {
×
33

×
34
        r := &ReconcileCRD{
×
35
                client:                mgr.GetClient(),
×
36
                restartCh:             restartCh,
×
37
                eventEmitter:          hcoutil.GetEventEmitter(),
×
38
                persesAvailableOnBoot: persesAvailableOnBoot,
×
39
        }
×
40

×
41
        return r
×
42
}
×
43

44
// add adds a new Controller to mgr with r as the reconcile.Reconciler
45
func add(mgr manager.Manager, r reconcile.Reconciler) error {
×
46

×
47
        // Create a new controller
×
48
        c, err := controller.New("crd-controller", mgr, controller.Options{Reconciler: r})
×
49
        if err != nil {
×
50
                return err
×
51
        }
×
52

53
        // Watch for changes to selected (by name) CRDs
54
        // look at descheduler and perses CRDs
55
        err = c.Watch(
×
56
                source.Kind(
×
57
                        mgr.GetCache(), client.Object(&apiextensionsv1.CustomResourceDefinition{}),
×
58
                        &operatorhandler.InstrumentedEnqueueRequestForObject[client.Object]{},
×
59
                        predicate.NewPredicateFuncs(func(object client.Object) bool {
×
60
                                switch object.GetName() {
×
61
                                case hcoutil.DeschedulerCRDName, hcoutil.PersesDashboardsCRDName, hcoutil.PersesDatasourcesCRDName:
×
62
                                        return true
×
63
                                }
64
                                return false
×
65
                        }),
66
                ))
67
        if err != nil {
×
68
                return err
×
69
        }
×
70

71
        return nil
×
72
}
73

74
// ReconcileCRD reconciles a CRD object
75
type ReconcileCRD struct {
76
        // This client, initialized using mgr.Client() above, is a split client
77
        // that reads objects from the cache and writes to the apiserver
78
        client                client.Client
79
        eventEmitter          hcoutil.EventEmitter
80
        restartCh             chan<- struct{}
81
        persesAvailableOnBoot bool
82
}
83

84
// operatorRestart triggers a restart of the operator:
85
// the controller-runtime caching client can only handle kinds
86
// that were already defined when the client cache got initialized.
87
// If a new relevant kind got deployed at runtime,
88
// the operator should restart to be able to read it.
89
// See: https://github.com/kubernetes-sigs/controller-runtime/issues/2456
90
func (r *ReconcileCRD) operatorRestart() {
1✔
91
        r.restartCh <- struct{}{}
1✔
92
}
1✔
93

94
// Reconcile refreshes KubeDesheduler view on ClusterInfo singleton
95
func (r *ReconcileCRD) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
1✔
96
        logger, err := logr.FromContext(ctx)
1✔
97
        if err != nil {
2✔
98
                logger = log
1✔
99
        }
1✔
100

101
        logger = logger.WithName("crd-reconciler").WithValues("Request.Name", req.Name)
1✔
102

1✔
103
        logger.Info("Triggered by a CRD")
1✔
104

1✔
105
        switch req.Name {
1✔
106
        case hcoutil.DeschedulerCRDName:
1✔
107
                // if the KubeDescheduler CRD was already exist on boot, do nothing.
1✔
108
                if hcoutil.GetClusterInfo().IsDeschedulerAvailable() {
2✔
109
                        break
1✔
110
                }
111

112
                // if the CRD deployed now, after boot, restart, so the cache setup will include it.
113
                if hcoutil.GetClusterInfo().IsDeschedulerCRDDeployed(ctx, r.client) {
2✔
114
                        const logMsg = "KubeDescheduler CRD got deployed, restarting the operator to reconfigure the operator for the new kind"
1✔
115
                        logger.Info(logMsg)
1✔
116
                        r.eventEmitter.EmitEvent(nil, corev1.EventTypeNormal, "Restarting", logMsg+"; Restarting the operator to allow reading KubeDescheduler CRs")
1✔
117
                        r.operatorRestart()
1✔
118
                }
1✔
119

NEW
120
        case hcoutil.PersesDashboardsCRDName, hcoutil.PersesDatasourcesCRDName:
×
NEW
121
                // if Perses CRDs were already exist on boot, do nothing
×
NEW
122
                if r.persesAvailableOnBoot {
×
NEW
123
                        break
×
124
                }
125

126
                // If Perses CRDs became available after boot, restart once to register Perses controller and cache the new GVKs.
NEW
127
                if hcoutil.IsPersesAvailable(ctx, r.client) {
×
NEW
128
                        const logMsg = "Perses CRDs detected, restarting the operator to register the Perses controller"
×
NEW
129
                        logger.Info(logMsg)
×
NEW
130
                        r.eventEmitter.EmitEvent(nil, corev1.EventTypeNormal, "Restarting", logMsg+"; Restarting the operator to allow reading Perses CRs")
×
NEW
131
                        r.operatorRestart()
×
NEW
132
                }
×
133
        }
134

135
        return reconcile.Result{}, nil
1✔
136
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc