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

kubevirt / hyperconverged-cluster-operator / 25800721794

13 May 2026 01:00PM UTC coverage: 80.481% (+0.03%) from 80.454%
25800721794

Pull #4237

github

web-flow
Merge 2a20c044a into a19bdc233
Pull Request #4237: Use v1 in the hyperconverged controller

517 of 535 new or added lines in 45 files covered. (96.64%)

9 existing lines in 5 files now uncovered.

10436 of 12967 relevant lines covered (80.48%)

2.06 hits per line

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

78.66
/controllers/hyperconverged/hyperconverged_controller.go
1
package hyperconverged
2

3
import (
4
        "cmp"
5
        "context"
6
        "fmt"
7
        "io/fs"
8
        "os"
9
        "reflect"
10
        "slices"
11
        "time"
12

13
        "github.com/blang/semver/v4"
14
        jsonpatch "github.com/evanphx/json-patch/v5"
15
        consolev1 "github.com/openshift/api/console/v1"
16
        imagev1 "github.com/openshift/api/image/v1"
17
        routev1 "github.com/openshift/api/route/v1"
18
        securityv1 "github.com/openshift/api/security/v1"
19
        operatorhandler "github.com/operator-framework/operator-lib/handler"
20
        monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
21
        appsv1 "k8s.io/api/apps/v1"
22
        corev1 "k8s.io/api/core/v1"
23
        networkingv1 "k8s.io/api/networking/v1"
24
        rbacv1 "k8s.io/api/rbac/v1"
25
        schedulingv1 "k8s.io/api/scheduling/v1"
26
        apierrors "k8s.io/apimachinery/pkg/api/errors"
27
        apimetav1 "k8s.io/apimachinery/pkg/api/meta"
28
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29
        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30
        "k8s.io/apimachinery/pkg/runtime"
31
        "k8s.io/apimachinery/pkg/runtime/schema"
32
        "k8s.io/apimachinery/pkg/types"
33
        "k8s.io/utils/ptr"
34
        "sigs.k8s.io/controller-runtime/pkg/client"
35
        "sigs.k8s.io/controller-runtime/pkg/controller"
36
        "sigs.k8s.io/controller-runtime/pkg/event"
37
        "sigs.k8s.io/controller-runtime/pkg/handler"
38
        logf "sigs.k8s.io/controller-runtime/pkg/log"
39
        "sigs.k8s.io/controller-runtime/pkg/manager"
40
        "sigs.k8s.io/controller-runtime/pkg/predicate"
41
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
42
        "sigs.k8s.io/controller-runtime/pkg/source"
43

44
        networkaddonsv1 "github.com/kubevirt/cluster-network-addons-operator/pkg/apis/networkaddonsoperator/v1"
45
        kubevirtcorev1 "kubevirt.io/api/core/v1"
46
        aaqv1alpha1 "kubevirt.io/application-aware-quota/staging/src/kubevirt.io/application-aware-quota-api/pkg/apis/core/v1alpha1"
47
        cdiv1beta1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
48
        migrationv1alpha1 "kubevirt.io/kubevirt-migration-operator/api/v1alpha1"
49
        sspv1beta3 "kubevirt.io/ssp-operator/api/v1beta3"
50

51
        hcov1 "github.com/kubevirt/hyperconverged-cluster-operator/api/v1"
52
        hcov1beta1 "github.com/kubevirt/hyperconverged-cluster-operator/api/v1beta1"
53
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/alerts"
54
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/common"
55
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/operandhandler"
56
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/reqresolver"
57
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/monitoring/hyperconverged/metrics"
58
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/nodeinfo"
59
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/tlssecprofile"
60
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/upgradepatch"
61
        hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
62
        "github.com/kubevirt/hyperconverged-cluster-operator/version"
63
)
64

65
var (
66
        log = logf.Log.WithName("controller_hyperconverged")
67
)
68

69
const (
70
        // We cannot set owner reference of cluster-wide resources to namespaced HyperConverged object. Therefore,
71
        // use finalizers to manage the cleanup.
72
        FinalizerName = "kubevirt.io/hyperconverged"
73

74
        // OpenshiftNamespace is for resources that belong in the openshift namespace
75

76
        reconcileInit               = "Init"
77
        reconcileInitMessage        = "Initializing HyperConverged cluster"
78
        reconcileCompleted          = "ReconcileCompleted"
79
        reconcileCompletedMessage   = "Reconcile completed successfully"
80
        invalidRequestReason        = "InvalidRequest"
81
        invalidRequestMessageFormat = "Request does not match expected name (%v) and namespace (%v)"
82
        commonDegradedReason        = "HCODegraded"
83
        commonProgressingReason     = "HCOProgressing"
84
        taintedConfigurationReason  = "UnsupportedFeatureAnnotation"
85
        taintedConfigurationMessage = "Unsupported feature was activated via an HCO annotation"
86
        systemHealthStatusHealthy   = "healthy"
87
        systemHealthStatusWarning   = "warning"
88
        systemHealthStatusError     = "error"
89

90
        hcoVersionName = "operator"
91

92
        requestedStatusKey = "requested status"
93

94
        requeueAfter = time.Millisecond * 100
95
)
96

97
// JSONPatchAnnotationNames - annotations used to patch operand CRs with unsupported/unofficial/hidden features.
98
// The presence of any of these annotations raises the hcov1.ConditionTaintedConfiguration condition.
99
var JSONPatchAnnotationNames = []string{
100
        common.JSONPatchKVAnnotationName,
101
        common.JSONPatchCDIAnnotationName,
102
        common.JSONPatchCNAOAnnotationName,
103
        common.JSONPatchSSPAnnotationName,
104
}
105

106
// RegisterReconciler creates a new HyperConverged Reconciler and registers it into manager.
107
func RegisterReconciler(mgr manager.Manager,
108
        ci hcoutil.ClusterInfo,
109
        upgradeableCond hcoutil.Condition,
110
        ingressEventCh <-chan event.GenericEvent,
111
        nodeEventChannel <-chan event.GenericEvent,
112
        apiServerEventCh <-chan event.GenericEvent) error {
×
113

×
114
        return add(mgr, newReconciler(mgr, ci, upgradeableCond), ci, ingressEventCh, nodeEventChannel, apiServerEventCh)
×
115
}
×
116

117
// newReconciler returns a new reconcile.Reconciler
118
func newReconciler(mgr manager.Manager, ci hcoutil.ClusterInfo, upgradeableCond hcoutil.Condition) reconcile.Reconciler {
×
119
        reqresolver.GeneratePlaceHolders()
×
120

×
121
        ownVersion := cmp.Or(os.Getenv(hcoutil.HcoKvIoVersionName), version.Version)
×
122

×
123
        var pwdFS fs.FS
×
124
        pwd, err := os.Getwd()
×
125
        if err != nil {
×
126
                panic("can't get the working directory")
×
127
        }
128

129
        pwdFS = os.DirFS(pwd)
×
130

×
131
        r := &ReconcileHyperConverged{
×
132
                client:               mgr.GetClient(),
×
133
                scheme:               mgr.GetScheme(),
×
134
                operandHandler:       operandhandler.NewOperandHandler(mgr.GetClient(), mgr.GetScheme(), ci, hcoutil.GetEventEmitter()),
×
135
                upgradeMode:          false,
×
136
                ownVersion:           ownVersion,
×
137
                eventEmitter:         hcoutil.GetEventEmitter(),
×
138
                firstLoop:            true,
×
139
                upgradeableCondition: upgradeableCond,
×
140
                pwdFS:                pwdFS,
×
141
        }
×
142

×
143
        if ci.IsMonitoringAvailable() {
×
144
                r.monitoringReconciler = alerts.NewMonitoringReconciler(ci, r.client, hcoutil.GetEventEmitter(), r.scheme)
×
145
        }
×
146

147
        return r
×
148
}
149

