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

kubevirt / containerized-data-importer / #5400

24 Jun 2025 07:02AM UTC coverage: 59.418% (+0.003%) from 59.415%
#5400

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)

27 of 59 new or added lines in 5 files covered. (45.76%)

83 existing lines in 2 files now uncovered.

16980 of 28577 relevant lines covered (59.42%)

0.66 hits per line

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

71.34
/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
        // Check if pod has failed and, in that case, record an event with the error
1✔
550
        if podErr := cc.HandleFailedPod(err, pvc.Annotations[cc.AnnImportPod], pvc, r.recorder, r.client); podErr != nil {
1✔
NEW
551
                r.log.V(1).Error(err, "failed creating pod", "pod.Name", pod.Name)
×
552
                return podErr
×
UNCOV
553
        }
×
554

555
        r.log.V(1).Info("Created POD", "pod.Name", pod.Name)
1✔
556

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

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

571
        return nil
1✔
572
}
573

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

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

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

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

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

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

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

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

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

664
        return podEnvVar, nil
1✔
665
}
666

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

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

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

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

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

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

UNCOV
708
                return "", err
×
709
        }
710

711
        return value, nil
1✔
712
}
713

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

894
        pod := makeImporterPodSpec(args)
1✔
895

1✔
896
        util.SetRecommendedLabels(pod, installerLabels, "cdi-controller")
1✔
897

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

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

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

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

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

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

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

1✔
974
        return pod
1✔
975
}
976

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

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

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

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

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

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

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

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

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

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

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

1389
        return false
1✔
1390
}
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