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

kubevirt / containerized-data-importer / #5925

06 Feb 2026 05:59AM UTC coverage: 49.529% (+0.03%) from 49.499%
#5925

Pull #4010

travis-ci

halfcrazy
feat: Add checksum validation for HTTP/HTTPS DataVolume sources

Introduces cryptographic hash validation for HTTP/HTTPS
import sources to prevent data tampering during download.

**Usage Example:**

```yaml
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  name: fedora-dv
spec:
  source:
    http:
      url: "https://download.fedoraproject.org/pub/fedora/linux/releases/39/Cloud/x86_64/images/Fedora-Cloud-Base-39-1.5.x86_64.qcow2"
      checksum: "sha256:c5b50f903e39b3c5d3b7c7bb9a4c5e4f3"
  pvc:
    accessModes:
      - ReadWriteOnce
    resources:
      requests:
        storage: 10Gi
```

Signed-off-by: Yan Zhu <hackzhuyan@gmail.com>
Pull Request #4010: feat: Add checksum validation for HTTP/HTTPS DataVolume sources

158 of 247 new or added lines in 14 files covered. (63.97%)

1342 existing lines in 22 files now uncovered.

14926 of 30136 relevant lines covered (49.53%)

0.55 hits per line

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

74.6
/pkg/controller/clone-controller.go
1
package controller
2

3
import (
4
        "context"
5
        "crypto/rsa"
6
        "fmt"
7
        "reflect"
8
        "strconv"
9
        "strings"
10
        "time"
11

12
        "github.com/go-logr/logr"
13
        "github.com/pkg/errors"
14

15
        corev1 "k8s.io/api/core/v1"
16
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
17
        "k8s.io/apimachinery/pkg/api/resource"
18
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19
        "k8s.io/apimachinery/pkg/runtime"
20
        "k8s.io/apimachinery/pkg/types"
21
        "k8s.io/apimachinery/pkg/util/sets"
22
        "k8s.io/client-go/tools/cache"
23
        "k8s.io/client-go/tools/record"
24

25
        "sigs.k8s.io/controller-runtime/pkg/client"
26
        "sigs.k8s.io/controller-runtime/pkg/controller"
27
        "sigs.k8s.io/controller-runtime/pkg/handler"
28
        "sigs.k8s.io/controller-runtime/pkg/manager"
29
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
30
        "sigs.k8s.io/controller-runtime/pkg/source"
31

32
        cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
33
        "kubevirt.io/containerized-data-importer/pkg/common"
34
        cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
35
        "kubevirt.io/containerized-data-importer/pkg/operator"
36
        "kubevirt.io/containerized-data-importer/pkg/util"
37
        "kubevirt.io/containerized-data-importer/pkg/util/cert/fetcher"
38
        "kubevirt.io/containerized-data-importer/pkg/util/cert/generator"
39
        sdkapi "kubevirt.io/controller-lifecycle-operator-sdk/api"
40
)
41

42
const (
43
        // TokenKeyDir is the path to the apiserver public key dir
44
        //nolint:gosec // This is a path, not the key itself
45
        TokenKeyDir = "/var/run/cdi/token/keys"
46

47
        // TokenPublicKeyPath is the path to the apiserver public key
48
        TokenPublicKeyPath = TokenKeyDir + "/id_rsa.pub"
49

50
        // TokenPrivateKeyPath is the path to the apiserver private key
51
        TokenPrivateKeyPath = TokenKeyDir + "/id_rsa"
52

53
        // CloneSucceededPVC provides a const to indicate a clone to the PVC succeeded
54
        CloneSucceededPVC = "CloneSucceeded"
55

56
        cloneSourcePodFinalizer = "cdi.kubevirt.io/cloneSource"
57

58
        hostAssistedCloneSource = "cdi.kubevirt.io/hostAssistedSourcePodCloneSource"
59
)
60

61
// CloneReconciler members
62
type CloneReconciler struct {
63
        client              client.Client
64
        scheme              *runtime.Scheme
65
        recorder            record.EventRecorder
66
        clientCertGenerator generator.CertGenerator
67
        serverCAFetcher     fetcher.CertBundleFetcher
68
        log                 logr.Logger
69
        multiTokenValidator *cc.MultiTokenValidator
70
        image               string
71
        verbose             string
72
        pullPolicy          string
73
        installerLabels     map[string]string
74
}
75

76
// NewCloneController creates a new instance of the config controller.
77
func NewCloneController(mgr manager.Manager,
78
        log logr.Logger,
79
        image, pullPolicy,
80
        verbose string,
81
        clientCertGenerator generator.CertGenerator,
82
        serverCAFetcher fetcher.CertBundleFetcher,
83
        apiServerKey *rsa.PublicKey,
84
        installerLabels map[string]string) (controller.Controller, error) {
85
        reconciler := &CloneReconciler{
×
86
                client:              mgr.GetClient(),
×
87
                scheme:              mgr.GetScheme(),
×
88
                log:                 log.WithName("clone-controller"),
×
89
                multiTokenValidator: cc.NewMultiTokenValidator(apiServerKey),
×
90
                image:               image,
×
91
                verbose:             verbose,
×
92
                pullPolicy:          pullPolicy,
×
93
                recorder:            mgr.GetEventRecorderFor("clone-controller"),
×
94
                clientCertGenerator: clientCertGenerator,
×
95
                serverCAFetcher:     serverCAFetcher,
×
96
                installerLabels:     installerLabels,
×
97
        }
×
98
        cloneController, err := controller.New("clone-controller", mgr, controller.Options{
×
99
                MaxConcurrentReconciles: 3,
×
100
                Reconciler:              reconciler,
×
101
        })
×
102
        if err != nil {
×
103
                return nil, err
×
104
        }
×
105
        if err := addCloneControllerWatches(mgr, cloneController); err != nil {
×
106
                return nil, err
×
107
        }
×
108
        return cloneController, nil
×
UNCOV
109
}
×
110