150
// newCRDremover returns a new CRDRemover
151
func add(mgr manager.Manager, r reconcile.Reconciler, ci hcoutil.ClusterInfo, ingressEventCh, nodeEventChannel, apiServerEventCh <-chan event.GenericEvent) error {
×
152
        // Create a new controller
×
153
        c, err := controller.New("hyperconverged-controller", mgr, controller.Options{Reconciler: r})
×
154
        if err != nil {
×
155
                return err
×
156
        }
×
157

158
        // Watch for changes to primary resource HyperConverged
159
        err = c.Watch(
×
160
                source.Kind(
×
NEW
161
                        mgr.GetCache(), client.Object(&hcov1.HyperConverged{}),
×
162
                        &operatorhandler.InstrumentedEnqueueRequestForObject[client.Object]{},
×
163
                        predicate.Or[client.Object](predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{},
×
164
                                predicate.ResourceVersionChangedPredicate{}),
×
165
                ))
×
166
        if err != nil {
×
167
                return err
×
168
        }
×
169

170
        // To limit the memory usage, the controller manager got instantiated with a custom cache
171
        // that is watching only a specific set of objects with selectors.
172
        // When a new object got added here, it has also to be added to the custom cache
173
        // managed by getNewManagerCache()
174
        secondaryResources := []client.Object{
×
175
                &kubevirtcorev1.KubeVirt{},
×
176
                &cdiv1beta1.CDI{},
×
177
                &networkaddonsv1.NetworkAddonsConfig{},
×
178
                &aaqv1alpha1.AAQ{},
×
179
                &migrationv1alpha1.MigController{},
×
180
                &schedulingv1.PriorityClass{},
×
181
                &corev1.ConfigMap{},
×
182
                &corev1.Service{},
×
183
                &corev1.ServiceAccount{},
×
184
                &appsv1.DaemonSet{},
×
185
                &rbacv1.Role{},
×
186
                &rbacv1.RoleBinding{},
×
187
                &rbacv1.ClusterRole{},
×
188
                &rbacv1.ClusterRoleBinding{},
×
189
        }
×
190
        if ci.IsMonitoringAvailable() {
×
191
                secondaryResources = append(secondaryResources, []client.Object{
×
192
                        &monitoringv1.ServiceMonitor{},
×
193
                        &monitoringv1.PrometheusRule{},
×
194
                        &networkingv1.NetworkPolicy{},
×
195
                        &corev1.Secret{},
×
196
                }...)
×
197
        }
×
198
        if ci.IsOpenshift() {
×
199
                secondaryResources = append(secondaryResources, []client.Object{
×
200
                        &sspv1beta3.SSP{},
×
201
                        &corev1.Service{},
×
202
                        &routev1.Route{},
×
203
                        &consolev1.ConsoleCLIDownload{},
×
204
                        &consolev1.ConsoleQuickStart{},
×
205
                        &consolev1.ConsolePlugin{},
×
206
                        &imagev1.ImageStream{},
×
207
                        &corev1.Namespace{},
×
208
                        &appsv1.Deployment{},
×
209
                        &securityv1.SecurityContextConstraints{},
×
210
                }...)
×
211
        }
×
212

213
        // Watch secondary resources
214
        for _, resource := range secondaryResources {
×
215
                msg := fmt.Sprintf("Reconciling for %T", resource)
×
216
                err = c.Watch(
×
217
                        source.Kind(mgr.GetCache(), resource,
×
218
                                handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request {
×
219
                                        // enqueue using a placeholder to be able to discriminate request triggered
×
220
                                        // by changes on the HyperConverged object from request triggered by changes
×
221
                                        // on a secondary CR controlled by HCO
×
222
                                        log.Info(msg)
×
223
                                        return []reconcile.Request{
×
224
                                                reqresolver.GetSecondaryCRRequest(),
×
225
                                        }
×
226
                                }),
×
227
                        ))
228
                if err != nil {
×
229
                        return err
×
230
                }
×
231
        }
232

233
        if ci.IsOpenshift() {
×
234
                err = c.Watch(
×
235
                        source.Channel(
×
236
                                apiServerEventCh,
×
237
                                handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request {
×
238
                                        // enqueue using a placeholder to signal that the change is not
×
239
                                        // directly on HCO CR but on the APIServer CR that we want to reload
×
240
                                        // only if really changed
×
241
                                        log.Info("Reconciling for openshiftconfigv1.APIServer")
×
242
                                        return []reconcile.Request{
×
243
                                                reqresolver.GetAPIServerCRRequest(),
×
244
                                        }
×
245
                                }),
×
246
                        ))
247
                if err != nil {
×
248
                        return err
×
249
                }
×
250

251
                err = c.Watch(
×
252
                        source.Channel(
×
253
                                ingressEventCh,
×
254
                                handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request {
×
255
                                        // the ingress-cluster controller initiate this by pushing an event to the ingressEventCh channel
×
256
                                        // This will force this controller to update the URL of the cli download route, if the user
×
257
                                        // customized the hostname.
×
258
                                        log.Info("Reconciling for openshiftconfigv1.Ingress")
×
259
                                        return []reconcile.Request{
×
260
                                                reqresolver.GetIngressCRResource(),
×
261
                                        }
×
262
                                }),
×
263
                        ))
264
                if err != nil {
×
265
                        return err
×
266
                }
×
267
        }
268

269
        err = c.Watch(
×
270
                source.Channel(
×
271
                        nodeEventChannel,
×
272
                        handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request {
×
273
                                // the nodes controller initiate this by pushing an event to the nodeEventChannel channel
×
274
                                // This will force this controller to update the status fields related to the cluster nodes, and
×
275
                                // to re-generate the DataImportCronTemplates in the SSP CR.
×
276
                                log.Info("Reconciling for core.Node")
×
277
                                return []reconcile.Request{
×
278
                                        reqresolver.GetNodeResource(),
×
279
                                }
×
280
                        }),
×
281
                ))
282
        if err != nil {
×
283
                return err
×
284
        }
×
285

286
        return nil
×
287
}
288

289
var _ reconcile.Reconciler = &ReconcileHyperConverged{}
290

291
// ReconcileHyperConverged reconciles a HyperConverged object
292
type ReconcileHyperConverged struct {
293
        // This client, initialized using mgr.Client() above, is a split client
294
        // that reads objects from the cache and writes to the apiserver
295
        client               client.Client
296
        scheme               *runtime.Scheme
297
        operandHandler       *operandhandler.OperandHandler
298
        upgradeMode          bool
299
        ownVersion           string
300
        eventEmitter         hcoutil.EventEmitter
301
        firstLoop            bool
302
        upgradeableCondition hcoutil.Condition
303
        monitoringReconciler *alerts.MonitoringReconciler
304
        pwdFS                fs.FS
305
}
306

307
// Reconcile reads that state of the cluster for a HyperConverged object and makes changes based on the state read
308
// and what is in the HyperConverged.Spec
309
// Note:
310
// The Controller will requeue the Request to be processed again if the returned error is non-nil or
311
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
312
func (r *ReconcileHyperConverged) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
1✔
313
        logger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
1✔
314

1✔
315
        resolvedRequest, hcoTriggered := reqresolver.ResolveReconcileRequest(logger, request)
1✔
316
        hcoRequest := common.NewHcoRequest(ctx, resolvedRequest, logger, r.upgradeMode, hcoTriggered)
1✔
317

1✔
318
        if hcoTriggered {
2✔
319
                r.operandHandler.Reset()
1✔
320
        }
1✔
321

322
        err := r.monitoringReconciler.Reconcile(hcoRequest, r.firstLoop)
1✔
323
        if err != nil {
1✔
324
                return reconcile.Result{}, err
×
325
        }
×
326

327
        // Fetch the HyperConverged instance
328
        instance, err := r.getHyperConverged(hcoRequest)
1✔
329
        if err != nil {
1✔
330
                return reconcile.Result{}, err
×
331
        }
×
332

333
        hcoRequest.Instance = instance
