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

kubevirt / hyperconverged-cluster-operator / 13310017403

13 Feb 2025 02:31PM UTC coverage: 72.286% (+0.3%) from 71.958%
13310017403

Pull #3281

github

nunnatsa
add missing permissions

Signed-off-by: Nahshon Unna-Tsameret <nunnatsa@redhat.com>
Pull Request #3281: Allow customizing the CLI download link

302 of 402 new or added lines in 12 files covered. (75.12%)

5 existing lines in 2 files now uncovered.

6359 of 8797 relevant lines covered (72.29%)

0.8 hits per line

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

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

3
import (
4
        "cmp"
5
        "context"
6
        "fmt"
7
        "os"
8
        "reflect"
9
        "slices"
10

11
        "github.com/blang/semver/v4"
12
        jsonpatch "github.com/evanphx/json-patch/v5"
13
        "github.com/go-logr/logr"
14
        openshiftconfigv1 "github.com/openshift/api/config/v1"
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
        operatorhandler "github.com/operator-framework/operator-lib/handler"
19
        monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
20
        appsv1 "k8s.io/api/apps/v1"
21
        corev1 "k8s.io/api/core/v1"
22
        rbacv1 "k8s.io/api/rbac/v1"
23
        schedulingv1 "k8s.io/api/scheduling/v1"
24
        apierrors "k8s.io/apimachinery/pkg/api/errors"
25
        apimetav1 "k8s.io/apimachinery/pkg/api/meta"
26
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27
        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28
        "k8s.io/apimachinery/pkg/runtime"
29
        "k8s.io/apimachinery/pkg/runtime/schema"
30
        "k8s.io/apimachinery/pkg/types"
31
        "k8s.io/utils/ptr"
32
        "sigs.k8s.io/controller-runtime/pkg/client"
33
        "sigs.k8s.io/controller-runtime/pkg/controller"
34
        "sigs.k8s.io/controller-runtime/pkg/event"
35
        "sigs.k8s.io/controller-runtime/pkg/handler"
36
        logf "sigs.k8s.io/controller-runtime/pkg/log"
37
        "sigs.k8s.io/controller-runtime/pkg/manager"
38
        "sigs.k8s.io/controller-runtime/pkg/predicate"
39
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
40
        "sigs.k8s.io/controller-runtime/pkg/source"
41

42
        networkaddonsv1 "github.com/kubevirt/cluster-network-addons-operator/pkg/apis/networkaddonsoperator/v1"
43
        kubevirtcorev1 "kubevirt.io/api/core/v1"
44
        aaqv1alpha1 "kubevirt.io/application-aware-quota/staging/src/kubevirt.io/application-aware-quota-api/pkg/apis/core/v1alpha1"
45
        cdiv1beta1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
46
        sspv1beta2 "kubevirt.io/ssp-operator/api/v1beta2"
47

48
        hcov1beta1 "github.com/kubevirt/hyperconverged-cluster-operator/api/v1beta1"
49
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/alerts"
50
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/common"
51
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/operands"
52
        "github.com/kubevirt/hyperconverged-cluster-operator/controllers/reqresolver"
53
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/monitoring/hyperconverged/metrics"
54
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/upgradepatch"
55
        hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
56
        "github.com/kubevirt/hyperconverged-cluster-operator/version"
57
)
58

59
var (
60
        log = logf.Log.WithName("controller_hyperconverged")
61
)
62

63
const (
64
        // We cannot set owner reference of cluster-wide resources to namespaced HyperConverged object. Therefore,
65
        // use finalizers to manage the cleanup.
66
        FinalizerName = "kubevirt.io/hyperconverged"
67

68
        // OpenshiftNamespace is for resources that belong in the openshift namespace
69

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

84
        hcoVersionName = "operator"
85

86
        requestedStatusKey = "requested status"
87
)
88

89
// JSONPatchAnnotationNames - annotations used to patch operand CRs with unsupported/unofficial/hidden features.
90
// The presence of any of these annotations raises the hcov1beta1.ConditionTaintedConfiguration condition.
91
var JSONPatchAnnotationNames = []string{
92
        common.JSONPatchKVAnnotationName,
93
        common.JSONPatchCDIAnnotationName,
94
        common.JSONPatchCNAOAnnotationName,
95
        common.JSONPatchSSPAnnotationName,
96
}
97

98
// RegisterReconciler creates a new HyperConverged Reconciler and registers it into manager.
NEW
99
func RegisterReconciler(mgr manager.Manager, ci hcoutil.ClusterInfo, upgradeableCond hcoutil.Condition, ingressEventCh <-chan event.TypedGenericEvent[client.Object]) error {
×
NEW
100
        return add(mgr, newReconciler(mgr, ci, upgradeableCond), ci, ingressEventCh)
×
UNCOV
101
}
×
102

103
// newReconciler returns a new reconcile.Reconciler
104
func newReconciler(mgr manager.Manager, ci hcoutil.ClusterInfo, upgradeableCond hcoutil.Condition) reconcile.Reconciler {
×
105

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

×
108
        r := &ReconcileHyperConverged{
×
109
                client:               mgr.GetClient(),
×
110
                scheme:               mgr.GetScheme(),
×
111
                operandHandler:       operands.NewOperandHandler(mgr.GetClient(), mgr.GetScheme(), ci, hcoutil.GetEventEmitter()),
×
112
                upgradeMode:          false,
×
113
                ownVersion:           ownVersion,
×
114
                eventEmitter:         hcoutil.GetEventEmitter(),
×
115
                firstLoop:            true,
×
116
                upgradeableCondition: upgradeableCond,
×
117
        }
×
118

×
119
        if ci.IsMonitoringAvailable() {
×
120
                r.monitoringReconciler = alerts.NewMonitoringReconciler(ci, r.client, hcoutil.GetEventEmitter(), r.scheme)
×
121
        }
×
122

123
        return r
×
124
}
125

