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

kubevirt / containerized-data-importer / #6009

14 May 2026 08:29PM UTC coverage: 49.608% (+0.04%) from 49.568%
#6009

Pull #4137

travis-ci

dsanatar
refactor UpdatePVCBoundContionFromEvents

update logic to prioritize bound condition msg
from prime pvc if one exists. update function
name to reflect this behavior change

no longer set conditions for prime pvc as well,
only the target pvc needs to be updated in order for
events to get propogated to the DV.

extract out event parsing logic to new
getLatestEventMessage helper func.

Signed-off-by: dsanatar <dsanatar@redhat.com>
Assisted-by: Claude <noreply@anthropic.com>
Pull Request #4137: Refactor PVC event propagation for populators

20 of 56 new or added lines in 7 files covered. (35.71%)

38 existing lines in 2 files now uncovered.

14989 of 30215 relevant lines covered (49.61%)

0.56 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
171
        return nil
×
172
}
×
173

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

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

1✔
186
        return (!cc.IsPVCComplete(pvc) || cc.IsMultiStageImportInProgress(pvc)) &&
×
187
                        (checkPVC(pvc, cc.AnnEndpoint, log) || checkPVC(pvc, cc.AnnSource, log)) &&
×
188
                        shouldHandlePvc(pvc, waitForFirstConsumerEnabled, log),
189
                nil
1✔
190
}
1✔
191

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

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

1✔
206
        // only want to update bound condition for relevant type
×
207
        if checkPVC(pvc, cc.AnnEndpoint, log) || checkPVC(pvc, cc.AnnSource, log) {
208
                if err := cc.UpdatePVCBoundContion(pvc, r.client); err != nil {
209
                        return reconcile.Result{}, err
210
                }
2✔
211
        }
1✔
212

×
213
        shouldReconcile, err := r.shouldReconcilePVC(pvc, log)
×
214
        if err != nil {
215
                return reconcile.Result{}, err
216
        }
1✔
217
        if !shouldReconcile {
1✔
218
                multiStageImport := metav1.HasAnnotation(pvc.ObjectMeta, cc.AnnCurrentCheckpoint)
×
219
                multiStageAlreadyDone := metav1.HasAnnotation(pvc.ObjectMeta, cc.AnnMultiStageImportDone)
×
220

2✔
221
                log.V(3).Info("Should not reconcile this PVC",
1✔
222
                        "pvc.annotation.phase.complete", cc.IsPVCComplete(pvc),
1✔
223
                        "pvc.annotations.endpoint", checkPVC(pvc, cc.AnnEndpoint, log),
1✔
224
                        "pvc.annotations.source", checkPVC(pvc, cc.AnnSource, log),
1✔
225
                        "isBound", isBound(pvc, log), "isMultistage", multiStageImport, "multiStageDone", multiStageAlreadyDone)
1✔
226
                return reconcile.Result{}, nil
1✔
227
        }
1✔
228

1✔
229
        return r.reconcilePvc(pvc, log)
1✔
230
}
1✔
231

232
func (r *ImportReconciler) findImporterPod(pvc *corev1.PersistentVolumeClaim, log logr.Logger) (*corev1.Pod, error) {
1✔
233
        podName := getImportPodNameFromPvc(pvc)
234
        pod := &corev1.Pod{}
235
        if err := r.client.Get(context.TODO(), types.NamespacedName{Name: podName, Namespace: pvc.GetNamespace()}, pod); err != nil {
1✔
236
                if !k8serrors.IsNotFound(err) {
1✔
237
                        return nil, errors.Wrapf(err, "error getting import pod %s/%s", pvc.Namespace, podName)
1✔
238
                }
2✔
239
                return nil, nil
1✔
240
        }
×
241
        if !metav1.IsControlledBy(pod, pvc) && !cc.IsImageStream(pvc) {
×
242
                return nil, errors.Errorf("Pod is not owned by PVC")
1✔
243
        }
244
        log.V(1).Info("Pod is owned by PVC", pod.Name, pvc.Name)
2✔
245
        return pod, nil
1✔
246
}
1✔
247