111
// addCloneControllerWatches sets up the watches used by the clone controller.
112
func addCloneControllerWatches(mgr manager.Manager, cloneController controller.Controller) error {
113
        // Setup watches
×
114
        if err := cloneController.Watch(source.Kind(mgr.GetCache(), &corev1.PersistentVolumeClaim{}, &handler.TypedEnqueueRequestForObject[*corev1.PersistentVolumeClaim]{})); err != nil {
×
115
                return err
×
116
        }
×
117
        if err := cloneController.Watch(source.Kind(mgr.GetCache(), &corev1.Pod{}, handler.TypedEnqueueRequestForOwner[*corev1.Pod](
×
118
                mgr.GetScheme(), mgr.GetClient().RESTMapper(), &corev1.PersistentVolumeClaim{}, handler.OnlyControllerOwner()))); err != nil {
×
119
                return err
×
120
        }
×
121
        if err := cloneController.Watch(source.Kind(mgr.GetCache(), &corev1.Pod{}, handler.TypedEnqueueRequestsFromMapFunc[*corev1.Pod](
×
122
                func(ctx context.Context, obj *corev1.Pod) []reconcile.Request {
×
123
                        target, ok := obj.GetAnnotations()[AnnOwnerRef]
×
124
                        if !ok {
×
125
                                return nil
×
126
                        }
×
127
                        namespace, name, err := cache.SplitMetaNamespaceKey(target)
×
128
                        if err != nil {
×
129
                                return nil
×
130
                        }
×
131
                        return []reconcile.Request{
×
132
                                {
×
133
                                        NamespacedName: types.NamespacedName{
×
134
                                                Namespace: namespace,
×
135
                                                Name:      name,
×
136
                                        },
×
137
                                },
×
138
                        }
×
UNCOV
139
                },
×
140
        ))); err != nil {
141
                return err
×
142
        }
×
143
        return nil
×
UNCOV
144
}
×
145

146
func (r *CloneReconciler) shouldReconcile(pvc *corev1.PersistentVolumeClaim, log logr.Logger) bool {
147
        return checkPVC(pvc, cc.AnnCloneRequest, log) &&
1✔
148
                !metav1.HasAnnotation(pvc.ObjectMeta, cc.AnnCloneOf) &&
1✔
149
                isBound(pvc, log)
1✔
150
}
1✔
151

1✔
152
// Reconcile the reconcile loop for host assisted clone pvc.
153
func (r *CloneReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
154
        // Get the PVC.
1✔
155
        pvc := &corev1.PersistentVolumeClaim{}
1✔
156
        if err := r.client.Get(ctx, req.NamespacedName, pvc); err != nil {
1✔
157
                if k8serrors.IsNotFound(err) {
2✔
158
                        return reconcile.Result{}, nil
2✔
159
                }
1✔
160
                return reconcile.Result{}, err
1✔
UNCOV
161
        }
×
162

163
        log := r.log.WithValues("PVC", req.NamespacedName)
164
        log.V(1).Info("reconciling Clone PVCs")
1✔
165

1✔
166
        if checkPVC(pvc, cc.AnnCloneRequest, log) {
1✔
167
                if err := cc.UpdatePVCBoundContionFromEvents(pvc, r.client, log); err != nil {
2✔
168
                        return reconcile.Result{}, err
1✔
169
                }
×
UNCOV
170
        }
×
171

172
        if pvc.DeletionTimestamp != nil || !r.shouldReconcile(pvc, log) {
173
                log.V(1).Info("Should not reconcile this PVC",
2✔
174
                        "checkPVC(AnnCloneRequest)", checkPVC(pvc, cc.AnnCloneRequest, log),
1✔
175
                        "NOT has annotation(AnnCloneOf)", !metav1.HasAnnotation(pvc.ObjectMeta, cc.AnnCloneOf),
1✔
176
                        "isBound", isBound(pvc, log),
1✔
177
                        "has finalizer?", cc.HasFinalizer(pvc, cloneSourcePodFinalizer))
1✔
178
                if cc.HasFinalizer(pvc, cloneSourcePodFinalizer) || pvc.DeletionTimestamp != nil {
1✔
179
                        // Clone completed, remove source pod and/or finalizer
2✔
180
                        if err := r.cleanup(pvc, log); err != nil {
1✔
181
                                return reconcile.Result{}, err
1✔
182
                        }
×
UNCOV
183
                }
×
184
                return reconcile.Result{}, nil
185
        }
1✔
186

187
        ready, err := r.waitTargetPodRunningOrSucceeded(pvc, log)
188
        if err != nil {
1✔
189
                return reconcile.Result{}, errors.Wrap(err, "error ensuring target upload pod running")
2✔
190
        }
1✔
191

1✔
192
        if !ready {
193
                log.V(3).Info("Upload pod not ready yet for PVC")
2✔
194
                return reconcile.Result{}, nil
1✔
195
        }
1✔
196

1✔
197
        sourcePod, err := r.findCloneSourcePod(pvc)
198
        if err != nil {
1✔
199
                return reconcile.Result{}, err
1✔
200
        }
×
UNCOV
201

×
202
        _, nameExists := pvc.Annotations[cc.AnnCloneSourcePod]
203
        if !nameExists && sourcePod == nil {
1✔
204
                pvc.Annotations[cc.AnnCloneSourcePod] = cc.CreateCloneSourcePodName(pvc)
2✔
205

1✔
206
                // add finalizer before creating clone source pod
1✔
207
                cc.AddFinalizer(pvc, cloneSourcePodFinalizer)
1✔
208

1✔
209
                if err := r.updatePVC(pvc); err != nil {
1✔
210
                        return reconcile.Result{}, err
1✔
211
                }
×
UNCOV
212

×
213
                // will reconcile again after PVC update notification
214
                return reconcile.Result{}, nil
215
        }
1✔
216

217
        if requeueAfter, err := r.reconcileSourcePod(ctx, sourcePod, pvc, log); requeueAfter != 0 || err != nil {
218
                return reconcile.Result{RequeueAfter: requeueAfter}, err
2✔
219
        }
1✔
220

1✔
221
        if err := r.ensureCertSecret(sourcePod, pvc); err != nil {
222
                return reconcile.Result{}, err
2✔
223
        }
1✔
224

1✔
225
        if err := r.updatePvcFromPod(sourcePod, pvc, log); err != nil {
226
                return reconcile.Result{}, err
1✔
227
        }
×
UNCOV
228
        return reconcile.Result{}, nil
×
229
}
1✔
230

