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

kubevirt / containerized-data-importer / #5878

12 Mar 2026 08:24PM UTC coverage: 49.326% (+0.03%) from 49.293%
#5878

push

travis-ci

web-flow
Bump github.com/cloudflare/circl in /tools/release-notes (#4048)

Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.6.1 to 1.6.3.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.6.1...v1.6.3)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-version: 1.6.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

14744 of 29891 relevant lines covered (49.33%)

0.55 hits per line

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

69.89
/pkg/controller/storageprofile-controller.go
1
package controller
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "reflect"
8
        "sort"
9
        "strconv"
10

11
        "github.com/go-logr/logr"
12
        snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
13
        ocpconfigv1 "github.com/openshift/api/config/v1"
14
        "github.com/prometheus/client_golang/prometheus"
15

16
        v1 "k8s.io/api/core/v1"
17
        storagev1 "k8s.io/api/storage/v1"
18
        apiequality "k8s.io/apimachinery/pkg/api/equality"
19
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
20
        "k8s.io/apimachinery/pkg/api/meta"
21
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22
        "k8s.io/apimachinery/pkg/runtime"
23
        "k8s.io/apimachinery/pkg/types"
24
        "k8s.io/client-go/tools/record"
25
        storagehelpers "k8s.io/component-helpers/storage/volume"
26

27
        "sigs.k8s.io/controller-runtime/pkg/client"
28
        "sigs.k8s.io/controller-runtime/pkg/controller"
29
        "sigs.k8s.io/controller-runtime/pkg/event"
30
        "sigs.k8s.io/controller-runtime/pkg/handler"
31
        "sigs.k8s.io/controller-runtime/pkg/manager"
32
        "sigs.k8s.io/controller-runtime/pkg/predicate"
33
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
34
        "sigs.k8s.io/controller-runtime/pkg/source"
35

36
        cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
37
        "kubevirt.io/containerized-data-importer/pkg/common"
38
        cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
39
        metrics "kubevirt.io/containerized-data-importer/pkg/monitoring/metrics/cdi-controller"
40
        "kubevirt.io/containerized-data-importer/pkg/operator"
41
        "kubevirt.io/containerized-data-importer/pkg/storagecapabilities"
42
        "kubevirt.io/containerized-data-importer/pkg/util"
43
)
44

45
const (
46
        storageProfileControllerName = "storageprofile-controller"
47
        counterLabelStorageClass     = "storageclass"
48
        counterLabelProvisioner      = "provisioner"
49
        counterLabelComplete         = "complete"
50
        counterLabelDefault          = "default"
51
        counterLabelVirtDefault      = "virtdefault"
52
        counterLabelRWX              = "rwx"
53
        counterLabelSmartClone       = "smartclone"
54
        counterLabelDegraded         = "degraded"
55

56
        recognizedProvisionerMessage              = "Provisioner is recognized"
57
        unrecognizedProvisionerMessage            = "Provisioner is not recognized"
58
        unrecognizedStorageClassParametersMessage = "Storage class parameters are not recognized"
59
)
60

61
// StorageProfileReconciler members
62
type StorageProfileReconciler struct {
63
        client client.Client
64
        // use this for getting any resources not in the install namespace or cluster scope
65
        uncachedClient  client.Client
66
        recorder        record.EventRecorder
67
        scheme          *runtime.Scheme
68
        log             logr.Logger
69
        installerLabels map[string]string
70
}
71

72
// Reconcile the reconcile.Reconciler implementation for the StorageProfileReconciler object.
73
func (r *StorageProfileReconciler) Reconcile(_ context.Context, req reconcile.Request) (reconcile.Result, error) {
1✔
74
        log := r.log.WithValues("StorageProfile", req.NamespacedName)
1✔
75
        log.Info("reconciling StorageProfile")
1✔
76

1✔
77
        storageClass := &storagev1.StorageClass{}
1✔
78
        if err := r.client.Get(context.TODO(), req.NamespacedName, storageClass); err != nil {
2✔
79
                if k8serrors.IsNotFound(err) {
2✔
80
                        return reconcile.Result{}, r.deleteStorageProfile(req.NamespacedName.Name, log)
1✔
81
                }
1✔
82
                return reconcile.Result{}, err
×
83
        } else if storageClass.GetDeletionTimestamp() != nil {
1✔
84
                return reconcile.Result{}, r.deleteStorageProfile(req.NamespacedName.Name, log)
×
85
        }
×
86

87
        return r.reconcileStorageProfile(storageClass)
1✔
88
}
89

