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

kubevirt / containerized-data-importer / #5402

24 Jun 2025 09:44AM UTC coverage: 59.378% (-0.04%) from 59.415%
#5402

Pull #3760

travis-ci

Acedus
testing: test pullMethod: node multi-arch import flake

Signed-off-by: Adi Aloni <aaloni@redhat.com>
Pull Request #3760: VEP48: Introduce DataSource Source DataSource (DataSource Pointers)

28 of 61 new or added lines in 5 files covered. (45.9%)

121 existing lines in 2 files now uncovered.

16969 of 28578 relevant lines covered (59.38%)

0.66 hits per line

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

71.27
/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
        registryImageArchitecture string
106
}
107

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

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

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

170
        return nil
×
171
}
172

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
386
        if statuses := pod.Status.ContainerStatuses; len(statuses) > 0 {
2✔
387
                if isOOMKilled(statuses[0]) {
2✔
388
                        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)
1✔
389
                        podModificationsNeeded = true
1✔
390
                        anno[cc.AnnRequiresDirectIO] = "true"
1✔
391
                }
1✔
392
                if terminated := statuses[0].State.Terminated; terminated != nil && terminated.ExitCode > 0 {
2✔
393
                        log.Info("Pod termination code", "pod.Name", pod.Name, "ExitCode", terminated.ExitCode)
1✔
394
                        r.recorder.Event(pvc, corev1.EventTypeWarning, ErrImportFailedPVC, terminated.Message)
1✔
395
                }
1✔
396
        }
397

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

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

409
        anno[cc.AnnPodSchedulable] = "true"
1✔
410
        if phase, ok := anno[cc.AnnPodPhase]; ok && phase == string(corev1.PodPending) {
2✔
411
                for _, cond := range pod.Status.Conditions {
1✔
412
                        if cond.Type == corev1.PodScheduled && cond.Reason == corev1.PodReasonUnschedulable {
×
413
                                anno[cc.AnnPodSchedulable] = "false"
×
414
                                break
×
415
                        }
416
                }
417
        }
418

419
        for _, ev := range pod.Spec.Containers[0].Env {
2✔
420
                if ev.Name == common.CacheMode && ev.Value == common.CacheModeTryNone {
1✔
421
                        anno[cc.AnnRequiresDirectIO] = "false"
×
422
                }
×
423
        }
424

425
        // Check if the POD is waiting for scratch space, if so create some.
426
        if pod.Status.Phase == corev1.PodPending && r.requiresScratchSpace(pvc) {
2✔
427
                if err := r.createScratchPvcForPod(pvc, pod); err != nil {
1✔
428
                        if !k8serrors.IsAlreadyExists(err) {
×
429
                                return err
×
430
                        }
×
431
                }
432
        } else {
1✔
433
                // No scratch space, or scratch space is bound, remove annotation
1✔
434
                delete(anno, cc.AnnBoundCondition)
1✔
435
                delete(anno, cc.AnnBoundConditionMessage)
1✔
436
                delete(anno, cc.AnnBoundConditionReason)
1✔
437
        }
1✔
438

439
        if pvc.GetLabels() == nil {
2✔
440
                pvc.SetLabels(make(map[string]string, 0))
1✔
441
        }
1✔
442
        if !checkIfLabelExists(pvc, common.CDILabelKey, common.CDILabelValue) {
2✔
443
                pvc.GetLabels()[common.CDILabelKey] = common.CDILabelValue
1✔
444
        }
1✔
445
        if cc.IsPVCComplete(pvc) {
2✔
446
                pvc.SetLabels(addLabelsFromTerminationMessage(pvc.GetLabels(), termMsg))
1✔
447
        }
1✔
448

449
        if !reflect.DeepEqual(currentPvcCopy, pvc) {
2✔
450
                if err := r.updatePVC(pvc, log); err != nil {
1✔
451
                        return err
×
452
                }
×
453
                log.V(1).Info("Updated PVC", "pvc.anno.Phase", anno[cc.AnnPodPhase], "pvc.anno.Restarts", anno[cc.AnnPodRestarts])
1✔
454
        }
455

456
        if cc.IsPVCComplete(pvc) || podModificationsNeeded {
2✔
457
                if !podModificationsNeeded {
2✔
458
                        r.recorder.Event(pvc, corev1.EventTypeNormal, ImportSucceededPVC, "Import Successful")
1✔
459
                        log.V(1).Info("Import completed successfully")
1✔
460
                }
1✔
461
                if cc.ShouldDeletePod(pvc) {
2✔
462
                        log.V(1).Info("Deleting pod", "pod.Name", pod.Name)
1✔
463
                        if err := r.cleanup(pvc, pod, log); err != nil {
1✔
464
                                return err
×
465
                        }
×
466
                }
467
        }
468
        return nil
1✔
469
}
470

471
func (r *ImportReconciler) cleanup(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod, log logr.Logger) error {
1✔
472
        if err := r.client.Delete(context.TODO(), pod); cc.IgnoreNotFound(err) != nil {
1✔
473
                return err
×
474
        }
×
475
        if cc.HasFinalizer(pvc, importPodImageStreamFinalizer) {
1✔
476
                cc.RemoveFinalizer(pvc, importPodImageStreamFinalizer)
×
477
                if err := r.updatePVC(pvc, log); err != nil {
×
478
                        return err
×
479
                }
×
480
        }
481
        return nil
1✔
482
}
483