126
// newCRDremover returns a new CRDRemover
NEW
127
func add(mgr manager.Manager, r reconcile.Reconciler, ci hcoutil.ClusterInfo, ingressEventCh <-chan event.TypedGenericEvent[client.Object]) error {
×
128
        // Create a new controller
×
129
        c, err := controller.New("hyperconverged-controller", mgr, controller.Options{Reconciler: r})
×
130
        if err != nil {
×
131
                return err
×
132
        }
×
133

134
        // Watch for changes to primary resource HyperConverged
135
        err = c.Watch(
×
136
                source.Kind(
×
137
                        mgr.GetCache(), client.Object(&hcov1beta1.HyperConverged{}),
×
138
                        &operatorhandler.InstrumentedEnqueueRequestForObject[client.Object]{},
×
139
                        predicate.Or[client.Object](predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{},
×
140
                                predicate.ResourceVersionChangedPredicate{}),
×
141
                ))
×
142
        if err != nil {
×
143
                return err
×
144
        }
×
145

146
        // To limit the memory usage, the controller manager got instantiated with a custom cache
147
        // that is watching only a specific set of objects with selectors.
148
        // When a new object got added here, it has also to be added to the custom cache
149
        // managed by getNewManagerCache()
150
        secondaryResources := []client.Object{
×
151
                &kubevirtcorev1.KubeVirt{},
×
152
                &cdiv1beta1.CDI{},
×
153
                &networkaddonsv1.NetworkAddonsConfig{},
×
154
                &aaqv1alpha1.AAQ{},
×
155
                &schedulingv1.PriorityClass{},
×
156
                &corev1.ConfigMap{},
×
157
                &corev1.Service{},
×
158
                &rbacv1.Role{},
×
159
                &rbacv1.RoleBinding{},
×
160
        }
×
161
        if ci.IsMonitoringAvailable() {
×
162
                secondaryResources = append(secondaryResources, []client.Object{
×
163
                        &monitoringv1.ServiceMonitor{},
×
164
                        &monitoringv1.PrometheusRule{},
×
165
                }...)
×
166
        }
×
167
        if ci.IsOpenshift() {
×
168
                secondaryResources = append(secondaryResources, []client.Object{
×
169
                        &sspv1beta2.SSP{},
×
170
                        &corev1.Service{},
×
171
                        &routev1.Route{},
×
172
                        &consolev1.ConsoleCLIDownload{},
×
173
                        &consolev1.ConsoleQuickStart{},
×
174
                        &consolev1.ConsolePlugin{},
×
175
                        &imagev1.ImageStream{},
×
176
                        &corev1.Namespace{},
×
177
                        &appsv1.Deployment{},
×
178
                }...)
×
179
        }
×
180

181
        // Watch secondary resources
182
        for _, resource := range secondaryResources {
×
183
                msg := fmt.Sprintf("Reconciling for %T", resource)
×
184
                err = c.Watch(
×
185
                        source.Kind(mgr.GetCache(), resource,
×
186
                                handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request {
×
187
                                        // enqueue using a placeholder to be able to discriminate request triggered
×
188
                                        // by changes on the HyperConverged object from request triggered by changes
×
189
                                        // on a secondary CR controlled by HCO
×
190
                                        log.Info(msg)
×
191
                                        return []reconcile.Request{
×
NEW
192
                                                reqresolver.GetSecondaryCRRequest(),
×
193
                                        }
×
194
                                }),
×
195
                        ))
196
                if err != nil {
×
197
                        return err
×
198
                }
×
199
        }
200

201
        if ci.IsOpenshift() {
×
202
                err = c.Watch(
×
203
                        source.Kind(
×
204
                                mgr.GetCache(),
×
205
                                client.Object(&openshiftconfigv1.APIServer{}),
×
206
                                handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request {
×
207
                                        // enqueue using a placeholder to signal that the change is not
×
208
                                        // directly on HCO CR but on the APIServer CR that we want to reload
×
209
                                        // only if really changed
×
NEW
210
                                        log.Info("Reconciling for openshiftconfigv1.APIServer")
×
211
                                        return []reconcile.Request{
×
NEW
212
                                                reqresolver.GetAPIServerCRRequest(),
×
NEW
213
                                        }
×
NEW
214
                                }),
×
215
                        ))
NEW
216
                if err != nil {
×
NEW
217
                        return err
×
NEW
218
                }
×
219

NEW
220
                err = c.Watch(
×
NEW
221
                        source.Channel(
×
NEW
222
                                ingressEventCh,
×
NEW
223
                                handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, a client.Object) []reconcile.Request {
×
NEW
224
                                        // the ingress-cluster controller initiate this by pushing an event to the ingressEventCh channel
×
NEW
225
                                        // This will force this controller to update the URL of the cli download route, if the user
×
NEW
226
                                        // customized the hostname.
×
NEW
227
                                        log.Info("Reconciling for openshiftconfigv1.Ingress")
×
NEW
228
                                        return []reconcile.Request{
×
NEW
229
                                                reqresolver.GetIngressCRResource(),
×
230
                                        }
×
231
                                }),
×
232
                        ))
233
                if err != nil {
×
234
                        return err
×
235
                }
×
236
        }
237

UNCOV
238
        return nil
×
239
}
240

241
var _ reconcile.Reconciler = &ReconcileHyperConverged{}
242

243
// ReconcileHyperConverged reconciles a HyperConverged object
244
type ReconcileHyperConverged struct {
245
        // This client, initialized using mgr.Client() above, is a split client
246
        // that reads objects from the cache and writes to the apiserver
247
        client               client.Client
248
        scheme               *runtime.Scheme
249
        operandHandler       *operands.OperandHandler
250
        upgradeMode          bool
251
        ownVersion           string
252
        eventEmitter         hcoutil.EventEmitter
253
        firstLoop            bool
254
        upgradeableCondition hcoutil.Condition
255
        monitoringReconciler *alerts.MonitoringReconciler
256
}
257

258
// Reconcile reads that state of the cluster for a HyperConverged object and makes changes based on the state read
259
// and what is in the HyperConverged.Spec
260
// Note:
261
// The Controller will requeue the Request to be processed again if the returned error is non-nil or
262
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
263
func (r *ReconcileHyperConverged) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
1✔
264
        logger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
1✔
265
        err := r.refreshAPIServerCR(ctx, logger, request)
1✔
266
        if err != nil {
1✔
267
                return reconcile.Result{}, err
×
268
        }
×
269

270
        resolvedRequest, hcoTriggered := reqresolver.ResolveReconcileRequest(log, request)
1✔
271
        hcoRequest := common.NewHcoRequest(ctx, resolvedRequest, log, r.upgradeMode, hcoTriggered)
1✔
272

1✔
273
        if hcoTriggered {
2✔
274
                r.operandHandler.Reset()
1✔
275
        }
1✔
276

277
        err = r.monitoringReconciler.Reconcile(hcoRequest, r.firstLoop)
1✔
278
        if err != nil {
1✔
279
                return reconcile.Result{}, err
×
280
        }
×
281

282
        // Fetch the HyperConverged instance
283
        instance, err := r.getHyperConverged(hcoRequest)
1✔
284
        if err != nil {
1✔
285
                return reconcile.Result{}, err
×
286
        }
×
287

288
        hcoRequest.Instance = instance
1✔
289

1✔
290
        if instance == nil {
2✔
291
                // if the HyperConverged CR was deleted during an upgrade process, then this is not an upgrade anymore
1✔
292
                r.upgradeMode = false
1✔
293
                err = r.setOperatorUpgradeableStatus(hcoRequest)
1✔
294

1✔
295
                return reconcile.Result{}, err
1✔
296
        }
1✔
297

298
        if r.firstLoop {
2✔
299
                r.firstLoopInitialization(hcoRequest)
1✔
300
        }
1✔
301

302
        if err = r.monitoringReconciler.UpdateRelatedObjects(hcoRequest); err != nil {
1✔
303
                logger.Error(err, "Failed to update the PrometheusRule as a related object")
×
304
                return reconcile.Result{}, err
×
305
        }
×
306

307
        result, err := r.doReconcile(hcoRequest)
1✔
308
        if err != nil {
2✔
309
                r.eventEmitter.EmitEvent(hcoRequest.Instance, corev1.EventTypeWarning, "ReconcileError", err.Error())
1✔
310
                return result, err
1✔
311
        }
1✔
312

313
        if err = r.setOperatorUpgradeableStatus(hcoRequest); err != nil {
1✔
314
                return reconcile.Result{}, err
×
315
        }
×
316

317
        requeue, err := r.updateHyperConverged(hcoRequest)
1✔
318
        if requeue || apierrors.IsConflict(err) {
2✔
319
                result.Requeue = true
1✔
320
        }
1✔
321

322
        return result, err
1✔
323
}
324