1✔
334

1✔
335
        if instance == nil {
2✔
336
                // if the HyperConverged CR was deleted during an upgrade process, then this is not an upgrade anymore
1✔
337
                r.upgradeMode = false
1✔
338
                err = r.setOperatorUpgradeableStatus(hcoRequest)
1✔
339

1✔
340
                return reconcile.Result{}, err
1✔
341
        }
1✔
342

343
        if r.firstLoop {
2✔
344
                r.firstLoopInitialization(hcoRequest)
1✔
345
        }
1✔
346

347
        // update the HyperConverged's TLS Security Profile cache, to be used in the
348
        // server's TLS configurations.
349
        tlssecprofile.SetHyperConvergedTLSSecurityProfile(instance.Spec.Security.TLSSecurityProfile)
1✔
350

1✔
351
        if err = r.monitoringReconciler.UpdateRelatedObjects(hcoRequest); err != nil {
1✔
352
                logger.Error(err, "Failed to update the PrometheusRule as a related object")
×
353
                return reconcile.Result{}, err
×
354
        }
×
355

356
        result, err := r.doReconcile(hcoRequest)
1✔
357
        if err != nil {
2✔
358
                r.eventEmitter.EmitEvent(hcoRequest.Instance, corev1.EventTypeWarning, "ReconcileError", err.Error())
1✔
359
                return result, err
1✔
360
        }
1✔
361

362
        if err = r.setOperatorUpgradeableStatus(hcoRequest); err != nil {
1✔
363
                return reconcile.Result{}, err
×
364
        }
×
365

366
        requeue, err := r.updateHyperConverged(hcoRequest)
1✔
367
        if requeue || apierrors.IsConflict(err) {
2✔
368
                result.RequeueAfter = requeueAfter
1✔
369
        }
1✔
370

371
        return result, err
1✔
372
}
373

374
func (r *ReconcileHyperConverged) doReconcile(req *common.HcoRequest) (reconcile.Result, error) {
1✔
375

1✔
376
        valid := r.validateNamespace(req)
1✔
377
        if !valid {
2✔
378
                return reconcile.Result{}, nil
1✔
379
        }
1✔
380

381
        // Add conditions if there are none
382
        init := req.Instance.Status.Conditions == nil
1✔
383
        if init {
2✔
384
                r.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeNormal, "InitHCO", "Initiating the HyperConverged")
1✔
385
                r.setInitialConditions(req)
1✔
386

1✔
387
                req.StatusDirty = true
1✔
388
        }
1✔
389

390
        r.setLabels(req)
1✔
391

1✔
392
        updateStatus(req)
1✔
393

1✔
394
        metrics.SetHCOMetricMemoryOvercommitPercentage(
1✔
395
                getMemoryOvercommitPercentage(req.Instance.Spec.Virtualization.HigherWorkloadDensity),
1✔
396
        )
1✔
397

1✔
398
        // in-memory conditions should start off empty. It will only ever hold
1✔
399
        // negative conditions (!Available, Degraded, Progressing)
1✔
400
        req.Conditions = common.NewHcoConditions()
1✔
401

1✔
402
        // Handle finalizers
1✔
403
        if !checkFinalizers(req) {
2✔
404
                if !req.HCOTriggered {
1✔
405
                        // this is just the effect of a delete request created by HCO
×
406
                        // in the previous iteration, ignore it
×
407
                        return reconcile.Result{}, nil
×
408
                }
×
409
                return r.ensureHcoDeleted(req)
1✔
410
        }
411

412
        applyDataImportSchedule(req)
1✔
413

1✔
414
        // If the current version is not updated in CR ,then we're updating. This is also works when updating from
1✔
415
        // an old version, since Status.Versions will be empty.
1✔
416
        knownHcoVersion, _ := GetVersion(&req.Instance.Status, hcoVersionName)
1✔
417

1✔
418
        // detect upgrade mode
1✔
419
        if !r.upgradeMode && !init && knownHcoVersion != r.ownVersion {
2✔
420
                // get into upgrade mode
1✔
421

1✔
422
                r.upgradeMode = true
1✔
423
                r.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeNormal, "UpgradeHCO", "Upgrading the HyperConverged to version "+r.ownVersion)
1✔
424
                req.Logger.Info(fmt.Sprintf("Start upgrading from version %s to version %s", knownHcoVersion, r.ownVersion))
1✔
425
        }
1✔
426

427
        req.SetUpgradeMode(r.upgradeMode)
1✔
428

1✔
429
        if r.upgradeMode {
2✔
430
                if result, err := r.handleUpgrade(req); result != nil {
2✔
431
                        return *result, err
1✔
432
                }
1✔
433
        }
434

435
        return r.EnsureOperandAndComplete(req, init)
1✔
436
}
437

438
func (r *ReconcileHyperConverged) handleUpgrade(req *common.HcoRequest) (*reconcile.Result, error) {
1✔
439
        modified, err := r.migrateBeforeUpgrade(req)
1✔
440
        if err != nil {
2✔
441
                return &reconcile.Result{RequeueAfter: requeueAfter}, err
1✔
442
        }
1✔
443

444
        if modified {
2✔
445
                r.updateConditions(req)
1✔
446
                return &reconcile.Result{RequeueAfter: requeueAfter}, nil
1✔
447
        }
1✔
448
        return nil, nil
1✔
449
}
450

451
func (r *ReconcileHyperConverged) EnsureOperandAndComplete(req *common.HcoRequest, init bool) (reconcile.Result, error) {
1✔
452
        if err := r.operandHandler.Ensure(req); err != nil {
2✔
453
                r.updateConditions(req)
1✔
454
                requeue := time.Duration(0)
1✔
455
                if init {
2✔
456
                        requeue = requeueAfter
1✔
457
                }
1✔
458
                return reconcile.Result{RequeueAfter: requeue}, nil
1✔
459
        }
460

461
        req.Logger.Info("Reconcile complete")
1✔
462

1✔
463
        // Requeue if we just created everything
1✔
464
        if init {
2✔
465
                return reconcile.Result{RequeueAfter: requeueAfter}, nil
1✔
466
        }
1✔
467

468
        r.completeReconciliation(req)
1✔
469

1✔
470
        return reconcile.Result{}, nil
1✔
471
}
472

473
func updateStatus(req *common.HcoRequest) {
1✔
474
        if req.Instance.Generation != req.Instance.Status.ObservedGeneration {
2✔
475
                req.Instance.Status.ObservedGeneration = req.Instance.Generation
1✔
476
                req.StatusDirty = true
1✔
477
        }
1✔
478

479
        if infraHighlyAvailable := nodeinfo.IsInfrastructureHighlyAvailable(); req.Instance.Status.InfrastructureHighlyAvailable == nil ||
1✔
480
                *req.Instance.Status.InfrastructureHighlyAvailable != infraHighlyAvailable {
2✔
481

1✔
482
                if infraHighlyAvailable {
1✔
483
                        req.Logger.Info("infrastructure became highly available")
×
484
                } else {
1✔
485
                        req.Logger.Info("infrastructure became not highly available")
1✔
486
                }
1✔
487

488
                req.Instance.Status.InfrastructureHighlyAvailable = ptr.To(infraHighlyAvailable)
1✔
489
                req.StatusDirty = true
1✔
490
        }
491

492
        if cpArch := nodeinfo.GetControlPlaneArchitectures(); slices.Compare(req.Instance.Status.NodeInfo.ControlPlaneArchitectures, cpArch) != 0 {
1✔
493
                req.Instance.Status.NodeInfo.ControlPlaneArchitectures = cpArch
×
494
                req.StatusDirty = true
×
495
        }
×
496

497
        if workloadsArch := nodeinfo.GetWorkloadsArchitectures(); slices.Compare(req.Instance.Status.NodeInfo.WorkloadsArchitectures, workloadsArch) != 0 {
1✔
498
                req.Instance.Status.NodeInfo.WorkloadsArchitectures = workloadsArch
×
499
                req.StatusDirty = true
×
500
        }
×
501
}
502