231
func (r *CloneReconciler) reconcileSourcePod(ctx context.Context, sourcePod *corev1.Pod, targetPvc *corev1.PersistentVolumeClaim, log logr.Logger) (time.Duration, error) {
232
        if sourcePod == nil {
1✔
233
                sourcePvc, err := r.getCloneRequestSourcePVC(targetPvc)
2✔
234
                if err != nil {
1✔
235
                        return 0, err
1✔
236
                }
×
UNCOV
237

×
238
                sourcePopulated, err := cc.IsPopulated(sourcePvc, r.client)
239
                if err != nil {
1✔
240
                        return 0, err
1✔
241
                }
×
UNCOV
242
                if !sourcePopulated {
×
243
                        return 2 * time.Second, nil
1✔
244
                }
×
UNCOV
245

×
246
                if err := r.validateSourceAndTarget(ctx, sourcePvc, targetPvc); err != nil {
247
                        return 0, err
2✔
248
                }
1✔
249

1✔
250
                pods, err := cc.GetPodsUsingPVCs(ctx, r.client, sourcePvc.Namespace, sets.New(sourcePvc.Name), true)
251
                if err != nil {
1✔
252
                        return 0, err
1✔
253
                }
×
UNCOV
254

×
255
                if len(pods) > 0 {
256
                        es, err := cc.GetAnnotatedEventSource(ctx, r.client, targetPvc)
2✔
257
                        if err != nil {
1✔
258
                                return 0, err
1✔
259
                        }
×
UNCOV
260
                        for _, pod := range pods {
×
261
                                r.log.V(1).Info("can't create clone source pod, pvc in use by other pod",
2✔
262
                                        "namespace", sourcePvc.Namespace, "name", sourcePvc.Name, "pod", pod.Name)
1✔
263
                                r.recorder.Eventf(es, corev1.EventTypeWarning, cc.CloneSourceInUse,
1✔
264
                                        "pod %s/%s using PersistentVolumeClaim %s", pod.Namespace, pod.Name, sourcePvc.Name)
1✔
265
                        }
1✔
266
                        return 2 * time.Second, nil
1✔
267
                }
1✔
268

269
                sourcePod, err := r.CreateCloneSourcePod(r.image, r.pullPolicy, targetPvc, log)
270
                // Check if pod has failed and, in that case, record an event with the error
1✔
271
                if podErr := cc.HandleFailedPod(err, cc.CreateCloneSourcePodName(targetPvc), targetPvc, r.recorder, r.client); podErr != nil {
1✔
272
                        return 0, podErr
1✔
273
                }
×
UNCOV
274

×
275
                log.V(3).Info("Created source pod ", "sourcePod.Namespace", sourcePod.Namespace, "sourcePod.Name", sourcePod.Name)
276
        }
1✔
277
        return 0, nil
278
}
1✔
279