325
// refreshAPIServerCR refreshes the APIServer cR, if the request is triggered by this CR.
326
func (r *ReconcileHyperConverged) refreshAPIServerCR(ctx context.Context, logger logr.Logger, originalRequest reconcile.Request) error {
1✔
327
        if reqresolver.IsTriggeredByAPIServerCR(originalRequest) {
2✔
328
                logger.Info("Refreshing the ApiServer CR")
1✔
329
                return hcoutil.GetClusterInfo().RefreshAPIServerCR(ctx, r.client)
1✔
330
        }
1✔
331

332
        return nil
1✔
333
}
334

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

1✔
337
        valid := r.validateNamespace(req)
1✔
338
        if !valid {
2✔
339
                return reconcile.Result{}, nil
1✔
340
        }
1✔
341

342
        // Add conditions if there are none
343
        init := req.Instance.Status.Conditions == nil
1✔
344
        if init {
2✔
345
                r.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeNormal, "InitHCO", "Initiating the HyperConverged")
1✔
346
                r.setInitialConditions(req)
1✔
347

1✔
348
                req.Instance.Status.InfrastructureHighlyAvailable = ptr.To(hcoutil.GetClusterInfo().IsInfrastructureHighlyAvailable())
1✔
349
                req.StatusDirty = true
1✔
350
        }
1✔
351

352
        r.setLabels(req)
1✔
353

1✔
354
        updateStatusGeneration(req)
1✔
355

1✔
356
        // in-memory conditions should start off empty. It will only ever hold
1✔
357
        // negative conditions (!Available, Degraded, Progressing)
1✔
358
        req.Conditions = common.NewHcoConditions()
1✔
359

1✔
360
        // Handle finalizers
1✔
361
        if !checkFinalizers(req) {
2✔
362
                if !req.HCOTriggered {
1✔
363
                        // this is just the effect of a delete request created by HCO
×
364
                        // in the previous iteration, ignore it
×
365
                        return reconcile.Result{}, nil
×
366
                }
×
367
                return r.ensureHcoDeleted(req)
1✔
368
        }
369

370
        applyDataImportSchedule(req)
1✔
371

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

1✔
376
        // detect upgrade mode
1✔
377
        if !r.upgradeMode && !init && knownHcoVersion != r.ownVersion {
2✔
378
                // get into upgrade mode
1✔
379

1✔
380
                r.upgradeMode = true
1✔
381
                r.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeNormal, "UpgradeHCO", "Upgrading the HyperConverged to version "+r.ownVersion)
1✔
382
                req.Logger.Info(fmt.Sprintf("Start upgrading from version %s to version %s", knownHcoVersion, r.ownVersion))
1✔
383
        }
1✔
384

385
        req.SetUpgradeMode(r.upgradeMode)
1✔
386

1✔
387
        if r.upgradeMode {
2✔
388
                if result, err := r.handleUpgrade(req); result != nil {
2✔
389
                        return *result, err
1✔
390
                }
1✔
391
        }
392

393
        return r.EnsureOperandAndComplete(req, init)
1✔
394
}
395

396
func (r *ReconcileHyperConverged) handleUpgrade(req *common.HcoRequest) (*reconcile.Result, error) {
1✔
397
        modified, err := r.migrateBeforeUpgrade(req)
1✔
398
        if err != nil {
2✔
399
                return &reconcile.Result{Requeue: true}, err
1✔
400
        }
1✔
401

402
        if modified {
2✔
403
                r.updateConditions(req)
1✔
404
                return &reconcile.Result{Requeue: true}, nil
1✔
405
        }
1✔
406
        return nil, nil
1✔
407
}
408

409
func (r *ReconcileHyperConverged) EnsureOperandAndComplete(req *common.HcoRequest, init bool) (reconcile.Result, error) {
1✔
410
        if err := r.operandHandler.Ensure(req); err != nil {
2✔
411
                r.updateConditions(req)
1✔
412
                return reconcile.Result{Requeue: init}, nil
1✔
413
        }
1✔
414

415
        req.Logger.Info("Reconcile complete")
1✔
416

1✔
417
        // Requeue if we just created everything
1✔
418
        if init {
2✔
419
                return reconcile.Result{Requeue: true}, nil
1✔
420
        }
1✔
421

422
        r.completeReconciliation(req)
1✔
423

1✔
424
        return reconcile.Result{}, nil
1✔
425
}
426

427
func updateStatusGeneration(req *common.HcoRequest) {
1✔
428
        if req.Instance.ObjectMeta.Generation != req.Instance.Status.ObservedGeneration {
2✔
429
                req.Instance.Status.ObservedGeneration = req.Instance.ObjectMeta.Generation
1✔
430
                req.StatusDirty = true
1✔
431
        }
1✔
432
}
433

434
// getHyperConverged gets the HyperConverged resource from the Kubernetes API.
435
func (r *ReconcileHyperConverged) getHyperConverged(req *common.HcoRequest) (*hcov1beta1.HyperConverged, error) {
1✔
436
        instance := &hcov1beta1.HyperConverged{}
1✔
437
        err := r.client.Get(req.Ctx, req.NamespacedName, instance)
1✔
438

1✔
439
        // Green path first
1✔
440
        if err == nil {
2✔
441
                metrics.SetHCOMetricHyperConvergedExists()
1✔
442
                return instance, nil
1✔
443
        }
1✔
444

445
        // Error path
446
        if apierrors.IsNotFound(err) {
2✔
447
                req.Logger.Info("No HyperConverged resource")
1✔
448
                metrics.SetHCOMetricHyperConvergedNotExists()
1✔
449

1✔
450
                // Request object not found, could have been deleted after reconcile request.
1✔
451
                // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
1✔
452
                // Return and don't requeue
1✔
453
                return nil, nil
1✔
454
        }
1✔
455

456
        // Another error reading the object.
457
        // Just return the error so that the request is requeued.
458
        return nil, err
×
459
}
460

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

1✔
464
        // Since the status subresource is enabled for the HyperConverged kind,
1✔
465
        // we need to update the status and the metadata separately.
1✔
466
        // Moreover, we need to update the status first, in order to prevent a conflict.
1✔
467
        // In addition, metadata and spec changes are removed by status update, but since status update done first, we need
1✔
468
        // to store metadata and spec and recover it after status update
1✔
469

1✔
470
        var spec hcov1beta1.HyperConvergedSpec
1✔
471
        var meta metav1.ObjectMeta
1✔
472
        if request.Dirty {
2✔
473
                request.Instance.Spec.DeepCopyInto(&spec)
1✔
474
                request.Instance.ObjectMeta.DeepCopyInto(&meta)
1✔
475
        }
1✔
476

477
        err := r.updateHyperConvergedStatus(request)
1✔
478
        if err != nil {
2✔
479
                request.Logger.Error(err, "Failed to update HCO Status")
1✔
480
                return false, err
1✔
481
        }
1✔
482

483
        if request.Dirty {
2✔
484
                request.Instance.ObjectMeta.Annotations = meta.Annotations
1✔
485
                request.Instance.ObjectMeta.Finalizers = meta.Finalizers
1✔
486
                request.Instance.ObjectMeta.Labels = meta.Labels
1✔
487
                request.Instance.Spec = spec
1✔
488

1✔
489
                err = r.updateHyperConvergedSpecMetadata(request)
1✔
490
                if err != nil {
2✔
491
                        request.Logger.Error(err, "Failed to update HCO CR")
1✔
492
                        return false, err
1✔
493
                }
1✔
494
                // version update is a two steps process
495
                knownHcoVersion, _ := GetVersion(&request.Instance.Status, hcoVersionName)
1✔
496
                if r.ownVersion != knownHcoVersion && request.StatusDirty {
2✔
497
                        return true, nil
1✔
498
                }
1✔
499
        }
