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

kubevirt / containerized-data-importer / #5182

12 Mar 2025 09:34AM UTC coverage: 59.477% (+0.1%) from 59.377%
#5182

Pull #3664

travis-ci

noamasu
Fallback to host-assised when volumeMode Differs
When creating a DataVolume from a snapshot source with a specified PVC, if the PVC's volume mode differs, the snapshot cloning process will fall back to using the host-assisted strategy.

Signed-off-by: Noam Assouline <nassouli@redhat.com>
Pull Request #3664: Clone: fallback to host-assisted when snapshot volume mode differs (#3621)

22 of 26 new or added lines in 3 files covered. (84.62%)

2 existing lines in 1 file now uncovered.

16832 of 28300 relevant lines covered (59.48%)

0.66 hits per line

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

64.55
/pkg/controller/clone/common.go
1
package clone
2

3
import (
4
        "context"
5
        "fmt"
6

7
        "github.com/go-logr/logr"
8
        snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
9

10
        corev1 "k8s.io/api/core/v1"
11
        storagev1 "k8s.io/api/storage/v1"
12
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
13
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14
        "k8s.io/apimachinery/pkg/types"
15
        "k8s.io/apimachinery/pkg/util/sets"
16
        "k8s.io/client-go/tools/record"
17

18
        "sigs.k8s.io/controller-runtime/pkg/client"
19

20
        cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
21
        "kubevirt.io/containerized-data-importer/pkg/common"
22
        cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
23
        "kubevirt.io/containerized-data-importer/pkg/util"
24
)
25

26
const (
27
        // PendingPhaseName is the phase when the clone is pending
28
        PendingPhaseName = "Pending"
29

30
        // SucceededPhaseName is the phase when the clone is succeeded
31
        SucceededPhaseName = "Succeeded"
32

33
        // ErrorPhaseName is the phase when the clone is in error
34
        ErrorPhaseName = "Error"
35
)
36

37
// IsDataSourcePVC checks for PersistentVolumeClaim source kind
38
func IsDataSourcePVC(kind string) bool {
1✔
39
        return kind == "PersistentVolumeClaim"
1✔
40
}
1✔
41

42
// IsDataSourceSnapshot checks for Snapshot source kind
43
func IsDataSourceSnapshot(kind string) bool {
1✔
44
        return kind == "VolumeSnapshot"
1✔
45
}
1✔
46

47
// AddCommonLabels adds common labels to a resource
48
func AddCommonLabels(obj metav1.Object) {
1✔
49
        if obj.GetLabels() == nil {
2✔
50
                obj.SetLabels(make(map[string]string))
1✔
51
        }
1✔
52
        obj.GetLabels()[common.CDILabelKey] = common.CDILabelValue
1✔
53
}
54

55
// AddCommonClaimLabels adds common labels to a pvc
56
func AddCommonClaimLabels(pvc *corev1.PersistentVolumeClaim) {
×
57
        AddCommonLabels(pvc)
×
58
        if util.ResolveVolumeMode(pvc.Spec.VolumeMode) == corev1.PersistentVolumeFilesystem {
×
59
                pvc.GetLabels()[common.KubePersistentVolumeFillingUpSuppressLabelKey] = common.KubePersistentVolumeFillingUpSuppressLabelValue
×
60
        }
×
61
}
62

63
// AddOwnershipLabel adds owner label
64
func AddOwnershipLabel(label string, obj, owner metav1.Object) {
1✔
65
        if obj.GetLabels() == nil {
2✔
66
                obj.SetLabels(make(map[string]string))
1✔
67
        }
1✔
68
        obj.GetLabels()[label] = string(owner.GetUID())
1✔
69
}
70

71
// IsSourceClaimReadyArgs are arguments for IsSourceClaimReady
72
type IsSourceClaimReadyArgs struct {
73
        Target          client.Object
74
        SourceNamespace string
75
        SourceName      string
76
        Client          client.Client
77
        Log             logr.Logger
78
        Recorder        record.EventRecorder
79
}
80