280
func (r *CloneReconciler) ensureCertSecret(sourcePod *corev1.Pod, targetPvc *corev1.PersistentVolumeClaim) error {
281
        if sourcePod == nil {
1✔
282
                return nil
2✔
283
        }
1✔
284

1✔
285
        if sourcePod.Status.Phase == corev1.PodRunning {
286
                return nil
1✔
287
        }
×
UNCOV
288

×
289
        clientName, ok := targetPvc.Annotations[AnnUploadClientName]
290
        if !ok {
1✔
291
                return errors.Errorf("PVC %s/%s missing required %s annotation", targetPvc.Namespace, targetPvc.Name, AnnUploadClientName)
2✔
292
        }
1✔
293

1✔
294
        certConfig, err := operator.GetCertConfigWithDefaults(context.TODO(), r.client)
295
        if err != nil {
1✔
296
                return err
1✔
297
        }
×
UNCOV
298

×
299
        cert, key, err := r.clientCertGenerator.MakeClientCert(clientName, nil, certConfig.Client.Duration.Duration)
300
        if err != nil {
1✔
301
                return err
1✔
302
        }
×
UNCOV
303

×
304
        secret := &corev1.Secret{
305
                ObjectMeta: metav1.ObjectMeta{
1✔
306
                        Name:      sourcePod.Name,
1✔
307
                        Namespace: sourcePod.Namespace,
1✔
308
                        Annotations: map[string]string{
1✔
309
                                cc.AnnCreatedBy: "yes",
1✔
310
                        },
1✔
311
                        Labels: map[string]string{
1✔
312
                                common.CDILabelKey:       common.CDILabelValue, //filtered by the podInformer
1✔
313
                                common.CDIComponentLabel: common.ClonerSourcePodName,
1✔
314
                        },
1✔
315
                        OwnerReferences: []metav1.OwnerReference{
1✔
316
                                MakePodOwnerReference(sourcePod),
1✔
317
                        },
1✔
318
                },
1✔
319
                Data: map[string][]byte{
1✔
320
                        "tls.key": key,
1✔
321
                        "tls.crt": cert,
1✔
322
                },
1✔
323
        }
1✔
324

1✔
325
        util.SetRecommendedLabels(secret, r.installerLabels, "cdi-controller")
1✔
326

1✔
327
        err = r.client.Create(context.TODO(), secret)
1✔
328
        if err != nil && !k8serrors.IsAlreadyExists(err) {
1✔
329
                return errors.Wrap(err, "error creating cert secret")
1✔
330
        }
×
UNCOV
331

×
332
        return nil
333
}
1✔
334

335
func (r *CloneReconciler) updatePvcFromPod(sourcePod *corev1.Pod, pvc *corev1.PersistentVolumeClaim, log logr.Logger) error {
336
        currentPvcCopy := pvc.DeepCopyObject()
1✔
337
        log.V(1).Info("Updating PVC from pod")
1✔
338

1✔
339
        log.V(3).Info("Pod phase for PVC", "PVC phase", pvc.Annotations[cc.AnnPodPhase])
1✔
340

1✔
341
        if podSucceededFromPVC(pvc) && pvc.Annotations[cc.AnnCloneOf] != "true" && sourcePodFinished(sourcePod) {
1✔
342
                log.V(1).Info("Adding CloneOf annotation to PVC")
2✔
343
                pvc.Annotations[cc.AnnCloneOf] = "true"
1✔
344
                r.recorder.Event(pvc, corev1.EventTypeNormal, CloneSucceededPVC, cc.CloneComplete)
1✔
345
        }
1✔
346

1✔
347
        setAnnotationsFromPodWithPrefix(pvc.Annotations, sourcePod, nil, cc.AnnSourceRunningCondition)
348

1✔
349
        if !reflect.DeepEqual(currentPvcCopy, pvc) {
1✔
350
                return r.updatePVC(pvc)
2✔
351
        }
1✔
352
        return nil
1✔
353
}
1✔
354

355
func sourcePodFinished(sourcePod *corev1.Pod) bool {
356
        if sourcePod == nil {
1✔
357
                return true
1✔
358
        }
×
UNCOV
359

×
360
        return sourcePod.Status.Phase == corev1.PodSucceeded || sourcePod.Status.Phase == corev1.PodFailed
361
}
1✔
362

363
func (r *CloneReconciler) updatePVC(pvc *corev1.PersistentVolumeClaim) error {
364
        if err := r.client.Update(context.TODO(), pvc); err != nil {
1✔
365
                return err
1✔
366
        }
×
UNCOV
367
        return nil
×
368
}
1✔
369

370
func (r *CloneReconciler) waitTargetPodRunningOrSucceeded(pvc *corev1.PersistentVolumeClaim, log logr.Logger) (bool, error) {
371
        rs, ok := pvc.Annotations[cc.AnnPodReady]
1✔
372
        if !ok {
1✔
373
                log.V(3).Info("clone target pod not ready")
2✔
374
                return false, nil
1✔
375
        }
1✔
376

1✔
377
        ready, err := strconv.ParseBool(rs)
378
        if err != nil {
1✔
379
                return false, errors.Wrapf(err, "error parsing %s annotation", cc.AnnPodReady)
2✔
380
        }
1✔
381

1✔
382
        return ready || podSucceededFromPVC(pvc), nil
383
}
1✔
384

385
func (r *CloneReconciler) findCloneSourcePod(pvc *corev1.PersistentVolumeClaim) (*corev1.Pod, error) {
386
        isCloneRequest, sourceNamespace, _ := ParseCloneRequestAnnotation(pvc)
1✔
387
        if !isCloneRequest {
1✔
388
                return nil, nil
1✔
389
        }
×
UNCOV
390
        cloneSourcePodName, exists := pvc.Annotations[cc.AnnCloneSourcePod]
×
391
        if !exists {
1✔
392
                // fallback to legacy name, to find any pod that still might be running after upgrade
2✔
393
                cloneSourcePodName = cc.CreateCloneSourcePodName(pvc)
1✔
394
        }
1✔
395

1✔
396
        selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
397
                MatchLabels: map[string]string{
1✔
398
                        cc.CloneUniqueID: cloneSourcePodName,
1✔
399
                },
1✔
400
        })
1✔
401
        if err != nil {
1✔
402
                return nil, errors.Wrap(err, "error creating label selector")
1✔
403
        }
×
UNCOV
404

×
405
        podList := &corev1.PodList{}