90
func (r *StorageProfileReconciler) reconcileStorageProfile(sc *storagev1.StorageClass) (reconcile.Result, error) {
1✔
91
        log := r.log.WithValues("StorageProfile", sc.Name)
1✔
92

1✔
93
        storageProfile, prevStorageProfile, err := r.getStorageProfile(sc)
1✔
94
        if err != nil {
1✔
95
                log.Error(err, "Unable to create StorageProfile")
×
96
                return reconcile.Result{}, err
×
97
        }
×
98

99
        storageProfile.Status.StorageClass = &sc.Name
1✔
100
        storageProfile.Status.Provisioner = &sc.Provisioner
1✔
101
        snapClass, err := cc.GetSnapshotClassForSmartClone(nil, &sc.Name, storageProfile.Spec.SnapshotClass, r.log, r.client, r.recorder)
1✔
102
        if err != nil {
2✔
103
                return reconcile.Result{}, err
1✔
104
        }
1✔
105
        if snapClass != "" {
2✔
106
                storageProfile.Status.SnapshotClass = &snapClass
1✔
107
        }
1✔
108
        storageProfile.Status.CloneStrategy = r.reconcileCloneStrategy(sc, storageProfile.Spec.CloneStrategy, snapClass)
1✔
109
        storageProfile.Status.DataImportCronSourceFormat = r.reconcileDataImportCronSourceFormat(sc, storageProfile.Spec.DataImportCronSourceFormat, snapClass)
1✔
110

1✔
111
        // Reconcile StorageProfile annotations based on provisioner capabilities
1✔
112
        r.reconcileMinimumSupportedPVCSize(sc, storageProfile)
1✔
113
        r.reconcileUseReadWriteOnceForDataImportCron(context.TODO(), sc, storageProfile)
1✔
114
        r.reconcileSnapshotClassForDataImportCron(context.TODO(), sc, storageProfile)
1✔
115

1✔
116
        var claimPropertySets []cdiv1.ClaimPropertySet
1✔
117

1✔
118
        if len(storageProfile.Spec.ClaimPropertySets) > 0 {
2✔
119
                for _, cps := range storageProfile.Spec.ClaimPropertySets {
2✔
120
                        if cps.VolumeMode == nil || len(cps.AccessModes) == 0 {
2✔
121
                                err = errors.New("each ClaimPropertySet must provide both volume mode and access modes")
1✔
122
                                log.Error(err, "Unable to update StorageProfile")
1✔
123
                                return reconcile.Result{}, err
1✔
124
                        }
1✔
125
                }
126
                claimPropertySets = storageProfile.Spec.ClaimPropertySets
1✔
127
        } else {
1✔
128
                claimPropertySets = r.reconcilePropertySets(sc)
1✔
129
        }
1✔
130

131
        storageProfile.Status.ClaimPropertySets = claimPropertySets
1✔
132
        r.reconcileConditions(context.TODO(), sc, storageProfile)
1✔
133

1✔
134
        util.SetRecommendedLabels(storageProfile, r.installerLabels, "cdi-controller")
1✔
135
        if err := r.updateStorageProfile(prevStorageProfile, storageProfile, log); err != nil {
1✔
136
                return reconcile.Result{}, err
×
137
        }
×
138

139
        return reconcile.Result{}, r.computeMetrics(storageProfile, sc)
1✔
140
}
141

142
func (r *StorageProfileReconciler) updateStorageProfile(prevStorageProfile runtime.Object, storageProfile *cdiv1.StorageProfile, log logr.Logger) error {
1✔
143
        var prevSP *cdiv1.StorageProfile
1✔
144
        if p, ok := prevStorageProfile.(*cdiv1.StorageProfile); ok {
2✔
145
                prevSP = p
1✔
146
        }
1✔
147

148
        if prevSP == nil {
2✔
149
                return r.client.Create(context.TODO(), storageProfile)
1✔
150
        }
1✔
151

152
        if storageProfileSpecMetaChanged(prevSP, storageProfile) {
2✔
153
                log.Info("Updating StorageProfile", "StorageProfile.Name", storageProfile.Name, "storageProfile", storageProfile)
1✔
154
                if err := r.client.Update(context.TODO(), storageProfile); err != nil {
1✔
155
                        return err
×
156
                }
×
157
        }
158

159
        if !reflect.DeepEqual(prevSP.Status, storageProfile.Status) {
2✔
160
                log.Info("Updating StorageProfile Status", "StorageProfile.Name", storageProfile.Name, "storageProfile", storageProfile)
1✔
161
                if err := r.client.Status().Update(context.TODO(), storageProfile); err != nil {
1✔
162
                        return err
×
163
                }
×
164
        }
165

166
        return nil
1✔
167
}
168