503
// getHyperConverged gets the HyperConverged resource from the Kubernetes API.
504
func (r *ReconcileHyperConverged) getHyperConverged(req *common.HcoRequest) (*hcov1.HyperConverged, error) {
1✔
505
        instance := &hcov1.HyperConverged{}
1✔
506
        err := r.client.Get(req.Ctx, req.NamespacedName, instance)
1✔
507

1✔
508
        // Green path first
1✔
509
        if err == nil {
2✔
510
                metrics.SetHCOMetricHyperConvergedExists()
1✔
511
                return instance, nil
1✔
512
        }
1✔
513

514
        // Error path
515
        if apierrors.IsNotFound(err) {
2✔
516
                req.Logger.Info("No HyperConverged resource")
1✔
517
                metrics.SetHCOMetricHyperConvergedNotExists()
1✔
518

1✔
519
                // Request object not found, could have been deleted after reconcile request.
1✔
520
                // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
1✔
521
                // Return and don't requeue
1✔
522
                return nil, nil
1✔
523
        }
1✔
524

525
        // Another error reading the object.
526
        // Just return the error so that the request is requeued.
527
        return nil, err
×
528
}
529

530
// updateHyperConverged updates the HyperConverged resource according to its state in the request.
531
func (r *ReconcileHyperConverged) updateHyperConverged(request *common.HcoRequest) (bool, error) {
1✔
532

1✔
533
        // Since the status subresource is enabled for the HyperConverged kind,
1✔
534
        // we need to update the status and the metadata separately.
1✔
535
        // Moreover, we need to update the status first, in order to prevent a conflict.
1✔
536
        // In addition, metadata and spec changes are removed by status update, but since status update done first, we need
1✔
537
        // to store metadata and spec and recover it after status update
1✔
538

1✔
539
        var spec hcov1.HyperConvergedSpec
1✔
540
        var meta metav1.ObjectMeta
1✔
541
        if request.Dirty {
2✔
542
                request.Instance.Spec.DeepCopyInto(&spec)
1✔
543
                request.Instance.ObjectMeta.DeepCopyInto(&meta)
1✔
544
        }
1✔
545

546
        err := r.updateHyperConvergedStatus(request)
1✔
547
        if err != nil {
2✔
548
                request.Logger.Error(err, "Failed to update HCO Status")
1✔
549
                return false, err
1✔
550
        }
1✔
551

552
        if request.Dirty {
2✔
553
                request.Instance.Annotations = meta.Annotations
1✔
554
                request.Instance.Finalizers = meta.Finalizers
1✔
555
                request.Instance.Labels = meta.Labels
1✔
556
                request.Instance.Spec = spec
1✔
557

1✔
558
                err = r.updateHyperConvergedSpecMetadata(request)
1✔
559
                if err != nil {
2✔
560
                        request.Logger.Error(err, "Failed to update HCO CR")
1✔
561
                        return false, err
1✔
562
                }
1✔
563
                // version update is a two steps process
564
                knownHcoVersion, _ := GetVersion(&request.Instance.Status, hcoVersionName)
1✔
565
                if r.ownVersion != knownHcoVersion && request.StatusDirty {
2✔
566
                        return true, nil
1✔
567
                }
1✔
568
        }
569

570
        return false, nil
1✔
571
}
572

573
// updateHyperConvergedSpecMetadata updates the HyperConverged resource's spec and metadata.
574
func (r *ReconcileHyperConverged) updateHyperConvergedSpecMetadata(request *common.HcoRequest) error {
1✔
575
        if !request.Dirty {
1✔
576
                return nil
×
577
        }
×
578

579
        return r.client.Update(request.Ctx, request.Instance)
1✔
580
}
581

582
// updateHyperConvergedSpecMetadata updates the HyperConverged resource's status (and metadata).
583
func (r *ReconcileHyperConverged) updateHyperConvergedStatus(request *common.HcoRequest) error {
1✔
584
        if !request.StatusDirty {
2✔
585
                return nil
1✔
586
        }
1✔
587

588
        return r.client.Status().Update(request.Ctx, request.Instance)
1✔
589
}
590

591
func (r *ReconcileHyperConverged) validateNamespace(req *common.HcoRequest) bool {
1✔
592
        // Ignore invalid requests
1✔
593
        if !reqresolver.IsTriggeredByHyperConverged(req.NamespacedName) {
2✔
594
                req.Logger.Info("Invalid request", "HyperConverged.Namespace", req.Namespace, "HyperConverged.Name", req.Name)
1✔
595
                hc := reqresolver.GetHyperConvergedNamespacedName()
1✔
596
                req.Conditions.SetStatusCondition(metav1.Condition{
1✔
597
                        Type:               hcov1.ConditionReconcileComplete,
1✔
598
                        Status:             metav1.ConditionFalse,
1✔
599
                        Reason:             invalidRequestReason,
1✔
600
                        Message:            fmt.Sprintf(invalidRequestMessageFormat, hc.Name, hc.Namespace),
1✔
601
                        ObservedGeneration: req.Instance.Generation,
1✔
602
                })
1✔
603
                r.updateConditions(req)
1✔
604
                return false
1✔
605
        }
1✔
606
        return true
1✔
607
}
608

609
func (r *ReconcileHyperConverged) setInitialConditions(req *common.HcoRequest) {
1✔
610
        UpdateVersion(&req.Instance.Status, hcoVersionName, r.ownVersion)
1✔
611

1✔
612
        req.Conditions.SetStatusCondition(metav1.Condition{
1✔
613
                Type:               hcov1.ConditionReconcileComplete,
1✔
614
                Status:             metav1.ConditionUnknown, // we just started trying to reconcile
1✔
615
                Reason:             reconcileInit,
1✔
616
                Message:            reconcileInitMessage,
1✔
617
                ObservedGeneration: req.Instance.Generation,
1✔
618
        })
1✔
619
        req.Conditions.SetStatusCondition(metav1.Condition{
1✔
620
                Type:               hcov1.ConditionAvailable,
1✔
621
                Status:             metav1.ConditionFalse,
1✔
622
                Reason:             reconcileInit,
1✔
623
                Message:            reconcileInitMessage,
1✔
624
                ObservedGeneration: req.Instance.Generation,
1✔
625
        })
1✔
626
        req.Conditions.SetStatusCondition(metav1.Condition{
1✔
627
                Type:               hcov1.ConditionProgressing,
1✔
628
                Status:             metav1.ConditionTrue,
1✔
629
                Reason:             reconcileInit,
1✔
630
                Message:            reconcileInitMessage,
1✔
631
                ObservedGeneration: req.Instance.Generation,
1✔
632
        })
1✔
633
        req.Conditions.SetStatusCondition(metav1.Condition{
1✔
634
                Type:               hcov1.ConditionDegraded,
1✔
635
                Status:             metav1.ConditionFalse,
1✔
636
                Reason:             reconcileInit,
1✔
637
                Message:            reconcileInitMessage,
1✔
638
                ObservedGeneration: req.Instance.Generation,
1✔
639
        })
1✔
640
        req.Conditions.SetStatusCondition(metav1.Condition{
1✔
641
                Type:               hcov1.ConditionUpgradeable,
1✔
642
                Status:             metav1.ConditionUnknown,
1✔
643
                Reason:             reconcileInit,
1✔
644
                Message:            reconcileInitMessage,
1✔
645
                ObservedGeneration: req.Instance.Generation,
1✔
646
        })
1✔
647

1✔
648
        r.updateConditions(req)
1✔
649
}
1✔
650