500

501
        return false, nil
1✔
502
}
503

504
// updateHyperConvergedSpecMetadata updates the HyperConverged resource's spec and metadata.
505
func (r *ReconcileHyperConverged) updateHyperConvergedSpecMetadata(request *common.HcoRequest) error {
1✔
506
        if !request.Dirty {
1✔
507
                return nil
×
508
        }
×
509

510
        return r.client.Update(request.Ctx, request.Instance)
1✔
511
}
512

513
// updateHyperConvergedSpecMetadata updates the HyperConverged resource's status (and metadata).
514
func (r *ReconcileHyperConverged) updateHyperConvergedStatus(request *common.HcoRequest) error {
1✔
515
        if !request.StatusDirty {
2✔
516
                return nil
1✔
517
        }
1✔
518

519
        return r.client.Status().Update(request.Ctx, request.Instance)
1✔
520
}
521

522
func (r *ReconcileHyperConverged) validateNamespace(req *common.HcoRequest) bool {
1✔
523
        // Ignore invalid requests
1✔
524
        if !reqresolver.IsTriggeredByHyperConverged(req.NamespacedName) {
2✔
525
                req.Logger.Info("Invalid request", "HyperConverged.Namespace", req.NamespacedName.Namespace, "HyperConverged.Name", req.NamespacedName.Name)
1✔
526
                hc := reqresolver.GetHyperConvergedNamespacedName()
1✔
527
                req.Conditions.SetStatusCondition(metav1.Condition{
1✔
528
                        Type:               hcov1beta1.ConditionReconcileComplete,
1✔
529
                        Status:             metav1.ConditionFalse,
1✔
530
                        Reason:             invalidRequestReason,
1✔
531
                        Message:            fmt.Sprintf(invalidRequestMessageFormat, hc.Name, hc.Namespace),
1✔
532
                        ObservedGeneration: req.Instance.Generation,
1✔
533
                })
1✔
534
                r.updateConditions(req)
1✔
535
                return false
1✔
536
        }
1✔
537
        return true
1✔
538
}
539

540
func (r *ReconcileHyperConverged) setInitialConditions(req *common.HcoRequest) {
1✔
541
        UpdateVersion(&req.Instance.Status, hcoVersionName, r.ownVersion)
1✔
542

1✔
543
        req.Conditions.SetStatusCondition(metav1.Condition{
1✔
544
                Type:               hcov1beta1.ConditionReconcileComplete,
1✔
545
                Status:             metav1.ConditionUnknown, // we just started trying to reconcile
1✔
546
                Reason:             reconcileInit,
1✔
547
                Message:            reconcileInitMessage,
1✔
548
                ObservedGeneration: req.Instance.Generation,
1✔
549
        })
1✔
550
        req.Conditions.SetStatusCondition(metav1.Condition{
1✔
551
                Type:               hcov1beta1.ConditionAvailable,
1✔
552
                Status:             metav1.ConditionFalse,
1✔
553
                Reason:             reconcileInit,
1✔
554
                Message:            reconcileInitMessage,
1✔
555
                ObservedGeneration: req.Instance.Generation,
1✔
556
        })
1✔
557
        req.Conditions.SetStatusCondition(metav1.Condition{
1✔
558
                Type:               hcov1beta1.ConditionProgressing,
1✔
559
                Status:             metav1.ConditionTrue,
1✔
560
                Reason:             reconcileInit,
1✔
561
                Message:            reconcileInitMessage,
1✔
562
                ObservedGeneration: req.Instance.Generation,
1✔
563
        })
1✔
564
        req.Conditions.SetStatusCondition(metav1.Condition{
1✔
565
                Type:               hcov1beta1.ConditionDegraded,
1✔
566
                Status:             metav1.ConditionFalse,
1✔
567
                Reason:             reconcileInit,
1✔
568
                Message:            reconcileInitMessage,
1✔
569
                ObservedGeneration: req.Instance.Generation,
1✔
570
        })
1✔
571
        req.Conditions.SetStatusCondition(metav1.Condition{
1✔
572
                Type:               hcov1beta1.ConditionUpgradeable,
1✔
573
                Status:             metav1.ConditionUnknown,
1✔
574
                Reason:             reconcileInit,
1✔
575
                Message:            reconcileInitMessage,
1✔
576
                ObservedGeneration: req.Instance.Generation,
1✔
577
        })
1✔
578

1✔
579
        r.updateConditions(req)
1✔
580
}
1✔
581

582
func (r *ReconcileHyperConverged) ensureHcoDeleted(req *common.HcoRequest) (reconcile.Result, error) {
1✔
583
        err := r.operandHandler.EnsureDeleted(req)
1✔
584
        if err != nil {
1✔
585
                return reconcile.Result{}, err
×
586
        }
×
587

588
        requeue := false
1✔
589

1✔
590
        // Remove the finalizers
1✔
591
        finDropped := false
1✔
592
        if slices.Contains(req.Instance.ObjectMeta.Finalizers, FinalizerName) {
2✔
593
                req.Instance.ObjectMeta.Finalizers, finDropped = drop(req.Instance.ObjectMeta.Finalizers, FinalizerName)
1✔
594
                req.Dirty = true
1✔
595
                requeue = finDropped
1✔
596
        }
1✔
597

598
        // Need to requeue because finalizer update does not change metadata.generation
599
        return reconcile.Result{Requeue: requeue}, nil
1✔
600
}
601

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

1✔
608
                @startuml ../../../design/aggregateComponentConditions.svg
1✔
609
                title Aggregate Component Conditions
1✔
610

1✔
611
                start
1✔
612
                  #springgreen:Set **ReconcileComplete = True**]
1✔
613
                  !x=1
1✔
614
                if ((x) [Degraded = True] Exists) then
1✔
615
                  !x=x+1
1✔
616
                  #orangered:<<implicit>>\n**Degraded = True** /