1✔
248
func (r *ImportReconciler) reconcilePvc(pvc *corev1.PersistentVolumeClaim, log logr.Logger) (reconcile.Result, error) {
1✔
249
        // See if we have a pod associated with the PVC, we know the PVC has the needed annotations.
250
        pod, err := r.findImporterPod(pvc, log)
251
        if err != nil {
1✔
252
                return reconcile.Result{}, err
1✔
253
        }
1✔
254

2✔
255
        if pod == nil {
1✔
256
                if cc.IsPVCComplete(pvc) {
1✔
257
                        // Don't create the POD if the PVC is completed already
258
                        log.V(1).Info("PVC is already complete")
2✔
259
                } else if pvc.DeletionTimestamp == nil {
1✔
260
                        podsUsingPVC, err := cc.GetPodsUsingPVCs(context.TODO(), r.client, pvc.Namespace, sets.New(pvc.Name), false)
×
261
                        if err != nil {
×
262
                                return reconcile.Result{}, err
1✔
263
                        }
×
264

×
265
                        if len(podsUsingPVC) > 0 {
2✔
266
                                for _, pod := range podsUsingPVC {
1✔
267
                                        r.log.V(1).Info("can't create import pod, pvc in use by other pod",
1✔
268
                                                "namespace", pvc.Namespace, "name", pvc.Name, "pod", pod.Name)
×
269
                                        r.recorder.Eventf(pvc, corev1.EventTypeWarning, ImportTargetInUse,
×
270
                                                "pod %s/%s using PersistentVolumeClaim %s", pod.Namespace, pod.Name, pvc.Name)
271
                                }
2✔
272
                                return reconcile.Result{Requeue: true}, nil
2✔
273
                        }
1✔
274

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

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

309
                return reconcile.Result{RequeueAfter: 2 * time.Second}, nil
310
        }
311
        return reconcile.Result{}, nil
2✔
312
}
1✔
313

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

×
348
// GetImportProxyConfigMapName returns the import proxy ConfigMap name
×
349
func GetImportProxyConfigMapName(pvcName string) string {
×
350
        return naming.GetResourceName("import-proxy-cm", pvcName)
×
351
}
×
352

353
func (r *ImportReconciler) initPvcPodName(pvc *corev1.PersistentVolumeClaim, log logr.Logger) error {
354
        currentPvcCopy := pvc.DeepCopyObject()
355

×
356
        log.V(1).Info("Init pod name on PVC")
×
357
        anno := pvc.GetAnnotations()
×
358

359
        anno[cc.AnnImportPod] = createImportPodNameFromPvc(pvc)
1✔
360

1✔
361
        requiresScratch := r.requiresScratchSpace(pvc)
1✔
362
        if requiresScratch {
1✔
363
                anno[cc.AnnRequiresScratch] = "true"
1✔
364
        }
1✔
365

1✔
366
        if !reflect.DeepEqual(currentPvcCopy, pvc) {
1✔
367
                if err := r.updatePVC(pvc, log); err != nil {
1✔
368
                        return err
1✔
369
                }
×
370
                log.V(1).Info("Updated PVC", "pvc.anno.AnnImportPod", anno[cc.AnnImportPod])
×
371
        }
372
        return nil
2✔
373
}
1✔
374

×
375
func (r *ImportReconciler) updatePvcFromPod(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod, log logr.Logger) error {
×
376
        // Keep a copy of the original for comparison later.
1✔
377
        currentPvcCopy := pvc.DeepCopyObject()
378

1✔
379
        log.V(1).Info("Updating PVC from pod")
380
        anno := pvc.GetAnnotations()
381

1✔
382
        termMsg, err := parseTerminationMessage(pod)
1✔
383
        if err != nil {
1✔
384
                log.V(3).Info("Ignoring failure to parse termination message", "error", err.Error())
1✔
385
        }
1✔
386
        setAnnotationsFromPodWithPrefix(anno, pod, termMsg, cc.AnnRunningCondition)
1✔
387

1✔
388
        scratchSpaceRequired := termMsg != nil && termMsg.ScratchSpaceRequired != nil && *termMsg.ScratchSpaceRequired
1✔
389
        if scratchSpaceRequired {
2✔
390
                log.V(1).Info("Pod requires scratch space, terminating pod, and restarting with scratch space", "pod.Name", pod.Name)
1✔
391
        }
1✔
392
        podModificationsNeeded := scratchSpaceRequired
1✔
393

1✔
394
        if statuses := pod.Status.ContainerStatuses; len(statuses) > 0 {
1✔
395
                if isOOMKilled(statuses[0]) {
1✔
396
                        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✔
397
                        podModificationsNeeded = true
1✔
398
                        anno[cc.AnnRequiresDirectIO] = "true"
1✔
399
                }
×
400
                if terminated := statuses[0].State.Terminated; terminated != nil && terminated.ExitCode > 0 {
×
401
                        log.Info("Pod termination code", "pod.Name", pod.Name, "ExitCode", terminated.ExitCode)
×
402
                        r.recorder.Event(pvc, corev1.EventTypeWarning, ErrImportFailedPVC, terminated.Message)
×
403
                }
×
404
        }
×
405

×
406
        if anno[cc.AnnCurrentCheckpoint] != "" {
×
407
                anno[cc.AnnCurrentPodID] = string(pod.ObjectMeta.UID)
×
408
        }
409

410
        anno[cc.AnnImportPod] = pod.Name
1✔
411
        if !podModificationsNeeded {
2✔
412
                // No scratch space required, update the phase based on the pod. If we require scratch space we don't want to update the
1✔
413
                // phase, because the pod might terminate cleanly and mistakenly mark the import complete.
1✔
414
                anno[cc.AnnPodPhase] = string(pod.Status.Phase)
1✔
415
        }
1✔
416

2✔
417
        anno[cc.AnnPodSchedulable] = "true"
2✔
418
        if phase, ok := anno[cc.AnnPodPhase]; ok && phase == string(corev1.PodPending) {
1✔
419
                for _, cond := range pod.Status.Conditions {
1✔
420
                        if cond.Type == corev1.PodScheduled && cond.Reason == corev1.PodReasonUnschedulable {
1✔
421
                                anno[cc.AnnPodSchedulable] = "false"
1✔
422
                                break
423
                        }
2✔
424
                }
1✔
425
        }
1✔
426

1✔
427
        for _, ev := range pod.Spec.Containers[0].Env {
428
                if ev.Name == common.CacheMode && ev.Value == common.CacheModeTryNone {
429
                        anno[cc.AnnRequiresDirectIO] = "false"
1✔
430
                }
×
431
        }
×
432

433
        // Check if the POD is waiting for scratch space, if so create some.
1✔
434
        if pod.Status.Phase == corev1.PodPending && r.requiresScratchSpace(pvc) {
2✔
435
                if err := r.createScratchPvcForPod(pvc, pod); err != nil {
1✔
436
                        if !k8serrors.IsAlreadyExists(err) {
1✔
437
                                return err
1✔
438
                        }
1✔
439
                }
440
        } else {
1✔
441
                // No scratch space, or scratch space is bound, remove annotation
2✔
442
                if anno[cc.AnnBoundConditionReason] == creatingScratch {
1✔
NEW
443
                        delete(anno, cc.AnnBoundCondition)
×
NEW
444
                        delete(anno, cc.AnnBoundConditionMessage)
×
NEW
445
                        delete(anno, cc.AnnBoundConditionReason)
×
446
                }
447
        }
448

449
        if pvc.GetLabels() == nil {
450
                pvc.SetLabels(make(map[string]string, 0))
2✔
451
        }
1✔
UNCOV
452
        if !checkIfLabelExists(pvc, common.CDILabelKey, common.CDILabelValue) {
×
UNCOV
453
                pvc.GetLabels()[common.CDILabelKey] = common.CDILabelValue
×
454
        }
455
        if cc.IsPVCComplete(pvc) {
456
                pvc.SetLabels(addLabelsFromTerminationMessage(pvc.GetLabels(), termMsg))
457
        }
2✔
458

1✔
UNCOV
459
        if !reflect.DeepEqual(currentPvcCopy, pvc) {
×
UNCOV
460
                if err := r.updatePVC(pvc, log); err != nil {
×
461
                        return err
×
462
                }
463
                log.V(1).Info("Updated PVC", "pvc.anno.Phase", anno[cc.AnnPodPhase], "pvc.anno.Restarts", anno[cc.AnnPodRestarts])
1✔
464
        }
1✔
465

1✔
UNCOV
466
        if cc.IsPVCComplete(pvc) || podModificationsNeeded {
×
UNCOV
467
                if !podModificationsNeeded {
×
UNCOV
468
                        r.recorder.Event(pvc, corev1.EventTypeNormal, ImportSucceededPVC, "Import Successful")
×
UNCOV
469
                        log.V(1).Info("Import completed successfully")
×
470
                }
471
                if cc.ShouldDeletePod(pvc) {
472
                        log.V(1).Info("Deleting pod", "pod.Name", pod.Name)
2✔
473
                        if err := r.cleanup(pvc, pod, log); err != nil {
1✔
474
                                return err
1✔
475
                        }
2✔
476
                }
1✔
477
        }
1✔
478
        return nil
2✔
479
}
1✔
480

1✔
481
func (r *ImportReconciler) cleanup(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod, log logr.Logger) error {
482
        if err := r.client.Delete(context.TODO(), pod); cc.IgnoreNotFound(err) != nil {
2✔
483
                return err
1✔
484
        }
×
485
        if cc.HasFinalizer(pvc, importPodImageStreamFinalizer) {
×
486
                cc.RemoveFinalizer(pvc, importPodImageStreamFinalizer)
1✔
487
                if err := r.updatePVC(pvc, log); err != nil {
488
                        return err
489
                }
490
        }
1✔
491
        return nil
×
492
}
×
493

×
494
func (r *ImportReconciler) updatePVC(pvc *corev1.PersistentVolumeClaim, log logr.Logger) error {
×
495
        if err := r.client.Update(context.TODO(), pvc); err != nil {
×
496
                return err
497
        }
498
        return nil
2✔
499
}
2✔
500

1✔
501
func (r *ImportReconciler) createImporterPod(pvc *corev1.PersistentVolumeClaim) error {
1✔
502
        r.log.V(1).Info("Creating importer POD for PVC", "pvc.Name", pvc.Name)
1✔
503
        var scratchPvcName *string
2✔
504
        var vddkImageName *string
1✔
505
        var vddkExtraArgs *string
1✔
506
        var err error
×
507

×
508
        requiresScratch := r.requiresScratchSpace(pvc)
509
        if requiresScratch {
510
                name := createScratchNameFromPvc(pvc)
1✔
511
                scratchPvcName = &name
512
        }
513

1✔
514
        if cc.GetSource(pvc) == cc.SourceVDDK {
1✔
515
                r.log.V(1).Info("Pod requires VDDK sidecar for VMware transfer")
×
516
                anno := pvc.GetAnnotations()
×
517
                if imageName, ok := anno[cc.AnnVddkInitImageURL]; ok {
1✔
518
                        vddkImageName = &imageName
×
519
                } else {
×
520
                        if vddkImageName, err = r.getVddkImageName(); err != nil {
×
521
                                r.log.V(1).Error(err, "failed to get VDDK image name from configmap")
×
522
                        }
523
                }
1✔
524
                if vddkImageName == nil {
525
                        message := fmt.Sprintf("waiting for %s configmap or %s annotation for VDDK image", common.VddkConfigMap, cc.AnnVddkInitImageURL)
526
                        anno[cc.AnnBoundCondition] = "false"
1✔
527
                        anno[cc.AnnBoundConditionMessage] = message
1✔
528
                        anno[cc.AnnBoundConditionReason] = common.AwaitingVDDK
×
529
                        if err := r.updatePVC(pvc, r.log); err != nil {
×
530
                                return err
1✔
531
                        }
532
                        return errors.New(message)
533
                }
1✔
534

1✔
535
                if extraArgs, ok := anno[cc.AnnVddkExtraArgs]; ok && extraArgs != "" {
1✔
536
                        r.log.V(1).Info("Mounting extra VDDK args ConfigMap to importer pod", "ConfigMap", extraArgs)
1✔
537
                        vddkExtraArgs = &extraArgs
1✔
538
                }
1✔
539
        }
1✔
540

1✔
541
        podEnvVar, err := r.createImportEnvVar(pvc)
1✔
542
        if err != nil {
1✔
543
                return err
×
544
        }
×
545
        // all checks passed, let's create the importer pod!
×
546
        podArgs := &importerPodArgs{
547
                image:              r.image,
2✔
548
                verbose:            r.verbose,
1✔
549
                pullPolicy:         r.pullPolicy,
1✔
550
                podEnvVar:          podEnvVar,
2✔
551
                pvc:                pvc,
1✔
552
                scratchPvcName:     scratchPvcName,
2✔
553
                vddkImageName:      vddkImageName,
2✔
554
                vddkExtraArgs:      vddkExtraArgs,
1✔
555
                priorityClassName:  cc.GetPriorityClass(pvc),
1✔
556
                serviceAccountName: cc.GetPodServiceAccount(pvc),
557
        }
2✔
558

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

×
565
        r.log.V(1).Info("Created POD", "pod.Name", pod.Name)
1✔
566

567
        // If importing from image stream, add finalizer. Note we don't watch the importer pod in this case,
568
        // so to prevent a deadlock we add finalizer only if the pod is not retained after completion.
2✔
569
        if cc.IsImageStream(pvc) && pvc.GetAnnotations()[cc.AnnPodRetainAfterCompletion] != "true" {
1✔
570
                cc.AddFinalizer(pvc, importPodImageStreamFinalizer)
1✔
571
                if err := r.updatePVC(pvc, r.log); err != nil {
1✔
572
                        return err
1✔
573
                }
2✔
574
        }
1✔
575

1✔
576
        if requiresScratch {
577
                r.log.V(1).Info("Pod requires scratch space")
578
                return r.createScratchPvcForPod(pvc, pod)
579
        }
1✔
580

1✔
581
        return nil
×
582
}
×
583

584
func createScratchNameFromPvc(pvc *v1.PersistentVolumeClaim) string {
1✔
585
        return naming.GetResourceName(pvc.Name, common.ScratchNameSuffix)
1✔
586
}
1✔
587

1✔
588
func (r *ImportReconciler) createImportEnvVar(pvc *corev1.PersistentVolumeClaim) (*importPodEnvVar, error) {
1✔
589
        podEnvVar := &importPodEnvVar{}
1✔
590
        podEnvVar.source = cc.GetSource(pvc)
1✔
591
        podEnvVar.contentType = string(cc.GetPVCContentType(pvc))
1✔
592

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

×
626
                for annotation, value := range pvc.Annotations {
627
                        if strings.HasPrefix(annotation, cc.AnnExtraHeaders) {
1✔
628
                                podEnvVar.extraHeaders = append(podEnvVar.extraHeaders, value)
1✔
629
                        }
1✔
630
                        if strings.HasPrefix(annotation, cc.AnnSecretExtraHeaders) {
1✔
631
                                podEnvVar.secretExtraHeaders = append(podEnvVar.secretExtraHeaders, value)
1✔
632
                        }
1✔
633
                }
2✔
634

1✔
635
                var field string
1✔
636
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyHTTP); err != nil {
×
637
                        r.log.V(3).Info("no proxy http url will be supplied:", "error", err.Error())
×
638
                }
1✔
639
                podEnvVar.httpProxy = field
2✔
640
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyHTTPS); err != nil {
1✔
641
                        r.log.V(3).Info("no proxy https url will be supplied:", "error", err.Error())
1✔
642
                }
