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

kubevirt / containerized-data-importer / #5392

18 Jun 2025 09:22AM UTC coverage: 59.444% (+0.04%) from 59.409%
#5392

Pull #3793

travis-ci

jing2uo
Make error message in getDefaultVolumeAndAccessMode return more explicit.

Signed-off-by: jing guo <mail@guojing.io>
Pull Request #3793: Fix error message in datavolume-import-controller

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

585 existing lines in 8 files now uncovered.

16960 of 28531 relevant lines covered (59.44%)

0.66 hits per line

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

71.4
/pkg/controller/import-controller.go
1
package controller
2

3
import (
4
        "context"
5
        "fmt"
6
        "net/url"
7
        "path"
8
        "reflect"
9
        "strconv"
10
        "strings"
11
        "time"
12

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

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

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

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

42
const (
43
        // ErrImportFailedPVC provides a const to indicate an import to the PVC failed
44
        ErrImportFailedPVC = "ErrImportFailed"
45
        // ImportSucceededPVC provides a const to indicate an import to the PVC failed
46
        ImportSucceededPVC = "ImportSucceeded"
47

48
        // creatingScratch provides a const to indicate scratch is being created.
49
        creatingScratch = "CreatingScratchSpace"
50

51
        // ImportTargetInUse is reason for event created when an import pvc is in use
52
        ImportTargetInUse = "ImportTargetInUse"
53

54
        // importPodImageStreamFinalizer ensures image stream import pod is deleted when pvc is deleted,
55
        // as in this case pod has no pvc OwnerReference
56
        importPodImageStreamFinalizer = "cdi.kubevirt.io/importImageStream"
57

58
        // secretExtraHeadersVolumeName is the format string that specifies where extra HTTP header secrets will be mounted
59
        secretExtraHeadersVolumeName = "cdi-secret-extra-headers-vol-%d"
60
)
61

62
// ImportReconciler members
63
type ImportReconciler struct {
64
        client             client.Client
65
        uncachedClient     client.Client
66
        recorder           record.EventRecorder
67
        scheme             *runtime.Scheme
68
        log                logr.Logger
69
        image              string
70
        verbose            string
71
        pullPolicy         string
72
        filesystemOverhead string //nolint:unused // TODO: check if need to remove this field
73
        cdiNamespace       string
74
        featureGates       featuregates.FeatureGates
75
        installerLabels    map[string]string
76
}
77

78
type importPodEnvVar struct {
79
        ep                 string
80
        secretName         string
81
        source             string
82
        contentType        string
83
        imageSize          string
84
        certConfigMap      string
85
        diskID             string
86
        uuid               string
87
        pullMethod         string
88
        readyFile          string
89
        doneFile           string
90
        backingFile        string
91
        thumbprint         string
92
        filesystemOverhead string
93
        insecureTLS        bool
94
        currentCheckpoint  string
95
        previousCheckpoint string
96
        finalCheckpoint    string
97
        preallocation      bool
98
        httpProxy          string
99
        httpsProxy         string
100
        noProxy            string
101
        certConfigMapProxy string
102
        extraHeaders       []string
103
        secretExtraHeaders []string
104
        cacheMode          string
105
}
106

107
type importerPodArgs struct {
108
        image                   string
109
        importImage             string
110
        verbose                 string
111
        pullPolicy              string
112
        podEnvVar               *importPodEnvVar
113
        pvc                     *corev1.PersistentVolumeClaim
114
        scratchPvcName          *string
115
        podResourceRequirements *corev1.ResourceRequirements
116
        imagePullSecrets        []corev1.LocalObjectReference
117
        workloadNodePlacement   *sdkapi.NodePlacement
118
        vddkImageName           *string
119
        vddkExtraArgs           *string
120
        priorityClassName       string
121
}
122

123
// NewImportController creates a new instance of the import controller.
124
func NewImportController(mgr manager.Manager, log logr.Logger, importerImage, pullPolicy, verbose string, installerLabels map[string]string) (controller.Controller, error) {
125
        uncachedClient, err := client.New(mgr.GetConfig(), client.Options{
×
126
                Scheme: mgr.GetScheme(),
×
127
                Mapper: mgr.GetRESTMapper(),
×
128
        })
×
129
        if err != nil {
×
130
                return nil, err
×
131
        }
×
132
        client := mgr.GetClient()
×
133
        reconciler := &ImportReconciler{
×
134
                client:          client,
×
135
                uncachedClient:  uncachedClient,
×
136
                scheme:          mgr.GetScheme(),
×
137
                log:             log.WithName("import-controller"),
×
138
                image:           importerImage,
×
139
                verbose:         verbose,
×
140
                pullPolicy:      pullPolicy,
×
141
                recorder:        mgr.GetEventRecorderFor("import-controller"),
×
142
                cdiNamespace:    util.GetNamespace(),
×
143
                featureGates:    featuregates.NewFeatureGates(client),
×
144
                installerLabels: installerLabels,
×
145
        }
×
146
        importController, err := controller.New("import-controller", mgr, controller.Options{
×
147
                MaxConcurrentReconciles: 3,
×
148
                Reconciler:              reconciler,
×
149
        })
×
150
        if err != nil {
×
151
                return nil, err
×
152
        }
×
153
        if err := addImportControllerWatches(mgr, importController); err != nil {
×
154
                return nil, err
×
155
        }
×
156
        return importController, nil
×
UNCOV
157
}
×
158

159
func addImportControllerWatches(mgr manager.Manager, importController controller.Controller) error {
160
        // Setup watches
×
161
        if err := importController.Watch(source.Kind(mgr.GetCache(), &corev1.PersistentVolumeClaim{}, &handler.TypedEnqueueRequestForObject[*corev1.PersistentVolumeClaim]{})); err != nil {
×
162
                return err
×
163
        }
×
164
        if err := importController.Watch(source.Kind(mgr.GetCache(), &corev1.Pod{}, handler.TypedEnqueueRequestForOwner[*corev1.Pod](
×
165
                mgr.GetScheme(), mgr.GetClient().RESTMapper(), &corev1.PersistentVolumeClaim{}, handler.OnlyControllerOwner()))); err != nil {
×
166
                return err
×
167
        }
×
UNCOV
168

×
169
        return nil
UNCOV
170
}
×
171

172
func (r *ImportReconciler) shouldReconcilePVC(pvc *corev1.PersistentVolumeClaim,
173
        log logr.Logger) (bool, error) {
174
        _, pvcUsesExternalPopulator := pvc.Annotations[cc.AnnExternalPopulation]
1✔
175
        if pvcUsesExternalPopulator {
1✔
176
                return false, nil
1✔
177
        }
×
UNCOV
178

×
179
        waitForFirstConsumerEnabled, err := cc.IsWaitForFirstConsumerEnabled(pvc, r.featureGates)
180
        if err != nil {
1✔
181
                return false, err
1✔
182
        }
×
UNCOV
183

×
184
        return (!cc.IsPVCComplete(pvc) || cc.IsMultiStageImportInProgress(pvc)) &&
185
                        (checkPVC(pvc, cc.AnnEndpoint, log) || checkPVC(pvc, cc.AnnSource, log)) &&
1✔
186
                        shouldHandlePvc(pvc, waitForFirstConsumerEnabled, log),
1✔
187
                nil
1✔
188
}
1✔
189

190
// Reconcile the reconcile loop for the CDIConfig object.
191
func (r *ImportReconciler) Reconcile(_ context.Context, req reconcile.Request) (reconcile.Result, error) {
192
        log := r.log.WithValues("PVC", req.NamespacedName)
1✔
193
        log.V(1).Info("reconciling Import PVCs")
1✔
194

1✔
195
        // Get the PVC.
1✔
196
        pvc := &corev1.PersistentVolumeClaim{}
1✔
197
        if err := r.client.Get(context.TODO(), req.NamespacedName, pvc); err != nil {
1✔
198
                if k8serrors.IsNotFound(err) {
2✔
199
                        return reconcile.Result{}, nil
2✔
200
                }
1✔
201
                return reconcile.Result{}, err
1✔
UNCOV
202
        }
×
203

204
        shouldReconcile, err := r.shouldReconcilePVC(pvc, log)
205
        if err != nil {
1✔
206
                return reconcile.Result{}, err
1✔
207
        }
×
UNCOV
208
        if !shouldReconcile {
×
209
                multiStageImport := metav1.HasAnnotation(pvc.ObjectMeta, cc.AnnCurrentCheckpoint)
2✔
210
                multiStageAlreadyDone := metav1.HasAnnotation(pvc.ObjectMeta, cc.AnnMultiStageImportDone)
1✔
211

1✔
212
                log.V(3).Info("Should not reconcile this PVC",
1✔
213
                        "pvc.annotation.phase.complete", cc.IsPVCComplete(pvc),
1✔
214
                        "pvc.annotations.endpoint", checkPVC(pvc, cc.AnnEndpoint, log),
1✔
215
                        "pvc.annotations.source", checkPVC(pvc, cc.AnnSource, log),
1✔
216
                        "isBound", isBound(pvc, log), "isMultistage", multiStageImport, "multiStageDone", multiStageAlreadyDone)
1✔
217
                return reconcile.Result{}, nil
1✔
218
        }
1✔
219

1✔
220
        return r.reconcilePvc(pvc, log)
221
}
1✔
222

223
func (r *ImportReconciler) findImporterPod(pvc *corev1.PersistentVolumeClaim, log logr.Logger) (*corev1.Pod, error) {
224
        podName := getImportPodNameFromPvc(pvc)
1✔
225
        pod := &corev1.Pod{}
1✔
226
        if err := r.client.Get(context.TODO(), types.NamespacedName{Name: podName, Namespace: pvc.GetNamespace()}, pod); err != nil {
1✔
227
                if !k8serrors.IsNotFound(err) {
2✔
228
                        return nil, errors.Wrapf(err, "error getting import pod %s/%s", pvc.Namespace, podName)
1✔
229
                }
×
UNCOV
230
                return nil, nil
×
231
        }
1✔
232
        if !metav1.IsControlledBy(pod, pvc) && !cc.IsImageStream(pvc) {
233
                return nil, errors.Errorf("Pod is not owned by PVC")
2✔
234
        }
1✔
235
        log.V(1).Info("Pod is owned by PVC", pod.Name, pvc.Name)
1✔
236
        return pod, nil
1✔
237
}
1✔
238

239
func (r *ImportReconciler) reconcilePvc(pvc *corev1.PersistentVolumeClaim, log logr.Logger) (reconcile.Result, error) {
240
        // See if we have a pod associated with the PVC, we know the PVC has the needed annotations.
1✔
241
        pod, err := r.findImporterPod(pvc, log)
1✔
242
        if err != nil {
1✔
243
                return reconcile.Result{}, err
2✔
244
        }
1✔
245

1✔
246
        if pod == nil {
247
                if cc.IsPVCComplete(pvc) {
2✔
248
                        // Don't create the POD if the PVC is completed already
1✔
249
                        log.V(1).Info("PVC is already complete")
×
UNCOV
250
                } else if pvc.DeletionTimestamp == nil {
×
251
                        podsUsingPVC, err := cc.GetPodsUsingPVCs(context.TODO(), r.client, pvc.Namespace, sets.New(pvc.Name), false)
2✔
252
                        if err != nil {
1✔
253
                                return reconcile.Result{}, err
1✔
254
                        }
×
UNCOV
255

×
256
                        if len(podsUsingPVC) > 0 {
257
                                for _, pod := range podsUsingPVC {
2✔
258
                                        r.log.V(1).Info("can't create import pod, pvc in use by other pod",
2✔
259
                                                "namespace", pvc.Namespace, "name", pvc.Name, "pod", pod.Name)
1✔
260
                                        r.recorder.Eventf(pvc, corev1.EventTypeWarning, ImportTargetInUse,
1✔
261
                                                "pod %s/%s using PersistentVolumeClaim %s", pod.Namespace, pod.Name, pvc.Name)
1✔
262
                                }
1✔
263
                                return reconcile.Result{Requeue: true}, nil
1✔
264
                        }
1✔
265

266
                        if _, ok := pvc.Annotations[cc.AnnImportPod]; ok {
267
                                // Create importer pod, make sure the PVC owns it.
2✔
268
                                if err := r.createImporterPod(pvc); err != nil {
1✔
269
                                        return reconcile.Result{}, err
1✔
270
                                }
×
UNCOV
271
                        } else {
×
272
                                // Create importer pod Name and store in PVC?
1✔
273
                                if err := r.initPvcPodName(pvc, log); err != nil {
1✔
274
                                        return reconcile.Result{}, err
1✔
275
                                }
×
UNCOV
276
                        }
×
277
                }
278
        } else {
279
                if pvc.DeletionTimestamp != nil {
1✔
280
                        log.V(1).Info("PVC being terminated, delete pods", "pod.Name", pod.Name)
1✔
281
                        if err := r.cleanup(pvc, pod, log); err != nil {
×
282
                                return reconcile.Result{}, err
×
283
                        }
×
UNCOV
284
                } else {
×
285
                        // Copy import proxy ConfigMap (if exists) from cdi namespace to the import namespace
1✔
286
                        if err := r.copyImportProxyConfigMap(pvc, pod); err != nil {
1✔
287
                                return reconcile.Result{}, err
1✔
288
                        }
×
UNCOV
289
                        // Pod exists, we need to update the PVC status.
×
290
                        if err := r.updatePvcFromPod(pvc, pod, log); err != nil {
291
                                return reconcile.Result{}, err
1✔
292
                        }
×
UNCOV
293
                }
×
294
        }
295

296
        if !cc.IsPVCComplete(pvc) {
297
                // We are not done yet, force a re-reconcile in 2 seconds to get an update.
2✔
298
                log.V(1).Info("Force Reconcile pvc import not finished", "pvc.Name", pvc.Name)
1✔
299

1✔
300
                return reconcile.Result{RequeueAfter: 2 * time.Second}, nil
1✔
301
        }
1✔
302
        return reconcile.Result{}, nil
1✔
303
}
1✔
304

305
func (r *ImportReconciler) copyImportProxyConfigMap(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod) error {
306
        cdiConfig := &cdiv1.CDIConfig{}
1✔
307
        if err := r.client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiConfig); err != nil {
1✔
308
                return err
1✔
309
        }
×
UNCOV
310
        cmName, err := GetImportProxyConfig(cdiConfig, common.ImportProxyConfigMapName)
×
311
        if err != nil || cmName == "" {
1✔
312
                return nil
2✔
313
        }
1✔
314
        cdiConfigMap := &corev1.ConfigMap{}
1✔
315
        if err := r.uncachedClient.Get(context.TODO(), types.NamespacedName{Name: cmName, Namespace: r.cdiNamespace}, cdiConfigMap); err != nil {
×
316
                return err
×
317
        }
×
318
        importConfigMap := &corev1.ConfigMap{
×
319
                ObjectMeta: metav1.ObjectMeta{
×
320
                        Name:      GetImportProxyConfigMapName(pvc.Name),
×
321
                        Namespace: pvc.Namespace,
×
322
                        OwnerReferences: []metav1.OwnerReference{{
×
323
                                APIVersion:         pod.APIVersion,
×
324
                                Kind:               pod.Kind,
×
325
                                Name:               pod.Name,
×
326
                                UID:                pod.UID,
×
327
                                BlockOwnerDeletion: ptr.To[bool](true),
×
328
                                Controller:         ptr.To[bool](true),
×
329
                        }},
×
330
                },
×
331
                Data: cdiConfigMap.Data,
×
332
        }
×
333
        if err := r.client.Create(context.TODO(), importConfigMap); err != nil && !k8serrors.IsAlreadyExists(err) {
×
334
                return err
×
335
        }
×
336
        return nil
×
UNCOV
337
}
×
338