651
func (r *ReconcileHyperConverged) ensureHcoDeleted(req *common.HcoRequest) (reconcile.Result, error) {
1✔
652
        err := r.operandHandler.EnsureDeleted(req)
1✔
653
        if err != nil {
1✔
654
                return reconcile.Result{}, err
×
655
        }
×
656

657
        requeue := time.Duration(0)
1✔
658

1✔
659
        // Remove the finalizers
1✔
660
        if idx := slices.Index(req.Instance.Finalizers, FinalizerName); idx >= 0 {
2✔
661
                req.Instance.Finalizers = slices.Delete(req.Instance.Finalizers, idx, idx+1)
1✔
662
                req.Dirty = true
1✔
663
                requeue = requeueAfter
1✔
664
        }
1✔
665

666
        // Need to requeue because finalizer update does not change metadata.generation
667
        return reconcile.Result{RequeueAfter: requeue}, nil
1✔
668
}
669

670
func (r *ReconcileHyperConverged) aggregateComponentConditions(req *common.HcoRequest) bool {
1✔
671
        /*
1✔
672
                See the chart at design/aggregateComponentConditions.svg; The numbers below follows the numbers in the chart
1✔
673
                Here is the PlantUML code for the chart that describes the aggregation of the sub-components conditions.
1✔
674
                Find the PlantURL syntax here: https://plantuml.com/activity-diagram-beta
1✔
675

1✔
676
                @startuml ../../../design/aggregateComponentConditions.svg
1✔
677
                title Aggregate Component Conditions
1✔
678

1✔
679
                start
1✔
680
                  #springgreen:Set **ReconcileComplete = True**]
1✔
681
                  !x=1
1✔
682
                if ((x) [Degraded = True] Exists) then
1✔
683
                  !x=x+1
1✔
684
                  #orangered:<<implicit>>\n**Degraded = True** /
1✔
685
                  -[#orangered]-> yes;
1✔
686
                  if ((x) [Progressing = True] Exists) then
1✔
687
                        !x=x+1
1✔
688
                        -[#springgreen]-> no;
1✔
689
                        #springgreen:(x) Set **Progressing = False**]
1✔
690
                        !x=x+1
1✔
691
                  else
1✔
692
                        -[#orangered]-> yes;
1✔
693
                        #orangered:<<implicit>>\n**Progressing = True** /
1✔
694
                  endif
1✔
695
                  if ((x) [Upgradable = False] Exists) then
1✔
696
                        !x=x+1
1✔
697
                        -[#springgreen]-> no;
1✔
698
                        #orangered:(x) Set **Upgradable = False**]
1✔
699
                        !x=x+1
1✔
700
                  else
1✔
701
                        -[#orangered]-> yes;
1✔
702
                        #orangered:<<implicit>>\n**Upgradable = False** /
1✔
703
                  endif
1✔
704
                  if ((x) [Available = False] Exists) then
1✔
705
                        !x=x+1
1✔
706
                        -[#springgreen]-> no;
1✔
707
                        #orangered:(x) Set **Available = False**]
1✔
708
                        !x=x+1
1✔
709
                  else
1✔
710
                        -[#orangered]-> yes;
1✔
711
                        #orangered:<<implicit>>\n**Available = False** /
1✔
712
                  endif
1✔
713
                else
1✔
714
                  -[#springgreen]-> no;
1✔
715
                  #springgreen:(x) Set **Degraded = False**]
1✔
716
                  !x=x+1
1✔
717
                  if ((x) [Progressing = True] Exists) then
1✔
718
                        !x=x+1
1✔
719
                        -[#orangered]-> yes;
1✔
720
                        #orangered:<<implicit>>\n**Progressing = True** /
1✔
721
                        if ((x) [Upgradable = False] Exists) then
1✔
722
                          !x=x+1
1✔
723
                          -[#springgreen]-> no;
1✔
724
                          #orangered:(x) Set **Upgradable = False**]
1✔
725
                          !x=x+1
1✔
726
                        else
1✔
727
                          -[#orangered]-> yes;
1✔
728
                          #orangered:<<implicit>>\n**Upgradable = False** /
1✔
729
                        endif
1✔
730
                        if ((x) [Available = False] Exists) then
1✔
731
                          !x=x+1
1✔
732
                          -[#springgreen]-> no;
1✔
733
                          #springgreen:(x) Set **Available = True**]
1✔
734
                          !x=x+1
1✔
735
                        else
1✔
736
                          #orangered:<<implicit>>\n**Available = False** /
1✔
737
                          -[#orangered]-> yes;
1✔
738
                        endif
1✔
739
                  else
1✔
740
                        -[#springgreen]-> no;
1✔
741
                        #springgreen:(x) Set **Progressing = False**]
1✔
742
                        !x=x+1
1✔
743
                        if ((x) [Upgradable = False] Exists) then
1✔
744
                          !x=x+1
1✔
745
                          -[#springgreen]-> no;
1✔
746
                          #springgreen:(x) Set **Upgradable = True**]
1✔
747
                          !x=x+1
1✔
748
                        else
1✔
749
                        #orangered:<<implicit>>\n**Upgradable = False** /
1✔
750
                          -[#orangered]-> yes;
1✔
751
                        endif
1✔
752
                        if ((x) [Available = False] Exists) then
1✔
753
                          !x=x+1
1✔
754
                          -[#springgreen]-> no;
1✔
755
                          #springgreen:(x) Set **Available = True**]
1✔
756
                          !x=x+1
1✔
757
                        else
1✔
758
                          -[#orangered]-> yes;
1✔
759
                          #orangered:<<implicit>>\n**Available = False** /
1✔
760
                        endif
1✔
761
                  endif
1✔
762
                endif
1✔
763
                end
1✔
764
                @enduml
1✔
765
        */
1✔
766

1✔
767
        /*
1✔
768
                    If any component operator reports negatively we want to write that to
1✔
769
                        the instance while preserving it's lastTransitionTime.
1✔
770
                        For example, consider the KubeVirt resource has the Available condition
1✔
771
                        type with type "False". When reconciling KubeVirt's resource we would
1✔
772
                        add it to the in-memory representation of HCO's conditions (r.conditions)
1✔
773
                        and here we are simply writing it back to the server.
1✔
774
                        One shortcoming is that only one failure of a particular condition can be
1✔
775
                        captured at one time (ie. if KubeVirt and CDI are both reporting !Available,
1✔
776
                    you will only see CDI as it updates last).
1✔
777
        */
1✔
778
        allComponentsAreUp := req.Conditions.IsEmpty()
1✔
779
        req.Conditions.SetStatusCondition(metav1.Condition{
1✔
780
                Type:               hcov1.ConditionReconcileComplete,
1✔
781
                Status:             metav1.ConditionTrue,
1✔
782
                Reason:             reconcileCompleted,
1✔
783
                Message:            reconcileCompletedMessage,
1✔
784
                ObservedGeneration: req.Instance.Generation,
1✔
785
        })
1✔
786

1✔
787
        if req.Conditions.HasCondition(hcov1.ConditionDegraded) { // (#chart 1)
2✔
788

1✔
789
                req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 2,3)
1✔
790
                        Type:               hcov1.ConditionProgressing,
1✔
791
                        Status:             metav1.ConditionFalse,
1✔
792
                        Reason:             reconcileCompleted,
1✔
793
                        Message:            reconcileCompletedMessage,
1✔
794
                        ObservedGeneration: req.Instance.Generation,
1✔
795
                })
1✔
796

1✔
797
                req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 4,5)
1✔
798
                        Type:               hcov1.ConditionUpgradeable,
1✔
799
                        Status:             metav1.ConditionFalse,
1✔
800
                        Reason:             commonDegradedReason,
1✔
801
                        Message:            "HCO is not Upgradeable due to degraded components",
1✔
802
                        ObservedGeneration: req.Instance.Generation,
1✔
803
                })
1✔
804

1✔
805
                req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 6,7)
1✔
806
                        Type:               hcov1.ConditionAvailable,
1✔
807
                        Status:             metav1.ConditionFalse,
1✔
808
                        Reason:             commonDegradedReason,
1✔
809
                        Message:            "HCO is not available due to degraded components",