643
                podEnvVar.httpsProxy = field
1✔
644
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyNoProxy); err != nil {
1✔
645
                        r.log.V(3).Info("the noProxy field will not be supplied:", "error", err.Error())
1✔
646
                }
×
647
                podEnvVar.noProxy = field
×
648
                if field, err = GetImportProxyConfig(cdiConfig, common.ImportProxyConfigMapName); err != nil {
1✔
649
                        r.log.V(3).Info("no proxy CA certiticate will be supplied:", "error", err.Error())
1✔
650
                }
×
651
                podEnvVar.certConfigMapProxy = field
×
652
        }
1✔
653

1✔
654
        fsOverhead, err := GetFilesystemOverhead(context.TODO(), r.client, pvc)
×
655
        if err != nil {
×
656
                return nil, err
1✔
657
        }
1✔
658
        podEnvVar.filesystemOverhead = string(fsOverhead)
1✔
659

1✔
660
        if preallocation, err := strconv.ParseBool(getValueFromAnnotation(pvc, cc.AnnPreallocationRequested)); err == nil {
1✔
661
                podEnvVar.preallocation = preallocation
1✔
662
        } // else use the default "false"
1✔
663

1✔
664
        //get the requested image size.
1✔
665
        podEnvVar.imageSize, err = cc.GetRequestedImageSize(pvc)
