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

kubevirt / containerized-data-importer / #4937

03 Sep 2024 12:06PM UTC coverage: 59.167% (+0.002%) from 59.165%
#4937

push

travis-ci

web-flow
Run bazelisk run //robots/cmd/uploader:uploader -- -workspace /home/prow/go/src/github.com/kubevirt/project-infra/../containerized-data-importer/WORKSPACE -dry-run=false (#3420)

Signed-off-by: kubevirt-bot <kubevirtbot@redhat.com>

16626 of 28100 relevant lines covered (59.17%)

0.65 hits per line

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

70.99
/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
×
160
        if err := importController.Watch(source.Kind(mgr.GetCache(), &corev1.PersistentVolumeClaim{}, &handler.TypedEnqueueRequestForObject[*corev1.PersistentVolumeClaim]{})); err != nil {
×
161
                return err
×
162
        }
×
163
        if err := importController.Watch(source.Kind(mgr.GetCache(), &corev1.Pod{}, handler.TypedEnqueueRequestForOwner[*corev1.Pod](
×
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
        if err := r.client.Update(context.TODO(), pvc); err != nil {
1✔
474
                return err
×
475
        }
×
476
        return nil
1✔
477
}
478

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

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

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

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

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

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

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

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

551
        return nil
1✔
552
}
553

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

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

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

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

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

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

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

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

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

643
        return podEnvVar, nil
1✔
644
}
645

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

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

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

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

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

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

687
                return "", err
×
688
        }
689

690
        return value, nil
1✔
691
}
692

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

870
        pod := makeImporterPodSpec(args)
1✔
871

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

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

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

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

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

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

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

1✔
947
        return pod
1✔
948
}
949

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

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

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

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

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

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

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

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

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

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

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

© 2026 Coveralls, Inc