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

kubevirt / hyperconverged-cluster-operator / 26160034989

20 May 2026 11:37AM UTC coverage: 80.345% (-0.2%) from 80.502%
26160034989

Pull #4264

github

web-flow
Merge 91b1f4662 into 510e4bd40
Pull Request #4264: CNV-86222: Move the `upgradepatch` package to v1

9 of 9 new or added lines in 3 files covered. (100.0%)

23 existing lines in 3 files now uncovered.

10436 of 12989 relevant lines covered (80.34%)

2.05 hits per line

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

78.37
/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
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/alerts"
53
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/common"
54
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/operandhandler"
55
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/reqresolver"
56
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/monitoring/hyperconverged/metrics"
57
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/nodeinfo"
58
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/tlssecprofile"
59
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/upgradepatch"
60
        hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
61
        "github.com/kubevirt/hyperconverged-cluster-operator/version"
62
)
63

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

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

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

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

89
        hcoVersionName = "operator"
90

91
        requestedStatusKey = "requested status"
92

93
        requeueAfter = time.Millisecond * 100
94
)
95

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

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

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

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

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

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

128
        pwdFS = os.DirFS(pwd)
×
129

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

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

146
        return r
×
147
}
148

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

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

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

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

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

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

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

285
        return nil
×
286
}
287

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

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

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

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

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

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

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

332
        hcoRequest.Instance = instance
1✔
333

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

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

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

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

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

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

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

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

370
        return result, err
1✔
371
}
372

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

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

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

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

389
        r.setLabels(req)
1✔
390

1✔
391
        updateStatus(req)
1✔
392

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

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

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

411
        applyDataImportSchedule(req)
1✔
412

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

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

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

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

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

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

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

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

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

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

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

467
        r.completeReconciliation(req)
1✔
468

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

569
        return false, nil
1✔
570
}
571

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
812
        } else {
2✔
813

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

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

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

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

1✔
841
                } else {
2✔
842

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

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

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

1✔
867
                }
1✔
868
        }
869

870
        return allComponentsAreUp
1✔
871
}
872

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

1✔
876
        hcoReady := false
1✔
877

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1027
        return systemHealthStatusHealthy
1✔
1028
}
1029

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1112
        }
1113

1114
        return nil
1✔
1115
}
1116

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

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

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

1130
        return upgradePatched, nil
1✔
1131
}
1132

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

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

1146
        tmpInstance, err := upgradepatch.ApplyUpgradePatch(req.Logger, req.Instance, knownHcoSV)
1✔
1147
        if err != nil {
1✔
1148
                return false, err
×
1149
        }
×
1150

1151
        if !reflect.DeepEqual(tmpInstance.Spec, req.Instance.Spec) {
2✔
1152
                req.Logger.Info("updating HCO spec as a result of upgrade patches")
1✔
1153
                tmpInstance.Spec.DeepCopyInto(&req.Instance.Spec)
1✔
1154
                modified = true
1✔
1155
                req.Dirty = true
1✔
1156
        }
1✔
1157

1158
        for _, p := range upgradepatch.GetObjectsToBeRemoved() {
2✔
1159
                removed, err := r.removeLeftover(req, knownHcoSV, p)
1✔
1160
                if err != nil {
1✔
1161
                        return removed, err
×
1162
                }
×
1163
        }
1164

1165
        return modified, nil
1✔
1166
}
1167

1168
func (r *ReconcileHyperConverged) removeLeftover(req *common.HcoRequest, knownHcoSV semver.Version, p upgradepatch.ObjectToBeRemoved) (bool, error) {
1✔
1169
        if p.IsAffectedRange(knownHcoSV) {
2✔
1170
                removeRelatedObject(req, r.client, p.GroupVersionKind, p.ObjectKey)
1✔
1171
                u := &unstructured.Unstructured{}
1✔
1172
                u.SetGroupVersionKind(p.GroupVersionKind)
1✔
1173
                gerr := r.client.Get(req.Ctx, p.ObjectKey, u)
1✔
1174
                if gerr != nil {
2✔
1175
                        if apierrors.IsNotFound(gerr) {
2✔
1176
                                return false, nil
1✔
1177
                        }
1✔
1178

1179
                        req.Logger.Error(gerr, "failed looking for leftovers", "objectToBeRemoved", p)
×
1180
                        return false, gerr
×
1181
                }
1182
                return r.deleteObj(req, u, false)
1✔
1183

1184
        }
1185
        return false, nil
1✔
1186
}
1187

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

1✔
1191
        if err != nil {
1✔
1192
                req.Logger.Error(
×
1193
                        err,
×
1194
                        fmt.Sprintf("failed to delete %s", obj.GetObjectKind().GroupVersionKind().Kind),
×
1195
                        "name",
×
1196
                        obj.GetName(),
×
1197
                )
×
1198

×
1199
                return removed, err
×
1200
        }
×
1201

1202
        if removed {
2✔
1203
                r.eventEmitter.EmitEvent(
1✔
1204
                        req.Instance, corev1.EventTypeNormal, "Killing",
1✔
1205
                        fmt.Sprintf("Removed %s %s", obj.GetName(), obj.GetObjectKind().GroupVersionKind().Kind),
1✔
1206
                )
1✔
1207
        }
1✔
1208

1209
        return removed, nil
1✔
1210
}
1211

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

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

1✔
1218
        for _, obj := range req.Instance.Status.RelatedObjects {
2✔
1219
                apiVersion, kind := gvk.ToAPIVersionAndKind()
1✔
1220
                if obj.APIVersion == apiVersion && obj.Kind == kind && obj.Namespace == objectKey.Namespace && obj.Name == objectKey.Name {
2✔
1221
                        foundRO = true
1✔
1222
                        req.Logger.Info("Removed relatedObject entry for", "gvk", gvk, "objectKey", objectKey)
1✔
1223
                        continue
1✔
1224
                }
1225
                if reflect.DeepEqual(gvk, crdGVK) {
1✔
UNCOV
1226
                        mapping, err := cl.RESTMapper().RESTMapping(obj.GroupVersionKind().GroupKind(), obj.GroupVersionKind().Version)
×
UNCOV
1227
                        if err == nil && mapping != nil && mapping.Resource.GroupResource().String() == objectKey.Name {
×
UNCOV
1228
                                foundRO = true
×
UNCOV
1229
                                req.Logger.Info("Removed relatedObject on CRD removal for", "gvk", gvk, "objectKey", objectKey)
×
UNCOV
1230
                                continue
×
1231
                        }
1232
                }
1233
                refs = append(refs, obj)
1✔
1234
        }
1235

1236
        if foundRO {
2✔
1237
                req.Instance.Status.RelatedObjects = refs
1✔
1238
                req.StatusDirty = true
1✔
1239
        }
1✔
1240

1241
}
1242

1243
func checkFinalizers(req *common.HcoRequest) bool {
1✔
1244
        if req.Instance.DeletionTimestamp.IsZero() {
2✔
1245
                // Add the finalizer if it's not there
1✔
1246
                if !slices.Contains(req.Instance.Finalizers, FinalizerName) {
2✔
1247
                        req.Logger.Info("setting a finalizer (with fully qualified name)")
1✔
1248
                        req.Instance.Finalizers = append(req.Instance.Finalizers, FinalizerName)
1✔
1249
                        req.Dirty = true
1✔
1250
                }
1✔
1251
                return true
1✔
1252
        }
1253
        return false
1✔
1254
}
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