1✔
810
                        ObservedGeneration: req.Instance.Generation,
1✔
811
                })
1✔
812

1✔
813
        } else {
2✔
814

1✔
815
                // Degraded is not found. add it.
1✔
816
                req.Conditions.SetStatusCondition(metav1.Condition{ // (#chart 8)
1✔
817
                        Type:               hcov1.ConditionDegraded,
1✔
818
                        Status:             metav1.ConditionFalse,
1✔
819
                        Reason:             reconcileCompleted,
1✔
820
                        Message:            reconcileCompletedMessage,
1✔
821
                        ObservedGeneration: req.Instance.Generation,
1✔
822
                })
1✔
823

1✔
824
                if req.Conditions.HasCondition(hcov1.ConditionProgressing) { // (#chart 9)
2✔
825

1✔
826
                        req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 10,11)
1✔
827
                                Type:               hcov1.ConditionUpgradeable,
1✔
828
                                Status:             metav1.ConditionFalse,
1✔
829
                                Reason:             commonProgressingReason,
1✔
830
                                Message:            "HCO is not Upgradeable due to progressing components",
1✔
831
                                ObservedGeneration: req.Instance.Generation,
1✔
832
                        })
1✔
833

1✔
834
                        req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 12,13)
1✔
835
                                Type:               hcov1.ConditionAvailable,
1✔
836
                                Status:             metav1.ConditionTrue,
1✔
837
                                Reason:             reconcileCompleted,
1✔
838
                                Message:            reconcileCompletedMessage,
1✔
839
                                ObservedGeneration: req.Instance.Generation,
1✔
840
                        })
1✔
841

1✔
842
                } else {
2✔
843

1✔
844
                        req.Conditions.SetStatusCondition(metav1.Condition{ // (#chart 14)
1✔
845
                                Type:               hcov1.ConditionProgressing,
1✔
846
                                Status:             metav1.ConditionFalse,
1✔
847
                                Reason:             reconcileCompleted,
1✔
848
                                Message:            reconcileCompletedMessage,
1✔
849
                                ObservedGeneration: req.Instance.Generation,
1✔
850
                        })
1✔
851

1✔
852
                        req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 15,16)
1✔
853
                                Type:               hcov1.ConditionUpgradeable,
1✔
854
                                Status:             metav1.ConditionTrue,
1✔
855
                                Reason:             reconcileCompleted,
1✔
856
                                Message:            reconcileCompletedMessage,
1✔
857
                                ObservedGeneration: req.Instance.Generation,
1✔
858
                        })
1✔
859

1✔
860
                        req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 17,18)
1✔
861
                                Type:               hcov1.ConditionAvailable,
1✔
862
                                Status:             metav1.ConditionTrue,
1✔
863
                                Reason:             reconcileCompleted,
1✔
864
                                Message:            reconcileCompletedMessage,
1✔
865
                                ObservedGeneration: req.Instance.Generation,
1✔
866
                        })
1✔
867

1✔
868
                }
1✔
869
        }
870

871
        return allComponentsAreUp
1✔
872
}
873

874
func (r *ReconcileHyperConverged) completeReconciliation(req *common.HcoRequest) {
1✔
875
        allComponentsAreUp := r.aggregateComponentConditions(req)
1✔
876

1✔
877
        hcoReady := false
1✔
878

1✔
879
        if allComponentsAreUp {
2✔
880
                req.Logger.Info("No component operator reported negatively")
1✔
881

1✔
882
                // if in upgrade mode, and all the components are upgraded, and nothing pending to be written - upgrade is completed
1✔
883
                if r.upgradeMode && req.ComponentUpgradeInProgress && !req.Dirty {
2✔
884
                        // update the new version only when upgrade is completed
1✔
885
                        UpdateVersion(&req.Instance.Status, hcoVersionName, r.ownVersion)
1✔
886
                        req.StatusDirty = true
1✔
887

1✔
888
                        r.upgradeMode = false
1✔
889
                        req.ComponentUpgradeInProgress = false
1✔
890
                        req.Logger.Info(fmt.Sprintf("Successfully upgraded to version %s", r.ownVersion))
1✔
891
                        r.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeNormal, "UpgradeHCO", fmt.Sprintf("Successfully upgraded to version %s", r.ownVersion))
1✔
892
                }
1✔
893

894
                // If not in upgrade mode, then we're ready, because all the operators reported positive conditions.
895
                // if upgrade was done successfully, r.upgradeMode is already false here.
896
                hcoReady = !r.upgradeMode
1✔
897
        }
898

899
        if r.upgradeMode {
2✔
900
                // override the Progressing condition during upgrade
1✔
901
                req.Conditions.SetStatusCondition(metav1.Condition{
1✔
902
                        Type:               hcov1.ConditionProgressing,
1✔
903
                        Status:             metav1.ConditionTrue,
1✔
904
                        Reason:             "HCOUpgrading",
1✔
905
                        Message:            "HCO is now upgrading to version " + r.ownVersion,
1✔
906
                        ObservedGeneration: req.Instance.Generation,
1✔
907
                })
1✔
908
        }
1✔
909

910
        // check if HCO was available before this reconcile loop
911
        hcoWasAvailable := apimetav1.IsStatusConditionTrue(req.Instance.Status.Conditions, hcov1.ConditionAvailable) &&
1✔
912
                apimetav1.IsStatusConditionFalse(req.Instance.Status.Conditions, hcov1.ConditionProgressing)
1✔
913

1✔
914
        if hcoReady {
2✔
915
                // If no operator whose conditions we are watching reports an error, then it is safe
1✔
916
                // to set readiness.
1✔
917
                if !hcoWasAvailable { // only when become available
2✔
918
                        r.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeNormal, "ReconcileHCO", "HCO Reconcile completed successfully")
1✔
919
                }
1✔
920
        } else {
1✔
921
                // If for any reason we marked ourselves !upgradeable...then unset readiness
1✔
922
                if !r.upgradeMode && hcoWasAvailable { // only when become not ready
1✔
923
                        r.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeWarning, "ReconcileHCO", "Not all the operators are ready")
×
924
                }
×
925
        }
926

927
        r.updateConditions(req)
1✔
928
}
929

930
// This function is used to exit from the reconcile function, updating the conditions and returns the reconcile result
931
func (r *ReconcileHyperConverged) updateConditions(req *common.HcoRequest) {
1✔
932
        conditions := slices.Clone(req.Instance.Status.Conditions)
1✔
933

1✔
934
        for _, condType := range common.HcoConditionTypes {
2✔
935
                cond, found := req.Conditions[condType]
1✔
936
                if !found {
2✔
937
                        cond = metav1.Condition{
1✔
938
                                Type:               condType,
1✔
939
                                Status:             metav1.ConditionUnknown,
1✔
940
                                Message:            "Unknown Status",
1✔
941
                                Reason:             "StatusUnknown",
1✔
942
                                ObservedGeneration: req.Instance.Generation,
1✔
943
                        }
1✔
944
                }
1✔
945

946
                apimetav1.SetStatusCondition(&conditions, cond)
1✔
947
        }
948

949
        // Detect a "TaintedConfiguration" state, and raise a corresponding event
950
        r.detectTaintedConfiguration(req, &conditions)
1✔
951

1✔
952
        if !reflect.DeepEqual(conditions, req.Instance.Status.Conditions) {
2✔
953
                req.Instance.Status.Conditions = conditions
1✔
954
                req.StatusDirty = true
1✔
955
        }
1✔
956

957
        systemHealthStatus := r.getSystemHealthStatus(req)
1✔
958

1✔
959
        if systemHealthStatus != req.Instance.Status.SystemHealthStatus {
2✔
960
                req.Instance.Status.SystemHealthStatus = systemHealthStatus
1✔
961
                req.StatusDirty = true
1✔
962
        }
1✔
963

964
        metrics.SetHCOMetricSystemHealthStatus(getNumericalHealthStatus(systemHealthStatus))
1✔
965
}
966