484
func (r *ImportReconciler) updatePVC(pvc *corev1.PersistentVolumeClaim, log logr.Logger) error {
1✔
485
        if err := r.client.Update(context.TODO(), pvc); err != nil {
1✔
486
                return err
×
487
        }
×
488
        return nil
1✔
489
}
490

491
func (r *ImportReconciler) createImporterPod(pvc *corev1.PersistentVolumeClaim) error {
1✔
492
        r.log.V(1).Info("Creating importer POD for PVC", "pvc.Name", pvc.Name)
1✔
493
        var scratchPvcName *string
1✔
494
        var vddkImageName *string
1✔
495
        var vddkExtraArgs *string
1✔
496
        var err error
1✔
497

1✔
498
        requiresScratch := r.requiresScratchSpace(pvc)
1✔
499
        if requiresScratch {
1✔
500
                name := createScratchNameFromPvc(pvc)
×
501
                scratchPvcName = &name
×
502
        }
×
503

504
        if cc.GetSource(pvc) == cc.SourceVDDK {
2✔
505
                r.log.V(1).Info("Pod requires VDDK sidecar for VMware transfer")
1✔
506
                anno := pvc.GetAnnotations()
1✔
507
                if imageName, ok := anno[cc.AnnVddkInitImageURL]; ok {
2✔
508
                        vddkImageName = &imageName
1✔
509
                } else {
2✔
510
                        if vddkImageName, err = r.getVddkImageName(); err != nil {
2✔
511
                                r.log.V(1).Error(err, "failed to get VDDK image name from configmap")
1✔
512
                        }
1✔
513
                }
514
                if vddkImageName == nil {
2✔
515
                        message := fmt.Sprintf("waiting for %s configmap or %s annotation for VDDK image", common.VddkConfigMap, cc.AnnVddkInitImageURL)
1✔
516
                        anno[cc.AnnBoundCondition] = "false"
1✔
517
                        anno[cc.AnnBoundConditionMessage] = message
1✔
518
                        anno[cc.AnnBoundConditionReason] = common.AwaitingVDDK
1✔
519
                        if err := r.updatePVC(pvc, r.log); err != nil {
1✔
520
                                return err
×
521
                        }
×
522
                        return errors.New(message)
1✔
523
                }
524

525
                if extraArgs, ok := anno[cc.AnnVddkExtraArgs]; ok && extraArgs != "" {
2✔
526
                        r.log.V(1).Info("Mounting extra VDDK args ConfigMap to importer pod", "ConfigMap", extraArgs)
1✔
527
                        vddkExtraArgs = &extraArgs
1✔
528
                }
1✔
529
        }
530

531
        podEnvVar, err := r.createImportEnvVar(pvc)
1✔
532
        if err != nil {
1✔
533
                return err
×
534
        }
×
535
        // all checks passed, let's create the importer pod!
536
        podArgs := &importerPodArgs{
1✔
537
                image:             r.image,
1✔
538
                verbose:           r.verbose,
1✔
539
                pullPolicy:        r.pullPolicy,
1✔
540
                podEnvVar:         podEnvVar,
1✔
541
                pvc:               pvc,
1✔
542
                scratchPvcName:    scratchPvcName,
1✔
543
                vddkImageName:     vddkImageName,
1✔
544
                vddkExtraArgs:     vddkExtraArgs,
1✔
545
                priorityClassName: cc.GetPriorityClass(pvc),
1✔
546
        }
1✔
547

1✔
548
        pod, err := createImporterPod(context.TODO(), r.log, r.client, podArgs, r.installerLabels)
1✔
549
        if err != nil {
1✔
NEW
UNCOV
550
                r.log.Error(err, "failed creating pod", "pod.Name", pod.Name)
×
NEW
551
        }
×
552
        // Check if pod has failed and, in that case, record an event with the error
553
        if podErr := cc.HandleFailedPod(err, pvc.Annotations[cc.AnnImportPod], pvc, r.recorder, r.client); podErr != nil {
1✔
UNCOV
554
                return podErr
×
UNCOV
555
        }
×
556

557
        r.log.V(1).Info("Created POD", "pod.Name", pod.Name)
1✔
558

1✔
559
        // If importing from image stream, add finalizer. Note we don't watch the importer pod in this case,
1✔
560
        // so to prevent a deadlock we add finalizer only if the pod is not retained after completion.
1✔
561
        if cc.IsImageStream(pvc) && pvc.GetAnnotations()[cc.AnnPodRetainAfterCompletion] != "true" {
1✔
562
                cc.AddFinalizer(pvc, importPodImageStreamFinalizer)
×
UNCOV
563
                if err := r.updatePVC(pvc, r.log); err != nil {
×
UNCOV
564
                        return err
×
UNCOV
565
                }
×
566
        }
567

568
        if requiresScratch {
1✔
UNCOV
569
                r.log.V(1).Info("Pod requires scratch space")
×
UNCOV
570
                return r.createScratchPvcForPod(pvc, pod)
×
UNCOV
571
        }
×
572

573
        return nil
1✔
574
}
575