81
// IsSourceClaimReady checks that PVC exists, is bound, and is not being used
82
func IsSourceClaimReady(ctx context.Context, args *IsSourceClaimReadyArgs) (bool, error) {
1✔
83
        claim := &corev1.PersistentVolumeClaim{}
1✔
84
        exists, err := getResource(ctx, args.Client, args.SourceNamespace, args.SourceName, claim)
1✔
85
        if err != nil {
1✔
86
                return false, err
×
87
        }
×
88

89
        if !exists {
2✔
90
                return false, nil
1✔
91
        }
1✔
92

93
        if claim.Status.Phase != corev1.ClaimBound {
1✔
94
                return false, nil
×
95
        }
×
96

97
        pods, err := cc.GetPodsUsingPVCs(ctx, args.Client, args.SourceNamespace, sets.New(args.SourceName), true)
1✔
98
        if err != nil {
1✔
99
                return false, err
×
100
        }
×
101

102
        for _, pod := range pods {
1✔
103
                args.Log.V(1).Info("Source PVC is being used by pod", "namespace", args.SourceNamespace, "name", args.SourceName, "pod", pod.Name)
×
104
                args.Recorder.Eventf(args.Target, corev1.EventTypeWarning, cc.CloneSourceInUse,
×
105
                        "pod %s/%s using PersistentVolumeClaim %s", pod.Namespace, pod.Name, args.SourceName)
×
106
        }
×
107

108
        if len(pods) > 0 {
1✔
109
                return false, nil
×
110
        }
×
111

112
        return cdiv1.IsPopulated(claim, dataVolumeGetter(ctx, args.Client))
1✔
113
}
114

115
// GetGlobalCloneStrategyOverride returns the global clone strategy override
116
func GetGlobalCloneStrategyOverride(ctx context.Context, c client.Client) (*cdiv1.CDICloneStrategy, error) {
1✔
117
        cr, err := cc.GetActiveCDI(ctx, c)
1✔
118
        if err != nil {
1✔
119
                return nil, err
×
120
        }
×
121

122
        if cr == nil {
1✔
123
                return nil, fmt.Errorf("no active CDI")
×
124
        }
×
125

126
        if cr.Spec.CloneStrategyOverride == nil {
2✔
127
                return nil, nil
1✔
128
        }
1✔
129

130
        return cr.Spec.CloneStrategyOverride, nil
1✔
131
}
132

133
// GetSnapshotContentFromSnapshot returns the VolumeSnapshotContent of a given VolumeSnapshot
134
func GetSnapshotContentFromSnapshot(ctx context.Context, c client.Client, snapshot *snapshotv1.VolumeSnapshot) (*snapshotv1.VolumeSnapshotContent, error) {
1✔
135
        if snapshot.Status == nil || snapshot.Status.BoundVolumeSnapshotContentName == nil {
2✔
136
                return nil, fmt.Errorf("volumeSnapshotContent name not found")
1✔
137
        }
1✔
138
        vsc := &snapshotv1.VolumeSnapshotContent{}
1✔
139
        if err := c.Get(ctx, types.NamespacedName{Name: *snapshot.Status.BoundVolumeSnapshotContentName}, vsc); err != nil {
1✔
NEW
140
                return nil, err
×
NEW
141
        }
×
142
        return vsc, nil
1✔
143
}
144

145
// GetStorageClassForClaim returns the storageclass for a PVC
146
func GetStorageClassForClaim(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim) (*storagev1.StorageClass, error) {
1✔
147
        if pvc.Spec.StorageClassName == nil || *pvc.Spec.StorageClassName == "" {
1✔
148
                return nil, nil
×
149
        }
×
150

151
        sc := &storagev1.StorageClass{}
1✔
152
        exists, err := getResource(ctx, c, "", *pvc.Spec.StorageClassName, sc)
1✔
153
        if err != nil {
1✔
154
                return nil, err
×
155
        }
×
156

157
        if exists {
2✔
158
                return sc, nil
1✔
159
        }
1✔
160

161
        return nil, nil
1✔
162
}
163