967
func (r *ReconcileHyperConverged) setLabels(req *common.HcoRequest) {
1✔
968
        if req.Instance.Labels == nil {
2✔
969
                req.Instance.Labels = map[string]string{}
1✔
970
        }
1✔
971
        if req.Instance.Labels[hcoutil.AppLabel] == "" {
2✔
972
                req.Instance.Labels[hcoutil.AppLabel] = req.Instance.Name
1✔
973
                req.Dirty = true
1✔
974
        }
1✔
975
}
976

977
func (r *ReconcileHyperConverged) detectTaintedConfiguration(req *common.HcoRequest, conditions *[]metav1.Condition) {
1✔
978
        conditionExists := apimetav1.IsStatusConditionTrue(req.Instance.Status.Conditions, hcov1.ConditionTaintedConfiguration)
1✔
979

1✔
980
        // A tainted configuration state is indicated by the
1✔
981
        // presence of at least one of the JSON Patch annotations
1✔
982
        tainted := false
1✔
983
        for _, jpa := range JSONPatchAnnotationNames {
2✔
984
                NumOfChanges := 0
1✔
985
                jsonPatch, exists := req.Instance.Annotations[jpa]
1✔
986
                if exists {
2✔
987
                        if NumOfChanges = getNumOfChangesJSONPatch(jsonPatch); NumOfChanges > 0 {
2✔
988
                                tainted = true
1✔
989
                        }
1✔
990
                }
991
                metrics.SetUnsafeModificationCount(NumOfChanges, jpa)
1✔
992
        }
993

994
        if tainted {
2✔
995
                apimetav1.SetStatusCondition(conditions, metav1.Condition{
1✔
996
                        Type:               hcov1.ConditionTaintedConfiguration,
1✔
997
                        Status:             metav1.ConditionTrue,
1✔
998
                        Reason:             taintedConfigurationReason,
1✔
999
                        Message:            taintedConfigurationMessage,
1✔
1000
                        ObservedGeneration: req.Instance.Generation,
1✔
1001
                })
1✔
1002

1✔
1003
                if !conditionExists {
2✔
1004
                        // Only log at "first occurrence" of detection
1✔
1005
                        req.Logger.Info("Detected tainted configuration state for HCO")
1✔
1006
                }
1✔
1007
        } else { // !tainted
1✔
1008

1✔
1009
                // For the sake of keeping the JSONPatch backdoor in low profile,
1✔
1010
                // we just remove the condition instead of False'ing it.
1✔
1011
                if conditionExists {
2✔
1012
                        apimetav1.RemoveStatusCondition(conditions, hcov1.ConditionTaintedConfiguration)
1✔
1013

1✔
1014
                        req.Logger.Info("Detected untainted configuration state for HCO")
1✔
1015
                }
1✔
1016
        }
1017
}
1018

1019
func (r *ReconcileHyperConverged) getSystemHealthStatus(req *common.HcoRequest) string {
1✔
1020
        if isSystemHealthStatusError(req) {
2✔
1021
                return systemHealthStatusError
1✔
1022
        }
1✔
1023

1024
        if isSystemHealthStatusWarning(req) {
2✔
1025
                return systemHealthStatusWarning
1✔
1026
        }
1✔
1027

1028
        return systemHealthStatusHealthy
1✔
1029
}
1030

1031
func isSystemHealthStatusError(req *common.HcoRequest) bool {
1✔
1032
        // During upgrade, only Degraded=True is an error. Temporary Available=false is expected.
1✔
1033
        if req.UpgradeMode {
2✔
1034
                return req.Conditions.IsStatusConditionTrue(hcov1.ConditionDegraded)
1✔
1035
        }
1✔
1036

1037
        return !req.Conditions.IsStatusConditionTrue(hcov1.ConditionAvailable) || req.Conditions.IsStatusConditionTrue(hcov1.ConditionDegraded)
1✔
1038
}
1039

1040
func isSystemHealthStatusWarning(req *common.HcoRequest) bool {
1✔
1041
        // During upgrade, treat health as non-warning while progressing; wait for reconcile complete.
1✔
1042
        if req.UpgradeMode {
2✔
1043
                return !req.Conditions.IsStatusConditionTrue(hcov1.ConditionReconcileComplete)
1✔
1044
        }
1✔
1045

1046
        return !req.Conditions.IsStatusConditionTrue(hcov1.ConditionReconcileComplete) || req.Conditions.IsStatusConditionTrue(hcov1.ConditionProgressing)
1✔
1047
}
1048

1049
func getNumOfChangesJSONPatch(jsonPatch string) int {
1✔
1050
        patches, err := jsonpatch.DecodePatch([]byte(jsonPatch))
1✔
1051
        if err != nil {
2✔
1052
                return 0
1✔
1053
        }
1✔
1054
        return len(patches)
1✔
1055
}
1056

1057
func getNumericalHealthStatus(status string) float64 {
1✔
1058
        healthStatusCodes := map[string]float64{
1✔
1059
                systemHealthStatusHealthy: metrics.SystemHealthStatusHealthy,
1✔
1060
                systemHealthStatusWarning: metrics.SystemHealthStatusWarning,
1✔
1061
                systemHealthStatusError:   metrics.SystemHealthStatusError,
1✔
1062
        }
1✔
1063

1✔
1064
        return healthStatusCodes[status]
1✔
1065
}
1✔
1066

1067
func getMemoryOvercommitPercentage(densityConfig *hcov1.HigherWorkloadDensityConfiguration) float64 {
1✔
1068
        if densityConfig == nil {
1✔
1069
                return 0
×
1070
        }
×
1071
        return float64(densityConfig.MemoryOvercommitPercentage)
1✔
1072
}
1073

1074
func (r *ReconcileHyperConverged) firstLoopInitialization(request *common.HcoRequest) {
1✔
1075
        // Initialize operand handler.
1✔
1076
        r.operandHandler.FirstUseInitiation(r.scheme, hcoutil.GetClusterInfo(), request.Instance, r.pwdFS)
1✔
1077

1✔
1078
        // Avoid re-initializing.
1✔
1079
        r.firstLoop = false
1✔
1080
}
1✔
1081

1082
func (r *ReconcileHyperConverged) setOperatorUpgradeableStatus(request *common.HcoRequest) error {
1✔
1083
        if hcoutil.GetClusterInfo().IsManagedByOLM() {
2✔
1084

1✔
1085
                upgradeable := !r.upgradeMode && request.Upgradeable
1✔
1086

1✔
1087
                request.Logger.Info("setting the Upgradeable operator condition", requestedStatusKey, upgradeable)
1✔
1088

1✔
1089
                msg := hcoutil.UpgradeableAllowMessage
1✔
1090
                status := metav1.ConditionTrue
1✔
1091
                reason := hcoutil.UpgradeableAllowReason
1✔
1092

1✔
1093
                if !upgradeable {
2✔
1094
                        status = metav1.ConditionFalse
1✔
1095

1✔
1096
                        if r.upgradeMode {
2✔
1097
                                msg = hcoutil.UpgradeableUpgradingMessage + r.ownVersion
1✔
1098
                                reason = hcoutil.UpgradeableUpgradingReason
1✔
1099
                        } else {
2✔
1100
                                condition, found := request.Conditions.GetCondition(hcov1.ConditionUpgradeable)
1✔
1101
                                if found && condition.Status == metav1.ConditionFalse {
2✔
1102
                                        reason = condition.Reason
1✔
1103
                                        msg = condition.Message
1✔
1104
                                }
1✔
1105
                        }
1106
                }
1107

1108
                if err := r.upgradeableCondition.Set(request.Ctx, status, reason, msg); err != nil {
1✔
1109
                        request.Logger.Error(err, "can't set the Upgradeable operator condition", requestedStatusKey, upgradeable)
×
1110
                        return err
×
1111
                }
×
1112

1113
        }
1114

1115
        return nil
1✔
1116
}
1117