UNCOV
576
func createScratchNameFromPvc(pvc *v1.PersistentVolumeClaim) string {
×
UNCOV
577
        return naming.GetResourceName(pvc.Name, common.ScratchNameSuffix)
×
UNCOV
578
}
×
579

580
func (r *ImportReconciler) createImportEnvVar(pvc *corev1.PersistentVolumeClaim) (*importPodEnvVar, error) {
1✔
581
        podEnvVar := &importPodEnvVar{}
1✔
582
        podEnvVar.source = cc.GetSource(pvc)
1✔
583
        podEnvVar.contentType = string(cc.GetPVCContentType(pvc))
1✔
584

1✔
585
        var err error
1✔
586
        if podEnvVar.source != cc.SourceNone {
2✔
587
                podEnvVar.ep, err = cc.GetEndpoint(pvc)
1✔
588
                if err != nil {
1✔
UNCOV
589
                        return nil, err
×
UNCOV
590
                }
×
591
                podEnvVar.secretName = r.getSecretName(pvc)
1✔
592
                if podEnvVar.secretName == "" {
2✔
593
                        r.log.V(2).Info("no secret will be supplied to endpoint", "endPoint", podEnvVar.ep)
1✔
594
                }
1✔
595
                //get the CDIConfig to extract the proxy configuration to be used to import an image
596
                cdiConfig := &cdiv1.CDIConfig{}
1✔
597
                err = r.client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiConfig)
1✔
598
                if err != nil {
1✔
UNCOV
599
                        return nil, err
×
600
                }
×
601
                podEnvVar.certConfigMap, err = r.getCertConfigMap(pvc)
1✔
602
                if err != nil {
1✔
UNCOV
603
                        return nil, err
×
604
                }
×
605
                podEnvVar.insecureTLS, err = r.isInsecureTLS(pvc, cdiConfig)
1✔
606
                if err != nil {
1✔
UNCOV
607
                        return nil, err
×
UNCOV
608
                }
×
609
                podEnvVar.diskID = getValueFromAnnotation(pvc, cc.AnnDiskID)
1✔
610
                podEnvVar.backingFile = getValueFromAnnotation(pvc, cc.AnnBackingFile)
1✔
611
                podEnvVar.uuid = getValueFromAnnotation(pvc, cc.AnnUUID)
1✔
612
                podEnvVar.thumbprint = getValueFromAnnotation(pvc, cc.AnnThumbprint)
1✔
613
                podEnvVar.previousCheckpoint = getValueFromAnnotation(pvc, cc.AnnPreviousCheckpoint)
1✔
614
                podEnvVar.currentCheckpoint = getValueFromAnnotation(pvc, cc.AnnCurrentCheckpoint)
1✔
615
                podEnvVar.finalCheckpoint = getValueFromAnnotation(pvc, cc.AnnFinalCheckpoint)
1✔
616
                podEnvVar.registryImageArchitecture = getValueFromAnnotation(pvc, cc.AnnRegistryImageArchitecture)
1✔
617

1✔
618
                for annotation, value := range pvc.Annotations {
2✔
619
                        if strings.HasPrefix(annotation, cc.AnnExtraHeaders) {
1✔
620
                                podEnvVar.extraHeaders = append(podEnvVar.extraHeaders, value)
×
621
                        }
×
622
                        if strings.HasPrefix(annotation, cc.AnnSecretExtraHeaders) {
1✔
UNCOV
623
                                podEnvVar.secretExtraHeaders = append(podEnvVar.secretExtraHeaders, value)
×
UNCOV
624
                        }
×
625
                }
626

627
                var field string
1✔
628
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyHTTP); err != nil {
2✔
629
                        r.log.V(3).Info("no proxy http url will be supplied:", "error", err.Error())
1✔
630
                }
1✔
631
                podEnvVar.httpProxy = field
1✔
632
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyHTTPS); err != nil {
2✔
633
                        r.log.V(3).Info("no proxy https url will be supplied:", "error", err.Error())
1✔
634
                }
1✔
635
                podEnvVar.httpsProxy = field
1✔
636
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyNoProxy); err != nil {
2✔
637
                        r.log.V(3).Info("the noProxy field will not be supplied:", "error", err.Error())
1✔
638
                }
1✔
639
                podEnvVar.noProxy = field
1✔
640
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyConfigMapName); err != nil {
2✔
641
                        r.log.V(3).Info("no proxy CA certiticate will be supplied:", "error", err.Error())
1✔
642
                }
1✔
643
                podEnvVar.certConfigMapProxy = field
1✔
644
        }
645

646
        fsOverhead, err := GetFilesystemOverhead(context.TODO(), r.client, pvc)
1✔
647
        if err != nil {
1✔
UNCOV
648
                return nil, err
×
UNCOV
649
        }
×
650
        podEnvVar.filesystemOverhead = string(fsOverhead)
1✔
651

1✔
652
        if preallocation, err := strconv.ParseBool(getValueFromAnnotation(pvc, cc.AnnPreallocationRequested)); err == nil {
1✔
UNCOV
653
                podEnvVar.preallocation = preallocation
×
UNCOV
654
        } // else use the default "false"
×
655

656
        //get the requested image size.
657
        podEnvVar.imageSize, err = cc.GetRequestedImageSize(pvc)
1✔
658
        if err != nil {
1✔
UNCOV
659
                return nil, err
×
UNCOV
660
        }