406
        if err := r.client.List(context.TODO(), podList, &client.ListOptions{Namespace: sourceNamespace, LabelSelector: selector}); err != nil {
1✔
407
                return nil, errors.Wrap(err, "error listing pods")
1✔
408
        }
×
UNCOV
409

×
410
        if len(podList.Items) > 1 {
411
                return nil, errors.Errorf("multiple source pods found for clone PVC %s/%s", pvc.Namespace, pvc.Name)
1✔
412
        }
×
UNCOV
413

×
414
        if len(podList.Items) == 0 {
415
                return nil, nil
2✔
416
        }
1✔
417

1✔
418
        return &podList.Items[0], nil
419
}
1✔
420

421
func (r *CloneReconciler) validateSourceAndTarget(ctx context.Context, sourcePvc, targetPvc *corev1.PersistentVolumeClaim) error {
422
        if err := r.multiTokenValidator.ValidatePVC(sourcePvc, targetPvc); err != nil {
1✔
423
                return err
1✔
424
        }
×
UNCOV
425
        contentType, err := ValidateCanCloneSourceAndTargetContentType(sourcePvc, targetPvc)
×
426
        if err != nil {
1✔
427
                return err
2✔
428
        }
1✔
429
        err = ValidateCanCloneSourceAndTargetSpec(ctx, r.client, sourcePvc, targetPvc, contentType)
1✔
430
        if err == nil {
1✔
431
                // Validation complete, put source PVC bound status in annotation
2✔
432
                setBoundConditionFromPVC(targetPvc.GetAnnotations(), cc.AnnBoundCondition, sourcePvc)
1✔
433
                return nil
1✔
434
        }
1✔
435
        return err
1✔
436
}
1✔
437

438
// returns the CloneRequest string which contains the pvc name (and namespace) from which we want to clone the image.
439
func (r *CloneReconciler) getCloneRequestSourcePVC(pvc *corev1.PersistentVolumeClaim) (*corev1.PersistentVolumeClaim, error) {
440
        exists, namespace, name := ParseCloneRequestAnnotation(pvc)
1✔
441
        if !exists {
1✔
442
                return nil, errors.New("error parsing clone request annotation")
1✔
443
        }
×
UNCOV
444
        pvc = &corev1.PersistentVolumeClaim{}
×
445
        if err := r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, pvc); err != nil {
1✔
446
                return nil, errors.Wrap(err, "error getting clone source PVC")
1✔
447
        }
×
UNCOV
448
        return pvc, nil
×
449
}
1✔
450

451
func (r *CloneReconciler) cleanup(pvc *corev1.PersistentVolumeClaim, log logr.Logger) error {
452
        log.V(3).Info("Cleaning up for PVC", "pvc.Namespace", pvc.Namespace, "pvc.Name", pvc.Name)
1✔
453

1✔
454
        pod, err := r.findCloneSourcePod(pvc)
1✔
455
        if err != nil {
1✔
456
                return err
1✔
457
        }
×
UNCOV
458

×
459
        if pod != nil && pod.DeletionTimestamp == nil {
460
                if podSucceededFromPVC(pvc) && pod.Status.Phase == corev1.PodRunning {
2✔
461
                        log.V(3).Info("Clone succeeded, waiting for source pod to stop running", "pod.Namespace", pod.Namespace, "pod.Name", pod.Name)
1✔
462
                        return nil
×
463
                }
×
UNCOV
464
                if cc.ShouldDeletePod(pvc) {
×
465
                        log.V(3).Info("Deleting pod", "pod.Name", pod.Name)
2✔
466
                        if err = r.client.Delete(context.TODO(), pod); err != nil {
1✔
467
                                if !k8serrors.IsNotFound(err) {
1✔
468
                                        return errors.Wrap(err, "error deleting clone source pod")
×
469
                                }
×
UNCOV
470
                        }
×
471
                }
472
        }
473

474
        cc.RemoveFinalizer(pvc, cloneSourcePodFinalizer)
475

1✔
476
        return r.updatePVC(pvc)
1✔
477
}
1✔
478

479
// CreateCloneSourcePod creates our cloning src pod which will be used for out of band cloning to read the contents of the src PVC
480
func (r *CloneReconciler) CreateCloneSourcePod(image, pullPolicy string, pvc *corev1.PersistentVolumeClaim, log logr.Logger) (*corev1.Pod, error) {
481
        exists, _, _ := ParseCloneRequestAnnotation(pvc)
1✔
482
        if !exists {
1✔
483
                return nil, errors.Errorf("bad CloneRequest Annotation")
1✔
484
        }
×
UNCOV
485

×
486
        ownerKey, err := cache.MetaNamespaceKeyFunc(pvc)
487
        if err != nil {
1✔
488
                return nil, errors.Wrap(err, "error getting cache key")
1✔
489
        }
×
UNCOV
490

×
491
        serverCABundle, err := r.serverCAFetcher.BundleBytes()
492
        if err != nil {
1✔
493
                return nil, err
1✔
494
        }
×
UNCOV
495

×
496
        podResourceRequirements, err := cc.GetDefaultPodResourceRequirements(r.client)
497
        if err != nil {
1✔
498
                return nil, err
1✔
499
        }
×
UNCOV
500

×
501
        imagePullSecrets, err := cc.GetImagePullSecrets(r.client)
502
        if err != nil {
1✔
503
                return nil, err
1✔
504
        }
×
UNCOV
505

×
506
        workloadNodePlacement, err := cc.GetWorkloadNodePlacement(context.TODO(), r.client)
507
        if err != nil {
1✔
508
                return nil, err
1✔
509
        }
×
UNCOV
510

×
511
        sourcePvc, err := r.getCloneRequestSourcePVC(pvc)
512
        if err != nil {
1✔
513
                return nil, err
1✔
514
        }
×
UNCOV
515

×
516
        var sourceVolumeMode corev1.PersistentVolumeMode
517
        if sourcePvc.Spec.VolumeMode != nil {
1✔
518
                sourceVolumeMode = *sourcePvc.Spec.VolumeMode
2✔
519
        } else {
1✔
520
                sourceVolumeMode = corev1.PersistentVolumeFilesystem
2✔
521
        }
1✔
522

1✔
523
        pod := MakeCloneSourcePodSpec(sourceVolumeMode, image, pullPolicy, ownerKey, imagePullSecrets, serverCABundle, pvc, sourcePvc, podResourceRequirements, workloadNodePlacement)
524
        util.SetRecommendedLabels(pod, r.installerLabels, "cdi-controller")
1✔
525

1✔
526
        if err := r.client.Create(context.TODO(), pod); err != nil {
1✔
527
                return nil, errors.Wrap(err, "source pod API create errored")
1✔
528
        }
×
UNCOV
529

×
530
        log.V(1).Info("cloning source pod (image) created\n", "pod.Namespace", pod.Namespace, "pod.Name", pod.Name, "image", image)
531

1✔
532
        return pod, nil
1✔
533
}
1✔
534