164
func getSnapshotClassForClaim(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim) (*string, error) {
1✔
165
        sc, err := cc.GetStorageClassByNameWithK8sFallback(ctx, c, pvc.Spec.StorageClassName)
1✔
166
        if err != nil || sc == nil {
2✔
167
                return nil, err
1✔
168
        }
1✔
169

170
        sp := &cdiv1.StorageProfile{}
1✔
171
        exists, err := getResource(ctx, c, "", sc.Name, sp)
1✔
172
        if err != nil {
1✔
173
                return nil, err
×
174
        }
×
175
        if exists {
1✔
176
                return sp.Status.SnapshotClass, nil
×
177
        }
×
178

179
        return nil, nil
1✔
180
}
181

182
// GetDriverFromVolume returns the CSI driver name for a PVC
183
func GetDriverFromVolume(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim) (*string, error) {
1✔
184
        if pvc.Spec.VolumeName == "" {
2✔
185
                return nil, nil
1✔
186
        }
1✔
187

188
        pv := &corev1.PersistentVolume{}
1✔
189
        exists, err := getResource(ctx, c, "", pvc.Spec.VolumeName, pv)
1✔
190
        if err != nil {
1✔
191
                return nil, err
×
192
        }
×
193

194
        if !exists || pv.Spec.ClaimRef == nil {
2✔
195
                return nil, nil
1✔
196
        }
1✔
197

198
        if pv.Spec.ClaimRef.Namespace != pvc.Namespace ||
1✔
199
                pv.Spec.ClaimRef.Name != pvc.Name {
1✔
200
                return nil, fmt.Errorf("pvc does not match volume claim ref")
×
201
        }
×
202

203
        if pv.Spec.CSI == nil {
1✔
204
                return nil, nil
×
205
        }
×
206

207
        return &pv.Spec.CSI.Driver, nil
1✔
208
}
209

210
// GetCommonDriver returns the name of the CSI driver shared by all PVCs
211
func GetCommonDriver(ctx context.Context, c client.Client, pvcs ...*corev1.PersistentVolumeClaim) (*string, error) {
1✔
212
        var result *string
1✔
213

1✔
214
        for _, pvc := range pvcs {
2✔
215
                driver, err := GetDriverFromVolume(ctx, c, pvc)
1✔
216
                if err != nil {
1✔
217
                        return nil, err
×
218
                }
×
219

220
                if driver == nil {
2✔
221
                        sc, err := GetStorageClassForClaim(ctx, c, pvc)
1✔
222
                        if err != nil {
1✔
223
                                return nil, err
×
224
                        }
×
225

226
                        if sc == nil {
1✔
227
                                return nil, nil
×
228
                        }
×
229

230
                        driver = &sc.Provisioner
1✔
231
                }
232

233
                if result == nil {
2✔
234
                        result = driver
1✔
235
                }
1✔
236

237
                if *result != *driver {
2✔
238
                        return nil, nil
1✔
239
                }
1✔
240
        }
241

242
        return result, nil
1✔
243
}
244

245
func getCommonSnapshotClass(ctx context.Context, c client.Client, pvcs ...*corev1.PersistentVolumeClaim) (*string, error) {
1✔
246
        var result *string
1✔
247

1✔
248
        for _, pvc := range pvcs {
2✔
249
                sc, err := getSnapshotClassForClaim(ctx, c, pvc)
1✔
250
                if err != nil {
1✔
251
                        return nil, err
×
252
                }
×
253
                if sc == nil {
2✔
254
                        return nil, nil
1✔
255
                }
1✔
256
                if result == nil {
×
257
                        result = sc
×
258
                } else if *result != *sc {
×
259
                        return nil, nil
×
260
                }
×
261
        }
262

263
        return result, nil
×
264
}
265