339
// GetImportProxyConfigMapName returns the import proxy ConfigMap name
340
func GetImportProxyConfigMapName(pvcName string) string {
341
        return naming.GetResourceName("import-proxy-cm", pvcName)
×
342
}
×
UNCOV
343

×
344
func (r *ImportReconciler) initPvcPodName(pvc *corev1.PersistentVolumeClaim, log logr.Logger) error {
345
        currentPvcCopy := pvc.DeepCopyObject()
1✔
346

1✔
347
        log.V(1).Info("Init pod name on PVC")
1✔
348
        anno := pvc.GetAnnotations()
1✔
349

1✔
350
        anno[cc.AnnImportPod] = createImportPodNameFromPvc(pvc)
1✔
351

1✔
352
        requiresScratch := r.requiresScratchSpace(pvc)
1✔
353
        if requiresScratch {
1✔
354
                anno[cc.AnnRequiresScratch] = "true"
1✔
355
        }
×
UNCOV
356

×
357
        if !reflect.DeepEqual(currentPvcCopy, pvc) {
358
                if err := r.updatePVC(pvc, log); err != nil {
2✔
359
                        return err
1✔
360
                }
×
UNCOV
361
                log.V(1).Info("Updated PVC", "pvc.anno.AnnImportPod", anno[cc.AnnImportPod])
×
362
        }
1✔
363
        return nil
364
}
1✔
365