535
// MakeCloneSourcePodSpec creates and returns the clone source pod spec based on the target pvc.
536
func MakeCloneSourcePodSpec(sourceVolumeMode corev1.PersistentVolumeMode, image, pullPolicy, ownerRefAnno string, imagePullSecrets []corev1.LocalObjectReference,
537
        serverCACert []byte, targetPvc, sourcePvc *corev1.PersistentVolumeClaim, resourceRequirements *corev1.ResourceRequirements,
538
        workloadNodePlacement *sdkapi.NodePlacement) *corev1.Pod {
539
        sourcePvcName := sourcePvc.GetName()
1✔
540
        sourcePvcNamespace := sourcePvc.GetNamespace()
1✔
541
        sourcePvcUID := string(sourcePvc.GetUID())
1✔
542

1✔
543
        var ownerID string
1✔
544
        cloneSourcePodName := targetPvc.Annotations[cc.AnnCloneSourcePod]
1✔
545
        url := GetUploadServerURL(targetPvc.Namespace, targetPvc.Name, common.UploadPathSync)
1✔
546
        pvcOwner := metav1.GetControllerOf(targetPvc)
1✔
547
        if pvcOwner != nil && pvcOwner.Kind == "DataVolume" {
1✔
548
                ownerID = string(pvcOwner.UID)
1✔
UNCOV
549
        } else {
×
550
                ouid, ok := targetPvc.Annotations[cc.AnnOwnerUID]
1✔
551
                if ok {
1✔
552
                        ownerID = ouid
1✔
553
                }
×
UNCOV
554
        }
×
555

556
        preallocationRequested := targetPvc.Annotations[cc.AnnPreallocationRequested]
557

1✔
558
        pod := &corev1.Pod{
1✔
559
                ObjectMeta: metav1.ObjectMeta{
1✔
560
                        Name:      cloneSourcePodName,
1✔
561
                        Namespace: sourcePvcNamespace,
1✔
562
                        Annotations: map[string]string{
1✔
563
                                cc.AnnCreatedBy: "yes",
1✔
564
                                AnnOwnerRef:     ownerRefAnno,
1✔
565
                        },
1✔
566
                        Labels: map[string]string{
1✔
567
                                common.CDILabelKey:       common.CDILabelValue, //filtered by the podInformer
1✔
568
                                common.CDIComponentLabel: common.ClonerSourcePodName,
1✔
569
                                // this label is used when searching for a pvc's cloner source pod.
1✔
570
                                cc.CloneUniqueID:          cloneSourcePodName,
1✔
571
                                common.PrometheusLabelKey: common.PrometheusLabelValue,
1✔
572
                                hostAssistedCloneSource:   sourcePvcUID,
1✔
573
                        },
1✔
574
                },
1✔
575
                Spec: corev1.PodSpec{
1✔
576
                        Containers: []corev1.Container{
1✔
577
                                {
1✔
578
                                        Name:            common.ClonerSourcePodName,
1✔
579
                                        Image:           image,
1✔
580
                                        ImagePullPolicy: corev1.PullPolicy(pullPolicy),
1✔
581
                                        Env: []corev1.EnvVar{
1✔
582
                                                {
1✔
583
                                                        Name: "CLIENT_KEY",
1✔
584
                                                        ValueFrom: &corev1.EnvVarSource{
1✔
585
                                                                SecretKeyRef: &corev1.SecretKeySelector{
1✔
586
                                                                        LocalObjectReference: corev1.LocalObjectReference{
1✔
587
                                                                                Name: cloneSourcePodName,
1✔
588
                                                                        },
1✔
589
                                                                        Key: "tls.key",
1✔
590
                                                                },
1✔
591
                                                        },
1✔
592
                                                },
1✔
593
                                                {
1✔
594
                                                        Name: "CLIENT_CERT",
1✔
595
                                                        ValueFrom: &corev1.EnvVarSource{
1✔
596
                                                                SecretKeyRef: &corev1.SecretKeySelector{
1✔
597
                                                                        LocalObjectReference: corev1.LocalObjectReference{
1✔
598
                                                                                Name: cloneSourcePodName,
1✔
599
                                                                        },
1✔
600
                                                                        Key: "tls.crt",
1✔
601
                                                                },
1✔
602
                                                        },
1✔
603
                                                },
1✔
604
                                                {
1✔
605
                                                        Name:  "SERVER_CA_CERT",
1✔
606
                                                        Value: string(serverCACert),
1✔
607
                                                },
1✔
608
                                                {
1✔
609
                                                        Name:  "UPLOAD_URL",
1✔
610
                                                        Value: url,
1✔
611
                                                },
1✔
612
                                                {
1✔
613
                                                        Name:  common.OwnerUID,
1✔
614
                                                        Value: ownerID,
1✔
615
                                                },
1✔
616
                                                {
1✔
617
                                                        Name:  common.Preallocation,
1✔
618
                                                        Value: preallocationRequested,
1✔
619
                                                },
1✔
620
                                        },
1✔
621
                                        Ports: []corev1.ContainerPort{
1✔
622
                                                {
1✔
623
                                                        Name:          "metrics",
1✔
624
                                                        ContainerPort: 8443,
1✔
625
                                                        Protocol:      corev1.ProtocolTCP,
1✔
626
                                                },
1✔
627
                                        },
1✔
628
                                        TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
1✔
629
                                },
1✔
630
                        },
1✔
631
                        ImagePullSecrets: imagePullSecrets,
1✔
632
                        RestartPolicy:    corev1.RestartPolicyOnFailure,
1✔
633
                        Volumes: []corev1.Volume{
1✔
634
                                {
1✔
635
                                        Name: cc.DataVolName,
1✔
636
                                        VolumeSource: corev1.VolumeSource{
1✔
637
                                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
1✔
638
                                                        ClaimName: sourcePvcName,
1✔
639
                                                },
1✔
640
                                        },
1✔
641
                                },
1✔
642
                        },
1✔
643
                        NodeSelector:      workloadNodePlacement.NodeSelector,
1✔
644
                        Tolerations:       workloadNodePlacement.Tolerations,
1✔
645
                        Affinity:          workloadNodePlacement.Affinity,
1✔
646
                        PriorityClassName: cc.GetPriorityClass(targetPvc),
1✔
647
                },
1✔
648
        }
