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

kubevirt / containerized-data-importer / #4794

14 Jul 2024 06:12PM UTC coverage: 58.983% (+0.01%) from 58.972%
#4794

push

travis-ci

web-flow
update to k8s 1.30 libs and controller-runtime 0.18.4 (#3336)

* make deps-update

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* ReourceRequirements -> VolumeResourceRequirements

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* fix calls to controller.Watch()

controller-runtime changed the API!

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* Fix errors with actual openshift/library-go lib

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* make all works now and everything compiles

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* fix "make update-codegen" because generate_groups.sh deprecated

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* run "make generate"

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* fix transfer unittest because of change to controller-runtime

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

---------

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

6 of 238 new or added lines in 24 files covered. (2.52%)

10 existing lines in 4 files now uncovered.

16454 of 27896 relevant lines covered (58.98%)

0.65 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

168
        return nil
×
169
}
170

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
384
        if statuses := pod.Status.ContainerStatuses; len(statuses) > 0 {
2✔
385
                if isOOMKilled(statuses[0]) {
2✔
386
                        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✔
387
                        podModificationsNeeded = true
1✔
388
                        anno[cc.AnnRequiresDirectIO] = "true"
1✔
389
                }
1✔
390
                if terminated := statuses[0].State.Terminated; terminated != nil && terminated.ExitCode > 0 {
2✔
391
                        log.Info("Pod termination code", "pod.Name", pod.Name, "ExitCode", terminated.ExitCode)
1✔
392
                        r.recorder.Event(pvc, corev1.EventTypeWarning, ErrImportFailedPVC, terminated.Message)
1✔
393
                }
1✔
394
        }
395

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

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

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

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

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

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

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

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

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

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

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

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

514
        podEnvVar, err := r.createImportEnvVar(pvc)
1✔
515
        if err != nil {
1✔
516
                return err
×
517
        }
×
518
        // all checks passed, let's create the importer pod!
519
        podArgs := &importerPodArgs{
1✔
520
                image:             r.image,
1✔
521
                verbose:           r.verbose,
1✔
522
                pullPolicy:        r.pullPolicy,
1✔
523
                podEnvVar:         podEnvVar,
1✔
524
                pvc:               pvc,
1✔
525
                scratchPvcName:    scratchPvcName,
1✔
526
                vddkImageName:     vddkImageName,
1✔
527
                priorityClassName: cc.GetPriorityClass(pvc),
1✔
528
        }
1✔
529

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

536
        r.log.V(1).Info("Created POD", "pod.Name", pod.Name)
1✔
537

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

547
        if requiresScratch {
1✔
548
                r.log.V(1).Info("Pod requires scratch space")
×
549
                return r.createScratchPvcForPod(pvc, pod)
×
550
        }
×
551

552
        return nil
1✔
553
}
554

555
func createScratchNameFromPvc(pvc *v1.PersistentVolumeClaim) string {
×
556
        return naming.GetResourceName(pvc.Name, common.ScratchNameSuffix)
×
557
}
×
558

559
func (r *ImportReconciler) createImportEnvVar(pvc *corev1.PersistentVolumeClaim) (*importPodEnvVar, error) {
1✔
560
        podEnvVar := &importPodEnvVar{}
1✔
561
        podEnvVar.source = cc.GetSource(pvc)
1✔
562
        podEnvVar.contentType = string(cc.GetPVCContentType(pvc))
1✔
563

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

1✔
596
                for annotation, value := range pvc.Annotations {
2✔
597
                        if strings.HasPrefix(annotation, cc.AnnExtraHeaders) {
1✔
598
                                podEnvVar.extraHeaders = append(podEnvVar.extraHeaders, value)
×
599
                        }
×
600
                        if strings.HasPrefix(annotation, cc.AnnSecretExtraHeaders) {
1✔
601
                                podEnvVar.secretExtraHeaders = append(podEnvVar.secretExtraHeaders, value)
×
602
                        }
×
603
                }
604

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

624
        fsOverhead, err := GetFilesystemOverhead(context.TODO(), r.client, pvc)
1✔
625
        if err != nil {
1✔
626
                return nil, err
×
627
        }
×
628
        podEnvVar.filesystemOverhead = string(fsOverhead)
1✔
629

1✔
630
        if preallocation, err := strconv.ParseBool(getValueFromAnnotation(pvc, cc.AnnPreallocationRequested)); err == nil {
1✔
631
                podEnvVar.preallocation = preallocation
×
632
        } // else use the default "false"
×
633

634
        //get the requested image size.
635
        podEnvVar.imageSize, err = cc.GetRequestedImageSize(pvc)
1✔
636
        if err != nil {
1✔
637
                return nil, err
×
638
        }
×
639

640
        if v, ok := pvc.Annotations[cc.AnnRequiresDirectIO]; ok && v == "true" {
2✔
641
                podEnvVar.cacheMode = common.CacheModeTryNone
1✔
642
        }
1✔
643

644
        return podEnvVar, nil
1✔
645
}
646