169
// storageProfileSpecMetaChanged returns true if Spec, Labels, or Annotations differ
170
func storageProfileSpecMetaChanged(previous, desired *cdiv1.StorageProfile) bool {
1✔
171
        if previous == nil || desired == nil {
1✔
172
                return previous != desired
×
173
        }
×
174
        if !apiequality.Semantic.DeepEqual(previous.Spec, desired.Spec) {
1✔
175
                return true
×
176
        }
×
177
        if !apiequality.Semantic.DeepEqual(previous.GetLabels(), desired.GetLabels()) {
2✔
178
                return true
1✔
179
        }
1✔
180
        if !apiequality.Semantic.DeepEqual(previous.GetAnnotations(), desired.GetAnnotations()) {
1✔
181
                return true
×
182
        }
×
183
        return false
1✔
184
}
185

186
func (r *StorageProfileReconciler) getStorageProfile(sc *storagev1.StorageClass) (*cdiv1.StorageProfile, runtime.Object, error) {
1✔
187
        var prevStorageProfile runtime.Object
1✔
188
        storageProfile := &cdiv1.StorageProfile{}
1✔
189

1✔
190
        if err := r.client.Get(context.TODO(), types.NamespacedName{Name: sc.Name}, storageProfile); err != nil {
2✔
191
                if k8serrors.IsNotFound(err) {
2✔
192
                        storageProfile, err = r.createEmptyStorageProfile(sc)
1✔
193
                        if err != nil {
1✔
194
                                return nil, nil, err
×
195
                        }
×
196
                } else {
×
197
                        return nil, nil, err
×
198
                }
×
199
        } else {
1✔
200
                prevStorageProfile = storageProfile.DeepCopyObject()
1✔
201
        }
1✔
202

203
        return storageProfile, prevStorageProfile, nil
1✔
204
}
205

206
func (r *StorageProfileReconciler) reconcileConditions(ctx context.Context, sc *storagev1.StorageClass, sp *cdiv1.StorageProfile) {
1✔
207
        cond := findStorageProfileConditionByType(sp, cdiv1.StorageProfileRecognized)
1✔
208
        if cond == nil {
2✔
209
                sp.Status.Conditions = append(sp.Status.Conditions, cdiv1.StorageProfileCondition{Type: cdiv1.StorageProfileRecognized})
1✔
210
                cond = &sp.Status.Conditions[len(sp.Status.Conditions)-1]
1✔
211
        }
1✔
212

213
        switch reason := storagecapabilities.IsRecognized(sc); reason {
1✔
214
        case storagecapabilities.RecognizedProvisioner:
1✔
215
                updateConditionState(&cond.ConditionState, v1.ConditionTrue, recognizedProvisionerMessage, string(reason))
1✔
216
        case storagecapabilities.UnrecognizedProvisioner:
1✔
217
                updateConditionState(&cond.ConditionState, v1.ConditionFalse, unrecognizedProvisionerMessage, string(reason))
1✔
218
        case storagecapabilities.UnrecognizedStorageClassParameters:
1✔
219
                updateConditionState(&cond.ConditionState, v1.ConditionFalse, unrecognizedStorageClassParametersMessage, string(reason))
1✔
220
        }
221
}
222

223
func findStorageProfileConditionByType(sp *cdiv1.StorageProfile, condType cdiv1.StorageProfileConditionType) *cdiv1.StorageProfileCondition {
1✔
224
        for i := range sp.Status.Conditions {
2✔
225
                if sp.Status.Conditions[i].Type == condType {
2✔
226
                        return &sp.Status.Conditions[i]
1✔
227
                }
1✔
228
        }
229
        return nil
1✔
230
}
231

232
func (r *StorageProfileReconciler) reconcilePropertySets(sc *storagev1.StorageClass) []cdiv1.ClaimPropertySet {
1✔
233
        claimPropertySets := []cdiv1.ClaimPropertySet{}
1✔
234
        capabilities, found := storagecapabilities.GetCapabilities(r.client, sc)
1✔
235
        if found {
2✔
236
                for i := range capabilities {
2✔
237
                        claimPropertySet := cdiv1.ClaimPropertySet{
1✔
238
                                AccessModes: []v1.PersistentVolumeAccessMode{capabilities[i].AccessMode},
1✔
239
                                VolumeMode:  &capabilities[i].VolumeMode,
1✔
240
                        }
1✔
241
                        claimPropertySets = append(claimPropertySets, claimPropertySet)
1✔
242
                }
1✔
243
        }
244
        return claimPropertySets
1✔
245
}
246