1✔
649

1✔
650
        if pod.Spec.Affinity == nil {
1✔
651
                pod.Spec.Affinity = &corev1.Affinity{}
1✔
652
        }
1✔
653

1✔
654
        if pod.Spec.Affinity.PodAffinity == nil {
1✔
655
                pod.Spec.Affinity.PodAffinity = &corev1.PodAffinity{}
2✔
656
        }
1✔
657

1✔
658
        if len(sourcePvc.Spec.AccessModes) == 1 && sourcePvc.Spec.AccessModes[0] == corev1.ReadWriteOnce {
659
                pod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution = append(
2✔
660
                        pod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution,
1✔
661
                        corev1.PodAffinityTerm{
1✔
662
                                LabelSelector: &metav1.LabelSelector{
663
                                        MatchExpressions: []metav1.LabelSelectorRequirement{
1✔
664
                                                {
×
665
                                                        Key:      hostAssistedCloneSource,
×
666
                                                        Operator: metav1.LabelSelectorOpIn,
×
667
                                                        Values:   []string{sourcePvcUID},
×
668
                                                },
×
669
                                        },
×
670
                                },
×
671
                                Namespaces:  []string{sourcePvcNamespace},
×
672
                                TopologyKey: corev1.LabelHostname,
×
673
                        },
×
674
                )
×
675
        }
×
UNCOV
676

×
UNCOV
677
        pod.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append(
×
UNCOV
678
                pod.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution,
×
UNCOV
679
                corev1.WeightedPodAffinityTerm{
×
UNCOV
680
                        Weight: 100,
×
681
                        PodAffinityTerm: corev1.PodAffinityTerm{
682
                                LabelSelector: &metav1.LabelSelector{
1✔
683
                                        MatchExpressions: []metav1.LabelSelectorRequirement{
1✔
684
                                                {
1✔
685
                                                        Key:      common.UploadTargetLabel,
1✔
686
                                                        Operator: metav1.LabelSelectorOpIn,
1✔
687
                                                        Values:   []string{string(targetPvc.UID)},
1✔
688
                                                },
1✔
689
                                        },
1✔
690
                                },
1✔
691
                                Namespaces:  []string{targetPvc.Namespace},
1✔
692
                                TopologyKey: corev1.LabelHostname,
1✔
693
                        },
1✔
694
                },
1✔
695
        )
1✔
696

1✔
697
        if resourceRequirements != nil {
1✔
698
                pod.Spec.Containers[0].Resources = *resourceRequirements
1✔
699
        }
1✔
700

1✔
701
        var addVars []corev1.EnvVar
1✔
702