×
661

662
        if v, ok := pvc.Annotations[cc.AnnRequiresDirectIO]; ok && v == "true" {
2✔
663
                podEnvVar.cacheMode = common.CacheModeTryNone
1✔
664
        }
1✔
665

666
        return podEnvVar, nil
1✔
667
}
668

669
func (r *ImportReconciler) isInsecureTLS(pvc *corev1.PersistentVolumeClaim, cdiConfig *cdiv1.CDIConfig) (bool, error) {
1✔
670
        ep, ok := pvc.Annotations[cc.AnnEndpoint]
1✔
671
        if !ok || ep == "" {
2✔
672
                return false, nil
1✔
673
        }
1✔
674
        return IsInsecureTLS(ep, cdiConfig, r.log)
1✔
675
}
676

677
// IsInsecureTLS checks if TLS security is disabled for the given endpoint
678
func IsInsecureTLS(ep string, cdiConfig *cdiv1.CDIConfig, log logr.Logger) (bool, error) {
1✔
679
        url, err := url.Parse(ep)
1✔
680
        if err != nil {
1✔
UNCOV
681
                return false, err
×
UNCOV
682
        }
×
683

684
        if url.Scheme != "docker" {
2✔
685
                return false, nil
1✔
686
        }
1✔
687

688
        for _, value := range cdiConfig.Spec.InsecureRegistries {
2✔
689
                log.V(1).Info("Checking host against value", "host", url.Host, "value", value)
1✔
690
                if value == url.Host {
2✔
691
                        return true, nil
1✔
692
                }
1✔
693
        }
694
        return false, nil
1✔
695
}
696

697
func (r *ImportReconciler) getCertConfigMap(pvc *corev1.PersistentVolumeClaim) (string, error) {
1✔
698
        value, ok := pvc.Annotations[cc.AnnCertConfigMap]
1✔
699
        if !ok || value == "" {
2✔
700
                return "", nil
1✔
701
        }
1✔
702

703
        configMap := &corev1.ConfigMap{}
1✔
704
        if err := r.uncachedClient.Get(context.TODO(), types.NamespacedName{Name: value, Namespace: pvc.Namespace}, configMap); err != nil {
2✔
705
                if k8serrors.IsNotFound(err) {
2✔
706
                        r.log.V(1).Info("Configmap does not exist, pod will not start until it does", "configMapName", value)
1✔
707
                        return value, nil
1✔
708
                }
1✔
709

UNCOV
710
                return "", err
×
711
        }
712

713
        return value, nil
1✔
714
}
715

716
// returns the name of the secret containing endpoint credentials consumed by the importer pod.
717
// A value of "" implies there are no credentials for the endpoint being used. A returned error
718
// causes processNextItem() to stop.
719
func (r *ImportReconciler) getSecretName(pvc *corev1.PersistentVolumeClaim) string {
1✔
720
        ns := pvc.Namespace
1✔
721
        name, found := pvc.Annotations[cc.AnnSecret]
1✔
722
        if !found || name == "" {
2✔
723
                msg := "getEndpointSecret: "
1✔
724
                if !found {
2✔
725
                        msg += fmt.Sprintf("annotation %q is missing in pvc \"%s/%s\"", cc.AnnSecret, ns, pvc.Name)
1✔
726
                } else {
1✔
UNCOV
727
                        msg += fmt.Sprintf("secret name is missing from annotation %q in pvc \"%s/%s\"", cc.AnnSecret, ns, pvc.Name)
×
UNCOV
728
                }
×
729
                r.log.V(2).Info(msg)
1✔
730
                return "" // importer pod will not contain secret credentials
1✔
731
        }
732
        return name
1✔
733
}
734

735
func (r *ImportReconciler) requiresScratchSpace(pvc *corev1.PersistentVolumeClaim) bool {
1✔
736
        scratchRequired := false
1✔
737
        contentType := cc.GetPVCContentType(pvc)
1✔
738
        // All archive requires scratch space.
1✔
739
        if contentType == cdiv1.DataVolumeArchive {
1✔
740
                scratchRequired = true
×
741
        } else {
1✔
742
                switch cc.GetSource(pvc) {
1✔
743
                case cc.SourceGlance:
×
744
                        scratchRequired = true
×
745
                case cc.SourceImageio:
×
UNCOV
746
                        if val, ok := pvc.Annotations[cc.AnnCurrentCheckpoint]; ok {
×
UNCOV
747
                                scratchRequired = val != ""
×
UNCOV
748
                        }
×
749
                case cc.SourceRegistry:
1✔
750
                        scratchRequired = pvc.Annotations[cc.AnnRegistryImportMethod] != string(cdiv1.RegistryPullNode)
1✔
751
                }
752
        }
753
        value, ok := pvc.Annotations[cc.AnnRequiresScratch]
1✔
754
        if ok {
2✔
755
                boolVal, _ := strconv.ParseBool(value)
1✔
756
                scratchRequired = scratchRequired || boolVal
1✔
757
        }
1✔
758
        return scratchRequired
1✔
759
}
760

761
func (r *ImportReconciler) createScratchPvcForPod(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod) error {
1✔
762
        scratchPvc := &corev1.PersistentVolumeClaim{}
1✔
763
        scratchPVCName, exists := getScratchNameFromPod(pod)
1✔
764
        if !exists {
1✔
UNCOV
765
                return errors.New("Scratch Volume not configured for pod")
×
UNCOV
766
        }
×
767
        anno := pvc.GetAnnotations()
1✔
768
        err := r.client.Get(context.TODO(), types.NamespacedName{Namespace: pvc.GetNamespace(), Name: scratchPVCName}, scratchPvc)
1✔
769
        if cc.IgnoreNotFound(err) != nil {
1✔
UNCOV
770
                return err
×
UNCOV
771
        }
×
772
        if k8serrors.IsNotFound(err) {
2✔
773
                r.log.V(1).Info("Creating scratch space for POD and PVC", "pod.Name", pod.Name, "pvc.Name", pvc.Name)
1✔
774

1✔
775
                storageClassName := GetScratchPvcStorageClass(r.client, pvc)
1✔
776
                // Scratch PVC doesn't exist yet, create it. Determine which storage class to use.
1✔
777
                _, err = createScratchPersistentVolumeClaim(r.client, pvc, pod, scratchPVCName, storageClassName, r.installerLabels, r.recorder)
1✔
778
                if err != nil {
1✔
UNCOV
779
                        return err
×
UNCOV
780
                }
×
781
                anno[cc.AnnBoundCondition] = "false"
1✔
782
                anno[cc.AnnBoundConditionMessage] = "Creating scratch space"
1✔
783
                anno[cc.AnnBoundConditionReason] = creatingScratch
1✔
784
        } else {
×
785
                if scratchPvc.DeletionTimestamp != nil {
×
786
                        // Delete the pod since we are in a deadlock situation now. The scratch PVC from the previous import is not gone
×
787
                        // yet but terminating, and the new pod is still being created and the scratch PVC now has a finalizer on it.
×
788
                        // Only way to break it, is to delete the importer pod, and give the pvc a chance to disappear.
×
789
                        err = r.client.Delete(context.TODO(), pod)
×
790
                        if err != nil {
×
UNCOV
791
                                return err
×
792
                        }
×
UNCOV
793
                        return fmt.Errorf("terminating scratch space found, deleting pod %s", pod.Name)
×
794
                }
UNCOV
795
                setBoundConditionFromPVC(anno, cc.AnnBoundCondition, scratchPvc)
×
796
        }
797
        anno[cc.AnnRequiresScratch] = "false"
1✔
798
        return nil
1✔
799
}
800

801
// Get path to VDDK image from 'v2v-vmware' ConfigMap
802
func (r *ImportReconciler) getVddkImageName() (*string, error) {
1✔
803
        namespace := util.GetNamespace()
1✔
804

1✔
805
        cm := &corev1.ConfigMap{}
1✔
806
        err := r.uncachedClient.Get(context.TODO(), types.NamespacedName{Name: common.VddkConfigMap, Namespace: namespace}, cm)
1✔
807
        if k8serrors.IsNotFound(err) {
2✔
808
                return nil, errors.Errorf("No %s ConfigMap present in namespace %s", common.VddkConfigMap, namespace)
1✔
809
        }
1✔
810

811
        image, found := cm.Data[common.VddkConfigDataKey]
1✔
812
        if found {
2✔
813
                msg := fmt.Sprintf("Found %s ConfigMap in namespace %s, VDDK image path is: ", common.VddkConfigMap, namespace)
1✔
814
                r.log.V(1).Info(msg, common.VddkConfigDataKey, image)
1✔
815
                return &image, nil
1✔
816
        }
1✔
817

UNCOV
818
        return nil, errors.Errorf("found %s ConfigMap in namespace %s, but it does not contain a '%s' entry", common.VddkConfigMap, namespace, common.VddkConfigDataKey)
×
819
}
820

821
// returns the import image part of the endpoint string
822
func getRegistryImportImage(pvc *corev1.PersistentVolumeClaim) (string, error) {
1✔
823
        ep, err := cc.GetEndpoint(pvc)
1✔
824
        if err != nil {
1✔
825
                return "", nil
×
826
        }
×
827
        if cc.IsImageStream(pvc) {
1✔
UNCOV
828
                return ep, nil
×
829
        }
×
830
        url, err := url.Parse(ep)
1✔
831
        if err != nil {
1✔
UNCOV
832
                return "", errors.Errorf("illegal registry endpoint %s", ep)
×
UNCOV
833
        }
×
834
        return url.Host + url.Path, nil
1✔
835
}
836

837
// getValueFromAnnotation returns the value of an annotation
838
func getValueFromAnnotation(pvc *corev1.PersistentVolumeClaim, annotation string) string {
1✔
839
        return pvc.Annotations[annotation]
1✔
840
}
1✔
841

842
// If this pod is going to transfer one checkpoint in a multi-stage import, attach the checkpoint name to the pod name so
843
// that each checkpoint gets a unique pod. That way each pod can be inspected using the retainAfterCompletion annotation.
844
func podNameWithCheckpoint(pvc *corev1.PersistentVolumeClaim) string {
1✔
845
        if checkpoint := pvc.Annotations[cc.AnnCurrentCheckpoint]; checkpoint != "" {
2✔
846
                return pvc.Name + "-checkpoint-" + checkpoint
1✔
847
        }
1✔
848
        return pvc.Name
1✔
849
}
850

