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

kubevirt / containerized-data-importer / #5326

14 May 2025 04:40PM UTC coverage: 59.303% (+0.02%) from 59.288%
#5326

push

travis-ci

web-flow
Enable WebhookPvcRendering feature gate by default (#3736)

The feature is available since v1.59, and we enable it by default to
allow increasing PVC size to the minimum supported by its provisioner
(#3711), and mainly in order to support:
https://github.com/kubevirt/kubevirt/pull/14637

As a bonus, the related Serial tests are now parallel. Thanks akalenyu:)

Signed-off-by: Arnon Gilboa <agilboa@redhat.com>

16870 of 28447 relevant lines covered (59.3%)

0.66 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

169
        return nil
×
170
}
171

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

559
        return nil
1✔
560
}
561

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

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

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

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

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

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

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

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

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

651
        return podEnvVar, nil
1✔
652
}
653

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

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

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

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

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

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

695
                return "", err
×
696
        }
697

698
        return value, nil
1✔
699
}
700

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

878
        pod := makeImporterPodSpec(args)
1✔
879

1✔
880
        util.SetRecommendedLabels(pod, installerLabels, "cdi-controller")
1✔
881

1✔
882
        if err = client.Create(context.TODO(), pod); err != nil {
1✔
883
                return nil, err
×
884
        }
×
885

886
        log.V(3).Info("importer pod created\n", "pod.Name", pod.Name, "pod.Namespace", pod.Namespace, "image name", args.image)
1✔
887
        return pod, nil
1✔
888
}
889

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

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

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

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

1✔
955
        return pod
1✔
956
}
957

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

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

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

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

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

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

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

1194
func createSecretVolume(thisVolName, objRef string) corev1.Volume {
×
1195
        return corev1.Volume{
×
1196
                Name: thisVolName,
×
1197
                VolumeSource: corev1.VolumeSource{
×
1198
                        Secret: &corev1.SecretVolumeSource{
×
1199
                                SecretName: objRef,
×
1200
                        },
×
1201
                },
×
1202
        }
×
1203
}
×
1204

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

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

1359
        return false
1✔
1360
}
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