247
func (r *StorageProfileReconciler) reconcileCloneStrategy(sc *storagev1.StorageClass, desiredCloneStrategy *cdiv1.CDICloneStrategy, snapClass string) *cdiv1.CDICloneStrategy {
1✔
248
        if desiredCloneStrategy != nil {
2✔
249
                return desiredCloneStrategy
1✔
250
        }
1✔
251

252
        if annStrategyVal, ok := sc.Annotations["cdi.kubevirt.io/clone-strategy"]; ok {
2✔
253
                return r.getCloneStrategyFromStorageClass(annStrategyVal)
1✔
254
        }
1✔
255

256
        // Default to trying snapshot clone unless volume snapshot class missing
257
        hostAssistedStrategy := cdiv1.CloneStrategyHostAssisted
1✔
258
        strategy := hostAssistedStrategy
1✔
259
        if snapClass != "" {
2✔
260
                strategy = cdiv1.CloneStrategySnapshot
1✔
261
        }
1✔
262

263
        if knownStrategy, ok := storagecapabilities.GetAdvisedCloneStrategy(sc); ok {
2✔
264
                strategy = knownStrategy
1✔
265
        }
1✔
266

267
        if strategy == cdiv1.CloneStrategySnapshot && snapClass == "" {
2✔
268
                r.log.Info("No VolumeSnapshotClass found for storage class, falling back to host assisted cloning", "StorageClass.Name", sc.Name)
1✔
269
                return &hostAssistedStrategy
1✔
270
        }
1✔
271

272
        return &strategy
1✔
273
}
274

275
func (r *StorageProfileReconciler) getCloneStrategyFromStorageClass(annStrategyVal string) *cdiv1.CDICloneStrategy {
1✔
276
        var strategy cdiv1.CDICloneStrategy
1✔
277

1✔
278
        switch annStrategyVal {
1✔
279
        case "copy":
1✔
280
                strategy = cdiv1.CloneStrategyHostAssisted
1✔
281
        case "snapshot":
1✔
282
                strategy = cdiv1.CloneStrategySnapshot
1✔
283
        case "csi-clone":
1✔
284
                strategy = cdiv1.CloneStrategyCsiClone
1✔
285
        }
286

287
        return &strategy
1✔
288
}
289

290
func (r *StorageProfileReconciler) reconcileDataImportCronSourceFormat(sc *storagev1.StorageClass, desiredFormat *cdiv1.DataImportCronSourceFormat, snapClass string) *cdiv1.DataImportCronSourceFormat {
1✔
291
        if desiredFormat != nil {
1✔
292
                return desiredFormat
×
293
        }
×
294

295
        // This can be changed later on
296
        // for example, if at some point we're confident snapshot sources should be the default
297
        pvcFormat := cdiv1.DataImportCronSourceFormatPvc
1✔
298
        format := pvcFormat
1✔
299

1✔
300
        if knownFormat, ok := storagecapabilities.GetAdvisedSourceFormat(sc); ok {
2✔
301
                format = knownFormat
1✔
302
        }
1✔
303

304
        if format == cdiv1.DataImportCronSourceFormatSnapshot && snapClass == "" {
2✔
305
                // No point using snapshots without a corresponding snapshot class
1✔
306
                r.log.Info("No VolumeSnapshotClass found for storage class, falling back to pvc sources for DataImportCrons", "StorageClass.Name", sc.Name)
1✔
307
                return &pvcFormat
1✔
308
        }
1✔
309

310
        return &format
1✔
311
}
312

313
func (r *StorageProfileReconciler) reconcileMinimumSupportedPVCSize(sc *storagev1.StorageClass, sp *cdiv1.StorageProfile) {
1✔
314
        if size, hasSize := storagecapabilities.GetMinimumSupportedPVCSize(sc); hasSize {
2✔
315
                if _, isAnnotated := sp.Annotations[cc.AnnMinimumSupportedPVCSize]; !isAnnotated {
2✔
316
                        if sp.Annotations == nil {
2✔
317
                                sp.Annotations = make(map[string]string)
1✔
318
                        }
1✔
319
                        sp.Annotations[cc.AnnMinimumSupportedPVCSize] = size
1✔
320
                }
321
        }
322
}
323