366
func (r *ImportReconciler) updatePvcFromPod(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod, log logr.Logger) error {
367
        // Keep a copy of the original for comparison later.
1✔
368
        currentPvcCopy := pvc.DeepCopyObject()
1✔
369

1✔
370
        log.V(1).Info("Updating PVC from pod")
1✔
371
        anno := pvc.GetAnnotations()
1✔
372

1✔
373
        termMsg, err := parseTerminationMessage(pod)
1✔
374
        if err != nil {
1✔
375
                log.V(3).Info("Ignoring failure to parse termination message", "error", err.Error())
2✔
376
        }
1✔
377
        setAnnotationsFromPodWithPrefix(anno, pod, termMsg, cc.AnnRunningCondition)
1✔
378

1✔
379
        scratchSpaceRequired := termMsg != nil && termMsg.ScratchSpaceRequired != nil && *termMsg.ScratchSpaceRequired
1✔
380
        if scratchSpaceRequired {
1✔
381
                log.V(1).Info("Pod requires scratch space, terminating pod, and restarting with scratch space", "pod.Name", pod.Name)
2✔
382
        }
1✔
383
        podModificationsNeeded := scratchSpaceRequired
1✔
384

1✔
385
        if statuses := pod.Status.ContainerStatuses; len(statuses) > 0 {
1✔
386
                if isOOMKilled(statuses[0]) {
2✔
387
                        log.V(1).Info("Pod died of an OOM, deleting pod, and restarting with qemu cache mode=none if storage supports it", "pod.Name", pod.Name)
2✔
388
                        podModificationsNeeded = true
1✔
389
                        anno[cc.AnnRequiresDirectIO] = "true"
1✔
390
                }
1✔
391
                if terminated := statuses[0].State.Terminated; terminated != nil && terminated.ExitCode > 0 {
1✔
392
                        log.Info("Pod termination code", "pod.Name", pod.Name, "ExitCode", terminated.ExitCode)
2✔
393
                        r.recorder.Event(pvc, corev1.EventTypeWarning, ErrImportFailedPVC, terminated.Message)
1✔
394
                }
1✔
395
        }
1✔
396

397
        if anno[cc.AnnCurrentCheckpoint] != "" {
398
                anno[cc.AnnCurrentPodID] = string(pod.ObjectMeta.UID)
1✔
399
        }
×
UNCOV
400

×
401
        anno[cc.AnnImportPod] = pod.Name
402
        if !podModificationsNeeded {
1✔
403
                // No scratch space required, update the phase based on the pod. If we require scratch space we don't want to update the
2✔
404
                // phase, because the pod might terminate cleanly and mistakenly mark the import complete.
1✔
405
                anno[cc.AnnPodPhase] = string(pod.Status.Phase)
1✔
406
        }
1✔
407

1✔
408
        for _, ev := range pod.Spec.Containers[0].Env {
409
                if ev.Name == common.CacheMode && ev.Value == common.CacheModeTryNone {
1✔
410
                        anno[cc.AnnRequiresDirectIO] = "false"
2✔
411
                }
1✔
UNCOV
412
        }
×
UNCOV
413

×
UNCOV
414
        // Check if the POD is waiting for scratch space, if so create some.
×
415
        if pod.Status.Phase == corev1.PodPending && r.requiresScratchSpace(pvc) {
416
                if err := r.createScratchPvcForPod(pvc, pod); err != nil {
417
                        if !k8serrors.IsAlreadyExists(err) {
418
                                return err
419
                        }
2✔
420
                }
1✔
UNCOV
421
        } else {
×
UNCOV
422
                // No scratch space, or scratch space is bound, remove annotation
×
423
                delete(anno, cc.AnnBoundCondition)
424
                delete(anno, cc.AnnBoundConditionMessage)
425
                delete(anno, cc.AnnBoundConditionReason)
426
        }
2✔
427

1✔
UNCOV
428
        if pvc.GetLabels() == nil {
×
UNCOV
429
                pvc.SetLabels(make(map[string]string, 0))
×
UNCOV
430
        }
×
431
        if !checkIfLabelExists(pvc, common.CDILabelKey, common.CDILabelValue) {
432
                pvc.GetLabels()[common.CDILabelKey] = common.CDILabelValue
1✔
433
        }
1✔
434
        if cc.IsPVCComplete(pvc) {
1✔
435
                pvc.SetLabels(addLabelsFromTerminationMessage(pvc.GetLabels(), termMsg))
1✔
436
        }
1✔
437

1✔
438
        if !reflect.DeepEqual(currentPvcCopy, pvc) {
439
                if err := r.updatePVC(pvc, log); err != nil {
2✔
440
                        return err
1✔
441
                }
1✔
442
                log.V(1).Info("Updated PVC", "pvc.anno.Phase", anno[cc.AnnPodPhase], "pvc.anno.Restarts", anno[cc.AnnPodRestarts])
2✔
443
        }
1✔
444

1✔
445
        if cc.IsPVCComplete(pvc) || podModificationsNeeded {
2✔
446
                if !podModificationsNeeded {
1✔
447
                        r.recorder.Event(pvc, corev1.EventTypeNormal, ImportSucceededPVC, "Import Successful")
1✔
448
                        log.V(1).Info("Import completed successfully")
449
                }
2✔
450
                if cc.ShouldDeletePod(pvc) {
1✔
UNCOV
451
                        log.V(1).Info("Deleting pod", "pod.Name", pod.Name)
×
UNCOV
452
                        if err := r.cleanup(pvc, pod, log); err != nil {
×
453
                                return err
1✔
454
                        }
455
                }
456
        }
2✔
457
        return nil
2✔
458
}
1✔
459

1✔
460
func (r *ImportReconciler) cleanup(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod, log logr.Logger) error {
1✔
461
        if err := r.client.Delete(context.TODO(), pod); cc.IgnoreNotFound(err) != nil {
2✔
462
                return err
1✔
463
        }
1✔
UNCOV
464
        if cc.HasFinalizer(pvc, importPodImageStreamFinalizer) {
×
465
                cc.RemoveFinalizer(pvc, importPodImageStreamFinalizer)
×
466
                if err := r.updatePVC(pvc, log); err != nil {
467
                        return err
468
                }
1✔
469
        }
470
        return nil
471
}
1✔
472

1✔
UNCOV
473
func (r *ImportReconciler) updatePVC(pvc *corev1.PersistentVolumeClaim, log logr.Logger) error {
×
UNCOV
474
        if err := r.client.Update(context.TODO(), pvc); err != nil {
×
475
                return err
1✔
476
        }
×
UNCOV
477
        return nil
×
UNCOV
478
}
×
UNCOV
479

×
480
func (r *ImportReconciler) createImporterPod(pvc *corev1.PersistentVolumeClaim) error {
481
        r.log.V(1).Info("Creating importer POD for PVC", "pvc.Name", pvc.Name)
1✔
482
        var scratchPvcName *string
483
        var vddkImageName *string
484
        var vddkExtraArgs *string
1✔
485
        var err error
1✔
UNCOV
486

×
UNCOV
487
        requiresScratch := r.requiresScratchSpace(pvc)
×
488
        if requiresScratch {
1✔
489
                name := createScratchNameFromPvc(pvc)
490
                scratchPvcName = &name
491
        }
1✔
492

1✔
493
        if cc.GetSource(pvc) == cc.SourceVDDK {
1✔
494
                r.log.V(1).Info("Pod requires VDDK sidecar for VMware transfer")
1✔
495
                anno := pvc.GetAnnotations()
1✔
496
                if imageName, ok := anno[cc.AnnVddkInitImageURL]; ok {
1✔
497
                        vddkImageName = &imageName
1✔
498
                } else {
1✔
499
                        if vddkImageName, err = r.getVddkImageName(); err != nil {
1✔
UNCOV
500
                                r.log.V(1).Error(err, "failed to get VDDK image name from configmap")
×
UNCOV
501
                        }
×
UNCOV
502
                }
×
503
                if vddkImageName == nil {
504
                        message := fmt.Sprintf("waiting for %s configmap or %s annotation for VDDK image", common.VddkConfigMap, cc.AnnVddkInitImageURL)
2✔
505
                        anno[cc.AnnBoundCondition] = "false"
1✔
506
                        anno[cc.AnnBoundConditionMessage] = message
1✔
507
                        anno[cc.AnnBoundConditionReason] = common.AwaitingVDDK
2✔
508
                        if err := r.updatePVC(pvc, r.log); err != nil {
1✔
509
                                return err
2✔
510
                        }
2✔
511
                        return errors.New(message)
1✔
512
                }
1✔
513

514
                if extraArgs, ok := anno[cc.AnnVddkExtraArgs]; ok && extraArgs != "" {
2✔
515
                        r.log.V(1).Info("Mounting extra VDDK args ConfigMap to importer pod", "ConfigMap", extraArgs)
1✔
516
                        vddkExtraArgs = &extraArgs
1✔
517
                }
1✔
518
        }
1✔
519

1✔
UNCOV
520
        podEnvVar, err := r.createImportEnvVar(pvc)
×
UNCOV
521
        if err != nil {
×
522
                return err
1✔
523
        }
524
        // all checks passed, let's create the importer pod!
525
        podArgs := &importerPodArgs{
2✔
526
                image:             r.image,
1✔
527
                verbose:           r.verbose,
1✔
528
                pullPolicy:        r.pullPolicy,
1✔
529
                podEnvVar:         podEnvVar,
530
                pvc:               pvc,
531
                scratchPvcName:    scratchPvcName,
1✔
532
                vddkImageName:     vddkImageName,
1✔
UNCOV
533
                vddkExtraArgs:     vddkExtraArgs,
×
UNCOV
534
                priorityClassName: cc.GetPriorityClass(pvc),
×
535
        }
536

1✔
537
        pod, err := createImporterPod(context.TODO(), r.log, r.client, podArgs, r.installerLabels)
1✔
538
        // Check if pod has failed and, in that case, record an event with the error
1✔
539
        if podErr := cc.HandleFailedPod(err, pvc.Annotations[cc.AnnImportPod], pvc, r.recorder, r.client); podErr != nil {
1✔
540
                return podErr
1✔
541
        }
1✔
542

1✔
543
        r.log.V(1).Info("Created POD", "pod.Name", pod.Name)
1✔
544

1✔
545
        // If importing from image stream, add finalizer. Note we don't watch the importer pod in this case,
1✔
546
        // so to prevent a deadlock we add finalizer only if the pod is not retained after completion.
1✔
547
        if cc.IsImageStream(pvc) && pvc.GetAnnotations()[cc.AnnPodRetainAfterCompletion] != "true" {
1✔
548
                cc.AddFinalizer(pvc, importPodImageStreamFinalizer)
1✔
549
                if err := r.updatePVC(pvc, r.log); err != nil {
1✔
550
                        return err
1✔
551
                }
×
UNCOV
552
        }
×
553

554
        if requiresScratch {
1✔
555
                r.log.V(1).Info("Pod requires scratch space")
1✔
556
                return r.createScratchPvcForPod(pvc, pod)
1✔
557
        }
1✔
558

1✔
UNCOV
559
        return nil
×
UNCOV
560
}
×
UNCOV
561