1✔
617
                  -[#orangered]-> yes;
1✔
618
                  if ((x) [Progressing = True] Exists) then
1✔
619
                        !x=x+1
1✔
620
                        -[#springgreen]-> no;
1✔
621
                        #springgreen:(x) Set **Progressing = False**]
1✔
622
                        !x=x+1
1✔
623
                  else
1✔
624
                        -[#orangered]-> yes;
1✔
625
                        #orangered:<<implicit>>\n**Progressing = True** /
1✔
626
                  endif
1✔
627
                  if ((x) [Upgradable = False] Exists) then
1✔
628
                        !x=x+1
1✔
629
                        -[#springgreen]-> no;
1✔
630
                        #orangered:(x) Set **Upgradable = False**]
1✔
631
                        !x=x+1
1✔
632
                  else
1✔
633
                        -[#orangered]-> yes;
1✔
634
                        #orangered:<<implicit>>\n**Upgradable = False** /
1✔
635
                  endif
1✔
636
                  if ((x) [Available = False] Exists) then
1✔
637
                        !x=x+1
1✔
638
                        -[#springgreen]-> no;
1✔
639
                        #orangered:(x) Set **Available = False**]
1✔
640
                        !x=x+1
1✔
641
                  else
1✔
642
                        -[#orangered]-> yes;
1✔
643
                        #orangered:<<implicit>>\n**Available = False** /
1✔
644
                  endif
1✔
645
                else
1✔
646
                  -[#springgreen]-> no;
1✔
647
                  #springgreen:(x) Set **Degraded = False**]
1✔
648
                  !x=x+1
1✔
649
                  if ((x) [Progressing = True] Exists) then
1✔
650
                        !x=x+1
1✔
651
                        -[#orangered]-> yes;
1✔
652
                        #orangered:<<implicit>>\n**Progressing = True** /
1✔
653
                        if ((x) [Upgradable = False] Exists) then
1✔
654
                          !x=x+1
1✔
655
                          -[#springgreen]-> no;
1✔
656
                          #orangered:(x) Set **Upgradable = False**]
1✔
657
                          !x=x+1
1✔
658
                        else
1✔
659
                          -[#orangered]-> yes;
1✔
660
                          #orangered:<<implicit>>\n**Upgradable = False** /
1✔
661
                        endif
1✔
662
                        if ((x) [Available = False] Exists) then
1✔
663
                          !x=x+1
1✔
664
                          -[#springgreen]-> no;
1✔
665
                          #springgreen:(x) Set **Available = True**]
1✔
666
                          !x=x+1
1✔
667
                        else
1✔
668
                          #orangered:<<implicit>>\n**Available = False** /
1✔
669
                          -[#orangered]-> yes;
1✔
670
                        endif
1✔
671
                  else
1✔
672
                        -[#springgreen]-> no;
1✔
673
                        #springgreen:(x) Set **Progressing = False**]
1✔
674
                        !x=x+1
1✔
675
                        if ((x) [Upgradable = False] Exists) then
1✔
676
                          !x=x+1
1✔
677
                          -[#springgreen]-> no;
1✔
678
                          #springgreen:(x) Set **Upgradable = True**]
1✔
679
                          !x=x+1
1✔
680
                        else
1✔
681
                        #orangered:<<implicit>>\n**Upgradable = False** /
1✔
682
                          -[#orangered]-> yes;
1✔
683
                        endif
1✔
684
                        if ((x) [Available = False] Exists) then
1✔
685
                          !x=x+1
1✔
686
                          -[#springgreen]-> no;
1✔
687
                          #springgreen:(x) Set **Available = True**]
1✔
688
                          !x=x+1
1✔
689
                        else
1✔
690
                          -[#orangered]-> yes;
1✔
691
                          #orangered:<<implicit>>\n**Available = False** /
1✔
692
                        endif
1✔
693
                  endif
1✔
694
                endif
1✔
695
                end
1✔
696
                @enduml
1✔
697
        */
1✔
698

1✔
699
        /*
1✔
700
                    If any component operator reports negatively we want to write that to
1✔
701
                        the instance while preserving it's lastTransitionTime.
1✔
702
                        For example, consider the KubeVirt resource has the Available condition
1✔
703
                        type with type "False". When reconciling KubeVirt's resource we would
1✔
704
                        add it to the in-memory representation of HCO's conditions (r.conditions)
1✔
705
                        and here we are simply writing it back to the server.
1✔
706
                        One shortcoming is that only one failure of a particular condition can be
1✔
707
                        captured at one time (ie. if KubeVirt and CDI are both reporting !Available,
1✔
708
                    you will only see CDI as it updates last).
1✔
709
        */
1✔
710
        allComponentsAreUp := req.Conditions.IsEmpty()
1✔
711
        req.Conditions.SetStatusCondition(metav1.Condition{
1✔
712
                Type:               hcov1beta1.ConditionReconcileComplete,
1✔
713
                Status:             metav1.ConditionTrue,
1✔
714
                Reason:             reconcileCompleted,
1✔
715
                Message:            reconcileCompletedMessage,
1✔
716
                ObservedGeneration: req.Instance.Generation,
1✔
717
        })
1✔
718

1✔
719
        if req.Conditions.HasCondition(hcov1beta1.ConditionDegraded) { // (#chart 1)
2✔
720

1✔
721
                req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 2,3)
1✔
722
                        Type:               hcov1beta1.ConditionProgressing,
1✔
723
                        Status:             metav1.ConditionFalse,
1✔
724
                        Reason:             reconcileCompleted,
1✔
725
                        Message:            reconcileCompletedMessage,
1✔
726
                        ObservedGeneration: req.Instance.Generation,
1✔
727
                })
1✔
728

1✔
729
                req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 4,5)
1✔
730
                        Type:               hcov1beta1.ConditionUpgradeable,
1✔
731
                        Status:             metav1.ConditionFalse,
1✔
732
                        Reason:             commonDegradedReason,
1✔
733
                        Message:            "HCO is not Upgradeable due to degraded components",
1✔
734
                        ObservedGeneration: req.Instance.Generation,
1✔
735
                })
1✔
736

1✔
737
                req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 6,7)
1✔
738
                        Type:               hcov1beta1.ConditionAvailable,
1✔
739
                        Status:             metav1.ConditionFalse,
1✔
740
                        Reason:             commonDegradedReason,
1✔
741
                        Message:            "HCO is not available due to degraded components",
1✔
742
                        ObservedGeneration: req.Instance.Generation,
1✔
743
                })
1✔
744

1✔
745
        } else {
2✔
746

1✔
747
                // Degraded is not found. add it.
1✔
748
                req.Conditions.SetStatusCondition(metav1.Condition{ // (#chart 8)
1✔
749
                        Type:               hcov1beta1.ConditionDegraded,
1✔
750
                        Status:             metav1.ConditionFalse,
1✔
751
                        Reason:             reconcileCompleted,
1✔
752
                        Message:            reconcileCompletedMessage,
1✔
753
                        ObservedGeneration: req.Instance.Generation,
1✔
754
                })
1✔
755

1✔
756
                if req.Conditions.HasCondition(hcov1beta1.ConditionProgressing) { // (#chart 9)
2✔
757

1✔
758
                        req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 10,11)
1✔
759
                                Type:               hcov1beta1.ConditionUpgradeable,
1✔
760
                                Status:             metav1.ConditionFalse,
1✔
761
                                Reason:             commonProgressingReason,
1✔
762
                                Message:            "HCO is not Upgradeable due to progressing components",
1✔
763
                                ObservedGeneration: req.Instance.Generation,
1✔
764
                        })
1✔
765

1✔
766
                        req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 12,13)
1✔
767
                                Type:               hcov1beta1.ConditionAvailable,
1✔
768
                                Status:             metav1.ConditionTrue,
1✔
769
                                Reason:             reconcileCompleted,
1✔
770
                                Message:            reconcileCompletedMessage,
1✔
771
                                ObservedGeneration: req.Instance.Generation,
1✔
772
                        })
1✔
773

1✔
774
                } else {
2✔
775

1✔
776
                        req.Conditions.SetStatusCondition(metav1.Condition{ // (#chart 14)
1✔
777
                                Type:               hcov1beta1.ConditionProgressing,
1✔
778
                                Status:             metav1.ConditionFalse,
1✔
779
                                Reason:             reconcileCompleted,
1✔
780
                                Message:            reconcileCompletedMessage,
1✔
781
                                ObservedGeneration: req.Instance.Generation,
1✔
782
                        })
1✔
783

1✔
784
                        req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 15,16)
1✔
785
                                Type:               hcov1beta1.ConditionUpgradeable,
1✔
786
                                Status:             metav1.ConditionTrue,
1✔
787
                                Reason:             reconcileCompleted,
1✔
788
                                Message:            reconcileCompletedMessage,
1✔
789
                                ObservedGeneration: req.Instance.Generation,
1✔
790
                        })
1✔
791

1✔
792
                        req.Conditions.SetStatusConditionIfUnset(metav1.Condition{ // (#chart 17,18)
1✔
793
                                Type:               hcov1beta1.ConditionAvailable,
1✔
794
                                Status:             metav1.ConditionTrue,
1✔
795
                                Reason:             reconcileCompleted,
1✔
796
                                Message:            reconcileCompletedMessage,
1✔
797
                                ObservedGeneration: req.Instance.Generation,
1✔
798
                        })
1✔
799

1✔
800
                }
1✔
801
        }
