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

kubevirt / containerized-data-importer / #5335

18 May 2025 12:30PM UTC coverage: 59.356% (+0.02%) from 59.34%
#5335

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.

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

40 of 54 new or added lines in 5 files covered. (74.07%)

1 existing line in 1 file now uncovered.

16904 of 28479 relevant lines covered (59.36%)

0.66 hits per line

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

71.52
/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
        for _, ev := range pod.Spec.Containers[0].Env {
2✔
410
                if ev.Name == common.CacheMode && ev.Value == common.CacheModeTryNone {
1✔
411
                        anno[cc.AnnRequiresDirectIO] = "false"
×
412
                }
×
413
        }
414

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

560
        return nil
1✔
561
}
562

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

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

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

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

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

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

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

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

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

653
        return podEnvVar, nil
1✔
654
}
655

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

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

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

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

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

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

697
                return "", err
×
698
        }
699

700
        return value, nil
1✔
701
}
702

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

872
        if isRegistryNodeImport(args) {
2✔
873
                args.importImage, err = getRegistryImportImage(args.pvc)
1✔
874
                if err != nil {
1✔
875
                        return nil, err
×
876
                }
×
877
                setRegistryNodeImportEnvVars(args)
1✔
878
                if args.podEnvVar.registryImageArchitecture != "" {
1✔
NEW
879
                        setRegistryNodeImportNodeSelector(args)
×
NEW
880
                }
×
881
        }
882

883
        pod := makeImporterPodSpec(args)
1✔
884

1✔
885
        util.SetRecommendedLabels(pod, installerLabels, "cdi-controller")
1✔
886

1✔
887
        if err = client.Create(context.TODO(), pod); err != nil {
1✔
888
                return nil, err
1✔
889
        }
1✔
890

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

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

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

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

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

1✔
960
        return pod
1✔
961
}
1✔
962

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

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

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

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

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

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

1✔
1186
func setRegistryNodeImportNodeSelector(args *importerPodArgs) {
1✔
1187
        if args.workloadNodePlacement.NodeSelector == nil {
1✔
1188
                args.workloadNodePlacement.NodeSelector = make(map[string]string, 0)
NEW
1189
        }
×
NEW
1190
        args.workloadNodePlacement.NodeSelector[v1.LabelArchStable] = args.podEnvVar.registryImageArchitecture
×
NEW
1191
}
×
NEW
1192

×
UNCOV
1193
func createConfigMapVolume(certVolName, objRef string) corev1.Volume {
×
1194
        return corev1.Volume{
1195
                Name: certVolName,
1196
                VolumeSource: corev1.VolumeSource{
1✔
1197
                        ConfigMap: &corev1.ConfigMapVolumeSource{
1✔
1198
                                LocalObjectReference: corev1.LocalObjectReference{
1✔
1199
                                        Name: objRef,
1✔
1200
                                },
1✔
1201
                        },
1✔
1202
                },
1✔
1203
        }
1✔
1204
}
1✔
1205

1✔
1206
func createSecretVolume(thisVolName, objRef string) corev1.Volume {
1✔
1207
        return corev1.Volume{
1✔
1208
                Name: thisVolName,
1209
                VolumeSource: corev1.VolumeSource{
×
1210
                        Secret: &corev1.SecretVolumeSource{
×
1211
                                SecretName: objRef,
×
1212
                        },
×
1213
                },
×
1214
        }
×
1215
}
×
1216

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

×
1363
func isOOMKilled(status v1.ContainerStatus) bool {
1✔
1364
        if terminated := status.State.Terminated; terminated != nil {
1365
                if terminated.Reason == cc.OOMKilledReason {
1366
                        return true
1✔
1367
                }
2✔
1368
        }
2✔
1369
        if terminated := status.LastTerminationState.Terminated; terminated != nil {
1✔
1370
                if terminated.Reason == cc.OOMKilledReason {
1✔
1371
                        return true
1372
                }
2✔
1373
        }
1✔
1374

×
1375
        return false
×
1376
}
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