647
func (r *ImportReconciler) isInsecureTLS(pvc *corev1.PersistentVolumeClaim, cdiConfig *cdiv1.CDIConfig) (bool, error) {
1✔
648
        ep, ok := pvc.Annotations[cc.AnnEndpoint]
1✔
649
        if !ok || ep == "" {
2✔
650
                return false, nil
1✔
651
        }
1✔
652
        return IsInsecureTLS(ep, cdiConfig, r.log)
1✔
653
}
654

655
// IsInsecureTLS checks if TLS security is disabled for the given endpoint
656
func IsInsecureTLS(ep string, cdiConfig *cdiv1.CDIConfig, log logr.Logger) (bool, error) {
1✔
657
        url, err := url.Parse(ep)
1✔
658
        if err != nil {
1✔
659
                return false, err
×
660
        }
×
661

662
        if url.Scheme != "docker" {
2✔
663
                return false, nil
1✔
664
        }
1✔
665

666
        for _, value := range cdiConfig.Spec.InsecureRegistries {
2✔
667
                log.V(1).Info("Checking host against value", "host", url.Host, "value", value)
1✔
668
                if value == url.Host {
2✔
669
                        return true, nil
1✔
670
                }
1✔
671
        }
672
        return false, nil
1✔
673
}
674

675
func (r *ImportReconciler) getCertConfigMap(pvc *corev1.PersistentVolumeClaim) (string, error) {
1✔
676
        value, ok := pvc.Annotations[cc.AnnCertConfigMap]
1✔
677
        if !ok || value == "" {
2✔
678
                return "", nil
1✔
679
        }
1✔
680

681
        configMap := &corev1.ConfigMap{}
1✔
682
        if err := r.uncachedClient.Get(context.TODO(), types.NamespacedName{Name: value, Namespace: pvc.Namespace}, configMap); err != nil {
2✔
683
                if k8serrors.IsNotFound(err) {
2✔
684
                        r.log.V(1).Info("Configmap does not exist, pod will not start until it does", "configMapName", value)
1✔
685
                        return value, nil
1✔
686
                }
1✔
687

688
                return "", err
×
689
        }
690

691
        return value, nil
1✔
692
}
693

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

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

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

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

779
// Get path to VDDK image from 'v2v-vmware' ConfigMap
780
func (r *ImportReconciler) getVddkImageName() (*string, error) {
1✔
781
        namespace := util.GetNamespace()
1✔
782

1✔
783
        cm := &corev1.ConfigMap{}
1✔
784
        err := r.uncachedClient.Get(context.TODO(), types.NamespacedName{Name: common.VddkConfigMap, Namespace: namespace}, cm)
1✔
785
        if k8serrors.IsNotFound(err) {
2✔
786
                return nil, errors.Errorf("No %s ConfigMap present in namespace %s", common.VddkConfigMap, namespace)
1✔
787
        }
1✔
788

789
        image, found := cm.Data[common.VddkConfigDataKey]
1✔
790
        if found {
2✔
791
                msg := fmt.Sprintf("Found %s ConfigMap in namespace %s, VDDK image path is: ", common.VddkConfigMap, namespace)
1✔
792
                r.log.V(1).Info(msg, common.VddkConfigDataKey, image)
1✔
793
                return &image, nil
1✔
794
        }
1✔
795

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

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

815
// getValueFromAnnotation returns the value of an annotation
816
func getValueFromAnnotation(pvc *corev1.PersistentVolumeClaim, annotation string) string {
1✔
817
        return pvc.Annotations[annotation]
1✔
818
}
1✔
819

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

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

839
func createImportPodNameFromPvc(pvc *corev1.PersistentVolumeClaim) string {
1✔
840
        return naming.GetResourceName(common.ImporterPodName, podNameWithCheckpoint(pvc))
1✔
841
}
1✔
842

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

853
        args.imagePullSecrets, err = cc.GetImagePullSecrets(client)
1✔
854
        if err != nil {
1✔
855
                return nil, err
×
856
        }
×
857

858
        args.workloadNodePlacement, err = cc.GetWorkloadNodePlacement(ctx, client)
1✔
859
        if err != nil {
1✔
860
                return nil, err
×
861
        }
×
862

863
        if isRegistryNodeImport(args) {
2✔
864
                args.importImage, err = getRegistryImportImage(args.pvc)
1✔
865
                if err != nil {
1✔
866
                        return nil, err
×
867
                }
×
868
                setRegistryNodeImportEnvVars(args)
1✔
869
        }
870

871
        pod := makeImporterPodSpec(args)
1✔
872

1✔
873
        util.SetRecommendedLabels(pod, installerLabels, "cdi-controller")
1✔
874

1✔
875
        if err = client.Create(context.TODO(), pod); err != nil {
1✔
876
                return nil, err
×
877
        }
×
878

879
        log.V(3).Info("importer pod created\n", "pod.Name", pod.Name, "pod.Namespace", pod.Namespace, "image name", args.image)
1✔
880
        return pod, nil
1✔
881
}
882

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

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

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

942
        cc.CopyAllowedAnnotations(args.pvc, pod)
1✔
943
        cc.SetRestrictedSecurityContext(&pod.Spec)
1✔
944
        // We explicitly define a NodeName for dynamically provisioned PVCs
1✔
945
        // when the PVC is being handled by a populator (PVC')
1✔
946
        cc.SetNodeNameIfPopulator(args.pvc, &pod.Spec)
1✔
947

1✔
948
        return pod
1✔
949
}
950

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

1035
func makeImporterVolumeSpec(args *importerPodArgs) []corev1.Volume {
1✔
1036
        volumes := []corev1.Volume{
1✔
1037
                {
1✔
1038
                        Name: cc.DataVolName,
1✔
1039
                        VolumeSource: corev1.VolumeSource{
1✔
1040
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
1✔
1041
                                        ClaimName: args.pvc.Name,
1✔
1042
                                        ReadOnly:  false,
1✔
1043
                                },
1✔
1044
                        },
1✔
1045
                },
1✔
1046
        }
1✔
1047
        if isRegistryNodeImport(args) {
2✔
1048
                volumes = append(volumes, corev1.Volume{
1✔
1049
                        Name: "shared-volume",
1✔
1050
                        VolumeSource: corev1.VolumeSource{
1✔
1051
                                EmptyDir: &corev1.EmptyDirVolumeSource{},
1✔
1052
                        },
1✔
1053
                })
1✔
1054
        }
1✔
1055
        if args.scratchPvcName != nil {
2✔
1056
                volumes = append(volumes, corev1.Volume{
1✔
1057
                        Name: cc.ScratchVolName,
1✔
1058
                        VolumeSource: corev1.VolumeSource{
1✔
1059
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
1✔
1060
                                        ClaimName: *args.scratchPvcName,
1✔
1061
                                        ReadOnly:  false,
1✔
1062
                                },
1✔
1063
                        },
1✔
1064
                })
1✔
1065
        }
1✔
1066
        if args.vddkImageName != nil {
2✔
1067
                volumes = append(volumes, corev1.Volume{
1✔
1068
                        Name: "vddk-vol-mount",
1✔
1069
                        VolumeSource: corev1.VolumeSource{
1✔
1070
                                EmptyDir: &corev1.EmptyDirVolumeSource{},
1✔
1071
                        },
1✔
1072
                })
1✔
1073
        }
1✔
1074
        if args.podEnvVar.certConfigMap != "" {
1✔
1075
                volumes = append(volumes, createConfigMapVolume(CertVolName, args.podEnvVar.certConfigMap))
×
1076
        }
×
1077
        if args.podEnvVar.certConfigMapProxy != "" {
1✔
1078
                volumes = append(volumes, createConfigMapVolume(ProxyCertVolName, GetImportProxyConfigMapName(args.pvc.Name)))
×
1079
        }
×
1080
        if args.podEnvVar.source == cc.SourceGCS && args.podEnvVar.secretName != "" {
1✔
1081
                volumes = append(volumes, createSecretVolume(SecretVolName, args.podEnvVar.secretName))
×
1082
        }
×
1083
        for index, header := range args.podEnvVar.secretExtraHeaders {
1✔
1084
                volumes = append(volumes, corev1.Volume{
×
1085
                        Name: fmt.Sprintf(secretExtraHeadersVolumeName, index),
×
1086
                        VolumeSource: corev1.VolumeSource{
×
1087
                                Secret: &corev1.SecretVolumeSource{
×
1088
                                        SecretName: header,
×
1089
                                },
×
1090
                        },
×
1091
                })
×
1092
        }
×
1093
        return volumes
1✔
1094
}
1095

1096
func makeImporterInitContainersSpec(args *importerPodArgs) []corev1.Container {
1✔
1097
        var initContainers []corev1.Container
1✔
1098
        if isRegistryNodeImport(args) {
2✔
1099
                initContainers = append(initContainers, corev1.Container{
1✔
1100
                        Name:            "init",
1✔
1101
                        Image:           args.image,
1✔
1102
                        ImagePullPolicy: corev1.PullPolicy(args.pullPolicy),
1✔
1103
                        Command:         []string{"sh", "-c", "cp /usr/bin/cdi-containerimage-server /shared/server"},
1✔
1104
                        VolumeMounts: []corev1.VolumeMount{
1✔
1105
                                {
1✔
1106
                                        MountPath: "/shared",
1✔
1107
                                        Name:      "shared-volume",
1✔
1108
                                },
1✔
1109
                        },
1✔
1110
                })
1✔
1111
        }
1✔
1112
        if args.vddkImageName != nil {
2✔
1113
                initContainers = append(initContainers, corev1.Container{
1✔
1114
                        Name:  "vddk-side-car",
1✔
1115
                        Image: *args.vddkImageName,
1✔
1116
                        VolumeMounts: []corev1.VolumeMount{
1✔
1117
                                {
1✔
1118
                                        Name:      "vddk-vol-mount",
1✔
1119
                                        MountPath: "/opt",
1✔
1120
                                },
1✔
1121
                        },
1✔
1122
                })
1✔
1123
        }
1✔
1124
        if args.podResourceRequirements != nil {
1✔
1125
                for i := range initContainers {
×
1126
                        initContainers[i].Resources = *args.podResourceRequirements
×
1127
                }
×
1128
        }
1129
        return initContainers
1✔
1130
}
1131

1132
func isRegistryNodeImport(args *importerPodArgs) bool {
1✔
1133
        return cc.GetSource(args.pvc) == cc.SourceRegistry &&
1✔
1134
                args.pvc.Annotations[cc.AnnRegistryImportMethod] == string(cdiv1.RegistryPullNode)
1✔
1135
}
1✔
1136

1137
func getOwnerUID(args *importerPodArgs) types.UID {
1✔
1138
        if len(args.pvc.OwnerReferences) == 1 {
1✔
1139
                return args.pvc.OwnerReferences[0].UID
×
1140
        }
×
1141
        return args.pvc.UID
1✔
1142
}
1143

1144
func setRegistryNodeImportEnvVars(args *importerPodArgs) {
1✔
1145
        args.podEnvVar.source = cc.SourceHTTP
1✔
1146
        args.podEnvVar.ep = "http://localhost:8100/disk.img"
1✔
1147
        args.podEnvVar.pullMethod = string(cdiv1.RegistryPullNode)
1✔
1148
        args.podEnvVar.readyFile = "/shared/ready"
1✔
1149
        args.podEnvVar.doneFile = "/shared/done"
1✔
1150
}
1✔
1151

1152
func createConfigMapVolume(certVolName, objRef string) corev1.Volume {
1✔
1153
        return corev1.Volume{
1✔
1154
                Name: certVolName,
1✔
1155
                VolumeSource: corev1.VolumeSource{
1✔
1156
                        ConfigMap: &corev1.ConfigMapVolumeSource{
1✔
1157
                                LocalObjectReference: corev1.LocalObjectReference{
1✔
1158
                                        Name: objRef,
1✔
1159
                                },
1✔
1160
                        },
1✔
1161
                },
1✔
1162
        }
1✔
1163
}
1✔
1164

1165
func createSecretVolume(thisVolName, objRef string) corev1.Volume {
×
1166
        return corev1.Volume{
×
1167
                Name: thisVolName,
×
1168
                VolumeSource: corev1.VolumeSource{
×
1169
                        Secret: &corev1.SecretVolumeSource{
×
1170
                                SecretName: objRef,
×
1171
                        },
×
1172
                },
×
1173
        }
×
1174
}
×
1175

1176
// return the Env portion for the importer container.
1177
func makeImportEnv(podEnvVar *importPodEnvVar, uid types.UID) []corev1.EnvVar {
1✔
1178
        env := []corev1.EnvVar{
1✔
1179
                {
1✔
1180
                        Name:  common.ImporterSource,
1✔
1181
                        Value: podEnvVar.source,
1✔
1182
                },
1✔
1183
                {
1✔
1184
                        Name:  common.ImporterEndpoint,
1✔
1185
                        Value: podEnvVar.ep,
1✔
1186
                },
1✔
1187
                {
1✔
1188
                        Name:  common.ImporterContentType,
1✔
1189
                        Value: podEnvVar.contentType,
1✔
1190
                },
1✔
1191
                {
1✔
1192
                        Name:  common.ImporterImageSize,
1✔
1193
                        Value: podEnvVar.imageSize,
1✔
1194
                },
1✔
1195
                {
1✔
1196
                        Name:  common.OwnerUID,
1✔
1197
                        Value: string(uid),
1✔
1198
                },
1✔
1199
                {
1✔
1200
                        Name:  common.FilesystemOverheadVar,
1✔
1201
                        Value: podEnvVar.filesystemOverhead,
1✔
1202
                },
1✔
1203
                {
1✔
1204
                        Name:  common.InsecureTLSVar,
1✔
1205
                        Value: strconv.FormatBool(podEnvVar.insecureTLS),
1✔
1206
                },
1✔
1207
                {
1✔
1208
                        Name:  common.ImporterDiskID,
1✔
1209
                        Value: podEnvVar.diskID,
1✔
1210
                },
1✔
1211
                {
1✔
1212
                        Name:  common.ImporterUUID,
1✔
1213
                        Value: podEnvVar.uuid,
1✔
1214
                },
1✔
1215
                {
1✔
1216
                        Name:  common.ImporterPullMethod,
1✔
1217
                        Value: podEnvVar.pullMethod,
1✔
1218
                },
1✔
1219
                {
1✔
1220
                        Name:  common.ImporterReadyFile,
1✔
1221
                        Value: podEnvVar.readyFile,
1✔
1222
                },
1✔
1223
                {
1✔
1224
                        Name:  common.ImporterDoneFile,
1✔
1225
                        Value: podEnvVar.doneFile,
1✔
1226
                },
1✔
1227
                {
1✔
1228
                        Name:  common.ImporterBackingFile,
1✔
1229
                        Value: podEnvVar.backingFile,
1✔
1230
                },
1✔
1231
                {
1✔
1232
                        Name:  common.ImporterThumbprint,
1✔
1233
                        Value: podEnvVar.thumbprint,
1✔
1234
                },
1✔
1235
                {
1✔
1236
                        Name:  common.ImportProxyHTTP,
1✔
1237
                        Value: podEnvVar.httpProxy,
1✔
1238
                },
1✔
1239
                {
1✔
1240
                        Name:  common.ImportProxyHTTPS,
1✔
1241
                        Value: podEnvVar.httpsProxy,
1✔
1242
                },
1✔
1243
                {
1✔
1244
                        Name:  common.ImportProxyNoProxy,
1✔
1245
                        Value: podEnvVar.noProxy,
1✔
1246
                },
1✔
1247
                {
1✔
1248
                        Name:  common.ImporterCurrentCheckpoint,
1✔
1249
                        Value: podEnvVar.currentCheckpoint,
1✔
1250
                },
1✔
1251
                {
1✔
1252
                        Name:  common.ImporterPreviousCheckpoint,
1✔
1253
                        Value: podEnvVar.previousCheckpoint,
1✔
1254
                },
1✔
1255
                {
1✔
1256
                        Name:  common.ImporterFinalCheckpoint,
1✔
1257
                        Value: podEnvVar.finalCheckpoint,
1✔
1258
                },
1✔
1259
                {
1✔
1260
                        Name:  common.Preallocation,
1✔
1261
                        Value: strconv.FormatBool(podEnvVar.preallocation),
1✔
1262
                },
1✔
1263
                {
1✔
1264
                        Name:  common.CacheMode,
1✔
1265
                        Value: podEnvVar.cacheMode,
1✔
1266
                },
1✔
1267
        }
1✔
1268
        if podEnvVar.secretName != "" && podEnvVar.source != cc.SourceGCS {
1✔
1269
                env = append(env, corev1.EnvVar{
×
1270
                        Name: common.ImporterAccessKeyID,
×
1271
                        ValueFrom: &corev1.EnvVarSource{
×
1272
                                SecretKeyRef: &corev1.SecretKeySelector{
×
1273
                                        LocalObjectReference: corev1.LocalObjectReference{
×
1274
                                                Name: podEnvVar.secretName,
×
1275
                                        },
×
1276
                                        Key: common.KeyAccess,
×
1277
                                },
×
1278
                        },
×
1279
                }, corev1.EnvVar{
×
1280
                        Name: common.ImporterSecretKey,
×
1281
                        ValueFrom: &corev1.EnvVarSource{
×
1282
                                SecretKeyRef: &corev1.SecretKeySelector{
×
1283
                                        LocalObjectReference: corev1.LocalObjectReference{
×
1284
                                                Name: podEnvVar.secretName,
×
1285
                                        },
×
1286
                                        Key: common.KeySecret,
×
1287
                                },
×
1288
                        },
×
1289
                })
×
1290
        }
×
1291
        if podEnvVar.secretName != "" && podEnvVar.source == cc.SourceGCS {
1✔
1292
                env = append(env, corev1.EnvVar{
×
1293
                        Name:  common.ImporterGoogleCredentialFileVar,
×
1294
                        Value: common.ImporterGoogleCredentialFile,
×
1295
                })
×
1296
        }
×
1297
        if podEnvVar.certConfigMap != "" {
1✔
1298
                env = append(env, corev1.EnvVar{
×
1299
                        Name:  common.ImporterCertDirVar,
×
1300
                        Value: common.ImporterCertDir,
×
1301
                })
×
1302
        }
×
1303
        if podEnvVar.certConfigMapProxy != "" {
1✔
1304
                env = append(env, corev1.EnvVar{
×
1305
                        Name:  common.ImporterProxyCertDirVar,
×
1306
                        Value: common.ImporterProxyCertDir,
×
1307
                })
×
1308
        }
×
1309
        for index, header := range podEnvVar.extraHeaders {
1✔
1310
                env = append(env, corev1.EnvVar{
×
1311
                        Name:  fmt.Sprintf("%s%d", common.ImporterExtraHeader, index),
×
1312
                        Value: header,
×
1313
                })
×
1314
        }
×
1315
        return env
1✔
1316
}
1317

1318
func isOOMKilled(status v1.ContainerStatus) bool {
1✔
1319
        if terminated := status.State.Terminated; terminated != nil {
2✔
1320
                if terminated.Reason == cc.OOMKilledReason {
2✔
1321
                        return true
1✔
1322
                }
1✔
1323
        }
1324
        if terminated := status.LastTerminationState.Terminated; terminated != nil {
2✔
1325
                if terminated.Reason == cc.OOMKilledReason {
1✔
1326
                        return true
×
1327
                }
×
1328
        }
1329

1330
        return false
1✔
1331
}
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