324
func (r *StorageProfileReconciler) reconcileUseReadWriteOnceForDataImportCron(ctx context.Context, sc *storagev1.StorageClass, sp *cdiv1.StorageProfile) {
1✔
325
        if !storagecapabilities.ShouldUseReadWriteOnceForDataImportCron(sc) {
2✔
326
                return
1✔
327
        }
1✔
328

329
        if _, exists := sp.Annotations[cc.AnnUseReadWriteOnceForDataImportCron]; exists {
2✔
330
                return
1✔
331
        }
1✔
332

333
        if sp.Annotations == nil {
2✔
334
                sp.Annotations = make(map[string]string)
1✔
335
        }
1✔
336
        sp.Annotations[cc.AnnUseReadWriteOnceForDataImportCron] = "true"
1✔
337
}
338

339
func (r *StorageProfileReconciler) reconcileSnapshotClassForDataImportCron(ctx context.Context, sc *storagev1.StorageClass, sp *cdiv1.StorageProfile) {
1✔
340
        desiredClass, err := r.findSnapshotClassForDataImportCron(ctx, sc)
1✔
341
        if err != nil {
1✔
342
                r.log.V(3).Info("Error finding snapshot class for DataImportCron", "error", err)
×
343
                return
×
344
        }
×
345

346
        if desiredClass == "" {
2✔
347
                delete(sp.Annotations, cc.AnnSnapshotClassForDataImportCron)
1✔
348
                return
1✔
349
        }
1✔
350

351
        if sp.Annotations == nil {
1✔
352
                sp.Annotations = make(map[string]string)
×
353
        }
×
354
        sp.Annotations[cc.AnnSnapshotClassForDataImportCron] = desiredClass
1✔
355
}
356

357
// findSnapshotClassForDataImportCron finds a VolumeSnapshotClass that is suitable for DataImportCron snapshots.
358
func (r *StorageProfileReconciler) findSnapshotClassForDataImportCron(ctx context.Context, sc *storagev1.StorageClass) (string, error) {
1✔
359
        vscList := &snapshotv1.VolumeSnapshotClassList{}
1✔
360
        if err := r.client.List(ctx, vscList); err != nil {
1✔
361
                if meta.IsNoMatchError(err) {
×
362
                        return "", nil
×
363
                }
×
364
                return "", err
×
365
        }
366

367
        var candidates []string
1✔
368
        for _, vsc := range vscList.Items {
2✔
369
                if !storagecapabilities.MatchesDataImportCronVSC(sc, &vsc) {
2✔
370
                        continue
1✔
371
                }
372
                // Found a match - prefer default-annotated one
373
                if vsc.Annotations[cc.AnnDefaultSnapshotClass] == "true" {
1✔
374
                        return vsc.Name, nil
×
375
                }
×
376
                candidates = append(candidates, vsc.Name)
1✔
377
        }
378

379
        if len(candidates) > 0 {
2✔
380
                sort.Strings(candidates)
1✔
381
                return candidates[0], nil
1✔
382
        }
1✔
383
        return "", nil
1✔
384
}
385

386
func (r *StorageProfileReconciler) createEmptyStorageProfile(sc *storagev1.StorageClass) (*cdiv1.StorageProfile, error) {
1✔
387
        storageProfile := MakeEmptyStorageProfileSpec(sc.Name)
1✔
388
        util.SetRecommendedLabels(storageProfile, r.installerLabels, "cdi-controller")
1✔
389
        // uncachedClient is used to directly get the config map
1✔
390
        // the controller runtime client caches objects that are read once, and thus requires a list/watch
1✔
391
        // should be cheaper than watching
1✔
392
        if err := operator.SetOwnerRuntime(r.uncachedClient, storageProfile); err != nil {
1✔
393
                return nil, err
×
394
        }
×
395
        return storageProfile, nil
1✔
396
}
397

398
func (r *StorageProfileReconciler) deleteStorageProfile(name string, log logr.Logger) error {
1✔
399
        log.Info("Cleaning up StorageProfile that corresponds to deleted StorageClass", "StorageClass.Name", name)
1✔
400
        profile := &cdiv1.StorageProfile{
1✔
401
                ObjectMeta: metav1.ObjectMeta{
1✔
402
                        Name: name,
1✔
403
                },
1✔
404
        }
1✔
405

1✔
406
        if err := r.client.Delete(context.TODO(), profile); cc.IgnoreNotFound(err) != nil {
1✔
407
                return err
×
408
        }
×
409

410
        labels := prometheus.Labels{
1✔
411
                counterLabelStorageClass: name,
1✔
412
        }
1✔
413
        metrics.DeleteStorageProfileStatus(labels)
1✔
414
        return nil
1✔
415
}
416

417
func isNoProvisioner(name string, cl client.Client) bool {
×
418
        storageClass := &storagev1.StorageClass{}
×
419
        if err := cl.Get(context.TODO(), types.NamespacedName{Name: name}, storageClass); err != nil {
×
420
                return false
×
421
        }
×
422
        return storageClass.Provisioner == storagehelpers.NotSupportedProvisioner
×
423
}
424

425
func (r *StorageProfileReconciler) computeMetrics(profile *cdiv1.StorageProfile, sc *storagev1.StorageClass) error {
1✔
426
        if profile.Status.StorageClass == nil || profile.Status.Provisioner == nil {
1✔
427
                return nil
×
428
        }
×
429

430
        storageClass := *profile.Status.StorageClass
1✔
431
        provisioner := *profile.Status.Provisioner
1✔
432

1✔
433
        // We don't count explicitly unsupported provisioners as incomplete
1✔
434
        _, found := storagecapabilities.UnsupportedProvisioners[*profile.Status.Provisioner]
1✔
435
        isComplete := found || !isIncomplete(profile.Status.ClaimPropertySets)
1✔
436
        isDefault := sc.Annotations[cc.AnnDefaultStorageClass] == "true"
1✔
437
        isVirtDefault := sc.Annotations[cc.AnnDefaultVirtStorageClass] == "true"
1✔
438
        isRWX := hasRWX(profile.Status.ClaimPropertySets)
1✔
439
        isSmartClone, err := r.hasSmartClone(profile)
1✔
440
        if err != nil {
1✔
441
                return err
×
442
        }
×
443

444
        isSNO := false
1✔
445
        clusterInfra := &ocpconfigv1.Infrastructure{}
1✔
446
        if err := r.client.Get(context.TODO(), types.NamespacedName{Name: "cluster"}, clusterInfra); err != nil {
2✔
447
                if !meta.IsNoMatchError(err) && !k8serrors.IsNotFound(err) {
1✔
448
                        return err
×
449
                }
×
450
        } else {
1✔
451
                isSNO = clusterInfra.Status.ControlPlaneTopology == ocpconfigv1.SingleReplicaTopologyMode &&
1✔
452
                        clusterInfra.Status.InfrastructureTopology == ocpconfigv1.SingleReplicaTopologyMode
1✔
453
        }
1✔
454

455
        isDegraded := (!isSNO && !isRWX) || !isSmartClone
1✔
456

1✔
457
        // Setting the labeled Gauge to 1 will not delete older metric, so we need to explicitly delete them
1✔
458
        scLabels := prometheus.Labels{counterLabelStorageClass: storageClass, counterLabelProvisioner: provisioner}
1✔
459
        metricsDeleted := metrics.DeleteStorageProfileStatus(scLabels)
1✔
460
        scLabels = createLabels(storageClass, provisioner, isComplete, isDefault, isVirtDefault, isRWX, isSmartClone, isDegraded)
1✔
461
        metrics.SetStorageProfileStatus(scLabels, 1)
1✔
462
        r.log.Info(fmt.Sprintf("Set metric:%s complete:%t default:%t vdefault:%t rwx:%t smartclone:%t degraded:%t (deleted %d)",
1✔
463
                storageClass, isComplete, isDefault, isVirtDefault, isRWX, isSmartClone, isDegraded, metricsDeleted))
1✔
464

1✔
465
        return nil
1✔
466
}
467

468
func (r *StorageProfileReconciler) hasSmartClone(sp *cdiv1.StorageProfile) (bool, error) {
1✔
469
        strategy := sp.Status.CloneStrategy
1✔
470
        provisioner := sp.Status.Provisioner
1✔
471

1✔
472
        if strategy != nil {
2✔
473
                if *strategy == cdiv1.CloneStrategyHostAssisted {
2✔
474
                        return false, nil
1✔
475
                }
1✔
476
                if *strategy == cdiv1.CloneStrategyCsiClone && provisioner != nil {
2✔
477
                        driver := &storagev1.CSIDriver{}
1✔
478
                        if err := r.client.Get(context.TODO(), types.NamespacedName{Name: *provisioner}, driver); err != nil {
2✔
479
                                return false, cc.IgnoreNotFound(err)
1✔
480
                        }
1✔
481
                        return true, nil
1✔
482
                }
483
        }
484

485
        if (strategy == nil || *strategy == cdiv1.CloneStrategySnapshot) && provisioner != nil {
2✔
486
                vscs := &snapshotv1.VolumeSnapshotClassList{}
1✔
487
                if err := r.client.List(context.TODO(), vscs); err != nil {
1✔
488
                        return false, err
×
489
                }
×
490
                return hasDriver(vscs, *provisioner), nil
1✔
491
        }
492

493
        return false, nil
×
494
}
495

496
func createLabels(storageClass, provisioner string, isComplete, isDefault, isVirtDefault, isRWX, isSmartClone, isDegraded bool) prometheus.Labels {
1✔
497
        return prometheus.Labels{
1✔
498
                counterLabelStorageClass: storageClass,
1✔
499
                counterLabelProvisioner:  provisioner,
1✔
500
                counterLabelComplete:     strconv.FormatBool(isComplete),
1✔
501
                counterLabelDefault:      strconv.FormatBool(isDefault),
1✔
502
                counterLabelVirtDefault:  strconv.FormatBool(isVirtDefault),
1✔
503
                counterLabelRWX:          strconv.FormatBool(isRWX),
1✔
504
                counterLabelSmartClone:   strconv.FormatBool(isSmartClone),
1✔
505
                counterLabelDegraded:     strconv.FormatBool(isDegraded),
1✔
506
        }
1✔
507
}
1✔
508

509
// MakeEmptyStorageProfileSpec creates StorageProfile manifest
510
func MakeEmptyStorageProfileSpec(name string) *cdiv1.StorageProfile {
1✔
511
        return &cdiv1.StorageProfile{
1✔
512
                TypeMeta: metav1.TypeMeta{
1✔
513
                        Kind:       "StorageProfile",
1✔
514
                        APIVersion: "cdi.kubevirt.io/v1beta1",
1✔
515
                },
1✔
516
                ObjectMeta: metav1.ObjectMeta{
1✔
517
                        Name: name,
1✔
518
                        Labels: map[string]string{
1✔
519
                                common.CDILabelKey:       common.CDILabelValue,
1✔
520
                                common.CDIComponentLabel: "",
1✔
521
                        },
1✔
522
                },
1✔
523
        }
1✔
524
}
1✔
525

526
// NewStorageProfileController creates a new instance of the StorageProfile controller.
527
func NewStorageProfileController(mgr manager.Manager, log logr.Logger, installerLabels map[string]string) (controller.Controller, error) {
×
528
        uncachedClient, err := client.New(mgr.GetConfig(), client.Options{
×
529
                Scheme: mgr.GetScheme(),
×
530
                Mapper: mgr.GetRESTMapper(),
×
531
        })
×
532
        if err != nil {
×
533
                return nil, err
×
534
        }
×
535

536
        reconciler := &StorageProfileReconciler{
×
537
                client:          mgr.GetClient(),
×
538
                uncachedClient:  uncachedClient,
×
539
                recorder:        mgr.GetEventRecorderFor(storageProfileControllerName),
×
540
                scheme:          mgr.GetScheme(),
×
541
                log:             log.WithName(storageProfileControllerName),
×
542
                installerLabels: installerLabels,
×
543
        }
×
544

×
545
        storageProfileController, err := controller.New(
×
546
                storageProfileControllerName,
×
547
                mgr,
×
548
                controller.Options{Reconciler: reconciler, MaxConcurrentReconciles: 3})
×
549
        if err != nil {
×
550
                return nil, err
×
551
        }
×
552
        if err := addStorageProfileControllerWatches(mgr, storageProfileController, log); err != nil {
×
553
                return nil, err
×
554
        }
×
555

556
        log.Info("Initialized StorageProfile controller")
×
557
        return storageProfileController, nil
×
558
}
559

560
func addStorageProfileControllerWatches(mgr manager.Manager, c controller.Controller, log logr.Logger) error {
×
561
        if err := c.Watch(source.Kind(mgr.GetCache(), &storagev1.StorageClass{}, &handler.TypedEnqueueRequestForObject[*storagev1.StorageClass]{})); err != nil {
×
562
                return err
×
563
        }
×
564

565
        if err := c.Watch(source.Kind(mgr.GetCache(), &cdiv1.StorageProfile{}, &handler.TypedEnqueueRequestForObject[*cdiv1.StorageProfile]{})); err != nil {
×
566
                return err
×
567
        }
×
568

569
        if err := c.Watch(source.Kind(mgr.GetCache(), &v1.PersistentVolume{}, handler.TypedEnqueueRequestsFromMapFunc[*v1.PersistentVolume](
×
570
                func(_ context.Context, obj *v1.PersistentVolume) []reconcile.Request {
×
571
                        return []reconcile.Request{{
×
572
                                NamespacedName: types.NamespacedName{Name: scName(obj)},
×
573
                        }}
×
574
                },
×
575
        ),
576
                predicate.TypedFuncs[*v1.PersistentVolume]{
577
                        CreateFunc: func(e event.TypedCreateEvent[*v1.PersistentVolume]) bool {
×
578
                                return isNoProvisioner(scName(e.Object), mgr.GetClient())
×
579
                        },
×
580
                        UpdateFunc: func(e event.TypedUpdateEvent[*v1.PersistentVolume]) bool {
×
581
                                return isNoProvisioner(scName(e.ObjectNew), mgr.GetClient())
×
582
                        },
×
583
                        DeleteFunc: func(e event.TypedDeleteEvent[*v1.PersistentVolume]) bool {
×
584
                                return isNoProvisioner(scName(e.Object), mgr.GetClient())
×
585
                        },
×
586
                })); err != nil {
×
587
                return err
×
588
        }
×
589

590
        mapSnapshotClassToProfile := func(ctx context.Context, vsc *snapshotv1.VolumeSnapshotClass) []reconcile.Request {
×
591
                var scList storagev1.StorageClassList
×
592
                if err := mgr.GetClient().List(ctx, &scList); err != nil {
×
593
                        c.GetLogger().Error(err, "Unable to list StorageClasses")
×
594
                        return nil
×
595
                }
×
596
                var reqs []reconcile.Request
×
597
                for _, sc := range scList.Items {
×
598
                        if sc.Provisioner == vsc.Driver {
×
599
                                reqs = append(reqs, reconcile.Request{NamespacedName: types.NamespacedName{Name: sc.Name}})
×
600
                        }
×
601
                }
602
                return reqs
×
603
        }
604
        if err := mgr.GetClient().List(context.TODO(), &snapshotv1.VolumeSnapshotClassList{}, &client.ListOptions{Limit: 1}); err != nil {
×
605
                if meta.IsNoMatchError(err) {
×
606
                        // Back out if there's no point to attempt watch
×
607
                        return nil
×
608
                }
×
609
                if !cc.IsErrCacheNotStarted(err) {
×
610
                        return err
×
611
                }
×
612
        }
613
        if err := c.Watch(source.Kind(mgr.GetCache(), &snapshotv1.VolumeSnapshotClass{},
×
614
                handler.TypedEnqueueRequestsFromMapFunc[*snapshotv1.VolumeSnapshotClass](mapSnapshotClassToProfile),
×
615
        )); err != nil {
×
616
                return err
×
617
        }
×
618

619
        return nil
×
620
}
621

622
func scName(obj client.Object) string {
×
623
        return obj.(*v1.PersistentVolume).Spec.StorageClassName
×
624
}
×
625

626
func isIncomplete(sets []cdiv1.ClaimPropertySet) bool {
1✔
627
        if len(sets) > 0 {
2✔
628
                for _, cps := range sets {
2✔
629
                        if len(cps.AccessModes) == 0 || cps.VolumeMode == nil {
1✔
630
                                return true
×
631
                        }
×
632
                }
633
        } else {
1✔
634
                return true
1✔
635
        }
1✔
636

637
        return false
1✔
638
}
639

640
func hasRWX(cpSets []cdiv1.ClaimPropertySet) bool {
1✔
641
        for _, cpSet := range cpSets {
2✔
642
                for _, am := range cpSet.AccessModes {
2✔
643
                        if am == v1.ReadWriteMany {
2✔
644
                                return true
1✔
645
                        }
1✔
646
                }
647
        }
648
        return false
1✔
649
}
650

651
func hasDriver(vscs *snapshotv1.VolumeSnapshotClassList, driver string) bool {
1✔
652
        for i := range vscs.Items {
2✔
653
                vsc := vscs.Items[i]
1✔
654
                if vsc.Driver == driver {
2✔
655
                        return true
1✔
656
                }
1✔
657
        }
658
        return false
1✔
659
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc