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

kubevirt / containerized-data-importer / #5251

24 Apr 2025 09:09AM UTC coverage: 59.199% (+0.03%) from 59.172%
#5251

Pull #3711

travis-ci

arnongilboa
Allow increasing PVC size to the minimum supported by its storage class

Supports both DataVolume PVC rendering and PVC mutating webhook
rendering, so it can cover the kubevirt-side created TPM small PVC and
any other PVC created by DV or independently. For independent PVCs they
should be labeled with cdi.kubevirt.io/applyStorageProfile: "true" and
CDI featureGate WebhookPvcRendering should be enabled. A storage class
can be annotated with its minimum supported volume size with e.g.:
    cdi.kubevirt.io/minimumSupportedPvcSize: 4Gi

Signed-off-by: Arnon Gilboa <agilboa@redhat.com>
Pull Request #3711: Allow increasing PVC size to the minimum supported by its storage class

5 of 11 new or added lines in 1 file covered. (45.45%)

4 existing lines in 1 file now uncovered.

16829 of 28428 relevant lines covered (59.2%)

0.65 hits per line

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

66.8
/pkg/controller/datavolume/util.go
1
/*
2
Copyright 2020 The CDI Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package datavolume
18

19
import (
20
        "context"
21
        "fmt"
22
        "strconv"
23

24
        "github.com/docker/go-units"
25
        "github.com/go-logr/logr"
26
        snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
27
        "github.com/pkg/errors"
28

29
        v1 "k8s.io/api/core/v1"
30
        storagev1 "k8s.io/api/storage/v1"
31
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
32
        "k8s.io/apimachinery/pkg/api/resource"
33
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34
        "k8s.io/apimachinery/pkg/types"
35
        "k8s.io/client-go/tools/cache"
36
        "k8s.io/client-go/tools/record"
37
        storagehelpers "k8s.io/component-helpers/storage/volume"
38
        "k8s.io/utils/ptr"
39

40
        "sigs.k8s.io/controller-runtime/pkg/client"
41
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
42

43
        cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
44
        "kubevirt.io/containerized-data-importer/pkg/common"
45
        cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
46
        "kubevirt.io/containerized-data-importer/pkg/controller/populators"
47
        featuregates "kubevirt.io/containerized-data-importer/pkg/feature-gates"
48
        "kubevirt.io/containerized-data-importer/pkg/util"
49
)
50

51
const (
52
        // AnnOwnedByDataVolume annotation has the owner DataVolume name
53
        AnnOwnedByDataVolume = "cdi.kubevirt.io/ownedByDataVolume"
54

55
        // AnnMinimumSupportedPVCSize annotation on a StorageClass specifies its minimum supported PVC size
56
        AnnMinimumSupportedPVCSize = "cdi.kubevirt.io/minimumSupportedPvcSize"
57

58
        // MessageErrStorageClassNotFound provides a const to indicate the PVC spec is missing accessMode and no storageClass to choose profile
59
        MessageErrStorageClassNotFound = "PVC spec is missing accessMode and no storageClass to choose profile"
60
)
61

62
var (
63
        // ErrStorageClassNotFound indicates the PVC spec is missing accessMode and no storageClass to choose profile
64
        ErrStorageClassNotFound = errors.New(MessageErrStorageClassNotFound)
65
)
66

67
// RenderPvc renders the PVC according to StorageProfiles
68
func RenderPvc(ctx context.Context, client client.Client, pvc *v1.PersistentVolumeClaim) error {
×
69
        if pvc.Spec.VolumeMode != nil &&
×
70
                *pvc.Spec.VolumeMode == cdiv1.PersistentVolumeFromStorageProfile {
×
71
                pvc.Spec.VolumeMode = nil
×
72
        }
×
73

74
        dvContentType := cc.GetPVCContentType(pvc)
×
75
        if err := renderPvcSpecVolumeModeAndAccessModesAndStorageClass(client, nil, nil, nil, &pvc.Spec, dvContentType); err != nil {
×
76
                return err
×
77
        }
×
78

79
        if hasCloneSourceRef(pvc) {
×
80
                return renderClonePvcVolumeSizeFromSource(ctx, client, pvc)
×
81
        }
×
82

83
        isClone := pvc.Annotations[cc.AnnCloneType] != ""
×
84
        return renderPvcSpecVolumeSize(client, &pvc.Spec, isClone)
×
85
}
86

87
// renderPvcSpec creates a new PVC Spec based on either the dv.spec.pvc or dv.spec.storage section
88
func renderPvcSpec(client client.Client, recorder record.EventRecorder, log logr.Logger, dv *cdiv1.DataVolume, pvc *v1.PersistentVolumeClaim) (*v1.PersistentVolumeClaimSpec, error) {
1✔
89
        if dv.Spec.PVC != nil {
2✔
90
                return dv.Spec.PVC.DeepCopy(), nil
1✔
91
        } else if dv.Spec.Storage != nil {
3✔
92
                return pvcFromStorage(client, recorder, log, dv, pvc)
1✔
93
        }
1✔
94

95
        return nil, errors.Errorf("datavolume one of {pvc, storage} field is required")
×
96
}
97

98
func pvcFromStorage(client client.Client, recorder record.EventRecorder, log logr.Logger, dv *cdiv1.DataVolume, pvc *v1.PersistentVolumeClaim) (*v1.PersistentVolumeClaimSpec, error) {
1✔
99
        var pvcSpec *v1.PersistentVolumeClaimSpec
1✔
100

1✔
101
        isWebhookRenderingEnabled, err := featuregates.IsWebhookPvcRenderingEnabled(client)
1✔
102
        if err != nil {
1✔
103
                return nil, err
×
104
        }
×
105

106
        shouldRender := !isWebhookRenderingEnabled || dv.Labels[common.PvcApplyStorageProfileLabel] != "true"
1✔
107

1✔
108
        if pvc == nil {
2✔
109
                pvcSpec = copyStorageAsPvc(dv.Spec.Storage)
1✔
110
                if shouldRender {
2✔
111
                        if err := renderPvcSpecVolumeModeAndAccessModesAndStorageClass(client, recorder, &log, dv, pvcSpec, dv.Spec.ContentType); err != nil {
2✔
112
                                return nil, err
1✔
113
                        }
1✔
114
                }
115
        } else {
1✔
116
                pvcSpec = pvc.Spec.DeepCopy()
1✔
117
        }
1✔
118

119
        if shouldRender {
2✔
120
                isClone := dv.Spec.Source.PVC != nil || dv.Spec.Source.Snapshot != nil
1✔
121
                if err := renderPvcSpecVolumeSize(client, pvcSpec, isClone); err != nil {
2✔
122
                        return nil, err
1✔
123
                }
1✔
124
        }
125

126
        return pvcSpec, nil
1✔
127
}
128

129
// func is called from both DV controller (with recorder and log) and PVC mutating webhook (without recorder and log)
130
// therefore we use wrappers for log and recorder calls
131
func renderPvcSpecVolumeModeAndAccessModesAndStorageClass(client client.Client, recorder record.EventRecorder, log *logr.Logger,
132
        dv *cdiv1.DataVolume, pvcSpec *v1.PersistentVolumeClaimSpec, dvContentType cdiv1.DataVolumeContentType) error {
1✔
133
        logInfo := func(msg string, keysAndValues ...interface{}) {
2✔
134
                if log != nil && dv != nil {
2✔
135
                        keysAndValuesWithDv := append(keysAndValues, "namespace", dv.Namespace, "name", dv.Name)
1✔
136
                        log.V(1).Info(msg, keysAndValuesWithDv...)
1✔
137
                }
1✔
138
        }
139

140
        recordEventf := func(eventtype, reason, messageFmt string, args ...interface{}) {
2✔
141
                if recorder != nil && dv != nil {
2✔
142
                        recorder.Eventf(dv, eventtype, reason, messageFmt, args...)
1✔
143
                }
1✔
144
        }
145

146
        if dvContentType == cdiv1.DataVolumeArchive {
2✔
147
                if pvcSpec.VolumeMode != nil && *pvcSpec.VolumeMode == v1.PersistentVolumeBlock {
2✔
148
                        logInfo("ContentType Archive cannot have block volumeMode")
1✔
149
                        recordEventf(v1.EventTypeWarning, cc.ErrClaimNotValid, "ContentType Archive cannot have block volumeMode")
1✔
150
                        return errors.Errorf("ContentType Archive cannot have block volumeMode")
1✔
151
                }
1✔
152
                pvcSpec.VolumeMode = ptr.To[v1.PersistentVolumeMode](v1.PersistentVolumeFilesystem)
1✔
153
        }
154

155
        storageClass, err := cc.GetStorageClassByNameWithVirtFallback(context.TODO(), client, pvcSpec.StorageClassName, dvContentType)
1✔
156
        if err != nil {
1✔
157
                return err
×
158
        }
×
159
        if storageClass == nil {
2✔
160
                if err := renderPvcSpecFromAvailablePv(client, pvcSpec); err != nil {
1✔
161
                        return err
×
162
                }
×
163
                // Not even default storageClass on the cluster, cannot apply the defaults, verify spec is ok
164
                if len(pvcSpec.AccessModes) == 0 {
2✔
165
                        logInfo("Cannot set accessMode for new pvc")
1✔
166
                        recordEventf(v1.EventTypeWarning, cc.ErrClaimNotValid, MessageErrStorageClassNotFound)
1✔
167
                        return ErrStorageClassNotFound
1✔
168
                }
1✔
169
                return nil
×
170
        }
171

172
        pvcSpec.StorageClassName = &storageClass.Name
1✔
173
        // given storageClass we can apply defaults if needed
1✔
174
        if (pvcSpec.VolumeMode == nil || *pvcSpec.VolumeMode == "") && len(pvcSpec.AccessModes) == 0 {
2✔
175
                accessModes, volumeMode, err := getDefaultVolumeAndAccessMode(client, storageClass)
1✔
176
                if err != nil {
1✔
177
                        logInfo("Cannot set accessMode and volumeMode for new pvc", "Error", err)
×
178
                        recordEventf(v1.EventTypeWarning, cc.ErrClaimNotValid,
×
179
                                fmt.Sprintf("Spec is missing accessMode and volumeMode, cannot get access mode from StorageProfile %s", getName(storageClass)))
×
180
                        return err
×
181
                }
×
182
                pvcSpec.AccessModes = append(pvcSpec.AccessModes, accessModes...)
1✔
183
                pvcSpec.VolumeMode = volumeMode
1✔
184
        } else if len(pvcSpec.AccessModes) == 0 {
2✔
185
                accessModes, err := getDefaultAccessModes(client, storageClass, pvcSpec.VolumeMode)
1✔
186
                if err != nil {
1✔
187
                        logInfo("Cannot set accessMode for new pvc", "Error", err)
×
188
                        recordEventf(v1.EventTypeWarning, cc.ErrClaimNotValid,
×
189
                                fmt.Sprintf("Spec is missing accessMode and cannot get access mode from StorageProfile %s", getName(storageClass)))
×
190
                        return err
×
191
                }
×
192
                pvcSpec.AccessModes = append(pvcSpec.AccessModes, accessModes...)
1✔
193
        } else if pvcSpec.VolumeMode == nil || *pvcSpec.VolumeMode == "" {
2✔
194
                volumeMode, err := getDefaultVolumeMode(client, storageClass, pvcSpec.AccessModes)
1✔
195
                if err != nil {
1✔
196
                        return err
×
197
                }
×
198
                pvcSpec.VolumeMode = volumeMode
1✔
199
        }
200

201
        return nil
1✔
202
}
203

204
func renderClonePvcVolumeSizeFromSource(ctx context.Context, client client.Client, pvc *v1.PersistentVolumeClaim) error {
×
205
        if size, exists := pvc.Spec.Resources.Requests[v1.ResourceStorage]; exists && !size.IsZero() {
×
206
                return nil
×
207
        }
×
208

209
        if !hasCloneSourceRef(pvc) {
×
210
                return nil
×
211
        }
×
212

213
        sourceNamespace, exists := pvc.Annotations[populators.AnnDataSourceNamespace]
×
214
        if !exists {
×
215
                sourceNamespace = pvc.Namespace
×
216
        }
×
217

218
        volumeCloneSource := &cdiv1.VolumeCloneSource{}
×
219
        if exists, err := cc.GetResource(ctx, client, sourceNamespace, pvc.Spec.DataSourceRef.Name, volumeCloneSource); err != nil || !exists {
×
220
                return err
×
221
        }
×
222

223
        source := volumeCloneSource.Spec.Source
×
224

×
225
        if source.Kind == "VolumeSnapshot" && source.Name != "" {
×
226
                sourceSnapshot := &snapshotv1.VolumeSnapshot{}
×
227
                if exists, err := cc.GetResource(ctx, client, sourceNamespace, source.Name, sourceSnapshot); err != nil || !exists {
×
228
                        return err
×
229
                }
×
230
                if sourceSnapshot.Status != nil && sourceSnapshot.Status.RestoreSize != nil {
×
231
                        setRequestedVolumeSize(&pvc.Spec, *sourceSnapshot.Status.RestoreSize)
×
232
                }
×
233
                return nil
×
234
        }
235

236
        if source.Kind != "PersistentVolumeClaim" || source.Name == "" {
×
237
                return nil
×
238
        }
×
239
        sourcePvc := &v1.PersistentVolumeClaim{}
×
240
        if exists, err := cc.GetResource(ctx, client, sourceNamespace, source.Name, sourcePvc); err != nil || !exists {
×
241
                return err
×
242
        }
×
243

244
        // We cannot fill in the PVC size when these conditions are met, where the size detection pod is used when PVC size is rendered by the controllor
245
        sourceVolumeMode := util.ResolveVolumeMode(sourcePvc.Spec.VolumeMode)
×
246
        targetVolumeMode := util.ResolveVolumeMode(pvc.Spec.VolumeMode)
×
247
        isKubevirtContent := cc.GetPVCContentType(sourcePvc) == cdiv1.DataVolumeKubeVirt
×
248
        isHostAssistedClone := pvc.Annotations[cc.AnnCloneType] == string(cdiv1.CloneStrategyHostAssisted)
×
249
        if sourceVolumeMode == v1.PersistentVolumeFilesystem &&
×
250
                targetVolumeMode == v1.PersistentVolumeBlock &&
×
251
                isKubevirtContent && isHostAssistedClone {
×
252
                return nil
×
253
        }
×
254

255
        sourceSC, err := cc.GetStorageClassByNameWithK8sFallback(ctx, client, sourcePvc.Spec.StorageClassName)
×
256
        if err != nil || sourceSC == nil {
×
257
                return err
×
258
        }
×
259
        targetSC, err := cc.GetStorageClassByNameWithK8sFallback(ctx, client, pvc.Spec.StorageClassName)
×
260
        if err != nil || targetSC == nil {
×
261
                return err
×
262
        }
×
263

264
        // If target has the source volume mode and storage class, it can request the source requested volume size.
265
        // Otherwise try using the source capacity.
266
        volSize := sourcePvc.Spec.Resources.Requests[v1.ResourceStorage]
×
267
        if targetVolumeMode != sourceVolumeMode || targetSC.Name != sourceSC.Name {
×
268
                if capacity, exists := sourcePvc.Status.Capacity[v1.ResourceStorage]; exists {
×
269
                        volSize = capacity
×
270
                }
×
271
        }
272
        setRequestedVolumeSize(&pvc.Spec, volSize)
×
273

×
274
        return nil
×
275
}
276

277
func hasCloneSourceRef(pvc *v1.PersistentVolumeClaim) bool {
×
278
        dsRef := pvc.Spec.DataSourceRef
×
279
        return dsRef != nil && dsRef.APIGroup != nil && *dsRef.APIGroup == cc.AnnAPIGroup && dsRef.Kind == cdiv1.VolumeCloneSourceRef && dsRef.Name != ""
×
280
}
×
281

282
func renderPvcSpecVolumeSize(client client.Client, pvcSpec *v1.PersistentVolumeClaimSpec, isClone bool) error {
1✔
283
        requestedSize, found := pvcSpec.Resources.Requests[v1.ResourceStorage]
1✔
284

1✔
285
        // Storage size can be empty when cloning
1✔
286
        if !found {
2✔
287
                if !isClone {
2✔
288
                        return errors.Errorf("PVC Spec is not valid - missing storage size")
1✔
289
                }
1✔
290
                setRequestedVolumeSize(pvcSpec, resource.Quantity{})
1✔
291
                return nil
1✔
292
        }
293

294
        // Kubevirt doesn't allow disks smaller than 1MiB. Rejecting for consistency.
295
        if requestedSize.Value() < units.MiB {
2✔
296
                return errors.Errorf("PVC Spec is not valid - storage size should be at least 1MiB")
1✔
297
        }
1✔
298

299
        requestedSize, err := cc.InflateSizeWithOverhead(context.TODO(), client, requestedSize.Value(), pvcSpec)
1✔
300
        if err != nil {
1✔
301
                return err
×
302
        }
×
303

304
        if scName := pvcSpec.StorageClassName; scName != nil {
2✔
305
                storageClass := &storagev1.StorageClass{}
1✔
306
                if err := client.Get(context.TODO(), types.NamespacedName{Name: *scName}, storageClass); err == nil {
2✔
307
                        if val, exists := storageClass.Annotations[AnnMinimumSupportedPVCSize]; exists {
1✔
NEW
308
                                minSize := resource.MustParse(val)
×
NEW
309
                                if requestedSize.Cmp(minSize) == -1 {
×
NEW
310
                                        requestedSize = minSize
×
NEW
311
                                }
×
312
                        }
313
                } else if !k8serrors.IsNotFound(err) {
1✔
NEW
314
                        return err
×
NEW
315
                }
×
316
        }
317

318
        setRequestedVolumeSize(pvcSpec, requestedSize)
1✔
319

1✔
320
        return nil
1✔
321
}
322

323
func setRequestedVolumeSize(pvcSpec *v1.PersistentVolumeClaimSpec, volumeSize resource.Quantity) {
1✔
324
        if pvcSpec.Resources.Requests == nil {
2✔
325
                pvcSpec.Resources.Requests = v1.ResourceList{}
1✔
326
        }
1✔
327
        pvcSpec.Resources.Requests[v1.ResourceStorage] = volumeSize
1✔
328
}
329

330
func getName(storageClass *storagev1.StorageClass) string {
×
331
        if storageClass != nil {
×
332
                return storageClass.Name
×
333
        }
×
334
        return ""
×
335
}
336

337
func copyStorageAsPvc(storage *cdiv1.StorageSpec) *v1.PersistentVolumeClaimSpec {
1✔
338
        input := storage.DeepCopy()
1✔
339
        pvcSpec := &v1.PersistentVolumeClaimSpec{
1✔
340
                AccessModes:      input.AccessModes,
1✔
341
                Selector:         input.Selector,
1✔
342
                Resources:        input.Resources,
1✔
343
                VolumeName:       input.VolumeName,
1✔
344
                StorageClassName: input.StorageClassName,
1✔
345
                VolumeMode:       input.VolumeMode,
1✔
346
                DataSource:       input.DataSource,
1✔
347
                DataSourceRef:    input.DataSourceRef,
1✔
348
        }
1✔
349

1✔
350
        return pvcSpec
1✔
351
}
1✔
352

353
// Renders the PVC spec VolumeMode and AccessModes from an available satisfying PV
354
func renderPvcSpecFromAvailablePv(c client.Client, pvcSpec *v1.PersistentVolumeClaimSpec) error {
1✔
355
        if pvcSpec.StorageClassName == nil {
2✔
356
                return nil
1✔
357
        }
1✔
358

359
        pvList := &v1.PersistentVolumeList{}
1✔
360
        fields := client.MatchingFields{claimStorageClassNameField: *pvcSpec.StorageClassName}
1✔
361
        if err := c.List(context.TODO(), pvList, fields); err != nil {
1✔
362
                return err
×
363
        }
×
364

365
        for _, pv := range pvList.Items {
1✔
366
                pvc := &v1.PersistentVolumeClaim{Spec: *pvcSpec}
×
367
                if err := CheckVolumeSatisfyClaim(&pv, pvc); err == nil {
×
368
                        pvcSpec.VolumeMode = pv.Spec.VolumeMode
×
369
                        if len(pvcSpec.AccessModes) == 0 {
×
370
                                pvcSpec.AccessModes = pv.Spec.AccessModes
×
371
                        }
×
372
                        return nil
×
373
                }
374
        }
375

376
        return nil
1✔
377
}
378

379
func getDefaultVolumeAndAccessMode(c client.Client, storageClass *storagev1.StorageClass) ([]v1.PersistentVolumeAccessMode, *v1.PersistentVolumeMode, error) {
1✔
380
        if storageClass == nil {
1✔
381
                return nil, nil, errors.Errorf("no accessMode specified and StorageClass not found")
×
382
        }
×
383

384
        storageProfile := &cdiv1.StorageProfile{}
1✔
385
        err := c.Get(context.TODO(), types.NamespacedName{Name: storageClass.Name}, storageProfile)
1✔
386
        if err != nil {
1✔
387
                return nil, nil, errors.Wrap(err, "cannot get StorageProfile")
×
388
        }
×
389

390
        if len(storageProfile.Status.ClaimPropertySets) > 0 &&
1✔
391
                len(storageProfile.Status.ClaimPropertySets[0].AccessModes) > 0 {
2✔
392
                accessModes := storageProfile.Status.ClaimPropertySets[0].AccessModes
1✔
393
                volumeMode := storageProfile.Status.ClaimPropertySets[0].VolumeMode
1✔
394
                return accessModes, volumeMode, nil
1✔
395
        }
1✔
396

397
        // no accessMode configured on storageProfile
398
        return nil, nil, errors.Errorf("no accessMode specified in StorageProfile %s", storageProfile.Name)
×
399
}
400

401
func getDefaultVolumeMode(c client.Client, storageClass *storagev1.StorageClass, pvcAccessModes []v1.PersistentVolumeAccessMode) (*v1.PersistentVolumeMode, error) {
1✔
402
        if storageClass == nil {
1✔
403
                // fallback to k8s defaults
×
404
                return nil, nil
×
405
        }
×
406

407
        storageProfile := &cdiv1.StorageProfile{}
1✔
408
        err := c.Get(context.TODO(), types.NamespacedName{Name: storageClass.Name}, storageProfile)
1✔
409
        if err != nil {
1✔
410
                return nil, errors.Wrap(err, "cannot get StorageProfile")
×
411
        }
×
412
        if len(storageProfile.Status.ClaimPropertySets) > 0 {
2✔
413
                volumeMode := storageProfile.Status.ClaimPropertySets[0].VolumeMode
1✔
414
                if len(pvcAccessModes) == 0 {
1✔
415
                        return volumeMode, nil
×
416
                }
×
417
                // check for volume mode matching with given pvc access modes
418
                for _, cps := range storageProfile.Status.ClaimPropertySets {
2✔
419
                        for _, accessMode := range cps.AccessModes {
2✔
420
                                for _, pvcAccessMode := range pvcAccessModes {
2✔
421
                                        if accessMode == pvcAccessMode {
2✔
422
                                                return cps.VolumeMode, nil
1✔
423
                                        }
1✔
424
                                }
425
                        }
426
                }
427
                // if not found return default volume mode for the storage class
428
                return volumeMode, nil
×
429
        }
430

431
        // since volumeMode is optional - > gracefully fallback to k8s defaults,
432
        return nil, nil
×
433
}
434

435
func getDefaultAccessModes(c client.Client, storageClass *storagev1.StorageClass, pvcVolumeMode *v1.PersistentVolumeMode) ([]v1.PersistentVolumeAccessMode, error) {
1✔
436
        if storageClass == nil {
1✔
437
                return nil, errors.Errorf("no accessMode specified and no StorageProfile")
×
438
        }
×
439

440
        storageProfile := &cdiv1.StorageProfile{}
1✔
441
        err := c.Get(context.TODO(), types.NamespacedName{Name: storageClass.Name}, storageProfile)
1✔
442
        if err != nil {
1✔
443
                return nil, errors.Wrapf(err, "no accessMode specified and cannot get StorageProfile %s", storageClass.Name)
×
444
        }
×
445

446
        if len(storageProfile.Status.ClaimPropertySets) > 0 {
2✔
447
                // check for access modes matching with given pvc volume mode
1✔
448
                defaultAccessModes := []v1.PersistentVolumeAccessMode{}
1✔
449
                for _, cps := range storageProfile.Status.ClaimPropertySets {
2✔
450
                        if cps.VolumeMode != nil && pvcVolumeMode != nil && *cps.VolumeMode == *pvcVolumeMode {
2✔
451
                                if len(cps.AccessModes) > 0 {
2✔
452
                                        return cps.AccessModes, nil
1✔
453
                                }
1✔
454
                        } else if len(cps.AccessModes) > 0 && len(defaultAccessModes) == 0 {
2✔
455
                                defaultAccessModes = cps.AccessModes
1✔
456
                        }
1✔
457
                }
458
                // if not found return default access modes for the storage profile
459
                if len(defaultAccessModes) > 0 {
×
460
                        return defaultAccessModes, nil
×
461
                }
×
462
        }
463

464
        // no accessMode configured on storageProfile
465
        return nil, errors.Errorf("no accessMode specified in StorageProfile %s", storageProfile.Name)
×
466
}
467

468
// storageClassCSIDriverExists returns true if the passed storage class has CSI drivers available
469
func storageClassCSIDriverExists(client client.Client, log logr.Logger, storageClassName *string) (bool, error) {
1✔
470
        log = log.WithName("storageClassCSIDriverExists").V(3)
1✔
471

1✔
472
        storageClass, err := cc.GetStorageClassByNameWithK8sFallback(context.TODO(), client, storageClassName)
1✔
473
        if err != nil {
1✔
474
                return false, err
×
475
        }
×
476
        if storageClass == nil {
2✔
477
                log.Info("Target PVC's Storage Class not found")
1✔
478
                return false, nil
1✔
479
        }
1✔
480

481
        csiDriver := &storagev1.CSIDriver{}
1✔
482

1✔
483
        if err := client.Get(context.TODO(), types.NamespacedName{Name: storageClass.Provisioner}, csiDriver); err != nil {
2✔
484
                if !k8serrors.IsNotFound(err) {
1✔
485
                        return false, err
×
486
                }
×
487
                return false, nil
1✔
488
        }
489

490
        return true, nil
1✔
491
}
492

493
// CheckPVCUsingPopulators returns true if pvc has dataSourceRef and has
494
// the usePopulator annotation
495
func CheckPVCUsingPopulators(pvc *v1.PersistentVolumeClaim) (bool, error) {
1✔
496
        if pvc.Spec.DataSourceRef == nil {
2✔
497
                return false, nil
1✔
498
        }
1✔
499
        usePopulator, ok := pvc.Annotations[cc.AnnUsePopulator]
1✔
500
        if !ok {
2✔
501
                return false, nil
1✔
502
        }
1✔
503
        boolUsePopulator, err := strconv.ParseBool(usePopulator)
1✔
504
        if err != nil {
1✔
505
                return false, err
×
506
        }
×
507
        return boolUsePopulator, nil
1✔
508
}
509

510
func updateDataVolumeUseCDIPopulator(syncState *dvSyncState) {
1✔
511
        cc.AddAnnotation(syncState.dvMutated, cc.AnnUsePopulator, strconv.FormatBool(syncState.usePopulator))
1✔
512
}
1✔
513

514
func updateDataVolumeDefaultInstancetypeLabels(client client.Client, syncState *dvSyncState) error {
1✔
515
        // Skip looking anything up if any default instance type labels are already present
1✔
516
        dv := syncState.dvMutated
1✔
517
        for _, defaultInstanceTypeLabel := range cc.DefaultInstanceTypeLabels {
2✔
518
                if _, ok := dv.Labels[defaultInstanceTypeLabel]; ok {
2✔
519
                        return nil
1✔
520
                }
1✔
521
        }
522
        if dv.Spec.Source != nil && dv.Spec.Source.PVC != nil {
2✔
523
                pvc := &v1.PersistentVolumeClaim{}
1✔
524
                key := types.NamespacedName{
1✔
525
                        Name:      dv.Spec.Source.PVC.Name,
1✔
526
                        Namespace: dv.Spec.Source.PVC.Namespace,
1✔
527
                }
1✔
528
                if err := client.Get(context.TODO(), key, pvc); err != nil {
2✔
529
                        if k8serrors.IsNotFound(err) {
2✔
530
                                return nil
1✔
531
                        }
1✔
532
                        return err
1✔
533
                }
534
                copyDefaultInstancetypeLabels(dv, pvc.Labels)
1✔
535
                return nil
1✔
536
        }
537
        if dv.Spec.Source != nil && dv.Spec.Source.Snapshot != nil {
2✔
538
                snapshot := &snapshotv1.VolumeSnapshot{}
1✔
539
                key := types.NamespacedName{
1✔
540
                        Name:      dv.Spec.Source.Snapshot.Name,
1✔
541
                        Namespace: dv.Spec.Source.Snapshot.Namespace,
1✔
542
                }
1✔
543
                if err := client.Get(context.TODO(), key, snapshot); err != nil {
2✔
544
                        if k8serrors.IsNotFound(err) {
2✔
545
                                return nil
1✔
546
                        }
1✔
547
                        return err
1✔
548
                }
549
                copyDefaultInstancetypeLabels(dv, snapshot.Labels)
1✔
550
                return nil
1✔
551
        }
552
        if dv.Spec.Source != nil && dv.Spec.Source.Registry != nil {
2✔
553
                pvc := &v1.PersistentVolumeClaim{}
1✔
554
                key := types.NamespacedName{
1✔
555
                        Name:      dv.Name,
1✔
556
                        Namespace: dv.Namespace,
1✔
557
                }
1✔
558
                if err := client.Get(context.TODO(), key, pvc); err != nil {
2✔
559
                        if k8serrors.IsNotFound(err) {
2✔
560
                                return nil
1✔
561
                        }
1✔
562
                        return err
1✔
563
                }
564
                copyDefaultInstancetypeLabels(dv, pvc.Labels)
1✔
565
                return nil
1✔
566
        }
567
        if dv.Spec.SourceRef != nil && dv.Spec.SourceRef.Namespace != nil && dv.Spec.SourceRef.Kind == cdiv1.DataVolumeDataSource {
2✔
568
                ds := &cdiv1.DataSource{}
1✔
569
                key := types.NamespacedName{
1✔
570
                        Name:      dv.Spec.SourceRef.Name,
1✔
571
                        Namespace: *dv.Spec.SourceRef.Namespace,
1✔
572
                }
1✔
573
                if err := client.Get(context.TODO(), key, ds); err != nil {
2✔
574
                        if k8serrors.IsNotFound(err) {
2✔
575
                                return nil
1✔
576
                        }
1✔
577
                        return err
1✔
578
                }
579
                copyDefaultInstancetypeLabels(dv, ds.Labels)
1✔
580
                return nil
1✔
581
        }
582
        return nil
1✔
583
}
584

585
func copyDefaultInstancetypeLabels(dataVolume *cdiv1.DataVolume, labels map[string]string) {
1✔
586
        for _, defaultInstancetypeLabel := range cc.DefaultInstanceTypeLabels {
2✔
587
                if v, ok := labels[defaultInstancetypeLabel]; ok {
2✔
588
                        cc.AddLabel(dataVolume, defaultInstancetypeLabel, v)
1✔
589
                }
1✔
590
        }
591
}
592

593
func checkDVUsingPopulators(dv *cdiv1.DataVolume) (bool, error) {
1✔
594
        usePopulator, ok := dv.Annotations[cc.AnnUsePopulator]
1✔
595
        if !ok {
2✔
596
                return false, nil
1✔
597
        }
1✔
598
        boolUsePopulator, err := strconv.ParseBool(usePopulator)
1✔
599
        if err != nil {
1✔
600
                return false, err
×
601
        }
×
602
        return boolUsePopulator, nil
1✔
603
}
604

605
func dvBoundOrPopulationInProgress(dataVolume *cdiv1.DataVolume, boundCond *cdiv1.DataVolumeCondition) bool {
1✔
606
        usePopulator, err := checkDVUsingPopulators(dataVolume)
1✔
607
        if err != nil {
1✔
608
                return false
×
609
        }
×
610
        return boundCond.Status == v1.ConditionTrue ||
1✔
611
                (usePopulator && dataVolume.Status.Phase != cdiv1.Pending && dataVolume.Status.Phase != cdiv1.PendingPopulation)
1✔
612
}
613

614
func createStorageProfile(name string,
615
        accessModes []v1.PersistentVolumeAccessMode,
616
        volumeMode v1.PersistentVolumeMode) *cdiv1.StorageProfile {
1✔
617
        claimPropertySets := []cdiv1.ClaimPropertySet{{
1✔
618
                AccessModes: accessModes,
1✔
619
                VolumeMode:  &volumeMode,
1✔
620
        }}
1✔
621
        return createStorageProfileWithClaimPropertySets(name, claimPropertySets)
1✔
622
}
1✔
623

624
func createStorageProfileWithClaimPropertySets(name string,
625
        claimPropertySets []cdiv1.ClaimPropertySet) *cdiv1.StorageProfile {
1✔
626
        return createStorageProfileWithCloneStrategy(name, claimPropertySets, nil)
1✔
627
}
1✔
628

629
func createStorageProfileWithCloneStrategy(name string,
630
        claimPropertySets []cdiv1.ClaimPropertySet,
631
        cloneStrategy *cdiv1.CDICloneStrategy) *cdiv1.StorageProfile {
1✔
632
        return &cdiv1.StorageProfile{
1✔
633
                ObjectMeta: metav1.ObjectMeta{
1✔
634
                        Name: name,
1✔
635
                },
1✔
636
                Status: cdiv1.StorageProfileStatus{
1✔
637
                        StorageClass:      &name,
1✔
638
                        ClaimPropertySets: claimPropertySets,
1✔
639
                        CloneStrategy:     cloneStrategy,
1✔
640
                },
1✔
641
        }
1✔
642
}
1✔
643

644
func hasAnnOwnedByDataVolume(obj metav1.Object) bool {
1✔
645
        _, ok := obj.GetAnnotations()[AnnOwnedByDataVolume]
1✔
646
        return ok
1✔
647
}
1✔
648

649
func getAnnOwnedByDataVolume(obj metav1.Object) (string, string, error) {
1✔
650
        val := obj.GetAnnotations()[AnnOwnedByDataVolume]
1✔
651
        return cache.SplitMetaNamespaceKey(val)
1✔
652
}
1✔
653

654
func setAnnOwnedByDataVolume(dest, obj metav1.Object) error {
1✔
655
        key, err := cache.MetaNamespaceKeyFunc(obj)
1✔
656
        if err != nil {
1✔
657
                return err
×
658
        }
×
659

660
        if dest.GetAnnotations() == nil {
2✔
661
                dest.SetAnnotations(make(map[string]string))
1✔
662
        }
1✔
663
        dest.GetAnnotations()[AnnOwnedByDataVolume] = key
1✔
664

1✔
665
        return nil
1✔
666
}
667

668
// CheckVolumeSatisfyClaim checks if the volume requested by the claim satisfies the requirements of the claim
669
// adapted from k8s.io/kubernetes/pkg/controller/volume/persistentvolume/pv_controller.go
670
func CheckVolumeSatisfyClaim(volume *v1.PersistentVolume, claim *v1.PersistentVolumeClaim) error {
1✔
671
        requestedQty := claim.Spec.Resources.Requests[v1.ResourceStorage]
1✔
672
        requestedSize := requestedQty.Value()
1✔
673

1✔
674
        // check if PV's DeletionTimeStamp is set, if so, return error.
1✔
675
        if volume.ObjectMeta.DeletionTimestamp != nil {
1✔
676
                return fmt.Errorf("the volume is marked for deletion %q", volume.Name)
×
677
        }
×
678

679
        volumeQty := volume.Spec.Capacity[v1.ResourceStorage]
1✔
680
        volumeSize := volumeQty.Value()
1✔
681
        if volumeSize < requestedSize {
1✔
682
                return fmt.Errorf("requested PV is too small")
×
683
        }
×
684

685
        // this differs from pv_controller, be loose on storage class if not specified
686
        requestedClass := storagehelpers.GetPersistentVolumeClaimClass(claim)
1✔
687
        if requestedClass != "" && storagehelpers.GetPersistentVolumeClass(volume) != requestedClass {
1✔
688
                return fmt.Errorf("storageClassName does not match")
×
689
        }
×
690

691
        if storagehelpers.CheckVolumeModeMismatches(&claim.Spec, &volume.Spec) {
1✔
692
                return fmt.Errorf("incompatible volumeMode")
×
693
        }
×
694

695
        if !storagehelpers.CheckAccessModes(claim, volume) {
1✔
696
                return fmt.Errorf("incompatible accessMode")
×
697
        }
×
698

699
        return nil
1✔
700
}
701

702
func getReconcileRequest(obj client.Object) reconcile.Request {
1✔
703
        return reconcile.Request{NamespacedName: client.ObjectKeyFromObject(obj)}
1✔
704
}
1✔
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

© 2025 Coveralls, Inc