802

803
        return allComponentsAreUp
1✔
804
}
805

806
func (r *ReconcileHyperConverged) completeReconciliation(req *common.HcoRequest) {
1✔
807
        allComponentsAreUp := r.aggregateComponentConditions(req)
1✔
808

1✔
809
        hcoReady := false
1✔
810

1✔
811
        if allComponentsAreUp {
2✔
812
                req.Logger.Info("No component operator reported negatively")
1✔
813

1✔
814
                // if in upgrade mode, and all the components are upgraded, and nothing pending to be written - upgrade is completed
1✔
815
                if r.upgradeMode && req.ComponentUpgradeInProgress && !req.Dirty {
2✔
816
                        // update the new version only when upgrade is completed
1✔
817
                        UpdateVersion(&req.Instance.Status, hcoVersionName, r.ownVersion)
1✔
818
                        req.StatusDirty = true
1✔
819

1✔
820
                        r.upgradeMode = false
1✔
821
                        req.ComponentUpgradeInProgress = false
1✔
822
                        req.Logger.Info(fmt.Sprintf("Successfully upgraded to version %s", r.ownVersion))
1✔
823
                        r.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeNormal, "UpgradeHCO", fmt.Sprintf("Successfully upgraded to version %s", r.ownVersion))
1✔
824
                }
1✔
825

826
                // If not in upgrade mode, then we're ready, because all the operators reported positive conditions.
827
                // if upgrade was done successfully, r.upgradeMode is already false here.
828
                hcoReady = !r.upgradeMode
1✔
829
        }
830

831
        if r.upgradeMode {
2✔
832
                // override the Progressing condition during upgrade
1✔
833
                req.Conditions.SetStatusCondition(metav1.Condition{
1✔
834
                        Type:               hcov1beta1.ConditionProgressing,
1✔
835
                        Status:             metav1.ConditionTrue,
1✔
836
                        Reason:             "HCOUpgrading",
1✔
837
                        Message:            "HCO is now upgrading to version " + r.ownVersion,
1✔
838
                        ObservedGeneration: req.Instance.Generation,
1✔
839
                })
1✔
840
        }
1✔
841

842
        // check if HCO was available before this reconcile loop
843
        hcoWasAvailable := apimetav1.IsStatusConditionTrue(req.Instance.Status.Conditions, hcov1beta1.ConditionAvailable) &&
1✔
844
                apimetav1.IsStatusConditionFalse(req.Instance.Status.Conditions, hcov1beta1.ConditionProgressing)
1✔
845

1✔
846
        if hcoReady {
2✔
847
                // If no operator whose conditions we are watching reports an error, then it is safe
1✔
848
                // to set readiness.
1✔
849
                if !hcoWasAvailable { // only when become available
2✔
850
                        r.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeNormal, "ReconcileHCO", "HCO Reconcile completed successfully")
1✔
851
                }
1✔
852
        } else {
1✔
853
                // If for any reason we marked ourselves !upgradeable...then unset readiness
1✔
854
                if !r.upgradeMode && hcoWasAvailable { // only when become not ready
1✔
855
                        r.eventEmitter.EmitEvent(req.Instance, corev1.EventTypeWarning, "ReconcileHCO", "Not all the operators are ready")
×
856
                }
×
857
        }
858

859
        r.updateConditions(req)
1✔
860
}
861

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

1✔
866
        for _, condType := range common.HcoConditionTypes {
2✔
867
                cond, found := req.Conditions[condType]
1✔
868
                if !found {
2✔
869
                        cond = metav1.Condition{
1✔
870
                                Type:               condType,
1✔
871
                                Status:             metav1.ConditionUnknown,
1✔
872
                                Message:            "Unknown Status",
1✔
873
                                Reason:             "StatusUnknown",
1✔
874
                                ObservedGeneration: req.Instance.ObjectMeta.Generation,
1✔
875
                        }
1✔
876
                }
1✔
877

878
                apimetav1.SetStatusCondition(&conditions, cond)
1✔
879
        }
880

881
        // Detect a "TaintedConfiguration" state, and raise a corresponding event
882
        r.detectTaintedConfiguration(req, &conditions)
1✔
883

1✔
884
        if !reflect.DeepEqual(conditions, req.Instance.Status.Conditions) {
2✔
885
                req.Instance.Status.Conditions = conditions
1✔
886
                req.StatusDirty = true
1✔
887
        }
1✔
888

889
        systemHealthStatus := r.getSystemHealthStatus(req.Conditions)
1✔
890

1✔
891
        if systemHealthStatus != req.Instance.Status.SystemHealthStatus {
2✔
892
                req.Instance.Status.SystemHealthStatus = systemHealthStatus
1✔
893
                req.StatusDirty = true
1✔
894
        }
1✔
895

896
        metrics.SetHCOMetricSystemHealthStatus(getNumericalHealthStatus(systemHealthStatus))
1✔
897
}
898

899
func (r *ReconcileHyperConverged) setLabels(req *common.HcoRequest) {
1✔
900
        if req.Instance.ObjectMeta.Labels == nil {
2✔
901
                req.Instance.ObjectMeta.Labels = map[string]string{}
1✔
902
        }
1✔
903
        if req.Instance.ObjectMeta.Labels[hcoutil.AppLabel] == "" {
2✔
904
                req.Instance.ObjectMeta.Labels[hcoutil.AppLabel] = req.Instance.Name
1✔
905
                req.Dirty = true
1✔
906
        }
1✔
907
}
908