851
func getImportPodNameFromPvc(pvc *corev1.PersistentVolumeClaim) string {
1✔
852
        podName, ok := pvc.Annotations[cc.AnnImportPod]
1✔
853
        if ok {
2✔
854
                return podName
1✔
855
        }
1✔
856
        // fallback to legacy naming, in fact the following function is fully compatible with legacy
857
        // name concatenation "importer-{pvc.Name}" if the name length is under the size limits,
858
        return naming.GetResourceName(common.ImporterPodName, podNameWithCheckpoint(pvc))
1✔
859
}
860

861
func createImportPodNameFromPvc(pvc *corev1.PersistentVolumeClaim) string {
1✔
862
        return naming.GetResourceName(common.ImporterPodName, podNameWithCheckpoint(pvc))
1✔
863
}
1✔
864

865
// createImporterPod creates and returns a pointer to a pod which is created based on the passed-in endpoint, secret
866
// name, and pvc. A nil secret means the endpoint credentials are not passed to the
867
// importer pod.
868
func createImporterPod(ctx context.Context, log logr.Logger, client client.Client, args *importerPodArgs, installerLabels map[string]string) (*corev1.Pod, error) {
1✔
869
        var err error
1✔
870
        args.podResourceRequirements, err = cc.GetDefaultPodResourceRequirements(client)
1✔
871
        if err != nil {
1✔
UNCOV
872
                return nil, err
×
UNCOV
873
        }
×
874

875
        args.imagePullSecrets, err = cc.GetImagePullSecrets(client)
1✔
876
        if err != nil {
1✔
UNCOV
877
                return nil, err
×
UNCOV
878
        }
×
879

880
        args.workloadNodePlacement, err = cc.GetWorkloadNodePlacement(ctx, client)
1✔
881
        if err != nil {
1✔
UNCOV
882
                return nil, err
×
UNCOV
883
        }
×
884

885
        if isRegistryNodeImport(args) {
2✔
886
                args.importImage, err = getRegistryImportImage(args.pvc)
1✔
887
                if err != nil {
1✔
UNCOV
888
                        return nil, err
×
889
                }
×
890
                setRegistryNodeImportEnvVars(args)
1✔
891
                if args.podEnvVar.registryImageArchitecture != "" {
1✔
UNCOV
892
                        setRegistryNodeImportNodeSelector(args)
×
UNCOV
893
                }
×
894
        }
895

896
        pod := makeImporterPodSpec(args)
1✔
897

1✔
898
        util.SetRecommendedLabels(pod, installerLabels, "cdi-controller")
1✔
899

1✔
900
        // add any labels from pvc to the importer pod
1✔
901
        util.MergeLabels(args.pvc.Labels, pod.Labels)
1✔
902

1✔
903
        if err = client.Create(context.TODO(), pod); err != nil {
1✔
UNCOV
904
                return nil, err
×
UNCOV
905
        }
×
906

907
        log.V(3).Info("importer pod created\n", "pod.Name", pod.Name, "pod.Namespace", pod.Namespace, "image name", args.image)
1✔
908
        return pod, nil
1✔
909
}
910

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

1✔
916
        pod := &corev1.Pod{
1✔
917
                TypeMeta: metav1.TypeMeta{
1✔
918
                        Kind:       "Pod",
1✔
919
                        APIVersion: "v1",
1✔
920
                },
1✔
921
                ObjectMeta: metav1.ObjectMeta{
1✔
922
                        Name:      podName,
1✔
923
                        Namespace: args.pvc.Namespace,
1✔
924
                        Annotations: map[string]string{
1✔
925
                                cc.AnnCreatedBy: "yes",
1✔
926
                        },
1✔
927
                        Labels: map[string]string{
1✔
928
                                common.CDILabelKey:        common.CDILabelValue,
1✔
929
                                common.CDIComponentLabel:  common.ImporterPodName,
1✔
930
                                common.PrometheusLabelKey: common.PrometheusLabelValue,
1✔
931
                        },
1✔
932
                        OwnerReferences: []metav1.OwnerReference{
1✔
933
                                {
1✔
934
                                        APIVersion:         "v1",
1✔
935
                                        Kind:               "PersistentVolumeClaim",
1✔
936
                                        Name:               args.pvc.Name,
1✔
937
                                        UID:                args.pvc.GetUID(),
1✔
938
                                        BlockOwnerDeletion: ptr.To[bool](true),
1✔
939
                                        Controller:         ptr.To[bool](true),
1✔
940
                                },
1✔
941
                        },
1✔
942
                },
1✔
943
                Spec: corev1.PodSpec{
1✔
944
                        Containers:        makeImporterContainerSpec(args),
1✔
945
                        InitContainers:    makeImporterInitContainersSpec(args),
1✔
946
                        Volumes:           makeImporterVolumeSpec(args),
1✔
947
                        RestartPolicy:     corev1.RestartPolicyOnFailure,
1✔
948
                        NodeSelector:      args.workloadNodePlacement.NodeSelector,
1✔
949
                        Tolerations:       args.workloadNodePlacement.Tolerations,
1✔
950
                        Affinity:          args.workloadNodePlacement.Affinity,
1✔
951
                        PriorityClassName: args.priorityClassName,
1✔
952
                        ImagePullSecrets:  args.imagePullSecrets,
1✔
953
                },
1✔
954
        }
1✔
955

1✔
956
        /**
1✔
957
        FIXME: When registry source is ImageStream, if we set importer pod OwnerReference (to its pvc, like all other cases),
1✔
958
        for some reason (OCP issue?) we get the following error:
1✔
959
                Failed to pull image "imagestream-name": rpc error: code = Unknown
1✔
960
                desc = Error reading manifest latest in docker.io/library/imagestream-name: errors:
1✔
961
                denied: requested access to the resource is denied
1✔
962
                unauthorized: authentication required
1✔
963
        When we don't set pod OwnerReferences, all works well.
1✔
964
        */
1✔
965
        if isRegistryNodeImport(args) && cc.IsImageStream(args.pvc) {
1✔
UNCOV
966
                pod.OwnerReferences = nil
×
UNCOV
967
                pod.Annotations[cc.AnnOpenShiftImageLookup] = "*"
×
UNCOV
968
        }
×
969

970
        cc.CopyAllowedAnnotations(args.pvc, pod)
1✔
971
        cc.SetRestrictedSecurityContext(&pod.Spec)
1✔
972
        // We explicitly define a NodeName for dynamically provisioned PVCs
1✔
973
        // when the PVC is being handled by a populator (PVC')
1✔
974
        cc.SetNodeNameIfPopulator(args.pvc, &pod.Spec)
1✔
975

1✔
976
        return pod
1✔
977
}
978

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

1071
func makeImporterVolumeSpec(args *importerPodArgs) []corev1.Volume {
1✔
1072
        volumes := []corev1.Volume{
1✔
1073
                {
1✔
1074
                        Name: cc.DataVolName,
1✔
1075
                        VolumeSource: corev1.VolumeSource{
1✔
1076
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
1✔
1077
                                        ClaimName: args.pvc.Name,
1✔
1078
                                        ReadOnly:  false,
1✔
1079
                                },
1✔
1080
                        },
1✔
1081
                },
1✔
1082
        }
1✔
1083
        if isRegistryNodeImport(args) {
2✔
1084
                volumes = append(volumes, corev1.Volume{
1✔
1085
                        Name: "shared-volume",
1✔
1086
                        VolumeSource: corev1.VolumeSource{
1✔
1087
                                EmptyDir: &corev1.EmptyDirVolumeSource{},
1✔
1088
                        },
1✔
1089
                })
1✔
1090
        }
1✔
1091
        if args.scratchPvcName != nil {
2✔
1092
                volumes = append(volumes, corev1.Volume{
1✔
1093
                        Name: cc.ScratchVolName,
1✔
1094
                        VolumeSource: corev1.VolumeSource{
1✔
1095
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
1✔
1096
                                        ClaimName: *args.scratchPvcName,
1✔
1097
                                        ReadOnly:  false,
1✔
1098
                                },
1✔
1099
                        },
1✔
1100
                })
1✔
1101
        }
1✔
1102
        if args.vddkImageName != nil {
2✔
1103
                volumes = append(volumes, corev1.Volume{
1✔
1104
                        Name: "vddk-vol-mount",
1✔
1105
                        VolumeSource: corev1.VolumeSource{
1✔
1106
                                EmptyDir: &corev1.EmptyDirVolumeSource{},
1✔
1107
                        },
1✔
1108
                })
1✔
1109
        }
1✔
1110
        if args.vddkExtraArgs != nil {
2✔
1111
                volumes = append(volumes, corev1.Volume{
1✔
1112
                        Name: common.VddkArgsVolName,
1✔
1113
                        VolumeSource: corev1.VolumeSource{
1✔
1114
                                ConfigMap: &v1.ConfigMapVolumeSource{
1✔
1115
                                        LocalObjectReference: v1.LocalObjectReference{
1✔
1116
                                                Name: *args.vddkExtraArgs,
1✔
1117
                                        },
1✔
1118
                                },
1✔
1119
                        },
1✔
1120
                })
1✔
1121
        }
1✔
1122
        if args.podEnvVar.certConfigMap != "" {
1✔
1123
                volumes = append(volumes, createConfigMapVolume(CertVolName, args.podEnvVar.certConfigMap))
×
1124
        }
×
1125
        if args.podEnvVar.certConfigMapProxy != "" {
1✔
1126
                volumes = append(volumes, createConfigMapVolume(ProxyCertVolName, GetImportProxyConfigMapName(args.pvc.Name)))
×
1127
        }
×
1128
        if args.podEnvVar.source == cc.SourceGCS && args.podEnvVar.secretName != "" {
1✔
1129
                volumes = append(volumes, createSecretVolume(SecretVolName, args.podEnvVar.secretName))
×
1130
        }
×
1131
        for index, header := range args.podEnvVar.secretExtraHeaders {
1✔
1132
                volumes = append(volumes, corev1.Volume{
×
1133
                        Name: fmt.Sprintf(secretExtraHeadersVolumeName, index),
×
1134
                        VolumeSource: corev1.VolumeSource{
×
1135
                                Secret: &corev1.SecretVolumeSource{
×
1136
                                        SecretName: header,
×
1137
                                },
×
UNCOV
1138
                        },
×
UNCOV
1139
                })
×
UNCOV
1140
        }
×
1141
        return volumes
1✔
1142
}
1143

1144
func makeImporterInitContainersSpec(args *importerPodArgs) []corev1.Container {
1✔
1145
        var initContainers []corev1.Container
1✔
1146
        if isRegistryNodeImport(args) {
2✔
1147
                initContainers = append(initContainers, corev1.Container{
1✔
1148
                        Name:            "init",
1✔
1149
                        Image:           args.image,
1✔
1150
                        ImagePullPolicy: corev1.PullPolicy(args.pullPolicy),
1✔
1151
                        Command:         []string{"sh", "-c", "cp /usr/bin/cdi-containerimage-server /shared/server"},
1✔
1152
                        VolumeMounts: []corev1.VolumeMount{
1✔
1153
                                {
1✔
1154
                                        MountPath: "/shared",
1✔
1155
                                        Name:      "shared-volume",
1✔
1156
                                },
1✔
1157
                        },
1✔
1158
                        TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
1✔
1159
                })
1✔
1160
        }
1✔
1161
        if args.vddkImageName != nil {
2✔
1162
                initContainers = append(initContainers, corev1.Container{
1✔
1163
                        Name:  "vddk-side-car",
1✔
1164
                        Image: *args.vddkImageName,
1✔
1165
                        VolumeMounts: []corev1.VolumeMount{
1✔
1166
                                {
1✔
1167
                                        Name:      "vddk-vol-mount",
1✔
1168
                                        MountPath: "/opt",
1✔
1169
                                },
1✔
1170
                        },
1✔
1171
                        TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
1✔
1172
                })
1✔
1173
        }
1✔
1174
        if args.podResourceRequirements != nil {
1✔
UNCOV
1175
                for i := range initContainers {
×
UNCOV
1176
                        initContainers[i].Resources = *args.podResourceRequirements
×
UNCOV
1177
                }
×
1178
        }
1179
        return initContainers
1✔
1180
}
1181

1182
func isRegistryNodeImport(args *importerPodArgs) bool {
1✔
1183
        return cc.GetSource(args.pvc) == cc.SourceRegistry &&
1✔
1184
                args.pvc.Annotations[cc.AnnRegistryImportMethod] == string(cdiv1.RegistryPullNode)
1✔
1185
}
1✔
1186

1187
func getOwnerUID(args *importerPodArgs) types.UID {
1✔
1188
        if len(args.pvc.OwnerReferences) == 1 {
1✔
UNCOV
1189
                return args.pvc.OwnerReferences[0].UID
×
UNCOV
1190
        }
×
1191
        return args.pvc.UID
1✔
1192
}
1193

1194
func setRegistryNodeImportEnvVars(args *importerPodArgs) {
1✔
1195
        args.podEnvVar.source = cc.SourceHTTP
1✔
1196
        args.podEnvVar.ep = "http://localhost:8100/disk.img"
1✔
1197
        args.podEnvVar.pullMethod = string(cdiv1.RegistryPullNode)
1✔
1198
        args.podEnvVar.readyFile = "/shared/ready"
1✔
1199
        args.podEnvVar.doneFile = "/shared/done"
1✔
1200
}
1✔
1201

1202
func setRegistryNodeImportNodeSelector(args *importerPodArgs) {
×
1203
        if args.workloadNodePlacement.NodeSelector == nil {
×
UNCOV
1204
                args.workloadNodePlacement.NodeSelector = make(map[string]string, 0)
×
UNCOV
1205
        }
×
UNCOV
1206
        args.workloadNodePlacement.NodeSelector[v1.LabelArchStable] = args.podEnvVar.registryImageArchitecture
×
1207
}
1208

1209
func createConfigMapVolume(certVolName, objRef string) corev1.Volume {
1✔
1210
        return corev1.Volume{
1✔
1211
                Name: certVolName,
1✔
1212
                VolumeSource: corev1.VolumeSource{
1✔
1213
                        ConfigMap: &corev1.ConfigMapVolumeSource{
1✔
1214
                                LocalObjectReference: corev1.LocalObjectReference{
1✔
1215
                                        Name: objRef,
1✔
1216
                                },
1✔
1217
                        },
1✔
1218
                },
1✔
1219
        }
1✔
1220
}
1✔
1221

1222
func createSecretVolume(thisVolName, objRef string) corev1.Volume {
×
1223
        return corev1.Volume{
×
1224
                Name: thisVolName,
×
1225
                VolumeSource: corev1.VolumeSource{
×
1226
                        Secret: &corev1.SecretVolumeSource{
×
1227
                                SecretName: objRef,
×
1228
                        },
×
UNCOV
1229
                },
×
UNCOV
1230
        }
×
UNCOV
1231
}
×
1232

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

1379
func isOOMKilled(status v1.ContainerStatus) bool {
1✔
1380
        if terminated := status.State.Terminated; terminated != nil {
2✔
1381
                if terminated.Reason == cc.OOMKilledReason {
2✔
1382
                        return true
1✔
1383
                }
1✔
1384
        }
1385
        if terminated := status.LastTerminationState.Terminated; terminated != nil {
2✔
1386
                if terminated.Reason == cc.OOMKilledReason {
1✔
UNCOV
1387
                        return true
×
UNCOV
1388
                }
×
1389
        }
1390

1391
        return false
1✔
1392
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc