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

kubevirt / containerized-data-importer / #5391

19 Jun 2025 11:38PM UTC coverage: 59.415% (+0.006%) from 59.409%
#5391

push

travis-ci

web-flow
VEP48: Support architecture specific image import for registry datasource (#3753)

* importer: introduce registry datasource architecture option

This commit introduces preliminary support for a new optional field under the
DV registry source.

The field, platform, has an additional subfield: architecture which
when specified serves as an image index filter to extract a
disk.img only from layers which match it.

If there's a mismatch between requested architecture and
available architectures in the image index the import will fail.

If the requested image is a manifest and not an index the architecture
of the manifest will be compared to the requested architecture and if
they mismatch the import will fail.

API naming has been chosen to mimic the OCI image index spec[1].

[1] https://github.com/opencontainers/image-spec/blob/main/image-index.md#oci-image-index-specification

Signed-off-by: Adi Aloni <aaloni@redhat.com>

* 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>

* docs, image-from-registry: add multi-platform support

Adds a section in the docs about using the platform field for
multi-platform image pull.

Signed-off-by: Adi Aloni <aaloni@redhat.com>

---------

Signed-off-by: Adi Aloni <aaloni@redhat.com>

42 of 61 new or added lines in 6 files covered. (68.85%)

4 existing lines in 1 file now uncovered.

16950 of 28528 relevant lines covered (59.42%)

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
        // add any labels from pvc to the importer pod
1✔
898
        util.MergeLabels(args.pvc.Labels, pod.Labels)
1✔
899

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

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

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

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

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

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

1✔
973
        return pod
1✔
974
}
975

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

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

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

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

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

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

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

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

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

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

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

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

© 2025 Coveralls, Inc