909
func (r *ReconcileHyperConverged) detectTaintedConfiguration(req *common.HcoRequest, conditions *[]metav1.Condition) {
1✔
910
        conditionExists := apimetav1.IsStatusConditionTrue(req.Instance.Status.Conditions, hcov1beta1.ConditionTaintedConfiguration)
1✔
911

1✔
912
        // A tainted configuration state is indicated by the
1✔
913
        // presence of at least one of the JSON Patch annotations
1✔
914
        tainted := false
1✔
915
        for _, jpa := range JSONPatchAnnotationNames {
2✔
916
                NumOfChanges := 0
1✔
917
                jsonPatch, exists := req.Instance.ObjectMeta.Annotations[jpa]
1✔
918
                if exists {
2✔
919
                        if NumOfChanges = getNumOfChangesJSONPatch(jsonPatch); NumOfChanges > 0 {
2✔
920
                                tainted = true
1✔
921
                        }
1✔
922
                }
923
                metrics.SetUnsafeModificationCount(NumOfChanges, jpa)
1✔
924
        }
925

926
        if tainted {
2✔
927
                apimetav1.SetStatusCondition(conditions, metav1.Condition{
1✔
928
                        Type:               hcov1beta1.ConditionTaintedConfiguration,
1✔
929
                        Status:             metav1.ConditionTrue,
1✔
930
                        Reason:             taintedConfigurationReason,
1✔
931
                        Message:            taintedConfigurationMessage,
1✔
932
                        ObservedGeneration: req.Instance.ObjectMeta.Generation,
1✔
933
                })
1✔
934

1✔
935
                if !conditionExists {
2✔
936
                        // Only log at "first occurrence" of detection
1✔
937
                        req.Logger.Info("Detected tainted configuration state for HCO")
1✔
938
                }
1✔
939
        } else { // !tainted
1✔
940

1✔
941
                // For the sake of keeping the JSONPatch backdoor in low profile,
1✔
942
                // we just remove the condition instead of False'ing it.
1✔
943
                if conditionExists {
2✔
944
                        apimetav1.RemoveStatusCondition(conditions, hcov1beta1.ConditionTaintedConfiguration)
1✔
945

1✔
946
                        req.Logger.Info("Detected untainted configuration state for HCO")
1✔
947
                }
1✔
948
        }
949
}
950

951
func (r *ReconcileHyperConverged) getSystemHealthStatus(conditions common.HcoConditions) string {
1✔
952
        if isSystemHealthStatusError(conditions) {
2✔
953
                return systemHealthStatusError
1✔
954
        }
1✔
955

956
        if isSystemHealthStatusWarning(conditions) {
2✔
957
                return systemHealthStatusWarning
1✔
958
        }
1✔
959

960
        return systemHealthStatusHealthy
1✔
961
}
962

963
func isSystemHealthStatusError(conditions common.HcoConditions) bool {
1✔
964
        return !conditions.IsStatusConditionTrue(hcov1beta1.ConditionAvailable) || conditions.IsStatusConditionTrue(hcov1beta1.ConditionDegraded)
1✔
965
}
1✔
966

967
func isSystemHealthStatusWarning(conditions common.HcoConditions) bool {
1✔
968
        return !conditions.IsStatusConditionTrue(hcov1beta1.ConditionReconcileComplete) || conditions.IsStatusConditionTrue(hcov1beta1.ConditionProgressing)
1✔
969
}
1✔
970

971
func getNumOfChangesJSONPatch(jsonPatch string) int {
1✔
972
        patches, err := jsonpatch.DecodePatch([]byte(jsonPatch))
1✔
973
        if err != nil {
2✔
974
                return 0
1✔
975
        }
1✔
976
        return len(patches)
1✔
977
}
978

979
func getNumericalHealthStatus(status string) float64 {
1✔
980
        healthStatusCodes := map[string]float64{
1✔
981
                systemHealthStatusHealthy: metrics.SystemHealthStatusHealthy,
1✔
982
                systemHealthStatusWarning: metrics.SystemHealthStatusWarning,
1✔
983
                systemHealthStatusError:   metrics.SystemHealthStatusError,
1✔
984
        }
1✔
985

1✔
986
        return healthStatusCodes[status]
1✔
987
}
1✔
988

989
func (r *ReconcileHyperConverged) firstLoopInitialization(request *common.HcoRequest) {
1✔
990
        // Initialize operand handler.
1✔
991
        r.operandHandler.FirstUseInitiation(r.scheme, hcoutil.GetClusterInfo(), request.Instance)
1✔
992

1✔
993
        // Avoid re-initializing.
1✔
994
        r.firstLoop = false
1✔
995
}
1✔
996

997
func (r *ReconcileHyperConverged) setOperatorUpgradeableStatus(request *common.HcoRequest) error {
1✔
998
        if hcoutil.GetClusterInfo().IsManagedByOLM() {
2✔
999

1✔
1000
                upgradeable := !r.upgradeMode && request.Upgradeable
1✔
1001

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

1✔
1004
                msg := hcoutil.UpgradeableAllowMessage
1✔
1005
                status := metav1.ConditionTrue
1✔
1006
                reason := hcoutil.UpgradeableAllowReason
1✔
1007

1✔
1008
                if !upgradeable {
2✔
1009
                        status = metav1.ConditionFalse
1✔
1010

1✔
1011
                        if r.upgradeMode {
2✔
1012
                                msg = hcoutil.UpgradeableUpgradingMessage + r.ownVersion
1✔
1013
                                reason = hcoutil.UpgradeableUpgradingReason
1✔
1014
                        } else {
2✔
1015
                                condition, found := request.Conditions.GetCondition(hcov1beta1.ConditionUpgradeable)
1✔
1016
                                if found && condition.Status == metav1.ConditionFalse {
2✔
1017
                                        reason = condition.Reason
1✔
1018
                                        msg = condition.Message
1✔
1019
                                }
1✔
1020
                        }
1021
                }
1022

1023
                if err := r.upgradeableCondition.Set(request.Ctx, status, reason, msg); err != nil {
1✔
1024
                        request.Logger.Error(err, "can't set the Upgradeable operator condition", requestedStatusKey, upgradeable)
×
1025
                        return err
×
1026
                }
×
1027

1028
        }
1029

1030
        return nil
1✔
1031
}
1032

1033
func (r *ReconcileHyperConverged) migrateBeforeUpgrade(req *common.HcoRequest) (bool, error) {
1✔
1034
        upgradePatched, err := r.applyUpgradePatches(req)
1✔
1035
        if err != nil {
2✔
1036
                return false, err
1✔
1037
        }
1✔
1038

1039
        removeOldQuickStartGuides(req, r.client, r.operandHandler.GetQuickStartNames())
1✔
1040

1✔
1041
        return upgradePatched, nil
1✔
1042
}
1043

1044
func (r *ReconcileHyperConverged) applyUpgradePatches(req *common.HcoRequest) (bool, error) {
1✔
1045
        modified := false
1✔
1046

1✔
1047
        knownHcoVersion, _ := GetVersion(&req.Instance.Status, hcoVersionName)
1✔
1048
        if knownHcoVersion == "" {
2✔
1049
                knownHcoVersion = "0.0.0"
1✔
1050
        }
1✔
1051
        knownHcoSV, err := semver.ParseTolerant(knownHcoVersion)
1✔
1052
        if err != nil {
2✔
1053
                req.Logger.Error(err, "Error!")
1✔
1054
                return false, err
1✔
1055
        }
1✔
1056

1057
        tmpInstance, err := upgradepatch.ApplyUpgradePatch(req.Logger, req.Instance, knownHcoSV)
1✔
1058
        if err != nil {
1✔
1059
                return false, err
×
1060
        }
×
1061

1062
        for _, p := range upgradepatch.GetObjectsToBeRemoved() {
2✔
1063
                removed, err := r.removeLeftover(req, knownHcoSV, p)
1✔
1064
                if err != nil {
1✔
1065
                        return removed, err
×
1066
                }
×
1067
        }
1068

1069
        if !reflect.DeepEqual(tmpInstance.Spec, req.Instance.Spec) {
2✔
1070
                req.Logger.Info("updating HCO spec as a result of upgrade patches")
1✔
1071
                tmpInstance.Spec.DeepCopyInto(&req.Instance.Spec)
1✔
1072
                modified = true
1✔
1073
                req.Dirty = true
1✔
1074
        }
1✔
1075

1076
        return modified, nil
1✔
1077
}
1078