1118
func (r *ReconcileHyperConverged) migrateBeforeUpgrade(req *common.HcoRequest) (bool, error) {
1✔
1119
        upgradePatched, err := r.applyUpgradePatches(req)
1✔
1120
        if err != nil {
2✔
1121
                return false, err
1✔
1122
        }
1✔
1123

1124
        removeOldQuickStartGuides(req, r.client, r.operandHandler.GetQuickStartNames())
1✔
1125
        removeOldImageStream(req, r.client, r.operandHandler.GetImageStreamNames())
1✔
1126

1✔
1127
        if err = removeOldNetworkPolicies(req, r.client); err != nil {
1✔
1128
                return upgradePatched, err
×
1129
        }
×
1130

1131
        return upgradePatched, nil
1✔
1132
}
1133

1134
func (r *ReconcileHyperConverged) applyUpgradePatches(req *common.HcoRequest) (bool, error) {
1✔
1135
        modified := false
1✔
1136

1✔
1137
        knownHcoVersion, _ := GetVersion(&req.Instance.Status, hcoVersionName)
1✔
1138
        if knownHcoVersion == "" {
2✔
1139
                knownHcoVersion = "0.0.0"
1✔
1140
        }
1✔
1141
        knownHcoSV, err := semver.ParseTolerant(knownHcoVersion)
1✔
1142
        if err != nil {
2✔
1143
                req.Logger.Error(err, "Error!")
1✔
1144
                return false, err
1✔
1145
        }
1✔
1146

1147
        // Temporary workaround, until  upgradepatch.ApplyUpgradePatch moves to v1; TODO: restore when done
1148
        v1Beta1Instance := &hcov1beta1.HyperConverged{}
1✔
1149
        err = v1Beta1Instance.ConvertFrom(req.Instance)
1✔
1150
        if err != nil {
1✔
1151
                return false, err
×
1152
        }
×
1153

1154
        tmpInstance, err := upgradepatch.ApplyUpgradePatch(req.Logger, v1Beta1Instance, knownHcoSV)
1✔
1155
        if err != nil {
1✔
NEW
1156
                return false, err
×
UNCOV
1157
        }
×
1158

1159
        if !reflect.DeepEqual(tmpInstance.Spec, v1Beta1Instance.Spec) {
2✔
1160
                req.Logger.Info("updating HCO spec as a result of upgrade patches")
1✔
1161
                tmpInstance.Spec.DeepCopyInto(&v1Beta1Instance.Spec)
1✔
1162
                err = v1Beta1Instance.ConvertTo(req.Instance)
1✔
1163
                if err != nil {
1✔
NEW
1164
                        return false, err
×
NEW
1165
                }
×
1166
                // End of workaround
1167

1168
                modified = true
1✔
1169
                req.Dirty = true
1✔
1170
        }
1171

1172
        for _, p := range upgradepatch.GetObjectsToBeRemoved() {
2✔
1173
                removed, err := r.removeLeftover(req, knownHcoSV, p)
1✔
1174
                if err != nil {
1✔
NEW
1175
                        return removed, err
×
NEW
1176
                }
×
1177
        }
1178

1179
        return modified, nil
1✔
1180
}
1181

1182
func (r *ReconcileHyperConverged) removeLeftover(req *common.HcoRequest, knownHcoSV semver.Version, p upgradepatch.ObjectToBeRemoved) (bool, error) {
1✔
1183
        if p.IsAffectedRange(knownHcoSV) {
2✔
1184
                removeRelatedObject(req, r.client, p.GroupVersionKind, p.ObjectKey)
1✔
1185
                u := &unstructured.Unstructured{}
1✔
1186
                u.SetGroupVersionKind(p.GroupVersionKind)
1✔
1187
                gerr := r.client.Get(req.Ctx, p.ObjectKey, u)
1✔
1188
                if gerr != nil {
2✔
1189
                        if apierrors.IsNotFound(gerr) {
2✔
1190
                                return false, nil
1✔
1191
                        }
1✔
1192

1193
                        req.Logger.Error(gerr, "failed looking for leftovers", "objectToBeRemoved", p)
×
1194
                        return false, gerr
×
1195
                }
1196
                return r.deleteObj(req, u, false)
1✔
1197

1198
        }
1199
        return false, nil
1✔
1200
}
1201

1202
func (r *ReconcileHyperConverged) deleteObj(req *common.HcoRequest, obj client.Object, protectNonHCOObjects bool) (bool, error) {
1✔
1203
        removed, err := hcoutil.EnsureDeleted(req.Ctx, r.client, obj, req.Instance.Name, req.Logger, false, false, protectNonHCOObjects)
1✔
1204

1✔
1205
        if err != nil {
1✔
1206
                req.Logger.Error(
×
1207
                        err,
×
1208
                        fmt.Sprintf("failed to delete %s", obj.GetObjectKind().GroupVersionKind().Kind),
×
1209
                        "name",
×
1210
                        obj.GetName(),
×
1211
                )
×
1212

×
1213
                return removed, err
×
1214
        }
×
1215

1216
        if removed {
2✔
1217
                r.eventEmitter.EmitEvent(
1✔
1218
                        req.Instance, corev1.EventTypeNormal, "Killing",
1✔
1219
                        fmt.Sprintf("Removed %s %s", obj.GetName(), obj.GetObjectKind().GroupVersionKind().Kind),
1✔
1220
                )
1✔
1221
        }
1✔
1222

1223
        return removed, nil
1✔
1224
}
1225

1226
func removeRelatedObject(req *common.HcoRequest, cl client.Client, gvk schema.GroupVersionKind, objectKey types.NamespacedName) {
1✔
1227
        refs := make([]corev1.ObjectReference, 0, len(req.Instance.Status.RelatedObjects))
1✔
1228
        foundRO := false
1✔
1229

1✔
1230
        crdGVK := schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}
1✔
1231

1✔
1232
        for _, obj := range req.Instance.Status.RelatedObjects {
2✔
1233
                apiVersion, kind := gvk.ToAPIVersionAndKind()
1✔
1234
                if obj.APIVersion == apiVersion && obj.Kind == kind && obj.Namespace == objectKey.Namespace && obj.Name == objectKey.Name {
2✔
1235
                        foundRO = true
1✔
1236
                        req.Logger.Info("Removed relatedObject entry for", "gvk", gvk, "objectKey", objectKey)
1✔
1237
                        continue
1✔
1238
                }
1239
                if reflect.DeepEqual(gvk, crdGVK) {
2✔
1240
                        mapping, err := cl.RESTMapper().RESTMapping(obj.GroupVersionKind().GroupKind(), obj.GroupVersionKind().Version)
1✔
1241
                        if err == nil && mapping != nil && mapping.Resource.GroupResource().String() == objectKey.Name {
2✔
1242
                                foundRO = true
1✔
1243
                                req.Logger.Info("Removed relatedObject on CRD removal for", "gvk", gvk, "objectKey", objectKey)
1✔
1244
                                continue
1✔
1245
                        }
1246
                }
1247
                refs = append(refs, obj)
1✔
1248
        }
1249

1250
        if foundRO {
2✔
1251
                req.Instance.Status.RelatedObjects = refs
1✔
1252
                req.StatusDirty = true
1✔
1253
        }
1✔
1254

1255
}
1256

1257
func checkFinalizers(req *common.HcoRequest) bool {
1✔
1258
        if req.Instance.DeletionTimestamp.IsZero() {
2✔
1259
                // Add the finalizer if it's not there
1✔
1260
                if !slices.Contains(req.Instance.Finalizers, FinalizerName) {
2✔
1261
                        req.Logger.Info("setting a finalizer (with fully qualified name)")
1✔
1262
                        req.Instance.Finalizers = append(req.Instance.Finalizers, FinalizerName)
1✔
1263
                        req.Dirty = true
1✔
1264
                }
1✔
1265
                return true
1✔
1266
        }
1267
        return false
1✔
1268
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc