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

kubevirt / containerized-data-importer / #5337

18 May 2025 02:53PM UTC coverage: 59.36% (+0.02%) from 59.34%
#5337

Pull #3753

travis-ci

Acedus
importer: pullMethod node architecture support

This commit adds support for specifying architecture for the registry
data source with pullMethod node. When configured, the importer Pod will
gain another NodeSelector for nodes that have the matching architecture
label.

In the event that the importer Pod is unschedulable due to the
pullMethod node's node selector, the condition will be propagated to the
DV's running condition until it becomes schedulable.

Signed-off-by: Adi Aloni <aaloni@redhat.com>
Pull Request #3753: DNM: Support architecture specific image import for registry datasource

44 of 62 new or added lines in 6 files covered. (70.97%)

1 existing line in 1 file now uncovered.

16910 of 28487 relevant lines covered (59.36%)

0.66 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

78
type importPodEnvVar struct {
79
        ep                        string
80
        secretName                string
81
        source                    string
82
        contentType               string
83
        imageSize                 string
84
        certConfigMap             string
85
        diskID                    string
86
        uuid                      string
87
        pullMethod                string
88
        readyFile                 string
89
        doneFile                  string
90
        backingFile               string
91
        thumbprint                string
92
        filesystemOverhead        string
93
        insecureTLS               bool
94
        currentCheckpoint         string
95
        previousCheckpoint        string
96
        finalCheckpoint           string
97
        preallocation             bool
98
        httpProxy                 string
99
        httpsProxy                string
100
        noProxy                   string
101
        certConfigMapProxy        string
102
        extraHeaders              []string
103
        secretExtraHeaders        []string
104
        cacheMode                 string
105
        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✔
NEW
412
                        if cond.Type == corev1.PodScheduled && cond.Reason == corev1.PodReasonUnschedulable {
×
NEW
413
                                anno[cc.AnnPodSchedulable] = "false"
×
NEW
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✔
551
                return podErr
×
552
        }
×
553

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

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

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

570
        return nil
1✔
571
}
572

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

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

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

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

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

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

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

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

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

663
        return podEnvVar, nil
1✔
664
}
665

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

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

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

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

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

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

707
                return "", err
×
708
        }
709

710
        return value, nil
1✔
711
}
712

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

893
        pod := makeImporterPodSpec(args)
1✔
894

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

1✔
897
        if err = client.Create(context.TODO(), pod); err != nil {
1✔
898
                return nil, err
1✔
899
        }
1✔
900

1✔
901
        log.V(3).Info("importer pod created\n", "pod.Name", pod.Name, "pod.Namespace", pod.Namespace, "image name", args.image)
×
902
        return pod, nil
×
903
}
904

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

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

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

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

1✔
970
        return pod
1✔
971
}
1✔
972

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

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

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

1176
func isRegistryNodeImport(args *importerPodArgs) bool {
1✔
1177
        return cc.GetSource(args.pvc) == cc.SourceRegistry &&
1178
                args.pvc.Annotations[cc.AnnRegistryImportMethod] == string(cdiv1.RegistryPullNode)
1179
}
1✔
1180

1✔
1181
func getOwnerUID(args *importerPodArgs) types.UID {
1✔
1182
        if len(args.pvc.OwnerReferences) == 1 {
1✔
1183
                return args.pvc.OwnerReferences[0].UID
1184
        }
1✔
1185
        return args.pvc.UID
1✔
1186
}
×
1187

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

1✔
1196
func setRegistryNodeImportNodeSelector(args *importerPodArgs) {
1✔
1197
        if args.workloadNodePlacement.NodeSelector == nil {
1✔
1198
                args.workloadNodePlacement.NodeSelector = make(map[string]string, 0)
NEW
1199
        }
×
NEW
1200
        args.workloadNodePlacement.NodeSelector[v1.LabelArchStable] = args.podEnvVar.registryImageArchitecture
×
NEW
1201
}
×
NEW
1202

×
UNCOV
1203
func createConfigMapVolume(certVolName, objRef string) corev1.Volume {
×
1204
        return corev1.Volume{
1205
                Name: certVolName,
1206
                VolumeSource: corev1.VolumeSource{
1✔
1207
                        ConfigMap: &corev1.ConfigMapVolumeSource{
1✔
1208
                                LocalObjectReference: corev1.LocalObjectReference{
1✔
1209
                                        Name: objRef,
1✔
1210
                                },
1✔
1211
                        },
1✔
1212
                },
1✔
1213
        }
1✔
1214
}
1✔
1215

1✔
1216
func createSecretVolume(thisVolName, objRef string) corev1.Volume {
1✔
1217
        return corev1.Volume{
1✔
1218
                Name: thisVolName,
1219
                VolumeSource: corev1.VolumeSource{
×
1220
                        Secret: &corev1.SecretVolumeSource{
×
1221
                                SecretName: objRef,
×
1222
                        },
×
1223
                },
×
1224
        }
×
1225
}
×
1226

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

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

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

© 2026 Coveralls, Inc