×
562
func createScratchNameFromPvc(pvc *v1.PersistentVolumeClaim) string {
×
563
        return naming.GetResourceName(pvc.Name, common.ScratchNameSuffix)
564
}
565

1✔
UNCOV
566
func (r *ImportReconciler) createImportEnvVar(pvc *corev1.PersistentVolumeClaim) (*importPodEnvVar, error) {
×
UNCOV
567
        podEnvVar := &importPodEnvVar{}
×
UNCOV
568
        podEnvVar.source = cc.GetSource(pvc)
×
569
        podEnvVar.contentType = string(cc.GetPVCContentType(pvc))
570

1✔
571
        var err error
572
        if podEnvVar.source != cc.SourceNone {
UNCOV
573
                podEnvVar.ep, err = cc.GetEndpoint(pvc)
×
UNCOV
574
                if err != nil {
×
575
                        return nil, err
×
576
                }
577
                podEnvVar.secretName = r.getSecretName(pvc)
1✔
578
                if podEnvVar.secretName == "" {
1✔
579
                        r.log.V(2).Info("no secret will be supplied to endpoint", "endPoint", podEnvVar.ep)
1✔
580
                }
1✔
581
                //get the CDIConfig to extract the proxy configuration to be used to import an image
1✔
582
                cdiConfig := &cdiv1.CDIConfig{}
1✔
583
                err = r.client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiConfig)
2✔
584
                if err != nil {
1✔
585
                        return nil, err
1✔
586
                }
×
UNCOV
587
                podEnvVar.certConfigMap, err = r.getCertConfigMap(pvc)
×
588
                if err != nil {
1✔
589
                        return nil, err
2✔
590
                }
1✔
591
                podEnvVar.insecureTLS, err = r.isInsecureTLS(pvc, cdiConfig)
1✔
592
                if err != nil {
593
                        return nil, err
1✔
594
                }
1✔
595
                podEnvVar.diskID = getValueFromAnnotation(pvc, cc.AnnDiskID)
1✔
UNCOV
596
                podEnvVar.backingFile = getValueFromAnnotation(pvc, cc.AnnBackingFile)
×
UNCOV
597
                podEnvVar.uuid = getValueFromAnnotation(pvc, cc.AnnUUID)
×
598
                podEnvVar.thumbprint = getValueFromAnnotation(pvc, cc.AnnThumbprint)
1✔
599
                podEnvVar.previousCheckpoint = getValueFromAnnotation(pvc, cc.AnnPreviousCheckpoint)
1✔
UNCOV
600
                podEnvVar.currentCheckpoint = getValueFromAnnotation(pvc, cc.AnnCurrentCheckpoint)
×
UNCOV
601
                podEnvVar.finalCheckpoint = getValueFromAnnotation(pvc, cc.AnnFinalCheckpoint)
×
602

1✔
603
                for annotation, value := range pvc.Annotations {
1✔
UNCOV
604
                        if strings.HasPrefix(annotation, cc.AnnExtraHeaders) {
×
605
                                podEnvVar.extraHeaders = append(podEnvVar.extraHeaders, value)
×
606
                        }
1✔
607
                        if strings.HasPrefix(annotation, cc.AnnSecretExtraHeaders) {
1✔
608
                                podEnvVar.secretExtraHeaders = append(podEnvVar.secretExtraHeaders, value)
1✔
609
                        }
1✔
610
                }
1✔
611

1✔
612
                var field string
1✔
613
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyHTTP); err != nil {
1✔
614
                        r.log.V(3).Info("no proxy http url will be supplied:", "error", err.Error())
1✔
615
                }
2✔
616
                podEnvVar.httpProxy = field
1✔
UNCOV
617
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyHTTPS); err != nil {
×
UNCOV
618
                        r.log.V(3).Info("no proxy https url will be supplied:", "error", err.Error())
×
619
                }
1✔
UNCOV
620
                podEnvVar.httpsProxy = field
×
UNCOV
621
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyNoProxy); err != nil {
×
622
                        r.log.V(3).Info("the noProxy field will not be supplied:", "error", err.Error())
623
                }
624
                podEnvVar.noProxy = field
1✔
625
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyConfigMapName); err != nil {
2✔
626
                        r.log.V(3).Info("no proxy CA certiticate will be supplied:", "error", err.Error())
1✔
627
                }
1✔
628
                podEnvVar.certConfigMapProxy = field
1✔
629
        }
2✔
630

1✔
631
        fsOverhead, err := GetFilesystemOverhead(context.TODO(), r.client, pvc)
1✔
632
        if err != nil {
1✔
633
                return nil, err
2✔
634
        }
1✔
635
        podEnvVar.filesystemOverhead = string(fsOverhead)
1✔
636

1✔
637
        if preallocation, err := strconv.ParseBool(getValueFromAnnotation(pvc, cc.AnnPreallocationRequested)); err == nil {
2✔
638
                podEnvVar.preallocation = preallocation
1✔
639
        } // else use the default "false"
1✔
640

1✔
641
        //get the requested image size.
642
        podEnvVar.imageSize, err = cc.GetRequestedImageSize(pvc)
643
        if err != nil {
1✔
644
                return nil, err
1✔
645
        }
×
UNCOV
646

×
647
        if v, ok := pvc.Annotations[cc.AnnRequiresDirectIO]; ok && v == "true" {
1✔
648
                podEnvVar.cacheMode = common.CacheModeTryNone
1✔
649
        }
1✔
UNCOV
650

×
UNCOV
651
        return podEnvVar, nil
×
652
}
653

654
func (r *ImportReconciler) isInsecureTLS(pvc *corev1.PersistentVolumeClaim, cdiConfig *cdiv1.CDIConfig) (bool, error) {
1✔
655
        ep, ok := pvc.Annotations[cc.AnnEndpoint]
1✔
UNCOV
656
        if !ok || ep == "" {
×
UNCOV
657
                return false, nil
×
658
        }
659
        return IsInsecureTLS(ep, cdiConfig, r.log)
2✔
660
}
1✔
661

1✔
662
// IsInsecureTLS checks if TLS security is disabled for the given endpoint
663
func IsInsecureTLS(ep string, cdiConfig *cdiv1.CDIConfig, log logr.Logger) (bool, error) {
1✔
664
        url, err := url.Parse(ep)
665
        if err != nil {
666
                return false, err
1✔
667
        }
1✔
668

2✔
669
        if url.Scheme != "docker" {
1✔
670
                return false, nil
1✔
671
        }
1✔
672

673
        for _, value := range cdiConfig.Spec.InsecureRegistries {
674
                log.V(1).Info("Checking host against value", "host", url.Host, "value", value)
675
                if value == url.Host {
1✔
676
                        return true, nil
1✔
677
                }
1✔
UNCOV
678
        }
×
UNCOV
679
        return false, nil
×
680
}
681

2✔
682
func (r *ImportReconciler) getCertConfigMap(pvc *corev1.PersistentVolumeClaim) (string, error) {
1✔
683
        value, ok := pvc.Annotations[cc.AnnCertConfigMap]
1✔
684
        if !ok || value == "" {
685
                return "", nil
2✔
686
        }
1✔
687

2✔
688
        configMap := &corev1.ConfigMap{}
1✔
689
        if err := r.uncachedClient.Get(context.TODO(), types.NamespacedName{Name: value, Namespace: pvc.Namespace}, configMap); err != nil {
1✔
690
                if k8serrors.IsNotFound(err) {
691
                        r.log.V(1).Info("Configmap does not exist, pod will not start until it does", "configMapName", value)
1✔
692
                        return value, nil
693
                }
694

1✔
695
                return "", err
1✔
696
        }
2✔
697

1✔
698
        return value, nil
1✔
699
}
700

1✔
701
// returns the name of the secret containing endpoint credentials consumed by the importer pod.
2✔
702
// A value of "" implies there are no credentials for the endpoint being used. A returned error
2✔
703
// causes processNextItem() to stop.
1✔
704
func (r *ImportReconciler) getSecretName(pvc *corev1.PersistentVolumeClaim) string {
1✔
705
        ns := pvc.Namespace
1✔
706
        name, found := pvc.Annotations[cc.AnnSecret]
UNCOV
707
        if !found || name == "" {
×
708
                msg := "getEndpointSecret: "
709
                if !found {
710
                        msg += fmt.Sprintf("annotation %q is missing in pvc \"%s/%s\"", cc.AnnSecret, ns, pvc.Name)
1✔
711
                } else {
712
                        msg += fmt.Sprintf("secret name is missing from annotation %q in pvc \"%s/%s\"", cc.AnnSecret, ns, pvc.Name)
713
                }
714
                r.log.V(2).Info(msg)
715
                return "" // importer pod will not contain secret credentials
716
        }
1✔
717
        return name
1✔
718
}
1✔
719

2✔
720
func (r *ImportReconciler) requiresScratchSpace(pvc *corev1.PersistentVolumeClaim) bool {
1✔
721
        scratchRequired := false
2✔
722
        contentType := cc.GetPVCContentType(pvc)
1✔
723
        // All archive requires scratch space.
1✔
UNCOV
724
        if contentType == cdiv1.DataVolumeArchive {
×
725
                scratchRequired = true
×
726
        } else {
1✔
727
                switch cc.GetSource(pvc) {
1✔
728
                case cc.SourceGlance:
729
                        scratchRequired = true
1✔
730
                case cc.SourceImageio:
731
                        if val, ok := pvc.Annotations[cc.AnnCurrentCheckpoint]; ok {
732
                                scratchRequired = val != ""
1✔
733
                        }
1✔
734
                case cc.SourceRegistry:
1✔
735
                        scratchRequired = pvc.Annotations[cc.AnnRegistryImportMethod] != string(cdiv1.RegistryPullNode)
1✔
736
                }
1✔
UNCOV
737
        }
×
738
        value, ok := pvc.Annotations[cc.AnnRequiresScratch]
1✔
739
        if ok {
1✔
UNCOV
740
                boolVal, _ := strconv.ParseBool(value)
×
UNCOV
741
                scratchRequired = scratchRequired || boolVal
×
UNCOV
742
        }
×
UNCOV
743
        return scratchRequired
×
UNCOV
744
}
×
UNCOV
745

×
746
func (r *ImportReconciler) createScratchPvcForPod(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod) error {
1✔
747
        scratchPvc := &corev1.PersistentVolumeClaim{}
1✔
748
        scratchPVCName, exists := getScratchNameFromPod(pod)
749
        if !exists {
750
                return errors.New("Scratch Volume not configured for pod")
1✔
751
        }
2✔
752
        anno := pvc.GetAnnotations()
1✔
753
        err := r.client.Get(context.TODO(), types.NamespacedName{Namespace: pvc.GetNamespace(), Name: scratchPVCName}, scratchPvc)
1✔
754
        if cc.IgnoreNotFound(err) != nil {
1✔
755
                return err
1✔
756
        }
757
        if k8serrors.IsNotFound(err) {
758
                r.log.V(1).Info("Creating scratch space for POD and PVC", "pod.Name", pod.Name, "pvc.Name", pvc.Name)
1✔
759

1✔
760
                storageClassName := GetScratchPvcStorageClass(r.client, pvc)
1✔
761
                // Scratch PVC doesn't exist yet, create it. Determine which storage class to use.
1✔
UNCOV
762
                _, err = createScratchPersistentVolumeClaim(r.client, pvc, pod, scratchPVCName, storageClassName, r.installerLabels, r.recorder)
×
UNCOV
763
                if err != nil {
×
764
                        return err
1✔
765
                }
1✔
766
                anno[cc.AnnBoundCondition] = "false"
1✔
UNCOV
767
                anno[cc.AnnBoundConditionMessage] = "Creating scratch space"
×
UNCOV
768
                anno[cc.AnnBoundConditionReason] = creatingScratch
×
769
        } else {
2✔
770
                if scratchPvc.DeletionTimestamp != nil {
1✔
771
                        // Delete the pod since we are in a deadlock situation now. The scratch PVC from the previous import is not gone
1✔
772
                        // yet but terminating, and the new pod is still being created and the scratch PVC now has a finalizer on it.
1✔
773
                        // Only way to break it, is to delete the importer pod, and give the pvc a chance to disappear.
1✔
774
                        err = r.client.Delete(context.TODO(), pod)
1✔
775
                        if err != nil {
1✔
776
                                return err
×
777
                        }
×
778
                        return fmt.Errorf("terminating scratch space found, deleting pod %s", pod.Name)
1✔
779
                }
1✔
780
                setBoundConditionFromPVC(anno, cc.AnnBoundCondition, scratchPvc)
1✔
UNCOV
781
        }
×
UNCOV
782
        anno[cc.AnnRequiresScratch] = "false"
×
UNCOV
783
        return nil
×
UNCOV
784
}
×
UNCOV
785

×
UNCOV
786
// Get path to VDDK image from 'v2v-vmware' ConfigMap
×
UNCOV
787
func (r *ImportReconciler) getVddkImageName() (*string, error) {
×
UNCOV
788
        namespace := util.GetNamespace()
×
UNCOV
789

×
UNCOV
790
        cm := &corev1.ConfigMap{}
×
791
        err := r.uncachedClient.Get(context.TODO(), types.NamespacedName{Name: common.VddkConfigMap, Namespace: namespace}, cm)
UNCOV
792
        if k8serrors.IsNotFound(err) {
×
793
                return nil, errors.Errorf("No %s ConfigMap present in namespace %s", common.VddkConfigMap, namespace)
794
        }
1✔
795

1✔
796
        image, found := cm.Data[common.VddkConfigDataKey]
797
        if found {
798
                msg := fmt.Sprintf("Found %s ConfigMap in namespace %s, VDDK image path is: ", common.VddkConfigMap, namespace)
799
                r.log.V(1).Info(msg, common.VddkConfigDataKey, image)
1✔
800
                return &image, nil
1✔
801
        }
1✔
802

1✔
803
        return nil, errors.Errorf("found %s ConfigMap in namespace %s, but it does not contain a '%s' entry", common.VddkConfigMap, namespace, common.VddkConfigDataKey)
1✔
804
}
2✔
805

1✔
806
// returns the import image part of the endpoint string
1✔
807
func getRegistryImportImage(pvc *corev1.PersistentVolumeClaim) (string, error) {
808
        ep, err := cc.GetEndpoint(pvc)
1✔
809
        if err != nil {
2✔
810
                return "", nil
1✔
811
        }
1✔
812
        if cc.IsImageStream(pvc) {
1✔
813
                return ep, nil
1✔
814
        }
UNCOV
815
        url, err := url.Parse(ep)
×
816
        if err != nil {
817
                return "", errors.Errorf("illegal registry endpoint %s", ep)
818
        }
819
        return url.Host + url.Path, nil
1✔
820
}
1✔
821

1✔
UNCOV
822
// getValueFromAnnotation returns the value of an annotation
×
UNCOV
823
func getValueFromAnnotation(pvc *corev1.PersistentVolumeClaim, annotation string) string {
×
824
        return pvc.Annotations[annotation]
1✔
UNCOV
825
}
×
UNCOV
826

×
827
// If this pod is going to transfer one checkpoint in a multi-stage import, attach the checkpoint name to the pod name so
1✔
828
// that each checkpoint gets a unique pod. That way each pod can be inspected using the retainAfterCompletion annotation.
1✔
UNCOV
829
func podNameWithCheckpoint(pvc *corev1.PersistentVolumeClaim) string {
×
UNCOV
830
        if checkpoint := pvc.Annotations[cc.AnnCurrentCheckpoint]; checkpoint != "" {
×
831
                return pvc.Name + "-checkpoint-" + checkpoint
1✔
832
        }
833
        return pvc.Name
834
}
835

1✔
836
func getImportPodNameFromPvc(pvc *corev1.PersistentVolumeClaim) string {
1✔
837
        podName, ok := pvc.Annotations[cc.AnnImportPod]
1✔
838
        if ok {
839
                return podName
840
        }
841
        // fallback to legacy naming, in fact the following function is fully compatible with legacy
1✔
842
        // name concatenation "importer-{pvc.Name}" if the name length is under the size limits,
2✔
843
        return naming.GetResourceName(common.ImporterPodName, podNameWithCheckpoint(pvc))
1✔
844
}
1✔
845

1✔
846
func createImportPodNameFromPvc(pvc *corev1.PersistentVolumeClaim) string {
847
        return naming.GetResourceName(common.ImporterPodName, podNameWithCheckpoint(pvc))
848
}
1✔
849

1✔
850
// createImporterPod creates and returns a pointer to a pod which is created based on the passed-in endpoint, secret
2✔
851
// name, and pvc. A nil secret means the endpoint credentials are not passed to the
1✔
852
// importer pod.
1✔
853
func createImporterPod(ctx context.Context, log logr.Logger, client client.Client, args *importerPodArgs, installerLabels map[string]string) (*corev1.Pod, error) {
854
        var err error
855
        args.podResourceRequirements, err = cc.GetDefaultPodResourceRequirements(client)
1✔
856
        if err != nil {
857
                return nil, err
858
        }
1✔
859

1✔
860
        args.imagePullSecrets, err = cc.GetImagePullSecrets(client)
1✔
861
        if err != nil {
862
                return nil, err
863
        }
864

865
        args.workloadNodePlacement, err = cc.GetWorkloadNodePlacement(ctx, client)
1✔
866
        if err != nil {
1✔
867
                return nil, err
1✔
868
        }
1✔
UNCOV
869

×
UNCOV
870
        if isRegistryNodeImport(args) {
×
871
                args.importImage, err = getRegistryImportImage(args.pvc)
872
                if err != nil {
1✔
873
                        return nil, err
1✔
874
                }
×
UNCOV
875
                setRegistryNodeImportEnvVars(args)
×
876
        }
877

1✔
878
        pod := makeImporterPodSpec(args)
1✔
UNCOV
879

×
UNCOV
880
        util.SetRecommendedLabels(pod, installerLabels, "cdi-controller")
×
881

882
        // add any labels from pvc to the importer pod
2✔
883
        util.MergeLabels(args.pvc.Labels, pod.Labels)
1✔
884

1✔
UNCOV
885
        if err = client.Create(context.TODO(), pod); err != nil {
×
886
                return nil, err
×
887
        }
1✔
888

1✔
UNCOV
889
        log.V(3).Info("importer pod created\n", "pod.Name", pod.Name, "pod.Namespace", pod.Namespace, "image name", args.image)
×
UNCOV
890
        return pod, nil
×
891
}
892

893
// makeImporterPodSpec creates and return the importer pod spec based on the passed-in endpoint, secret and pvc.
1✔
894
func makeImporterPodSpec(args *importerPodArgs) *corev1.Pod {
1✔
895
        // importer pod name contains the pvc name
1✔
896
        podName := args.pvc.Annotations[cc.AnnImportPod]
1✔
897

1✔
898
        pod := &corev1.Pod{
1✔
899
                TypeMeta: metav1.TypeMeta{
1✔
900
                        Kind:       "Pod",
1✔
UNCOV
901
                        APIVersion: "v1",
×
UNCOV
902
                },
×
903
                ObjectMeta: metav1.ObjectMeta{
904
                        Name:      podName,
1✔
905
                        Namespace: args.pvc.Namespace,
1✔
906
                        Annotations: map[string]string{
907
                                cc.AnnCreatedBy: "yes",
908
                        },
909
                        Labels: map[string]string{
1✔
910
                                common.CDILabelKey:        common.CDILabelValue,
1✔
911
                                common.CDIComponentLabel:  common.ImporterPodName,
1✔
912
                                common.PrometheusLabelKey: common.PrometheusLabelValue,
1✔
913
                        },
1✔
914
                        OwnerReferences: []metav1.OwnerReference{
1✔
915
                                {
1✔
916
                                        APIVersion:         "v1",
1✔
917
                                        Kind:               "PersistentVolumeClaim",
1✔
918
                                        Name:               args.pvc.Name,
1✔
919
                                        UID:                args.pvc.GetUID(),
1✔
920
                                        BlockOwnerDeletion: ptr.To[bool](true),
1✔
921
                                        Controller:         ptr.To[bool](true),
1✔
922
                                },
1✔
923
                        },
1✔
924
                },
1✔
925
                Spec: corev1.PodSpec{
1✔
926
                        Containers:        makeImporterContainerSpec(args),
1✔
927
                        InitContainers:    makeImporterInitContainersSpec(args),
1✔
928
                        Volumes:           makeImporterVolumeSpec(args),
1✔
929
                        RestartPolicy:     corev1.RestartPolicyOnFailure,
1✔
930
                        NodeSelector:      args.workloadNodePlacement.NodeSelector,
1✔
931
                        Tolerations:       args.workloadNodePlacement.Tolerations,
1✔
932
                        Affinity:          args.workloadNodePlacement.Affinity,
1✔
933
                        PriorityClassName: args.priorityClassName,
1✔
934
                        ImagePullSecrets:  args.imagePullSecrets,
1✔
935
                },
1✔
936
        }
1✔
937

1✔
938
        /**
1✔
939
        FIXME: When registry source is ImageStream, if we set importer pod OwnerReference (to its pvc, like all other cases),
1✔
940
        for some reason (OCP issue?) we get the following error:
1✔
941
                Failed to pull image "imagestream-name": rpc error: code = Unknown
1✔
942
                desc = Error reading manifest latest in docker.io/library/imagestream-name: errors:
1✔
943
                denied: requested access to the resource is denied
1✔
944
                unauthorized: authentication required
1✔
945
        When we don't set pod OwnerReferences, all works well.
1✔
946
        */
1✔
947
        if isRegistryNodeImport(args) && cc.IsImageStream(args.pvc) {
1✔
948
                pod.OwnerReferences = nil
1✔
949
                pod.Annotations[cc.AnnOpenShiftImageLookup] = "*"
1✔
950
        }
1✔
951

1✔
952
        cc.CopyAllowedAnnotations(args.pvc, pod)
1✔
953
        cc.SetRestrictedSecurityContext(&pod.Spec)
1✔
954
        // We explicitly define a NodeName for dynamically provisioned PVCs
1✔
955
        // when the PVC is being handled by a populator (PVC')
1✔
956
        cc.SetNodeNameIfPopulator(args.pvc, &pod.Spec)
1✔
957

1✔
958
        return pod
1✔
959
}
1✔
960

1✔
961
func makeImporterContainerSpec(args *importerPodArgs) []corev1.Container {
1✔
962
        containers := []corev1.Container{
1✔
UNCOV
963
                {
×
UNCOV
964
                        Name:            common.ImporterPodName,
×
UNCOV
965
                        Image:           args.image,
×
966
                        ImagePullPolicy: corev1.PullPolicy(args.pullPolicy),
967
                        Args:            []string{"-v=" + args.verbose},
1✔
968
                        Env:             makeImportEnv(args.podEnvVar, getOwnerUID(args)),
1✔
969
                        Ports: []corev1.ContainerPort{
1✔
970
                                {
1✔
971
                                        Name:          "metrics",
1✔
972
                                        ContainerPort: 8443,
1✔
973
                                        Protocol:      corev1.ProtocolTCP,
1✔
974
                                },
975
                        },
976
                        TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
1✔
977
                },
1✔
978
        }
1✔
979
        if cc.GetVolumeMode(args.pvc) == corev1.PersistentVolumeBlock {
1✔
980
                containers[0].VolumeDevices = cc.AddVolumeDevices()
1✔
981
        } else {
1✔
982
                containers[0].VolumeMounts = cc.AddImportVolumeMounts()
1✔
983
        }
1✔
984
        if isRegistryNodeImport(args) {
1✔
985
                containers = append(containers, corev1.Container{
1✔
986
                        Name:            "server",
1✔
987
                        Image:           args.importImage,
1✔
988
                        ImagePullPolicy: corev1.PullPolicy(args.pullPolicy),
1✔
989
                        Command:         []string{"/shared/server", "-p", "8100", "-image-dir", "/disk", "-ready-file", "/shared/ready", "-done-file", "/shared/done"},
1✔
990
                        VolumeMounts: []corev1.VolumeMount{
1✔
991
                                {
1✔
992
                                        MountPath: "/shared",
1✔
993
                                        Name:      "shared-volume",
1✔
994
                                },
2✔
995
                        },
1✔
996
                        TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
2✔
997
                })
1✔
998
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
1✔
999
                        MountPath: "/shared",
2✔
1000
                        Name:      "shared-volume",
1✔
1001
                })
1✔
1002
        }
1✔
1003
        if args.scratchPvcName != nil {
1✔
1004
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
1✔
1005
                        Name:      cc.ScratchVolName,
1✔
1006
                        MountPath: common.ScratchDataDir,
1✔
1007
                })
1✔
1008
        }
1✔
1009
        if args.vddkImageName != nil {
1✔
1010
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
1✔
1011
                        Name:      "vddk-vol-mount",
1✔
1012
                        MountPath: "/opt",
1✔
1013
                })
1✔
1014
        }
1✔
1015
        if args.vddkExtraArgs != nil {
1✔
1016
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
1✔
1017
                        Name:      common.VddkArgsVolName,
1✔
1018
                        MountPath: common.VddkArgsDir,
2✔
1019
                })
1✔
1020
        }
1✔
1021
        if args.podEnvVar.certConfigMap != "" {
1✔
1022
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
1✔
1023
                        Name:      CertVolName,
1✔
1024
                        MountPath: common.ImporterCertDir,
2✔
1025
                })
1✔
1026
        }
1✔
1027
        if args.podEnvVar.certConfigMapProxy != "" {
1✔
1028
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
1✔
1029
                        Name:      ProxyCertVolName,
1✔
1030
                        MountPath: common.ImporterProxyCertDir,
2✔
1031
                })
1✔
1032
        }
1✔
1033
        if args.podEnvVar.source == cc.SourceGCS && args.podEnvVar.secretName != "" {
1✔
1034
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
1✔
1035
                        Name:      SecretVolName,
1✔
1036
                        MountPath: common.ImporterGoogleCredentialDir,
1✔
1037
                })
×
1038
        }
×
UNCOV
1039
        for index := range args.podEnvVar.secretExtraHeaders {
×
1040
                containers[0].VolumeMounts = append(containers[0].VolumeMounts, corev1.VolumeMount{
×
1041
                        Name:      fmt.Sprintf(secretExtraHeadersVolumeName, index),
×
1042
                        MountPath: path.Join(common.ImporterSecretExtraHeadersDir, fmt.Sprint(index)),
1✔
1043
                })
×
1044
        }
×
UNCOV
1045
        if args.podResourceRequirements != nil {
×
1046
                for i := range containers {
×
1047
                        containers[i].Resources = *args.podResourceRequirements
×
1048
                }
1✔
UNCOV
1049
        }
×
UNCOV
1050
        return containers
×
UNCOV
1051
}
×
UNCOV
1052

×
UNCOV
1053
func makeImporterVolumeSpec(args *importerPodArgs) []corev1.Volume {
×
1054
        volumes := []corev1.Volume{
1✔
UNCOV
1055
                {
×
UNCOV
1056
                        Name: cc.DataVolName,
×
UNCOV
1057
                        VolumeSource: corev1.VolumeSource{
×
UNCOV
1058
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
UNCOV
1059
                                        ClaimName: args.pvc.Name,
×
1060
                                        ReadOnly:  false,
1✔
UNCOV
1061
                                },
×
UNCOV
1062
                        },
×
UNCOV
1063
                },
×
1064
        }
1065
        if isRegistryNodeImport(args) {
1✔
1066
                volumes = append(volumes, corev1.Volume{
1067
                        Name: "shared-volume",
1068
                        VolumeSource: corev1.VolumeSource{
1✔
1069
                                EmptyDir: &corev1.EmptyDirVolumeSource{},
1✔
1070
                        },
1✔
1071
                })
1✔
1072
        }
1✔
1073
        if args.scratchPvcName != nil {
1✔
1074
                volumes = append(volumes, corev1.Volume{
1✔
1075
                        Name: cc.ScratchVolName,
1✔
1076
                        VolumeSource: corev1.VolumeSource{
1✔
1077
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
1✔
1078
                                        ClaimName: *args.scratchPvcName,
1✔
1079
                                        ReadOnly:  false,
1✔
1080
                                },
2✔
1081
                        },
1✔
1082
                })
1✔
1083
        }
1✔
1084
        if args.vddkImageName != nil {
1✔
1085
                volumes = append(volumes, corev1.Volume{
1✔
1086
                        Name: "vddk-vol-mount",
1✔
1087
                        VolumeSource: corev1.VolumeSource{
1✔
1088
                                EmptyDir: &corev1.EmptyDirVolumeSource{},
2✔
1089
                        },
1✔
1090
                })
1✔
1091
        }
1✔
1092
        if args.vddkExtraArgs != nil {
1✔
1093
                volumes = append(volumes, corev1.Volume{
1✔
1094
                        Name: common.VddkArgsVolName,
1✔
1095
                        VolumeSource: corev1.VolumeSource{
1✔
1096
                                ConfigMap: &v1.ConfigMapVolumeSource{
1✔
1097
                                        LocalObjectReference: v1.LocalObjectReference{
1✔
1098
                                                Name: *args.vddkExtraArgs,
1✔
1099
                                        },
2✔
1100
                                },
1✔
1101
                        },
1✔
1102
                })
1✔
1103
        }
1✔
1104
        if args.podEnvVar.certConfigMap != "" {
1✔
1105
                volumes = append(volumes, createConfigMapVolume(CertVolName, args.podEnvVar.certConfigMap))
1✔
1106
        }
1✔
1107
        if args.podEnvVar.certConfigMapProxy != "" {
2✔
1108
                volumes = append(volumes, createConfigMapVolume(ProxyCertVolName, GetImportProxyConfigMapName(args.pvc.Name)))
1✔
1109
        }
1✔
1110
        if args.podEnvVar.source == cc.SourceGCS && args.podEnvVar.secretName != "" {
1✔
1111
                volumes = append(volumes, createSecretVolume(SecretVolName, args.podEnvVar.secretName))
1✔
1112
        }
1✔
1113
        for index, header := range args.podEnvVar.secretExtraHeaders {
1✔
1114
                volumes = append(volumes, corev1.Volume{
1✔
1115
                        Name: fmt.Sprintf(secretExtraHeadersVolumeName, index),
1✔
1116
                        VolumeSource: corev1.VolumeSource{
1✔
1117
                                Secret: &corev1.SecretVolumeSource{
1✔
1118
                                        SecretName: header,
1✔
1119
                                },
1✔
1120
                        },
×
1121
                })
×
1122
        }
1✔
UNCOV
1123
        return volumes
×
UNCOV
1124
}
×
1125

1✔
UNCOV
1126
func makeImporterInitContainersSpec(args *importerPodArgs) []corev1.Container {
×
UNCOV
1127
        var initContainers []corev1.Container
×
1128
        if isRegistryNodeImport(args) {
1✔
UNCOV
1129
                initContainers = append(initContainers, corev1.Container{
×
UNCOV
1130
                        Name:            "init",
×
UNCOV
1131
                        Image:           args.image,
×
UNCOV
1132
                        ImagePullPolicy: corev1.PullPolicy(args.pullPolicy),
×
UNCOV
1133
                        Command:         []string{"sh", "-c", "cp /usr/bin/cdi-containerimage-server /shared/server"},
×
UNCOV
1134
                        VolumeMounts: []corev1.VolumeMount{
×
UNCOV
1135
                                {
×
UNCOV
1136
                                        MountPath: "/shared",
×
UNCOV
1137
                                        Name:      "shared-volume",
×
1138
                                },
1✔
1139
                        },
1140
                        TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
1141
                })
1✔
1142
        }
1✔
1143
        if args.vddkImageName != nil {
2✔
1144
                initContainers = append(initContainers, corev1.Container{
1✔
1145
                        Name:  "vddk-side-car",
1✔
1146
                        Image: *args.vddkImageName,
1✔
1147
                        VolumeMounts: []corev1.VolumeMount{
1✔
1148
                                {
1✔
1149
                                        Name:      "vddk-vol-mount",
1✔
1150
                                        MountPath: "/opt",
1✔
1151
                                },
1✔
1152
                        },
1✔
1153
                        TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
1✔
1154
                })
1✔
1155
        }
1✔
1156
        if args.podResourceRequirements != nil {
1✔
1157
                for i := range initContainers {
1✔
1158
                        initContainers[i].Resources = *args.podResourceRequirements
2✔
1159
                }
1✔
1160
        }
1✔
1161
        return initContainers
1✔
1162
}
1✔
1163

1✔
1164
func isRegistryNodeImport(args *importerPodArgs) bool {
1✔
1165
        return cc.GetSource(args.pvc) == cc.SourceRegistry &&
1✔
1166
                args.pvc.Annotations[cc.AnnRegistryImportMethod] == string(cdiv1.RegistryPullNode)
1✔
1167
}
1✔
1168

1✔
1169
func getOwnerUID(args *importerPodArgs) types.UID {
1✔
1170
        if len(args.pvc.OwnerReferences) == 1 {
1✔
1171
                return args.pvc.OwnerReferences[0].UID
1✔
1172
        }
×
UNCOV
1173
        return args.pvc.UID
×
UNCOV
1174
}
×
1175

1176
func setRegistryNodeImportEnvVars(args *importerPodArgs) {
1✔
1177
        args.podEnvVar.source = cc.SourceHTTP
1178
        args.podEnvVar.ep = "http://localhost:8100/disk.img"
1179
        args.podEnvVar.pullMethod = string(cdiv1.RegistryPullNode)
1✔
1180
        args.podEnvVar.readyFile = "/shared/ready"
1✔
1181
        args.podEnvVar.doneFile = "/shared/done"
1✔
1182
}
1✔
1183

1184
func createConfigMapVolume(certVolName, objRef string) corev1.Volume {
1✔
1185
        return corev1.Volume{
1✔
UNCOV
1186
                Name: certVolName,
×
UNCOV
1187
                VolumeSource: corev1.VolumeSource{
×
1188
                        ConfigMap: &corev1.ConfigMapVolumeSource{
1✔
1189
                                LocalObjectReference: corev1.LocalObjectReference{
1190
                                        Name: objRef,
1191
                                },
1✔
1192
                        },
1✔
1193
                },
1✔
1194
        }
1✔
1195
}
1✔
1196

1✔
1197
func createSecretVolume(thisVolName, objRef string) corev1.Volume {
1✔
1198
        return corev1.Volume{
1199
                Name: thisVolName,
×
1200
                VolumeSource: corev1.VolumeSource{
×
1201
                        Secret: &corev1.SecretVolumeSource{
×
1202
                                SecretName: objRef,
×
1203
                        },
×
1204
                },
1205
        }
1206
}
1✔
1207

1✔
1208
// return the Env portion for the importer container.
1✔
1209
func makeImportEnv(podEnvVar *importPodEnvVar, uid types.UID) []corev1.EnvVar {
1✔
1210
        env := []corev1.EnvVar{
1✔
1211
                {
1✔
1212
                        Name:  common.ImporterSource,
1✔
1213
                        Value: podEnvVar.source,
1✔
1214
                },
1✔
1215
                {
1✔
1216
                        Name:  common.ImporterEndpoint,
1✔
1217
                        Value: podEnvVar.ep,
1✔
1218
                },
UNCOV
1219
                {
×
UNCOV
1220
                        Name:  common.ImporterContentType,
×
UNCOV
1221
                        Value: podEnvVar.contentType,
×
UNCOV
1222
                },
×
UNCOV
1223
                {
×
UNCOV
1224
                        Name:  common.ImporterImageSize,
×
UNCOV
1225
                        Value: podEnvVar.imageSize,
×
UNCOV
1226
                },
×
UNCOV
1227
                {
×
UNCOV
1228
                        Name:  common.OwnerUID,
×
1229
                        Value: string(uid),
1230
                },
1231
                {
1✔
1232
                        Name:  common.FilesystemOverheadVar,
1✔
1233
                        Value: podEnvVar.filesystemOverhead,
1✔
1234
                },
1✔
1235
                {
1✔
1236
                        Name:  common.InsecureTLSVar,
1✔
1237
                        Value: strconv.FormatBool(podEnvVar.insecureTLS),
1✔
1238
                },
1✔
1239
                {
1✔
1240
                        Name:  common.ImporterDiskID,
1✔
1241
                        Value: podEnvVar.diskID,
1✔
1242
                },
1✔
1243
                {
1✔
1244
                        Name:  common.ImporterUUID,
1✔
1245
                        Value: podEnvVar.uuid,
1✔
1246
                },
1✔
1247
                {
1✔
1248
                        Name:  common.ImporterPullMethod,
1✔
1249
                        Value: podEnvVar.pullMethod,
1✔
1250
                },
1✔
1251
                {
1✔
1252
                        Name:  common.ImporterReadyFile,
1✔
1253
                        Value: podEnvVar.readyFile,
1✔
1254
                },
1✔
1255
                {
1✔
1256
                        Name:  common.ImporterDoneFile,
1✔
1257
                        Value: podEnvVar.doneFile,
1✔
1258
                },
1✔
1259
                {
1✔
1260
                        Name:  common.ImporterBackingFile,
1✔
1261
                        Value: podEnvVar.backingFile,
1✔
1262
                },
1✔
1263
                {
1✔
1264
                        Name:  common.ImporterThumbprint,
1✔
1265
                        Value: podEnvVar.thumbprint,
1✔
1266
                },
1✔
1267
                {
1✔
1268
                        Name:  common.ImportProxyHTTP,
1✔
1269
                        Value: podEnvVar.httpProxy,
1✔
1270
                },
1✔
1271
                {
1✔
1272
                        Name:  common.ImportProxyHTTPS,
1✔
1273
                        Value: podEnvVar.httpsProxy,
1✔
1274
                },
1✔
1275
                {
1✔
1276
                        Name:  common.ImportProxyNoProxy,
1✔
1277
                        Value: podEnvVar.noProxy,
1✔
1278
                },
1✔
1279
                {
1✔
1280
                        Name:  common.ImporterCurrentCheckpoint,
1✔
1281
                        Value: podEnvVar.currentCheckpoint,
1✔
1282
                },
1✔
1283
                {
1✔
1284
                        Name:  common.ImporterPreviousCheckpoint,
1✔
1285
                        Value: podEnvVar.previousCheckpoint,
1✔
1286
                },
1✔
1287
                {
1✔
1288
                        Name:  common.ImporterFinalCheckpoint,
1✔
1289
                        Value: podEnvVar.finalCheckpoint,
1✔
1290
                },
1✔
1291
                {
1✔
1292
                        Name:  common.Preallocation,
1✔
1293
                        Value: strconv.FormatBool(podEnvVar.preallocation),
1✔
1294
                },
1✔
1295
                {
1✔
1296
                        Name:  common.CacheMode,
1✔
1297
                        Value: podEnvVar.cacheMode,
1✔
1298
                },
1✔
1299
        }
1✔
1300
        if podEnvVar.secretName != "" && podEnvVar.source != cc.SourceGCS {
1✔
1301
                env = append(env, corev1.EnvVar{
1✔
1302
                        Name: common.ImporterAccessKeyID,
1✔
1303
                        ValueFrom: &corev1.EnvVarSource{
1✔
1304
                                SecretKeyRef: &corev1.SecretKeySelector{
1✔
1305
                                        LocalObjectReference: corev1.LocalObjectReference{
1✔
1306
                                                Name: podEnvVar.secretName,
1✔
1307
                                        },
1✔
1308
                                        Key: common.KeyAccess,
1✔
1309
                                },
1✔
1310
                        },
1✔
1311
                }, corev1.EnvVar{
1✔
1312
                        Name: common.ImporterSecretKey,
1✔
1313
                        ValueFrom: &corev1.EnvVarSource{
1✔
1314
                                SecretKeyRef: &corev1.SecretKeySelector{
1✔
1315
                                        LocalObjectReference: corev1.LocalObjectReference{
1✔
1316
                                                Name: podEnvVar.secretName,
1✔
1317
                                        },
1✔
1318
                                        Key: common.KeySecret,
1✔
1319
                                },
1✔
1320
                        },
1✔
1321
                })
1✔
1322
        }
1✔
1323
        if podEnvVar.secretName != "" && podEnvVar.source == cc.SourceGCS {
1✔
1324
                env = append(env, corev1.EnvVar{
1✔
1325
                        Name:  common.ImporterGoogleCredentialFileVar,
1✔
1326
                        Value: common.ImporterGoogleCredentialFile,
1✔
1327
                })
×
1328
        }
×
UNCOV
1329
        if podEnvVar.certConfigMap != "" {
×
1330
                env = append(env, corev1.EnvVar{
×
1331
                        Name:  common.ImporterCertDirVar,
×
1332
                        Value: common.ImporterCertDir,
×
1333
                })
×
1334
        }
×
UNCOV
1335
        if podEnvVar.certConfigMapProxy != "" {
×
1336
                env = append(env, corev1.EnvVar{
×
1337
                        Name:  common.ImporterProxyCertDirVar,
×
1338
                        Value: common.ImporterProxyCertDir,
×
1339
                })
×
1340
        }
×
UNCOV
1341
        for index, header := range podEnvVar.extraHeaders {
×
1342
                env = append(env, corev1.EnvVar{
×
1343
                        Name:  fmt.Sprintf("%s%d", common.ImporterExtraHeader, index),
×
1344
                        Value: header,
×
1345
                })
×
1346
        }
×
UNCOV
1347
        return env
×
UNCOV
1348
}
×
1349

1✔
UNCOV
1350
func isOOMKilled(status v1.ContainerStatus) bool {
×
UNCOV
1351
        if terminated := status.State.Terminated; terminated != nil {
×
UNCOV
1352
                if terminated.Reason == cc.OOMKilledReason {
×
UNCOV
1353
                        return true
×
UNCOV
1354
                }
×
1355
        }
1✔
UNCOV
1356
        if terminated := status.LastTerminationState.Terminated; terminated != nil {
×
UNCOV
1357
                if terminated.Reason == cc.OOMKilledReason {
×
1358
                        return true
×
1359
                }
×
UNCOV
1360
        }
×
1361

1✔
UNCOV
1362
        return false
×
UNCOV
1363
}
×
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