1✔
666
        if err != nil {
2✔
667
                return nil, err
1✔
668
        }
×
669

×
670
        if v, ok := pvc.Annotations[cc.AnnRequiresDirectIO]; ok && v == "true" {
1✔
671
                podEnvVar.cacheMode = common.CacheModeTryNone
×
672
        }
×
673

674
        return podEnvVar, nil
675
}
1✔
676

2✔
677
func (r *ImportReconciler) isInsecureTLS(pvc *corev1.PersistentVolumeClaim, cdiConfig *cdiv1.CDIConfig) (bool, error) {
1✔
678
        // Check if insecureSkipVerify annotation is set (only applicable for ImageIO sources)
1✔
679
        source, sourceOk := pvc.Annotations[cc.AnnSource]
1✔
680
        if sourceOk && source == cc.SourceImageio {
2✔
681
                if insecureSkipVerify, ok := pvc.Annotations[cc.AnnInsecureSkipVerify]; ok && insecureSkipVerify == "true" {
1✔
682
                        return true, nil
1✔
683
                }
1✔
684
        }
2✔
685

1✔
686
        ep, ok := pvc.Annotations[cc.AnnEndpoint]
1✔
687
        if !ok || ep == "" {
1✔
688
                return false, nil
2✔
689
        }
1✔
690
        return IsInsecureTLS(ep, cdiConfig, r.log)
1✔
691
}
1✔
692

693
// IsInsecureTLS checks if TLS security is disabled for the given endpoint
694
func IsInsecureTLS(ep string, cdiConfig *cdiv1.CDIConfig, log logr.Logger) (bool, error) {
1✔
695
        url, err := url.Parse(ep)
1✔
696
        if err != nil {
×
697
                return false, err
×
698
        }
1✔
699

1✔
700
        if url.Scheme != "docker" {
1✔
701
                return false, nil
×
702
        }
×
703

704
        for _, value := range cdiConfig.Spec.InsecureRegistries {
705
                log.V(1).Info("Checking host against value", "host", url.Host, "value", value)
1✔
706
                if value == url.Host {
1✔
707
                        return true, nil
×
708
                }
×
709
        }
710
        return false, nil
2✔
711
}
1✔
712

1✔
713
func (r *ImportReconciler) getCertConfigMap(pvc *corev1.PersistentVolumeClaim) (string, error) {
714
        value, ok := pvc.Annotations[cc.AnnCertConfigMap]
1✔
715
        if !ok || value == "" {
716
                return "", nil
717
        }
1✔
718

1✔
719
        configMap := &corev1.ConfigMap{}
1✔
720
        if err := r.uncachedClient.Get(context.TODO(), types.NamespacedName{Name: value, Namespace: pvc.Namespace}, configMap); err != nil {
1✔
721
                if k8serrors.IsNotFound(err) {
×
722
                        r.log.V(1).Info("Configmap does not exist, pod will not start until it does", "configMapName", value)
×
723
                        return value, nil
×
724
                }
725

726
                return "", err
1✔
727
        }
2✔
728

1✔
729
        return value, nil
1✔
730
}
1✔
731

732
// returns the name of the secret containing endpoint credentials consumed by the importer pod.
733
// A value of "" implies there are no credentials for the endpoint being used. A returned error
734
// causes processNextItem() to stop.
1✔
735
func (r *ImportReconciler) getSecretName(pvc *corev1.PersistentVolumeClaim) string {
1✔
736
        ns := pvc.Namespace
1✔
737
        name, found := pvc.Annotations[cc.AnnSecret]
×
738
        if !found || name == "" {
×
739
                msg := "getEndpointSecret: "
740
                if !found {
2✔
741
                        msg += fmt.Sprintf("annotation %q is missing in pvc \"%s/%s\"", cc.AnnSecret, ns, pvc.Name)
1✔
742
                } else {
1✔
743
                        msg += fmt.Sprintf("secret name is missing from annotation %q in pvc \"%s/%s\"", cc.AnnSecret, ns, pvc.Name)
744
                }
2✔
745
                r.log.V(2).Info(msg)
1✔
746
                return "" // importer pod will not contain secret credentials
2✔
747
        }
1✔
748
        return name
1✔
749
}
750

1✔
751
func (r *ImportReconciler) requiresScratchSpace(pvc *corev1.PersistentVolumeClaim) bool {
752
        scratchRequired := false
753
        contentType := cc.GetPVCContentType(pvc)
1✔
754
        // All archive requires scratch space.
1✔
755
        if contentType == cdiv1.DataVolumeArchive {
2✔
756
                scratchRequired = true
1✔
757
        } else {
1✔
758
                switch cc.GetSource(pvc) {
759
                case cc.SourceGlance:
1✔
760
                        scratchRequired = true
2✔
761
                case cc.SourceImageio:
2✔
762
                        if val, ok := pvc.Annotations[cc.AnnCurrentCheckpoint]; ok {
1✔
763
                                scratchRequired = val != ""
1✔
764
                        }
1✔
765
                case cc.SourceRegistry:
766
                        scratchRequired = pvc.Annotations[cc.AnnRegistryImportMethod] != string(cdiv1.RegistryPullNode)
×
767
                }
768
        }
769
        value, ok := pvc.Annotations[cc.AnnRequiresScratch]
1✔
770
        if ok {
771
                boolVal, _ := strconv.ParseBool(value)
772
                scratchRequired = scratchRequired || boolVal
773
        }
774
        return scratchRequired
775
}
1✔
776

1✔
777
func (r *ImportReconciler) createScratchPvcForPod(pvc *corev1.PersistentVolumeClaim, pod *corev1.Pod) error {
1✔
778
        scratchPvc := &corev1.PersistentVolumeClaim{}
2✔
779
        scratchPVCName, exists := getScratchNameFromPod(pod)
1✔
780
        if !exists {
2✔
781
                return errors.New("Scratch Volume not configured for pod")
1✔
782
        }
1✔
783
        anno := pvc.GetAnnotations()
×
784
        err := r.client.Get(context.TODO(), types.NamespacedName{Namespace: pvc.GetNamespace(), Name: scratchPVCName}, scratchPvc)
×
785
        if cc.IgnoreNotFound(err) != nil {
1✔
786
                return err
1✔
787
        }
788
        if k8serrors.IsNotFound(err) {
1✔
789
                r.log.V(1).Info("Creating scratch space for POD and PVC", "pod.Name", pod.Name, "pvc.Name", pvc.Name)
790

791
                storageClassName := GetScratchPvcStorageClass(r.client, pvc)
1✔
792
                // Scratch PVC doesn't exist yet, create it. Determine which storage class to use.
1✔
793
                _, err = createScratchPersistentVolumeClaim(r.client, pvc, pod, scratchPVCName, storageClassName, r.installerLabels, r.recorder)
1✔
794
                if err != nil {
1✔
795
                        return err
1✔
796
                }
×
797
                anno[cc.AnnBoundCondition] = "false"
1✔
798
                anno[cc.AnnBoundConditionMessage] = "Creating scratch space"
1✔
799
                anno[cc.AnnBoundConditionReason] = creatingScratch
×
800
        } else {
×
801
                if scratchPvc.DeletionTimestamp != nil {
×
802
                        // Delete the pod since we are in a deadlock situation now. The scratch PVC from the previous import is not gone
×
803
                        // yet but terminating, and the new pod is still being created and the scratch PVC now has a finalizer on it.
×
804
                        // Only way to break it, is to delete the importer pod, and give the pvc a chance to disappear.
×
805
                        err = r.client.Delete(context.TODO(), pod)
1✔
806
                        if err != nil {
1✔
807
                                return err
808
                        }
809
                        return fmt.Errorf("terminating scratch space found, deleting pod %s", pod.Name)
1✔
810
                }
2✔
811
        }
1✔
812
        anno[cc.AnnRequiresScratch] = "false"
1✔
813
        return nil
1✔
814
}
1✔
815

816
// Get path to VDDK image from 'v2v-vmware' ConfigMap
817
func (r *ImportReconciler) getVddkImageName() (*string, error) {
1✔
818
        namespace := util.GetNamespace()
1✔
819

1✔
820
        cm := &corev1.ConfigMap{}
1✔
821
        err := r.uncachedClient.Get(context.TODO(), types.NamespacedName{Name: common.VddkConfigMap, Namespace: namespace}, cm)
×
822
        if k8serrors.IsNotFound(err) {
×
823
                return nil, errors.Errorf("No %s ConfigMap present in namespace %s", common.VddkConfigMap, namespace)
1✔
824
        }
1✔
825

1✔
826
        image, found := cm.Data[common.VddkConfigDataKey]
×
827
        if found {
×
828
                msg := fmt.Sprintf("Found %s ConfigMap in namespace %s, VDDK image path is: ", common.VddkConfigMap, namespace)
2✔
829
                r.log.V(1).Info(msg, common.VddkConfigDataKey, image)
1✔
830
                return &image, nil
1✔
831
        }
1✔
832

1✔
833
        return nil, errors.Errorf("found %s ConfigMap in namespace %s, but it does not contain a '%s' entry", common.VddkConfigMap, namespace, common.VddkConfigDataKey)
1✔
834
}
1✔
835

×
836
// returns the import image part of the endpoint string
×
837
func getRegistryImportImage(pvc *corev1.PersistentVolumeClaim) (string, error) {
1✔
838
        ep, err := cc.GetEndpoint(pvc)
1✔
839
        if err != nil {
1✔
840
                return "", nil
×
841
        }
×
842
        if cc.IsImageStream(pvc) {
×
843
                return ep, nil
×
844
        }
×
845
        url, err := url.Parse(ep)
×
846
        if err != nil {
×
847
                return "", errors.Errorf("illegal registry endpoint %s", ep)
×
848
        }
×
849
        return url.Host + url.Path, nil
×
850
}
851

852
// getValueFromAnnotation returns the value of an annotation
1✔
853
func getValueFromAnnotation(pvc *corev1.PersistentVolumeClaim, annotation string) string {
1✔
854
        return pvc.Annotations[annotation]
855
}
856

857
// If this pod is going to transfer one checkpoint in a multi-stage import, attach the checkpoint name to the pod name so
1✔
858
// that each checkpoint gets a unique pod. That way each pod can be inspected using the retainAfterCompletion annotation.
1✔
859
func podNameWithCheckpoint(pvc *corev1.PersistentVolumeClaim) string {
1✔
860
        if checkpoint := pvc.Annotations[cc.AnnCurrentCheckpoint]; checkpoint != "" {
1✔
861
                return pvc.Name + "-checkpoint-" + checkpoint
1✔
862
        }
2✔
863
        return pvc.Name
1✔
864
}
1✔
865

866
func getImportPodNameFromPvc(pvc *corev1.PersistentVolumeClaim) string {
1✔
867
        podName, ok := pvc.Annotations[cc.AnnImportPod]
2✔
868
        if ok {
1✔
869
                return podName
1✔
870
        }
1✔
871
        // fallback to legacy naming, in fact the following function is fully compatible with legacy
1✔
872
        // name concatenation "importer-{pvc.Name}" if the name length is under the size limits,
873
        return naming.GetResourceName(common.ImporterPodName, podNameWithCheckpoint(pvc))
×
874
}
875

876
func createImportPodNameFromPvc(pvc *corev1.PersistentVolumeClaim) string {
877
        return naming.GetResourceName(common.ImporterPodName, podNameWithCheckpoint(pvc))
878
}
1✔
879

1✔
880
// createImporterPod creates and returns a pointer to a pod which is created based on the passed-in endpoint, secret
2✔
881
// name, and pvc. A nil secret means the endpoint credentials are not passed to the
2✔
882
// importer pod.
1✔
883
func createImporterPod(ctx context.Context, log logr.Logger, client client.Client, args *importerPodArgs, installerLabels map[string]string) (*corev1.Pod, error) {
1✔
884
        var err error
×
885
        args.podResourceRequirements, err = cc.GetDefaultPodResourceRequirements(client)
886
        if err != nil {
1✔
887
                return nil, err
2✔
888
        }
1✔
889

1✔
890
        args.imagePullSecrets, err = cc.GetImagePullSecrets(client)
1✔
891
        if err != nil {
2✔
892
                return nil, err
1✔
893
        }
1✔
894

1✔
895
        args.workloadNodePlacement, err = cc.GetWorkloadNodePlacement(ctx, client)
1✔
896
        if err != nil {
897
                return nil, err
898
        }
899

1✔
900
        if isRegistryNodeImport(args) {
1✔
901
                args.importImage, err = getRegistryImportImage(args.pvc)
1✔
902
                if err != nil {
×
903
                        return nil, err
×
904
                }
1✔
905
                setRegistryNodeImportEnvVars(args)
×
906
                if args.podEnvVar.registryImageArchitecture != "" {
×
907
                        setRegistryNodeImportNodeSelector(args)
1✔
908
                }
1✔
909
        }
×
910

×
911
        pod := makeImporterPodSpec(args)
1✔
912

913
        util.SetRecommendedLabels(pod, installerLabels, "cdi-controller")
914

915
        // add any labels from pvc to the importer pod
1✔
916
        util.MergeLabels(args.pvc.Labels, pod.Labels)
1✔
917

1✔
918
        if err = client.Create(context.TODO(), pod); err != nil {
919
                return nil, err
920
        }
921

1✔
922
        log.V(3).Info("importer pod created\n", "pod.Name", pod.Name, "pod.Namespace", pod.Namespace, "image name", args.image)
2✔
923
        return pod, nil
1✔
924
}
1✔
925

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

2✔
931
        pod := &corev1.Pod{
1✔
932
                TypeMeta: metav1.TypeMeta{
1✔
933
                        Kind:       "Pod",
934
                        APIVersion: "v1",
935
                },
1✔
936
                ObjectMeta: metav1.ObjectMeta{
937
                        Name:      podName,
938
                        Namespace: args.pvc.Namespace,
1✔
939
                        Annotations: map[string]string{
1✔
940
                                cc.AnnCreatedBy: "yes",
1✔
941
                        },
942
                        Labels: map[string]string{
943
                                common.CDILabelKey:        common.CDILabelValue,
944
                                common.CDIComponentLabel:  common.ImporterPodName,
945
                                common.PrometheusLabelKey: common.PrometheusLabelValue,
1✔
946
                        },
1✔
947
                        OwnerReferences: []metav1.OwnerReference{
1✔
948
                                {
1✔
949
                                        APIVersion:         "v1",
×
950
                                        Kind:               "PersistentVolumeClaim",
×
951
                                        Name:               args.pvc.Name,
952
                                        UID:                args.pvc.GetUID(),
1✔
953
                                        BlockOwnerDeletion: ptr.To[bool](true),
1✔
954
                                        Controller:         ptr.To[bool](true),
×
955
                                },
×
956
                        },
957
                },
1✔
958
                Spec: corev1.PodSpec{
1✔
959
                        Containers:         makeImporterContainerSpec(args),
×
960
                        InitContainers:     makeImporterInitContainersSpec(args),
×
961
                        Volumes:            makeImporterVolumeSpec(args),
962
                        RestartPolicy:      corev1.RestartPolicyOnFailure,
2✔
963
                        NodeSelector:       args.workloadNodePlacement.NodeSelector,
1✔
964
                        Tolerations:        args.workloadNodePlacement.Tolerations,
1✔
965
                        Affinity:           args.workloadNodePlacement.Affinity,
×
966
                        PriorityClassName:  args.priorityClassName,
×
967
                        ServiceAccountName: args.serviceAccountName,
1✔
968
                        ImagePullSecrets:   args.imagePullSecrets,
1✔
969
                        // https://kubernetes.io/docs/concepts/services-networking/service/#environment-variables
×
970
                        // Disable service environment variable injection to avoid 'argument list too long'
×
971
                        // errors in namespaces with many Services (each injects ~7 env vars).
972
                        EnableServiceLinks: ptr.To(false),
973
                },
1✔
974
        }
1✔
975

1✔
976
        /**
1✔
977
        FIXME: When registry source is ImageStream, if we set importer pod OwnerReference (to its pvc, like all other cases),
1✔
978
        for some reason (OCP issue?) we get the following error:
2✔
979
                Failed to pull image "imagestream-name": rpc error: code = Unknown
1✔
980
                desc = Error reading manifest latest in docker.io/library/imagestream-name: errors:
1✔
981
                denied: requested access to the resource is denied
×
982
                unauthorized: authentication required
×
983
        When we don't set pod OwnerReferences, all works well.
984
        */
1✔
985
        if isRegistryNodeImport(args) && cc.IsImageStream(args.pvc) {
1✔
986
                pod.OwnerReferences = nil
×
987
                pod.Annotations[cc.AnnOpenShiftImageLookup] = "*"
×
988
        }
1✔
989

990
        cc.CopyAllowedAnnotations(args.pvc, pod)
991
        cc.SetRestrictedSecurityContext(&pod.Spec)
992
        // We explicitly define a NodeName for dynamically provisioned PVCs
1✔
993
        // when the PVC is being handled by a populator (PVC')
1✔
994
        cc.SetNodeNameIfPopulator(args.pvc, &pod.Spec)
1✔
995

×
996
        return pod
×
997
}
998

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

1✔
1091
func makeImporterVolumeSpec(args *importerPodArgs) []corev1.Volume {
1✔
1092
        volumes := []corev1.Volume{
1✔
1093
                {
1✔
1094
                        Name: cc.DataVolName,
1✔
1095
                        VolumeSource: corev1.VolumeSource{
1✔
1096
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
1✔
1097
                                        ClaimName: args.pvc.Name,
1✔
1098
                                        ReadOnly:  false,
1✔
1099
                                },
1✔
1100
                        },
1✔
1101
                },
1✔
1102
        }
2✔
1103
        if isRegistryNodeImport(args) {
1✔
1104
                volumes = append(volumes, corev1.Volume{
2✔
1105
                        Name: "shared-volume",
1✔
1106
                        VolumeSource: corev1.VolumeSource{
1✔
1107
                                EmptyDir: &corev1.EmptyDirVolumeSource{},
2✔
1108
                        },
1✔
1109
                })
1✔
1110
        }
1✔
1111
        if args.scratchPvcName != nil {
1✔
1112
                volumes = append(volumes, corev1.Volume{
1✔
1113
                        Name: cc.ScratchVolName,
1✔
1114
                        VolumeSource: corev1.VolumeSource{
1✔
1115
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
1✔
1116
                                        ClaimName: *args.scratchPvcName,
1✔
1117
                                        ReadOnly:  false,
1✔
1118
                                },
1✔
1119
                        },
1✔
1120
                })
1✔
1121
        }
1✔
1122
        if args.vddkImageName != nil {
1✔
1123
                volumes = append(volumes, corev1.Volume{
1✔
1124
                        Name: "vddk-vol-mount",
1✔
1125
                        VolumeSource: corev1.VolumeSource{
1✔
1126
                                EmptyDir: &corev1.EmptyDirVolumeSource{},
2✔
1127
                        },
1✔
1128
                })
1✔
1129
        }
1✔
1130
        if args.vddkExtraArgs != nil {
1✔
1131
                volumes = append(volumes, corev1.Volume{
1✔
1132
                        Name: common.VddkArgsVolName,
2✔
1133
                        VolumeSource: corev1.VolumeSource{
1✔
1134
                                ConfigMap: &v1.ConfigMapVolumeSource{
1✔
1135
                                        LocalObjectReference: v1.LocalObjectReference{
1✔
1136
                                                Name: *args.vddkExtraArgs,
1✔
1137
                                        },
1✔
1138
                                },
2✔
1139
                        },
1✔
1140
                })
1✔
1141
        }
1✔
1142
        if args.podEnvVar.certConfigMap != "" {
1✔
1143
                volumes = append(volumes, createConfigMapVolume(CertVolName, args.podEnvVar.certConfigMap))
1✔
1144
        }
1✔
1145
        if args.podEnvVar.certConfigMapProxy != "" {
×
1146
                volumes = append(volumes, createConfigMapVolume(ProxyCertVolName, GetImportProxyConfigMapName(args.pvc.Name)))
×
1147
        }
×
1148
        if args.podEnvVar.source == cc.SourceGCS && args.podEnvVar.secretName != "" {
×
1149
                volumes = append(volumes, createSecretVolume(SecretVolName, args.podEnvVar.secretName))
×
1150
        }
1✔
1151
        for index, header := range args.podEnvVar.secretExtraHeaders {
×
1152
                volumes = append(volumes, corev1.Volume{
×
1153
                        Name: fmt.Sprintf(secretExtraHeadersVolumeName, index),
×
1154
                        VolumeSource: corev1.VolumeSource{
×
1155
                                Secret: &corev1.SecretVolumeSource{
×
1156
                                        SecretName: header,
1✔
1157
                                },
×
1158
                        },
×
1159
                })
×
1160
        }
×
1161
        return volumes
×
1162
}
1✔
1163

×
1164
func makeImporterInitContainersSpec(args *importerPodArgs) []corev1.Container {
×
1165
        var initContainers []corev1.Container
×
1166
        if isRegistryNodeImport(args) {
×
1167
                initContainers = append(initContainers, corev1.Container{
×
1168
                        Name:            "init",
1✔
1169
                        Image:           args.image,
×
1170
                        ImagePullPolicy: corev1.PullPolicy(args.pullPolicy),
×
1171
                        Command:         []string{"sh", "-c", "cp /usr/bin/cdi-containerimage-server /shared/server"},
×
1172
                        VolumeMounts: []corev1.VolumeMount{
1173
                                {
1✔
1174
                                        MountPath: "/shared",
1175
                                        Name:      "shared-volume",
1176
                                },
1✔
1177
                        },
1✔
1178
                        TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
1✔
1179
                })
1✔
1180
        }
1✔
1181
        if args.vddkImageName != nil {
1✔
1182
                initContainers = append(initContainers, corev1.Container{
1✔
1183
                        Name:  "vddk-side-car",
1✔
1184
                        Image: *args.vddkImageName,
1✔
1185
                        VolumeMounts: []corev1.VolumeMount{
1✔
1186
                                {
1✔
1187
                                        Name:      "vddk-vol-mount",
1✔
1188
                                        MountPath: "/opt",
2✔
1189
                                },
1✔
1190
                        },
1✔
1191
                        TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
1✔
1192
                })
1✔
1193
        }
1✔
1194
        if args.podResourceRequirements != nil {
1✔
1195
                for i := range initContainers {
1✔
1196
                        initContainers[i].Resources = *args.podResourceRequirements
2✔
1197
                }
1✔
1198
        }
1✔
1199
        return initContainers
1✔
1200
}
1✔
1201

1✔
1202
func isRegistryNodeImport(args *importerPodArgs) bool {
1✔
1203
        return cc.GetSource(args.pvc) == cc.SourceRegistry &&
1✔
1204
                args.pvc.Annotations[cc.AnnRegistryImportMethod] == string(cdiv1.RegistryPullNode)
1✔
1205
}
1✔
1206

1✔
1207
func getOwnerUID(args *importerPodArgs) types.UID {
2✔
1208
        if len(args.pvc.OwnerReferences) == 1 {
1✔
1209
                return args.pvc.OwnerReferences[0].UID
1✔
1210
        }
1✔
1211
        return args.pvc.UID
1✔
1212
}
1✔
1213

1✔
1214
func setRegistryNodeImportEnvVars(args *importerPodArgs) {
1✔
1215
        args.podEnvVar.source = cc.SourceHTTP
2✔
1216
        args.podEnvVar.ep = "http://localhost:8100/disk.img"
1✔
1217
        args.podEnvVar.pullMethod = string(cdiv1.RegistryPullNode)
1✔
1218
        args.podEnvVar.readyFile = "/shared/ready"
1✔
1219
        args.podEnvVar.doneFile = "/shared/done"
1✔
1220
}
1✔
1221

1✔
1222
func setRegistryNodeImportNodeSelector(args *importerPodArgs) {
1✔
1223
        if args.workloadNodePlacement.NodeSelector == nil {
1✔
1224
                args.workloadNodePlacement.NodeSelector = make(map[string]string, 0)
1✔
1225
        }
1✔
1226
        args.workloadNodePlacement.NodeSelector[v1.LabelArchStable] = args.podEnvVar.registryImageArchitecture
1✔
1227
}
1✔
1228

×
1229
func createConfigMapVolume(certVolName, objRef string) corev1.Volume {
×
1230
        return corev1.Volume{
1✔
1231
                Name: certVolName,
×
1232
                VolumeSource: corev1.VolumeSource{
×
1233
                        ConfigMap: &corev1.ConfigMapVolumeSource{
1✔
1234
                                LocalObjectReference: corev1.LocalObjectReference{
×
1235
                                        Name: objRef,
×
1236
                                },
1✔
1237
                        },
×
1238
                },
×
1239
        }
×
1240
}
×
1241

×
1242
func createSecretVolume(thisVolName, objRef string) corev1.Volume {
×
1243
        return corev1.Volume{
×
1244
                Name: thisVolName,
×
1245
                VolumeSource: corev1.VolumeSource{
×
1246
                        Secret: &corev1.SecretVolumeSource{
1✔
1247
                                SecretName: objRef,
1248
                        },
1249
                },
1✔
1250
        }
1✔
1251
}
2✔
1252

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

1✔
1399
func isOOMKilled(status v1.ContainerStatus) bool {
1✔
1400
        if terminated := status.State.Terminated; terminated != nil {
1✔
1401
                if terminated.Reason == cc.OOMKilledReason {
1✔
1402
                        return true
1✔
1403
                }
1✔
1404
        }
1✔
1405
        if terminated := status.LastTerminationState.Terminated; terminated != nil {
1✔
1406
                if terminated.Reason == cc.OOMKilledReason {
1✔
1407
                        return true
1✔
1408
                }
1✔
1409
        }
1✔
1410

1✔
1411
        return false
1✔
1412
}
1✔
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