1079
func (r *ReconcileHyperConverged) removeLeftover(req *common.HcoRequest, knownHcoSV semver.Version, p upgradepatch.ObjectToBeRemoved) (bool, error) {
1✔
1080
        if p.IsAffectedRange(knownHcoSV) {
2✔
1081
                removeRelatedObject(req, r.client, p.GroupVersionKind, p.ObjectKey)
1✔
1082
                u := &unstructured.Unstructured{}
1✔
1083
                u.SetGroupVersionKind(p.GroupVersionKind)
1✔
1084
                gerr := r.client.Get(req.Ctx, p.ObjectKey, u)
1✔
1085
                if gerr != nil {
2✔
1086
                        if apierrors.IsNotFound(gerr) {
2✔
1087
                                return false, nil
1✔
1088
                        }
1✔
1089

1090
                        req.Logger.Error(gerr, "failed looking for leftovers", "objectToBeRemoved", p)
×
1091
                        return false, gerr
×
1092
                }
1093
                return r.deleteObj(req, u, false)
1✔
1094

1095
        }
1096
        return false, nil
1✔
1097
}
1098

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

1✔
1102
        if err != nil {
1✔
1103
                req.Logger.Error(
×
1104
                        err,
×
1105
                        fmt.Sprintf("failed to delete %s", obj.GetObjectKind().GroupVersionKind().Kind),
×
1106
                        "name",
×
1107
                        obj.GetName(),
×
1108
                )
×
1109

×
1110
                return removed, err
×
1111
        }
×
1112

1113
        if removed {
2✔
1114
                r.eventEmitter.EmitEvent(
1✔
1115
                        req.Instance, corev1.EventTypeNormal, "Killing",
1✔
1116
                        fmt.Sprintf("Removed %s %s", obj.GetName(), obj.GetObjectKind().GroupVersionKind().Kind),
1✔
1117
                )
1✔
1118
        }
1✔
1119

1120
        return removed, nil
1✔
1121
}
1122

1123
func removeOldQuickStartGuides(req *common.HcoRequest, cl client.Client, requiredQSList []string) {
1✔
1124
        existingQSList := &consolev1.ConsoleQuickStartList{}
1✔
1125
        req.Logger.Info("reading quickstart guides")
1✔
1126
        err := cl.List(req.Ctx, existingQSList, client.MatchingLabels{hcoutil.AppLabelManagedBy: hcoutil.OperatorName})
1✔
1127
        if err != nil {
1✔
1128
                req.Logger.Error(err, "failed to read list of quickstart guides")
×
1129
                return
×
1130
        }
×
1131

1132
        var existingQSNames map[string]consolev1.ConsoleQuickStart
1✔
1133
        if len(existingQSList.Items) > 0 {
2✔
1134
                existingQSNames = make(map[string]consolev1.ConsoleQuickStart)
1✔
1135
                for _, qs := range existingQSList.Items {
2✔
1136
                        existingQSNames[qs.Name] = qs
1✔
1137
                }
1✔
1138

1139
                for name, existQs := range existingQSNames {
2✔
1140
                        if !slices.Contains(requiredQSList, name) {
2✔
1141
                                req.Logger.Info("deleting ConsoleQuickStart", "name", name)
1✔
1142
                                if _, err = hcoutil.EnsureDeleted(req.Ctx, cl, &existQs, req.Instance.Name, req.Logger, false, false, true); err != nil {
1✔
1143
                                        req.Logger.Error(err, "failed to delete ConsoleQuickStart", "name", name)
×
1144
                                }
×
1145
                        }
1146
                }
1147

1148
                removeRelatedQSObjects(req, requiredQSList)
1✔
1149
        }
1150
}
1151

1152
// removeRelatedQSObjects removes old quickstart from the related object list
1153
// can't use the removeRelatedObject function because the status not get updated during each reconcile loop,
1154
// but the old qs already removed (above) so you loos track of it. That why we must re-check all the qs names
1155
func removeRelatedQSObjects(req *common.HcoRequest, requiredNames []string) {
1✔
1156
        refs := make([]corev1.ObjectReference, 0, len(req.Instance.Status.RelatedObjects))
1✔
1157
        foundOldQs := false
1✔
1158

1✔
1159
        for _, obj := range req.Instance.Status.RelatedObjects {
2✔
1160
                if obj.Kind == "ConsoleQuickStart" && !slices.Contains(requiredNames, obj.Name) {
2✔
1161
                        foundOldQs = true
1✔
1162
                        continue
1✔
1163
                }
1164
                refs = append(refs, obj)
1✔
1165
        }
1166

1167
        if foundOldQs {
2✔
1168
                req.Instance.Status.RelatedObjects = refs
1✔
1169
                req.StatusDirty = true
1✔
1170
        }
1✔
1171

1172
}
1173

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

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

1✔
1180
        for _, obj := range req.Instance.Status.RelatedObjects {
2✔
1181
                apiVersion, kind := gvk.ToAPIVersionAndKind()
1✔
1182
                if obj.APIVersion == apiVersion && obj.Kind == kind && obj.Namespace == objectKey.Namespace && obj.Name == objectKey.Name {
2✔
1183
                        foundRO = true
1✔
1184
                        req.Logger.Info("Removed relatedObject entry for", "gvk", gvk, "objectKey", objectKey)
1✔
1185
                        continue
1✔
1186
                }
1187
                if reflect.DeepEqual(gvk, crdGVK) {
2✔
1188
                        mapping, err := cl.RESTMapper().RESTMapping(obj.GroupVersionKind().GroupKind(), obj.GroupVersionKind().Version)
1✔
1189
                        if err == nil && mapping != nil && mapping.Resource.GroupResource().String() == objectKey.Name {
2✔
1190
                                foundRO = true
1✔
1191
                                req.Logger.Info("Removed relatedObject on CRD removal for", "gvk", gvk, "objectKey", objectKey)
1✔
1192
                                continue
1✔
1193
                        }
1194
                }
1195
                refs = append(refs, obj)
1✔
1196
        }
1197

1198
        if foundRO {
2✔
1199
                req.Instance.Status.RelatedObjects = refs
1✔
1200
                req.StatusDirty = true
1✔
1201
        }
1✔
1202

1203
}
1204

1205
func drop(slice []string, s string) ([]string, bool) {
1✔
1206
        var newSlice []string
1✔
1207
        dropped := false
1✔
1208
        for _, element := range slice {
2✔
1209
                if element != s {
1✔
1210
                        newSlice = append(newSlice, element)
×
1211
                } else {
1✔
1212
                        dropped = true
1✔
1213
                }
1✔
1214
        }
1215
        return newSlice, dropped
1✔
1216
}
1217

1218
func checkFinalizers(req *common.HcoRequest) bool {
1✔
1219
        if req.Instance.ObjectMeta.DeletionTimestamp.IsZero() {
2✔
1220
                // Add the finalizer if it's not there
1✔
1221
                if !slices.Contains(req.Instance.ObjectMeta.Finalizers, FinalizerName) {
2✔
1222
                        req.Logger.Info("setting a finalizer (with fully qualified name)")
1✔
1223
                        req.Instance.ObjectMeta.Finalizers = append(req.Instance.ObjectMeta.Finalizers, FinalizerName)
1✔
1224
                        req.Dirty = true
1✔
1225
                }
1✔
1226
                return true
1✔
1227
        }
1228
        return false
1✔
1229
}
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