2✔
703
        if sourceVolumeMode == corev1.PersistentVolumeBlock {
1✔
704
                pod.Spec.Containers[0].VolumeDevices = cc.AddVolumeDevices()
1✔
705
                addVars = []corev1.EnvVar{
706
                        {
1✔
707
                                Name:  "VOLUME_MODE",
1✔
708
                                Value: "block",
2✔
709
                        },
1✔
710
                        {
1✔
711
                                Name:  "MOUNT_POINT",
1✔
712
                                Value: common.WriteBlockPath,
1✔
713
                        },
1✔
714
                }
1✔
715
        } else {
1✔
716
                pod.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{
1✔
717
                        {
1✔
718
                                Name:      cc.DataVolName,
1✔
719
                                MountPath: common.ClonerMountPath,
1✔
720
                                ReadOnly:  true,
2✔
721
                        },
1✔
722
                }
1✔
723
                addVars = []corev1.EnvVar{
1✔
724
                        {
1✔
725
                                Name:  "VOLUME_MODE",
1✔
726
                                Value: "filesystem",
1✔
727
                        },
1✔
728
                        {
1✔
729
                                Name:  "MOUNT_POINT",
1✔
730
                                Value: common.ClonerMountPath,
1✔
731
                        },
1✔
732
                }
1✔
733
        }
1✔
734

1✔
735
        pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, addVars...)
1✔
736
        cc.CopyAllowedAnnotations(targetPvc, pod)
1✔
737
        cc.SetRestrictedSecurityContext(&pod.Spec)
1✔
738
        return pod
1✔
739
}
740

1✔
741
// ParseCloneRequestAnnotation parses the clone request annotation
1✔
742
func ParseCloneRequestAnnotation(pvc *corev1.PersistentVolumeClaim) (exists bool, namespace, name string) {
1✔
743
        var ann string
1✔
744
        ann, exists = pvc.Annotations[cc.AnnCloneRequest]
745
        if !exists {
746
                return false, "", ""
747
        }
1✔
748

1✔
749
        sp := strings.Split(ann, "/")
1✔
750
        if len(sp) != 2 {
2✔
751
                return false, "", ""
1✔
752
        }
1✔
753

754
        return true, sp[0], sp[1]
1✔
755
}
2✔
756

1✔
757
// ValidateCanCloneSourceAndTargetContentType validates the pvcs passed has the same content type.
1✔
758
func ValidateCanCloneSourceAndTargetContentType(sourcePvc, targetPvc *corev1.PersistentVolumeClaim) (cdiv1.DataVolumeContentType, error) {
759
        sourceContentType := cc.GetPVCContentType(sourcePvc)
1✔
760
        targetContentType := cc.GetPVCContentType(targetPvc)
761
        if sourceContentType != targetContentType {
762
                return "", fmt.Errorf("source contentType (%s) and target contentType (%s) do not match", sourceContentType, targetContentType)
763
        }
1✔
764
        return sourceContentType, nil
1✔
765
}
1✔
766

2✔
767
// ValidateCanCloneSourceAndTargetSpec validates the specs passed in are compatible for cloning.
1✔
768
func ValidateCanCloneSourceAndTargetSpec(ctx context.Context, c client.Client, sourcePvc, targetPvc *corev1.PersistentVolumeClaim, contentType cdiv1.DataVolumeContentType) error {
1✔
769
        // This annotation is only needed for some specific cases, when the target size is actually calculated by
1✔
770
        // the size detection pod, and there are some differences in fs overhead between src and target volumes
771
        _, permissive := targetPvc.Annotations[cc.AnnPermissiveClone]
772

773
        // Allow different source and target volume modes only on KubeVirt content type
1✔
774
        sourceVolumeMode := util.ResolveVolumeMode(sourcePvc.Spec.VolumeMode)
1✔
775
        targetVolumeMode := util.ResolveVolumeMode(targetPvc.Spec.VolumeMode)
1✔
776
        if sourceVolumeMode != targetVolumeMode && contentType != cdiv1.DataVolumeKubeVirt {
1✔
777
                return fmt.Errorf("source volumeMode (%s) and target volumeMode (%s) do not match, contentType (%s)",
1✔
778
                        sourceVolumeMode, targetVolumeMode, contentType)
1✔
779
        }
1✔
780

1✔
781
        // TODO: use detection pod here, then permissive should not be needed
2✔
782
        sourceUsableSpace, err := getUsableSpace(ctx, c, sourcePvc)
1✔
783
        if err != nil {
1✔
784
                return err
1✔
785
        }
786
        targetUsableSpace, err := getUsableSpace(ctx, c, targetPvc)
787
        if err != nil {
1✔
788
                return err
1✔
789
        }
×
UNCOV
790

×
791
        if !permissive && sourceUsableSpace.Cmp(targetUsableSpace) > 0 {
1✔
792
                return errors.New("target resources requests storage size is smaller than the source")
1✔
793
        }
×
UNCOV
794

×
795
        // Can clone.
796
        return nil
1✔
UNCOV
797
}
×
UNCOV
798

×
799
func getUsableSpace(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim) (resource.Quantity, error) {
800
        sizeRequest := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
801
        volumeMode := util.ResolveVolumeMode(pvc.Spec.VolumeMode)
1✔
802

803
        if volumeMode == corev1.PersistentVolumeFilesystem {
804
                fsOverhead, err := cc.GetFilesystemOverheadForStorageClass(ctx, c, pvc.Spec.StorageClassName)
1✔
805
                if err != nil {
1✔
806
                        return resource.Quantity{}, err
1✔
807
                }
1✔
808
                fsOverheadFloat, _ := strconv.ParseFloat(string(fsOverhead), 64)
2✔
809
                usableSpaceRaw := util.GetUsableSpace(fsOverheadFloat, sizeRequest.Value())
1✔
810

1✔
UNCOV
811
                return *resource.NewScaledQuantity(usableSpaceRaw, 0), nil
×
UNCOV
812
        }
×
813

1✔
814
        return sizeRequest, nil
1✔
815
}
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

© 2026 Coveralls, Inc