266
// GetCompatibleVolumeSnapshotClass returns a VolumeSnapshotClass name that works for all PVCs
267
// Note the last PVC passed is considered the target PVC for logs and events
268
func GetCompatibleVolumeSnapshotClass(ctx context.Context, c client.Client, log logr.Logger, recorder record.EventRecorder, pvcs ...*corev1.PersistentVolumeClaim) (*string, error) {
1✔
269
        targetClaim := pvcs[len(pvcs)-1]
1✔
270
        driver, err := GetCommonDriver(ctx, c, pvcs...)
1✔
271
        if err != nil {
1✔
272
                return nil, err
×
273
        }
×
274
        if driver == nil {
1✔
275
                return nil, nil
×
276
        }
×
277

278
        snapshotClassName, err := getCommonSnapshotClass(ctx, c, pvcs...)
1✔
279
        if err != nil {
1✔
280
                return nil, err
×
281
        }
×
282

283
        return cc.GetVolumeSnapshotClass(context.TODO(), c, targetClaim, *driver, snapshotClassName, log, recorder)
1✔
284
}
285

286
// SameVolumeMode returns true if all target pvcs have the same volume mode as the source
287
func SameVolumeMode(srcVolumeMode *corev1.PersistentVolumeMode, others ...*corev1.PersistentVolumeClaim) bool {
1✔
288
        vm := util.ResolveVolumeMode(srcVolumeMode)
1✔
289
        for _, pvc := range others {
2✔
290
                if util.ResolveVolumeMode(pvc.Spec.VolumeMode) != vm {
2✔
291
                        return false
1✔
292
                }
1✔
293
        }
294
        return true
1✔
295
}
296

297
func getResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
1✔
298
        obj.SetNamespace(namespace)
1✔
299
        obj.SetName(name)
1✔
300

1✔
301
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
1✔
302
        if err != nil {
2✔
303
                if k8serrors.IsNotFound(err) {
2✔
304
                        return false, nil
1✔
305
                }
1✔
306

307
                return false, err
×
308
        }
309

310
        return true, nil
1✔
311
}
312

313
func dataVolumeGetter(ctx context.Context, c client.Client) func(name, namespace string) (*cdiv1.DataVolume, error) {
1✔
314
        return func(name, namespace string) (*cdiv1.DataVolume, error) {
1✔
315
                obj := &cdiv1.DataVolume{
×
316
                        ObjectMeta: metav1.ObjectMeta{
×
317
                                Namespace: namespace,
×
318
                                Name:      name,
×
319
                        },
×
320
                }
×
321

×
322
                err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
323
                if err != nil {
×
324
                        return nil, err
×
325
                }
×
326

327
                return obj, nil
×
328
        }
329
}
330

331
func checkQuotaExceeded(r record.EventRecorder, owner client.Object, err error) {
×
332
        if cc.ErrQuotaExceeded(err) {
×
333
                r.Event(owner, corev1.EventTypeWarning, cc.ErrExceededQuota, err.Error())
×
334
        }
×
335
}
336

337
func isClaimBoundOrWFFC(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim) (bool, error) {
1✔
338
        if cc.IsBound(pvc) {
2✔
339
                return true, nil
1✔
340
        }
1✔
341

342
        sc, err := GetStorageClassForClaim(ctx, c, pvc)
1✔
343
        if err != nil {
1✔
344
                return false, err
×
345
        }
×
346

347
        if sc == nil {
1✔
348
                return false, fmt.Errorf("no storageclass for pvc")
×
349
        }
×
350

351
        if sc.VolumeBindingMode != nil && *sc.VolumeBindingMode == storagev1.VolumeBindingWaitForFirstConsumer {
2✔
352
                return true, nil
1✔
353
        }
1✔
354

355
        return false, nil
1✔
356
}
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