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

kubevirt / containerized-data-importer / #6012

15 May 2026 08:12PM UTC coverage: 49.573% (-0.04%) from 49.614%
#6012

Pull #4127

travis-ci

mrnold
Use ptr instead of confusing instanceUUID ref.

Signed-off-by: Matthew Arnold <marnold@redhat.com>
Pull Request #4127: VDDK: use instance UUIDs instead of BIOS UUIDs

0 of 23 new or added lines in 1 file covered. (0.0%)

425 existing lines in 5 files now uncovered.

14983 of 30224 relevant lines covered (49.57%)

0.56 hits per line

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

13.24
/pkg/controller/common/util.go
1
/*
2
Copyright 2022 The CDI Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
        http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package common
18

19
import (
20
        "context"
21
        "crypto/rand"
22
        "crypto/rsa"
23
        "crypto/tls"
24
        "fmt"
25
        "io"
26
        "net"
27
        "net/http"
28
        "reflect"
29
        "regexp"
30
        "sort"
31
        "strconv"
32
        "strings"
33
        "sync"
34
        "time"
35

36
        "github.com/go-logr/logr"
37
        snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
38
        ocpconfigv1 "github.com/openshift/api/config/v1"
39
        "github.com/pkg/errors"
40

41
        corev1 "k8s.io/api/core/v1"
42
        storagev1 "k8s.io/api/storage/v1"
43
        extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
44
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
45
        "k8s.io/apimachinery/pkg/api/meta"
46
        "k8s.io/apimachinery/pkg/api/resource"
47
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
48
        "k8s.io/apimachinery/pkg/labels"
49
        "k8s.io/apimachinery/pkg/runtime"
50
        "k8s.io/apimachinery/pkg/types"
51
        "k8s.io/apimachinery/pkg/util/sets"
52
        "k8s.io/client-go/tools/cache"
53
        "k8s.io/client-go/tools/record"
54
        "k8s.io/klog/v2"
55
        "k8s.io/utils/ptr"
56

57
        runtimecache "sigs.k8s.io/controller-runtime/pkg/cache"
58
        "sigs.k8s.io/controller-runtime/pkg/client"
59
        "sigs.k8s.io/controller-runtime/pkg/client/fake"
60

61
        cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
62
        cdiv1utils "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1/utils"
63
        "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned/scheme"
64
        "kubevirt.io/containerized-data-importer/pkg/common"
65
        featuregates "kubevirt.io/containerized-data-importer/pkg/feature-gates"
66
        "kubevirt.io/containerized-data-importer/pkg/token"
67
        "kubevirt.io/containerized-data-importer/pkg/util"
68
        sdkapi "kubevirt.io/controller-lifecycle-operator-sdk/api"
69
)
70

71
const (
72
        // DataVolName provides a const to use for creating volumes in pod specs
73
        DataVolName = "cdi-data-vol"
74

75
        // ScratchVolName provides a const to use for creating scratch pvc volumes in pod specs
76
        ScratchVolName = "cdi-scratch-vol"
77

78
        // AnnAPIGroup is the APIGroup for CDI
79
        AnnAPIGroup = "cdi.kubevirt.io"
80
        // AnnCreatedBy is a pod annotation indicating if the pod was created by the PVC
81
        AnnCreatedBy = AnnAPIGroup + "/storage.createdByController"
82
        // AnnPodPhase is a PVC annotation indicating the related pod progress (phase)
83
        AnnPodPhase = AnnAPIGroup + "/storage.pod.phase"
84
        // AnnPodReady tells whether the pod is ready
85
        AnnPodReady = AnnAPIGroup + "/storage.pod.ready"
86
        // AnnPodRestarts is a PVC annotation that tells how many times a related pod was restarted
87
        AnnPodRestarts = AnnAPIGroup + "/storage.pod.restarts"
88
        // AnnPodSchedulable is a PVC annotation that tells if the Pod is schedulable or not
89
        AnnPodSchedulable = AnnAPIGroup + "/storage.pod.schedulable"
90
        // AnnImportFatalError is a PVC annotation that indicates a fatal import error that should not be retried
91
        AnnImportFatalError = AnnAPIGroup + "/storage.import.fatalError"
92
        // AnnPopulatedFor is a PVC annotation telling the datavolume controller that the PVC is already populated
93
        AnnPopulatedFor = AnnAPIGroup + "/storage.populatedFor"
94
        // AnnPrePopulated is a PVC annotation telling the datavolume controller that the PVC is already populated
95
        AnnPrePopulated = AnnAPIGroup + "/storage.prePopulated"
96
        // AnnPriorityClassName is PVC annotation to indicate the priority class name for importer, cloner and uploader pod
97
        AnnPriorityClassName = AnnAPIGroup + "/storage.pod.priorityclassname"
98
        // AnnPodServiceAccount is a PVC annotation to indicate the service account name for importer and uploader pod
99
        AnnPodServiceAccount = AnnAPIGroup + "/storage.pod.serviceAccountName"
100
        // AnnExternalPopulation annotation marks a PVC as "externally populated", allowing the import-controller to skip it
101
        AnnExternalPopulation = AnnAPIGroup + "/externalPopulation"
102

103
        // AnnPodRetainAfterCompletion is PVC annotation for retaining transfer pods after completion
104
        AnnPodRetainAfterCompletion = AnnAPIGroup + "/storage.pod.retainAfterCompletion"
105

106
        // AnnPreviousCheckpoint provides a const to indicate the previous snapshot for a multistage import
107
        AnnPreviousCheckpoint = AnnAPIGroup + "/storage.checkpoint.previous"
108
        // AnnCurrentCheckpoint provides a const to indicate the current snapshot for a multistage import
109
        AnnCurrentCheckpoint = AnnAPIGroup + "/storage.checkpoint.current"
110
        // AnnFinalCheckpoint provides a const to indicate whether the current checkpoint is the last one
111
        AnnFinalCheckpoint = AnnAPIGroup + "/storage.checkpoint.final"
112
        // AnnCheckpointsCopied is a prefix for recording which checkpoints have already been copied
113
        AnnCheckpointsCopied = AnnAPIGroup + "/storage.checkpoint.copied"
114

115
        // AnnCurrentPodID keeps track of the latest pod servicing this PVC
116
        AnnCurrentPodID = AnnAPIGroup + "/storage.checkpoint.pod.id"
117
        // AnnMultiStageImportDone marks a multi-stage import as totally finished
118
        AnnMultiStageImportDone = AnnAPIGroup + "/storage.checkpoint.done"
119

120
        // AnnPopulatorProgress is a standard annotation that can be used progress reporting
121
        AnnPopulatorProgress = AnnAPIGroup + "/storage.populator.progress"
122

123
        // AnnPreallocationRequested provides a const to indicate whether preallocation should be performed on the PV
124
        AnnPreallocationRequested = AnnAPIGroup + "/storage.preallocation.requested"
125
        // AnnPreallocationApplied provides a const for PVC preallocation annotation
126
        AnnPreallocationApplied = AnnAPIGroup + "/storage.preallocation"
127

128
        // AnnRunningCondition provides a const for the running condition
129
        AnnRunningCondition = AnnAPIGroup + "/storage.condition.running"
130
        // AnnRunningConditionMessage provides a const for the running condition
131
        AnnRunningConditionMessage = AnnAPIGroup + "/storage.condition.running.message"
132
        // AnnRunningConditionReason provides a const for the running condition
133
        AnnRunningConditionReason = AnnAPIGroup + "/storage.condition.running.reason"
134

135
        // AnnBoundCondition provides a const for the running condition
136
        AnnBoundCondition = AnnAPIGroup + "/storage.condition.bound"
137
        // AnnBoundConditionMessage provides a const for the running condition
138
        AnnBoundConditionMessage = AnnAPIGroup + "/storage.condition.bound.message"
139
        // AnnBoundConditionReason provides a const for the running condition
140
        AnnBoundConditionReason = AnnAPIGroup + "/storage.condition.bound.reason"
141

142
        // AnnSourceRunningCondition provides a const for the running condition
143
        AnnSourceRunningCondition = AnnAPIGroup + "/storage.condition.source.running"
144
        // AnnSourceRunningConditionMessage provides a const for the running condition
145
        AnnSourceRunningConditionMessage = AnnAPIGroup + "/storage.condition.source.running.message"
146
        // AnnSourceRunningConditionReason provides a const for the running condition
147
        AnnSourceRunningConditionReason = AnnAPIGroup + "/storage.condition.source.running.reason"
148

149
        // AnnVddkVersion shows the last VDDK library version used by a DV's importer pod
150
        AnnVddkVersion = AnnAPIGroup + "/storage.pod.vddk.version"
151
        // AnnVddkHostConnection shows the last ESX host that serviced a DV's importer pod
152
        AnnVddkHostConnection = AnnAPIGroup + "/storage.pod.vddk.host"
153
        // AnnVddkInitImageURL saves a per-DV VDDK image URL on the PVC
154
        AnnVddkInitImageURL = AnnAPIGroup + "/storage.pod.vddk.initimageurl"
155
        // AnnVddkExtraArgs references a ConfigMap that holds arguments to pass directly to the VDDK library
156
        AnnVddkExtraArgs = AnnAPIGroup + "/storage.pod.vddk.extraargs"
157

158
        // AnnRequiresScratch provides a const for our PVC requiring scratch annotation
159
        AnnRequiresScratch = AnnAPIGroup + "/storage.import.requiresScratch"
160

161
        // AnnRequiresDirectIO provides a const for our PVC requiring direct io annotation (due to OOMs we need to try qemu cache=none)
162
        AnnRequiresDirectIO = AnnAPIGroup + "/storage.import.requiresDirectIo"
163
        // OOMKilledReason provides a value that container runtimes must return in the reason field for an OOMKilled container
164
        OOMKilledReason = "OOMKilled"
165

166
        // AnnContentType provides a const for the PVC content-type
167
        AnnContentType = AnnAPIGroup + "/storage.contentType"
168

169
        // AnnSource provide a const for our PVC import source annotation
170
        AnnSource = AnnAPIGroup + "/storage.import.source"
171
        // AnnEndpoint provides a const for our PVC endpoint annotation
172
        AnnEndpoint = AnnAPIGroup + "/storage.import.endpoint"
173

174
        // AnnSecret provides a const for our PVC secretName annotation
175
        AnnSecret = AnnAPIGroup + "/storage.import.secretName"
176
        // AnnCertConfigMap is the name of a configmap containing tls certs
177
        AnnCertConfigMap = AnnAPIGroup + "/storage.import.certConfigMap"
178
        // AnnRegistryImportMethod provides a const for registry import method annotation
179
        AnnRegistryImportMethod = AnnAPIGroup + "/storage.import.registryImportMethod"
180
        // AnnRegistryImageStream provides a const for registry image stream annotation
181
        AnnRegistryImageStream = AnnAPIGroup + "/storage.import.registryImageStream"
182
        // AnnImportPod provides a const for our PVC importPodName annotation
183
        AnnImportPod = AnnAPIGroup + "/storage.import.importPodName"
184
        // AnnDiskID provides a const for our PVC diskId annotation
185
        AnnDiskID = AnnAPIGroup + "/storage.import.diskId"
186
        // AnnUUID provides a const for our PVC uuid annotation
187
        AnnUUID = AnnAPIGroup + "/storage.import.uuid"
188
        // AnnInsecureSkipVerify provides a const for skipping certificate verification
189
        AnnInsecureSkipVerify = AnnAPIGroup + "/storage.import.insecureSkipVerify"
190
        // AnnBackingFile provides a const for our PVC backing file annotation
191
        AnnBackingFile = AnnAPIGroup + "/storage.import.backingFile"
192
        // AnnThumbprint provides a const for our PVC backing thumbprint annotation
193
        AnnThumbprint = AnnAPIGroup + "/storage.import.vddk.thumbprint"
194
        // AnnExtraHeaders provides a const for our PVC extraHeaders annotation
195
        AnnExtraHeaders = AnnAPIGroup + "/storage.import.extraHeaders"
196
        // AnnSecretExtraHeaders provides a const for our PVC secretExtraHeaders annotation
197
        AnnSecretExtraHeaders = AnnAPIGroup + "/storage.import.secretExtraHeaders"
198
        // AnnChecksum provides a const for our PVC checksum annotation
199
        AnnChecksum = AnnAPIGroup + "/storage.import.checksum"
200
        // AnnRegistryImageArchitecture provides a const for our PVC registryImageArchitecture annotation
201
        AnnRegistryImageArchitecture = AnnAPIGroup + "/storage.import.registryImageArchitecture"
202

203
        // AnnCloneToken is the annotation containing the clone token
204
        AnnCloneToken = AnnAPIGroup + "/storage.clone.token"
205
        // AnnExtendedCloneToken is the annotation containing the long term clone token
206
        AnnExtendedCloneToken = AnnAPIGroup + "/storage.extended.clone.token"
207
        // AnnPermissiveClone annotation allows the clone-controller to skip the clone size validation
208
        AnnPermissiveClone = AnnAPIGroup + "/permissiveClone"
209
        // AnnOwnerUID annotation has the owner UID
210
        AnnOwnerUID = AnnAPIGroup + "/ownerUID"
211
        // AnnCloneType is the comuuted/requested clone type
212
        AnnCloneType = AnnAPIGroup + "/cloneType"
213
        // AnnCloneSourcePod name of the source clone pod
214
        AnnCloneSourcePod = AnnAPIGroup + "/storage.sourceClonePodName"
215

216
        // AnnUploadRequest marks that a PVC should be made available for upload
217
        AnnUploadRequest = AnnAPIGroup + "/storage.upload.target"
218

219
        // AnnCheckStaticVolume checks if a statically allocated PV exists before creating the target PVC.
220
        // If so, PVC is still created but population is skipped
221
        AnnCheckStaticVolume = AnnAPIGroup + "/storage.checkStaticVolume"
222

223
        // AnnPersistentVolumeList is an annotation storing a list of PV names
224
        AnnPersistentVolumeList = AnnAPIGroup + "/storage.persistentVolumeList"
225

226
        // AnnPopulatorKind annotation is added to a PVC' to specify the population kind, so it's later
227
        // checked by the common populator watches.
228
        AnnPopulatorKind = AnnAPIGroup + "/storage.populator.kind"
229
        // AnnUsePopulator annotation indicates if the datavolume population will use populators
230
        AnnUsePopulator = AnnAPIGroup + "/storage.usePopulator"
231

232
        // AnnMinimumSupportedPVCSize annotation on a StorageProfile specifies its minimum supported PVC size
233
        AnnMinimumSupportedPVCSize = AnnAPIGroup + "/minimumSupportedPvcSize"
234
        // AnnUseReadWriteOnceForDataImportCron annotation on a StorageProfile signals that DataImportCron should use RWO for DataImportCron PVCs
235
        AnnUseReadWriteOnceForDataImportCron = AnnAPIGroup + "/useReadWriteOnceForDataImportCron"
236
        // AnnSnapshotClassForDataImportCron annotation on a StorageProfile specifies the VolumeSnapshotClass to use for DataImportCron snapshots
237
        AnnSnapshotClassForDataImportCron = AnnAPIGroup + "/snapshotClassForDataImportCron"
238

239
        // AnnDefaultStorageClass is the annotation indicating that a storage class is the default one
240
        AnnDefaultStorageClass = "storageclass.kubernetes.io/is-default-class"
241
        // AnnDefaultVirtStorageClass is the annotation indicating that a storage class is the default one for virtualization purposes
242
        AnnDefaultVirtStorageClass = "storageclass.kubevirt.io/is-default-virt-class"
243
        // AnnDefaultSnapshotClass is the annotation indicating that a snapshot class is the default one
244
        AnnDefaultSnapshotClass = "snapshot.storage.kubernetes.io/is-default-class"
245

246
        // AnnSourceVolumeMode is the volume mode of the source PVC specified as an annotation on snapshots
247
        AnnSourceVolumeMode = AnnAPIGroup + "/storage.import.sourceVolumeMode"
248
        // AnnAdvisedRestoreSize is the advised restore size for disks restored from the snapshot
249
        AnnAdvisedRestoreSize = AnnAPIGroup + "/storage.import.advisedRestoreSize"
250

251
        // AnnOpenShiftImageLookup is the annotation for OpenShift image stream lookup
252
        AnnOpenShiftImageLookup = "alpha.image.policy.openshift.io/resolve-names"
253

254
        // AnnCloneRequest sets our expected annotation for a CloneRequest
255
        AnnCloneRequest = "k8s.io/CloneRequest"
256
        // AnnCloneOf is used to indicate that cloning was complete
257
        AnnCloneOf = "k8s.io/CloneOf"
258

259
        // AnnPodNetwork is used for specifying Pod Network
260
        AnnPodNetwork = "k8s.v1.cni.cncf.io/networks"
261
        // AnnPodMultusDefaultNetwork is used for specifying default Pod Network
262
        AnnPodMultusDefaultNetwork = "v1.multus-cni.io/default-network"
263
        // AnnPodSidecarInjectionIstio is used for enabling/disabling Pod istio/AspenMesh sidecar injection
264
        AnnPodSidecarInjectionIstio = "sidecar.istio.io/inject"
265
        // AnnPodSidecarInjectionIstioDefault is the default value passed for AnnPodSidecarInjection
266
        AnnPodSidecarInjectionIstioDefault = "false"
267
        // AnnPodSidecarInjectionLinkerd is used to enable/disable linkerd sidecar injection
268
        AnnPodSidecarInjectionLinkerd = "linkerd.io/inject"
269
        // AnnPodSidecarInjectionLinkerdDefault is the default value passed for AnnPodSidecarInjectionLinkerd
270
        AnnPodSidecarInjectionLinkerdDefault = "disabled"
271

272
        // AnnImmediateBinding provides a const to indicate whether immediate binding should be performed on the PV (overrides global config)
273
        AnnImmediateBinding = AnnAPIGroup + "/storage.bind.immediate.requested"
274

275
        // AnnSelectedNode annotation is added to a PVC that has been triggered by scheduler to
276
        // be dynamically provisioned. Its value is the name of the selected node.
277
        AnnSelectedNode = "volume.kubernetes.io/selected-node"
278

279
        // CloneUniqueID is used as a special label to be used when we search for the pod
280
        CloneUniqueID = AnnAPIGroup + "/storage.clone.cloneUniqeId"
281

282
        // CloneSourceInUse is reason for event created when clone source pvc is in use
283
        CloneSourceInUse = "CloneSourceInUse"
284

285
        // CloneComplete message
286
        CloneComplete = "Clone Complete"
287

288
        cloneTokenLeeway = 10 * time.Second
289

290
        // Default value for preallocation option if not defined in DV or CDIConfig
291
        defaultPreallocation = false
292

293
        // ErrStartingPod provides a const to indicate that a pod wasn't able to start without providing sensitive information (reason)
294
        ErrStartingPod = "ErrStartingPod"
295
        // MessageErrStartingPod provides a const to indicate that a pod wasn't able to start without providing sensitive information (message)
296
        MessageErrStartingPod = "Error starting pod '%s': For more information, request access to cdi-deploy logs from your sysadmin"
297
        // ErrClaimNotValid provides a const to indicate a claim is not valid
298
        ErrClaimNotValid = "ErrClaimNotValid"
299
        // ErrExceededQuota provides a const to indicate the claim has exceeded the quota
300
        ErrExceededQuota = "ErrExceededQuota"
301
        // ErrIncompatiblePVC provides a const to indicate a clone is not possible due to an incompatible PVC
302
        ErrIncompatiblePVC = "ErrIncompatiblePVC"
303

304
        // SourceHTTP is the source type HTTP, if unspecified or invalid, it defaults to SourceHTTP
305
        SourceHTTP = "http"
306
        // SourceS3 is the source type S3
307
        SourceS3 = "s3"
308
        // SourceGCS is the source type GCS
309
        SourceGCS = "gcs"
310
        // SourceGlance is the source type of glance
311
        SourceGlance = "glance"
312
        // SourceNone means there is no source.
313
        SourceNone = "none"
314
        // SourceRegistry is the source type of Registry
315
        SourceRegistry = "registry"
316
        // SourceImageio is the source type ovirt-imageio
317
        SourceImageio = "imageio"
318
        // SourceVDDK is the source type of VDDK
319
        SourceVDDK = "vddk"
320

321
        // VolumeSnapshotClassSelected reports that a VolumeSnapshotClass was selected
322
        VolumeSnapshotClassSelected = "VolumeSnapshotClassSelected"
323
        // MessageStorageProfileVolumeSnapshotClassSelected reports that a VolumeSnapshotClass was selected according to StorageProfile
324
        MessageStorageProfileVolumeSnapshotClassSelected = "VolumeSnapshotClass selected according to StorageProfile"
325
        // MessageDefaultVolumeSnapshotClassSelected reports that the default VolumeSnapshotClass was selected
326
        MessageDefaultVolumeSnapshotClassSelected = "Default VolumeSnapshotClass selected"
327
        // MessageFirstVolumeSnapshotClassSelected reports that the first VolumeSnapshotClass was selected
328
        MessageFirstVolumeSnapshotClassSelected = "First VolumeSnapshotClass selected"
329

330
        // ClaimLost reason const
331
        ClaimLost = "ClaimLost"
332
        // NotFound reason const
333
        NotFound = "NotFound"
334

335
        // LabelDefaultInstancetype provides a default VirtualMachine{ClusterInstancetype,Instancetype} that can be used by a VirtualMachine booting from a given PVC
336
        LabelDefaultInstancetype = "instancetype.kubevirt.io/default-instancetype"
337
        // LabelDefaultInstancetypeKind provides a default kind of either VirtualMachineClusterInstancetype or VirtualMachineInstancetype
338
        LabelDefaultInstancetypeKind = "instancetype.kubevirt.io/default-instancetype-kind"
339
        // LabelDefaultPreference provides a default VirtualMachine{ClusterPreference,Preference} that can be used by a VirtualMachine booting from a given PVC
340
        LabelDefaultPreference = "instancetype.kubevirt.io/default-preference"
341
        // LabelDefaultPreferenceKind provides a default kind of either VirtualMachineClusterPreference or VirtualMachinePreference
342
        LabelDefaultPreferenceKind = "instancetype.kubevirt.io/default-preference-kind"
343

344
        // LabelDynamicCredentialSupport specifies if the OS supports updating credentials at runtime.
345
        //nolint:gosec // These are not credentials
346
        LabelDynamicCredentialSupport = "kubevirt.io/dynamic-credentials-support"
347

348
        // LabelExcludeFromVeleroBackup provides a const to indicate whether an object should be excluded from velero backup
349
        LabelExcludeFromVeleroBackup = "velero.io/exclude-from-backup"
350

351
        // ProgressDone this means we are DONE
352
        ProgressDone = "100.0%"
353

354
        // AnnEventSourceKind is the source kind that should be related to events
355
        AnnEventSourceKind = AnnAPIGroup + "/events.source.kind"
356
        // AnnEventSource is the source that should be related to events (namespace/name)
357
        AnnEventSource = AnnAPIGroup + "/events.source"
358

359
        // AnnAllowClaimAdoption is the annotation that allows a claim to be adopted by a DataVolume
360
        AnnAllowClaimAdoption = AnnAPIGroup + "/allowClaimAdoption"
361

362
        // AnnCdiCustomizeComponentHash annotation is a hash of all customizations that live under spec.CustomizeComponents
363
        AnnCdiCustomizeComponentHash = AnnAPIGroup + "/customizer-identifier"
364

365
        // AnnCreatedForDataVolume stores the UID of the datavolume that the PVC was created for
366
        AnnCreatedForDataVolume = AnnAPIGroup + "/createdForDataVolume"
367

368
        // AnnPVCPrimeName annotation is the name of the PVC' that is used to populate the PV which is then rebound to the target PVC
369
        AnnPVCPrimeName = AnnAPIGroup + "/storage.populator.pvcPrime"
370
)
371

372
// Size-detection pod error codes
373
const (
374
        NoErr int = iota
375
        ErrBadArguments
376
        ErrInvalidFile
377
        ErrInvalidPath
378
        ErrBadTermFile
379
        ErrUnknown
380
)
381

382
var (
383
        // BlockMode is raw block device mode
384
        BlockMode = corev1.PersistentVolumeBlock
385
        // FilesystemMode is filesystem device mode
386
        FilesystemMode = corev1.PersistentVolumeFilesystem
387

388
        // DefaultInstanceTypeLabels is a list of currently supported default instance type labels
389
        DefaultInstanceTypeLabels = []string{
390
                LabelDefaultInstancetype,
391
                LabelDefaultInstancetypeKind,
392
                LabelDefaultPreference,
393
                LabelDefaultPreferenceKind,
394
        }
395

396
        apiServerKeyOnce sync.Once
397
        apiServerKey     *rsa.PrivateKey
398

399
        // allowedAnnotations is a list of annotations
400
        // that can be propagated from the pvc/dv to a pod
401
        allowedAnnotations = map[string]string{
402
                AnnPodNetwork:                 "",
403
                AnnPodSidecarInjectionIstio:   AnnPodSidecarInjectionIstioDefault,
404
                AnnPodSidecarInjectionLinkerd: AnnPodSidecarInjectionLinkerdDefault,
405
                AnnPriorityClassName:          "",
406
                AnnPodServiceAccount:          "",
407
                AnnPodMultusDefaultNetwork:    "",
408
        }
409

410
        validLabelsMatch = regexp.MustCompile(`^([\w.]+\.kubevirt.io|kubevirt.io)/[\w.-]+$`)
411

412
        ErrDataSourceMaxDepthReached = errors.New("DataSource reference chain exceeds maximum depth of 1")
413
        ErrDataSourceSelfReference   = errors.New("DataSource cannot self-reference")
414
        ErrDataSourceCrossNamespace  = errors.New("DataSource cannot reference a DataSource in another namespace")
415
)
416

417
// FakeValidator is a fake token validator
418
type FakeValidator struct {
419
        Match     string
420
        Operation token.Operation
421
        Name      string
422
        Namespace string
423
        Resource  metav1.GroupVersionResource
424
        Params    map[string]string
425
}
426

427
// Validate is a fake token validation
428
func (v *FakeValidator) Validate(value string) (*token.Payload, error) {
429
        if value != v.Match {
430
                return nil, fmt.Errorf("token does not match expected")
431
        }
×
432
        resource := metav1.GroupVersionResource{
×
433
                Resource: "persistentvolumeclaims",
×
434
        }
×
435
        return &token.Payload{
×
436
                Name:      v.Name,
×
437
                Namespace: v.Namespace,
×
438
                Operation: token.OperationClone,
×
439
                Resource:  resource,
×
440
                Params:    v.Params,
×
441
        }, nil
×
UNCOV
442
}
×
UNCOV
443

×
UNCOV
444
// MultiTokenValidator is a token validator that can validate both short and long tokens
×
445
type MultiTokenValidator struct {
446
        ShortTokenValidator token.Validator
447
        LongTokenValidator  token.Validator
448
}
449

450
// ValidatePVC validates a PVC
451
func (mtv *MultiTokenValidator) ValidatePVC(source, target *corev1.PersistentVolumeClaim) error {
452
        tok, v := mtv.getTokenAndValidator(target)
453
        return ValidateCloneTokenPVC(tok, v, source, target)
454
}
×
UNCOV
455

×
UNCOV
456
// ValidatePopulator valades a token for a populator
×
457
func (mtv *MultiTokenValidator) ValidatePopulator(vcs *cdiv1.VolumeCloneSource, pvc *corev1.PersistentVolumeClaim) error {
×
458
        if vcs.Namespace == pvc.Namespace {
459
                return nil
460
        }
×
UNCOV
461

×
462
        tok, v := mtv.getTokenAndValidator(pvc)
×
463

×
464
        tokenData, err := v.Validate(tok)
465
        if err != nil {
×
466
                return errors.Wrap(err, "error verifying token")
×
467
        }
×
UNCOV
468

×
469
        var tokenResourceName string
×
470
        switch vcs.Spec.Source.Kind {
×
471
        case "PersistentVolumeClaim":
472
                tokenResourceName = "persistentvolumeclaims"
×
473
        case "VolumeSnapshot":
×
474
                tokenResourceName = "volumesnapshots"
×
UNCOV
475
        }
×
476
        srcName := vcs.Spec.Source.Name
×
477

×
478
        return validateTokenData(tokenData, vcs.Namespace, srcName, pvc.Namespace, pvc.Name, string(pvc.UID), tokenResourceName)
UNCOV
479
}
×
UNCOV
480

×
481
func (mtv *MultiTokenValidator) getTokenAndValidator(pvc *corev1.PersistentVolumeClaim) (string, token.Validator) {
×
482
        v := mtv.LongTokenValidator
483
        tok, ok := pvc.Annotations[AnnExtendedCloneToken]
484
        if !ok {
×
485
                // if token doesn't exist, no prob for same namespace
×
486
                tok = pvc.Annotations[AnnCloneToken]
×
487
                v = mtv.ShortTokenValidator
×
488
        }
×
489
        return tok, v
×
UNCOV
490
}
×
UNCOV
491

×
UNCOV
492
// NewMultiTokenValidator returns a new multi token validator
×
493
func NewMultiTokenValidator(key *rsa.PublicKey) *MultiTokenValidator {
494
        return &MultiTokenValidator{
495
                ShortTokenValidator: NewCloneTokenValidator(common.CloneTokenIssuer, key),
496
                LongTokenValidator:  NewCloneTokenValidator(common.ExtendedCloneTokenIssuer, key),
×
497
        }
×
498
}
×
UNCOV
499

×
UNCOV
500
// NewCloneTokenValidator returns a new token validator
×
501
func NewCloneTokenValidator(issuer string, key *rsa.PublicKey) token.Validator {
×
502
        return token.NewValidator(issuer, key, cloneTokenLeeway)
503
}
UNCOV
504

×
UNCOV
505
// GetRequestedImageSize returns the PVC requested size
×
UNCOV
506
func GetRequestedImageSize(pvc *corev1.PersistentVolumeClaim) (string, error) {
×
507
        pvcSize, found := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
508
        if !found {
509
                return "", errors.Errorf("storage request is missing in pvc \"%s/%s\"", pvc.Namespace, pvc.Name)
1✔
510
        }
1✔
511
        return pvcSize.String(), nil
2✔
512
}
1✔
513

1✔
514
// GetVolumeMode returns the volumeMode from PVC handling default empty value
1✔
515
func GetVolumeMode(pvc *corev1.PersistentVolumeClaim) corev1.PersistentVolumeMode {
516
        return util.ResolveVolumeMode(pvc.Spec.VolumeMode)
517
}
UNCOV
518

×
UNCOV
519
// IsDataVolumeUsingDefaultStorageClass checks if the DataVolume is using the default StorageClass
×
520
func IsDataVolumeUsingDefaultStorageClass(dv *cdiv1.DataVolume) bool {
×
521
        return GetStorageClassFromDVSpec(dv) == nil
522
}
UNCOV
523

×
UNCOV
524
// GetStorageClassFromDVSpec returns the StorageClassName from DataVolume PVC or Storage spec
×
525
func GetStorageClassFromDVSpec(dv *cdiv1.DataVolume) *string {
×
526
        if dv.Spec.PVC != nil {
527
                return dv.Spec.PVC.StorageClassName
528
        }
×
UNCOV
529

×
530
        if dv.Spec.Storage != nil {
×
531
                return dv.Spec.Storage.StorageClassName
×
532
        }
UNCOV
533

×
534
        return nil
×
UNCOV
535
}
×
536

UNCOV
537
// getStorageClassByName looks up the storage class based on the name.
×
538
// If name is nil, it performs fallback to default according to the provided content type
539
// If no storage class is found, returns nil
540
func getStorageClassByName(ctx context.Context, client client.Client, name *string, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
541
        if name == nil {
542
                return getFallbackStorageClass(ctx, client, contentType)
543
        }
1✔
544

2✔
545
        // look up storage class by name
1✔
546
        storageClass := &storagev1.StorageClass{}
1✔
547
        if err := client.Get(ctx, types.NamespacedName{Name: *name}, storageClass); err != nil {
548
                if k8serrors.IsNotFound(err) {
549
                        return nil, nil
×
550
                }
×
551
                klog.V(3).Info("Unable to retrieve storage class", "storage class name", *name)
×
552
                return nil, errors.Errorf("unable to retrieve storage class %s", *name)
×
UNCOV
553
        }
×
UNCOV
554

×
555
        return storageClass, nil
×
556
}
557

UNCOV
558
// GetStorageClassByNameWithK8sFallback looks up the storage class based on the name
×
559
// If name is nil, it looks for the default k8s storage class storageclass.kubernetes.io/is-default-class
560
// If no storage class is found, returns nil
561
func GetStorageClassByNameWithK8sFallback(ctx context.Context, client client.Client, name *string) (*storagev1.StorageClass, error) {
562
        return getStorageClassByName(ctx, client, name, cdiv1.DataVolumeArchive)
563
}
564

1✔
565
// GetStorageClassByNameWithVirtFallback looks up the storage class based on the name
1✔
566
// If name is nil, it looks for the following, in this order:
1✔
567
// default kubevirt storage class (if the caller is interested) storageclass.kubevirt.io/is-default-class
568
// default k8s storage class storageclass.kubernetes.io/is-default-class
569
// If no storage class is found, returns nil
570
func GetStorageClassByNameWithVirtFallback(ctx context.Context, client client.Client, name *string, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
571
        return getStorageClassByName(ctx, client, name, contentType)
572
}
573

1✔
574
// getFallbackStorageClass looks for a default virt/k8s storage class according to the content type
1✔
575
// If no storage class is found, returns nil
1✔
576
func getFallbackStorageClass(ctx context.Context, client client.Client, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
577
        storageClasses := &storagev1.StorageClassList{}
578
        if err := client.List(ctx, storageClasses); err != nil {
579
                klog.V(3).Info("Unable to retrieve available storage classes")
1✔
580
                return nil, errors.New("unable to retrieve storage classes")
1✔
581
        }
1✔
UNCOV
582

×
UNCOV
583
        if GetContentType(contentType) == cdiv1.DataVolumeKubeVirt {
×
UNCOV
584
                if virtSc := GetPlatformDefaultStorageClass(storageClasses, AnnDefaultVirtStorageClass); virtSc != nil {
×
585
                        return virtSc, nil
586
                }
2✔
587
        }
2✔
588
        return GetPlatformDefaultStorageClass(storageClasses, AnnDefaultStorageClass), nil
1✔
589
}
1✔
590

591
// GetPlatformDefaultStorageClass returns the default storage class according to the provided annotation or nil if none found
1✔
592
func GetPlatformDefaultStorageClass(storageClasses *storagev1.StorageClassList, defaultAnnotationKey string) *storagev1.StorageClass {
593
        defaultClasses := []storagev1.StorageClass{}
594

595
        for _, storageClass := range storageClasses.Items {
1✔
596
                if storageClass.Annotations[defaultAnnotationKey] == "true" {
1✔
597
                        defaultClasses = append(defaultClasses, storageClass)
1✔
598
                }
2✔
599
        }
2✔
600

1✔
601
        if len(defaultClasses) == 0 {
1✔
602
                return nil
603
        }
604

2✔
605
        // Primary sort by creation timestamp, newest first
1✔
606
        // Secondary sort by class name, ascending order
1✔
607
        // Follows k8s behavior
608
        // https://github.com/kubernetes/kubernetes/blob/731068288e112c8b5af70f676296cc44661e84f4/pkg/volume/util/storageclass.go#L58-L59
609
        sort.Slice(defaultClasses, func(i, j int) bool {
610
                if defaultClasses[i].CreationTimestamp.UnixNano() == defaultClasses[j].CreationTimestamp.UnixNano() {
611
                        return defaultClasses[i].Name < defaultClasses[j].Name
612
                }
2✔
613
                return defaultClasses[i].CreationTimestamp.UnixNano() > defaultClasses[j].CreationTimestamp.UnixNano()
2✔
614
        })
1✔
615
        if len(defaultClasses) > 1 {
1✔
616
                klog.V(3).Infof("%d default StorageClasses were found, choosing: %s", len(defaultClasses), defaultClasses[0].Name)
1✔
617
        }
618

2✔
619
        return &defaultClasses[0]
1✔
620
}
1✔
621

622
// GetFilesystemOverheadForStorageClass determines the filesystem overhead defined in CDIConfig for the storageClass.
1✔
623
func GetFilesystemOverheadForStorageClass(ctx context.Context, client client.Client, storageClassName *string) (cdiv1.Percent, error) {
624
        if storageClassName != nil && *storageClassName == "" {
625
                klog.V(3).Info("No storage class name passed")
626
                return "0", nil
×
627
        }
×
UNCOV
628

×
629
        cdiConfig := &cdiv1.CDIConfig{}
×
630
        if err := client.Get(ctx, types.NamespacedName{Name: common.ConfigName}, cdiConfig); err != nil {
×
631
                if k8serrors.IsNotFound(err) {
632
                        klog.V(1).Info("CDIConfig does not exist, pod will not start until it does")
×
633
                        return "0", nil
×
634
                }
×
635
                return "0", err
×
UNCOV
636
        }
×
UNCOV
637

×
638
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(ctx, client, storageClassName)
×
639
        if err != nil || targetStorageClass == nil {
640
                klog.V(3).Info("Storage class", storageClassName, "not found, trying default storage class")
641
                targetStorageClass, err = GetStorageClassByNameWithK8sFallback(ctx, client, nil)
×
642
                if err != nil {
×
643
                        klog.V(3).Info("No default storage class found, continuing with global overhead")
×
644
                        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
645
                }
×
UNCOV
646
        }
×
UNCOV
647

×
648
        if cdiConfig.Status.FilesystemOverhead == nil {
×
649
                klog.Errorf("CDIConfig filesystemOverhead used before config controller ran reconcile. Hopefully this only happens during unit testing.")
650
                return "0", nil
651
        }
×
UNCOV
652

×
653
        if targetStorageClass == nil {
×
654
                klog.V(3).Info("Storage class", storageClassName, "not found, continuing with global overhead")
×
655
                return cdiConfig.Status.FilesystemOverhead.Global, nil
656
        }
×
UNCOV
657

×
658
        klog.V(3).Info("target storage class for overhead", targetStorageClass.GetName())
×
659

×
660
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
661

×
662
        storageClassOverhead, found := perStorageConfig[targetStorageClass.GetName()]
×
663
        if found {
×
664
                return storageClassOverhead, nil
×
665
        }
×
UNCOV
666

×
667
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
UNCOV
668
}
×
669

UNCOV
670
// GetDefaultPodResourceRequirements gets default pod resource requirements from cdi config status
×
671
func GetDefaultPodResourceRequirements(client client.Client) (*corev1.ResourceRequirements, error) {
672
        cdiconfig := &cdiv1.CDIConfig{}
673
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
674
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
675
                return nil, err
×
676
        }
×
UNCOV
677

×
678
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
UNCOV
679
}
×
680

UNCOV
681
// GetImagePullSecrets gets the imagePullSecrets needed to pull images from the cdi config
×
682
func GetImagePullSecrets(client client.Client) ([]corev1.LocalObjectReference, error) {
683
        cdiconfig := &cdiv1.CDIConfig{}
684
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
685
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
686
                return nil, err
×
687
        }
×
UNCOV
688

×
689
        return cdiconfig.Status.ImagePullSecrets, nil
×
UNCOV
690
}
×
691

UNCOV
692
// GetPodFromPvc determines the pod associated with the pvc passed in.
×
693
func GetPodFromPvc(c client.Client, namespace string, pvc *corev1.PersistentVolumeClaim) (*corev1.Pod, error) {
694
        l, _ := labels.Parse(common.PrometheusLabelKey)
695
        pods := &corev1.PodList{}
696
        listOptions := client.ListOptions{
×
697
                LabelSelector: l,
×
698
        }
×
699
        if err := c.List(context.TODO(), pods, &listOptions); err != nil {
×
700
                return nil, err
×
701
        }
×
UNCOV
702

×
703
        pvcUID := pvc.GetUID()
×
704
        for _, pod := range pods.Items {
×
705
                if ShouldIgnorePod(&pod, pvc) {
706
                        continue
×
UNCOV
707
                }
×
708
                for _, or := range pod.OwnerReferences {
×
709
                        if or.UID == pvcUID {
×
710
                                return &pod, nil
711
                        }
×
UNCOV
712
                }
×
UNCOV
713

×
UNCOV
714
                // TODO: check this
×
715
                val, exists := pod.Labels[CloneUniqueID]
716
                if exists && val == string(pvcUID)+common.ClonerSourcePodNameSuffix {
717
                        return &pod, nil
718
                }
×
UNCOV
719
        }
×
720
        return nil, errors.Errorf("Unable to find pod owned by UID: %s, in namespace: %s", string(pvcUID), namespace)
×
UNCOV
721
}
×
722

UNCOV
723
// AddVolumeDevices returns VolumeDevice slice with one block device for pods using PV with block volume mode
×
724
func AddVolumeDevices() []corev1.VolumeDevice {
725
        volumeDevices := []corev1.VolumeDevice{
726
                {
727
                        Name:       DataVolName,
×
728
                        DevicePath: common.WriteBlockPath,
×
729
                },
×
730
        }
×
731
        return volumeDevices
×
732
}
×
UNCOV
733

×
UNCOV
734
// GetPodsUsingPVCs returns Pods currently using PVCs
×
735
func GetPodsUsingPVCs(ctx context.Context, c client.Client, namespace string, names sets.Set[string], allowReadOnly bool) ([]corev1.Pod, error) {
×
736
        pl := &corev1.PodList{}
737
        // hopefully using cached client here
738
        err := c.List(ctx, pl, &client.ListOptions{Namespace: namespace})
×
739
        if err != nil {
×
740
                return nil, err
×
741
        }
×
UNCOV
742

×
743
        var pods []corev1.Pod
×
744
        for _, pod := range pl.Items {
×
745
                if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
746
                        continue
×
UNCOV
747
                }
×
748
                for _, volume := range pod.Spec.Volumes {
×
749
                        if volume.VolumeSource.PersistentVolumeClaim != nil &&
×
750
                                names.Has(volume.PersistentVolumeClaim.ClaimName) {
751
                                addPod := true
×
752
                                if allowReadOnly {
×
753
                                        if !volume.VolumeSource.PersistentVolumeClaim.ReadOnly {
×
754
                                                onlyReadOnly := true
×
755
                                                for _, c := range pod.Spec.Containers {
×
756
                                                        for _, vm := range c.VolumeMounts {
×
757
                                                                if vm.Name == volume.Name && !vm.ReadOnly {
×
758
                                                                        onlyReadOnly = false
×
759
                                                                }
×
UNCOV
760
                                                        }
×
761
                                                        for _, vm := range c.VolumeDevices {
×
762
                                                                if vm.Name == volume.Name {
×
763
                                                                        // Node level rw mount and container can't mount block device ro
764
                                                                        onlyReadOnly = false
×
765
                                                                }
×
UNCOV
766
                                                        }
×
UNCOV
767
                                                }
×
768
                                                if onlyReadOnly {
×
769
                                                        // no rw mounts
770
                                                        addPod = false
771
                                                }
×
772
                                        } else {
×
773
                                                // all mounts must be ro
×
774
                                                addPod = false
×
775
                                        }
×
776
                                        if strings.HasSuffix(pod.Name, common.ClonerSourcePodNameSuffix) && pod.Labels != nil &&
×
777
                                                pod.Labels[common.CDIComponentLabel] == common.ClonerSourcePodName {
×
778
                                                // Host assisted clone source pod only reads from source
×
779
                                                // But some drivers disallow mounting a block PVC ReadOnly
×
780
                                                addPod = false
×
781
                                        }
×
UNCOV
782
                                }
×
783
                                if addPod {
×
784
                                        pods = append(pods, pod)
×
785
                                        break
UNCOV
786
                                }
×
UNCOV
787
                        }
×
UNCOV
788
                }
×
789
        }
790

791
        return pods, nil
792
}
793

UNCOV
794
// GetWorkloadNodePlacement extracts the workload-specific nodeplacement values from the CDI CR
×
795
func GetWorkloadNodePlacement(ctx context.Context, c client.Client) (*sdkapi.NodePlacement, error) {
796
        cr, err := GetActiveCDI(ctx, c)
797
        if err != nil {
798
                return nil, err
×
799
        }
×
UNCOV
800

×
801
        if cr == nil {
×
802
                return nil, fmt.Errorf("no active CDI")
×
803
        }
UNCOV
804

×
805
        return &cr.Spec.Workloads, nil
×
UNCOV
806
}
×
807

UNCOV
808
// GetActiveCDI returns the active CDI CR
×
809
func GetActiveCDI(ctx context.Context, c client.Client) (*cdiv1.CDI, error) {
810
        crList := &cdiv1.CDIList{}
811
        if err := c.List(ctx, crList, &client.ListOptions{}); err != nil {
812
                return nil, err
1✔
813
        }
1✔
814

1✔
UNCOV
815
        if len(crList.Items) == 0 {
×
UNCOV
816
                return nil, nil
×
817
        }
818

2✔
819
        if len(crList.Items) == 1 {
1✔
820
                return &crList.Items[0], nil
1✔
821
        }
822

2✔
823
        var activeResources []cdiv1.CDI
1✔
824
        for _, cr := range crList.Items {
1✔
825
                if cr.Status.Phase != sdkapi.PhaseError {
826
                        activeResources = append(activeResources, cr)
1✔
827
                }
2✔
828
        }
2✔
829

1✔
830
        if len(activeResources) != 1 {
1✔
831
                return nil, fmt.Errorf("invalid number of active CDI resources: %d", len(activeResources))
832
        }
833

2✔
834
        return &activeResources[0], nil
1✔
835
}
1✔
836

837
// IsPopulated returns if the passed in PVC has been populated according to the rules outlined in pkg/apis/core/<version>/utils.go
1✔
838
func IsPopulated(pvc *corev1.PersistentVolumeClaim, c client.Client) (bool, error) {
839
        return cdiv1utils.IsPopulated(pvc, func(name, namespace string) (*cdiv1.DataVolume, error) {
840
                dv := &cdiv1.DataVolume{}
841
                err := c.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, dv)
×
842
                return dv, err
×
843
        })
×
UNCOV
844
}
×
UNCOV
845

×
UNCOV
846
// GetPreallocation returns the preallocation setting for the specified object (DV or VolumeImportSource), falling back to StorageClass and global setting (in this order)
×
847
func GetPreallocation(ctx context.Context, client client.Client, preallocation *bool) bool {
848
        // First, the DV's preallocation
849
        if preallocation != nil {
850
                return *preallocation
×
851
        }
×
UNCOV
852

×
853
        cdiconfig := &cdiv1.CDIConfig{}
×
854
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
×
855
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
856
                return defaultPreallocation
×
857
        }
×
UNCOV
858

×
859
        return cdiconfig.Status.Preallocation
×
UNCOV
860
}
×
861

UNCOV
862
// ImmediateBindingRequested returns if an object has the ImmediateBinding annotation
×
863
func ImmediateBindingRequested(obj metav1.Object) bool {
864
        _, isImmediateBindingRequested := obj.GetAnnotations()[AnnImmediateBinding]
865
        return isImmediateBindingRequested
866
}
×
UNCOV
867

×
UNCOV
868
// GetPriorityClass gets PVC priority class
×
869
func GetPriorityClass(pvc *corev1.PersistentVolumeClaim) string {
×
870
        anno := pvc.GetAnnotations()
871
        return anno[AnnPriorityClassName]
872
}
×
UNCOV
873

×
UNCOV
874
// GetPodServiceAccount gets PVC service account name
×
875
func GetPodServiceAccount(pvc *corev1.PersistentVolumeClaim) string {
×
876
        anno := pvc.GetAnnotations()
877
        return anno[AnnPodServiceAccount]
878
}
×
UNCOV
879

×
UNCOV
880
// ShouldDeletePod returns whether the PVC workload pod should be deleted
×
881
func ShouldDeletePod(pvc *corev1.PersistentVolumeClaim) bool {
×
882
        return pvc.GetAnnotations()[AnnPodRetainAfterCompletion] != "true" || pvc.GetAnnotations()[AnnRequiresScratch] == "true" || pvc.GetAnnotations()[AnnRequiresDirectIO] == "true" || pvc.DeletionTimestamp != nil
883
}
UNCOV
884

×
UNCOV
885
// AddFinalizer adds a finalizer to a resource
×
886
func AddFinalizer(obj metav1.Object, name string) {
×
887
        if HasFinalizer(obj, name) {
888
                return
889
        }
×
UNCOV
890

×
891
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
UNCOV
892
}
×
893

UNCOV
894
// RemoveFinalizer removes a finalizer from a resource
×
895
func RemoveFinalizer(obj metav1.Object, name string) {
896
        if !HasFinalizer(obj, name) {
897
                return
898
        }
×
UNCOV
899

×
900
        var finalizers []string
×
901
        for _, f := range obj.GetFinalizers() {
×
902
                if f != name {
903
                        finalizers = append(finalizers, f)
×
904
                }
×
UNCOV
905
        }
×
UNCOV
906

×
907
        obj.SetFinalizers(finalizers)
×
908
}
909

UNCOV
910
// HasFinalizer returns true if a resource has a specific finalizer
×
911
func HasFinalizer(object metav1.Object, value string) bool {
912
        for _, f := range object.GetFinalizers() {
913
                if f == value {
914
                        return true
×
915
                }
×
UNCOV
916
        }
×
917
        return false
×
UNCOV
918
}
×
919

UNCOV
920
// ValidateCloneTokenPVC validates clone token for source and target PVCs
×
921
func ValidateCloneTokenPVC(t string, v token.Validator, source, target *corev1.PersistentVolumeClaim) error {
922
        if source.Namespace == target.Namespace {
923
                return nil
924
        }
×
UNCOV
925

×
926
        tokenData, err := v.Validate(t)
×
927
        if err != nil {
×
928
                return errors.Wrap(err, "error verifying token")
929
        }
×
UNCOV
930

×
931
        tokenResourceName := getTokenResourceNamePvc(source)
×
932
        srcName := getSourceNamePvc(source)
×
933

934
        return validateTokenData(tokenData, source.Namespace, srcName, target.Namespace, target.Name, string(target.UID), tokenResourceName)
×
UNCOV
935
}
×
UNCOV
936

×
UNCOV
937
// ValidateCloneTokenDV validates clone token for DV
×
938
func ValidateCloneTokenDV(validator token.Validator, dv *cdiv1.DataVolume) error {
939
        _, sourceName, sourceNamespace := GetCloneSourceInfo(dv)
940
        if sourceNamespace == "" || sourceNamespace == dv.Namespace {
941
                return nil
×
942
        }
×
UNCOV
943

×
944
        tok, ok := dv.Annotations[AnnCloneToken]
×
945
        if !ok {
×
946
                return errors.New("clone token missing")
947
        }
×
UNCOV
948

×
949
        tokenData, err := validator.Validate(tok)
×
950
        if err != nil {
×
951
                return errors.Wrap(err, "error verifying token")
952
        }
×
UNCOV
953

×
954
        tokenResourceName := getTokenResourceNameDataVolume(dv.Spec.Source)
×
955
        if tokenResourceName == "" {
×
956
                return errors.New("token resource name empty, can't verify properly")
957
        }
×
UNCOV
958

×
959
        return validateTokenData(tokenData, sourceNamespace, sourceName, dv.Namespace, dv.Name, "", tokenResourceName)
×
UNCOV
960
}
×
961

962
func getTokenResourceNameDataVolume(source *cdiv1.DataVolumeSource) string {
×
963
        if source.PVC != nil {
964
                return "persistentvolumeclaims"
965
        } else if source.Snapshot != nil {
×
966
                return "volumesnapshots"
×
967
        }
×
UNCOV
968

×
969
        return ""
×
UNCOV
970
}
×
971

972
func getTokenResourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
973
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
974
                return "volumesnapshots"
975
        }
×
UNCOV
976

×
977
        return "persistentvolumeclaims"
×
UNCOV
978
}
×
979

980
func getSourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
981
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
982
                if sourcePvc.Spec.DataSourceRef != nil {
983
                        return sourcePvc.Spec.DataSourceRef.Name
×
984
                }
×
UNCOV
985
        }
×
UNCOV
986

×
987
        return sourcePvc.Name
×
988
}
989

990
func validateTokenData(tokenData *token.Payload, srcNamespace, srcName, targetNamespace, targetName, targetUID, tokenResourceName string) error {
×
991
        uid := tokenData.Params["uid"]
992
        if tokenData.Operation != token.OperationClone ||
993
                tokenData.Name != srcName ||
×
994
                tokenData.Namespace != srcNamespace ||
×
995
                tokenData.Resource.Resource != tokenResourceName ||
×
996
                tokenData.Params["targetNamespace"] != targetNamespace ||
×
997
                tokenData.Params["targetName"] != targetName ||
×
998
                (uid != "" && uid != targetUID) {
×
999
                return errors.New("invalid token")
×
1000
        }
×
UNCOV
1001

×
1002
        return nil
×
UNCOV
1003
}
×
1004

UNCOV
1005
// IsSnapshotValidForClone returns an error if the passed snapshot is not valid for cloning
×
1006
func IsSnapshotValidForClone(sourceSnapshot *snapshotv1.VolumeSnapshot) error {
1007
        if sourceSnapshot.Status == nil {
1008
                return fmt.Errorf("no status on source snapshot yet")
1009
        }
×
1010
        if !IsSnapshotReady(sourceSnapshot) {
×
1011
                klog.V(3).Info("snapshot not ReadyToUse, while we allow this, probably going to be an issue going forward", "namespace", sourceSnapshot.Namespace, "name", sourceSnapshot.Name)
×
1012
        }
×
1013
        if sourceSnapshot.Status.Error != nil {
×
1014
                errMessage := "no details"
×
1015
                if msg := sourceSnapshot.Status.Error.Message; msg != nil {
×
1016
                        errMessage = *msg
×
1017
                }
×
1018
                return fmt.Errorf("snapshot in error state with msg: %s", errMessage)
×
UNCOV
1019
        }
×
1020
        if sourceSnapshot.Spec.VolumeSnapshotClassName == nil ||
×
1021
                *sourceSnapshot.Spec.VolumeSnapshotClassName == "" {
×
1022
                return fmt.Errorf("snapshot %s/%s does not have volume snap class populated, can't clone", sourceSnapshot.Name, sourceSnapshot.Namespace)
1023
        }
×
1024
        return nil
×
UNCOV
1025
}
×
UNCOV
1026

×
UNCOV
1027
// AddAnnotation adds an annotation to an object
×
1028
func AddAnnotation(obj metav1.Object, key, value string) {
1029
        if obj.GetAnnotations() == nil {
1030
                obj.SetAnnotations(make(map[string]string))
1031
        }
1✔
1032
        obj.GetAnnotations()[key] = value
2✔
1033
}
1✔
1034

1✔
1035
// AddLabel adds a label to an object
1✔
1036
func AddLabel(obj metav1.Object, key, value string) {
1037
        if obj.GetLabels() == nil {
1038
                obj.SetLabels(make(map[string]string))
1039
        }
1✔
1040
        obj.GetLabels()[key] = value
2✔
1041
}
1✔
1042

1✔
1043
// HandleFailedPod handles pod-creation errors and updates the pod's PVC without providing sensitive information
1✔
1044
func HandleFailedPod(err error, podName string, pvc *corev1.PersistentVolumeClaim, recorder record.EventRecorder, c client.Client) error {
1045
        if err == nil {
1046
                return nil
1047
        }
×
UNCOV
1048
        // Generic reason and msg to avoid providing sensitive information
×
1049
        reason := ErrStartingPod
×
1050
        msg := fmt.Sprintf(MessageErrStartingPod, podName)
×
1051

1052
        // Error handling to fine-tune the event with pertinent info
×
1053
        if ErrQuotaExceeded(err) {
×
1054
                reason = ErrExceededQuota
×
1055
        }
×
UNCOV
1056

×
1057
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1058

×
1059
        if isCloneSourcePod := CreateCloneSourcePodName(pvc) == podName; isCloneSourcePod {
1060
                AddAnnotation(pvc, AnnSourceRunningCondition, "false")
×
1061
                AddAnnotation(pvc, AnnSourceRunningConditionReason, reason)
×
1062
                AddAnnotation(pvc, AnnSourceRunningConditionMessage, msg)
×
1063
        } else {
×
1064
                AddAnnotation(pvc, AnnRunningCondition, "false")
×
1065
                AddAnnotation(pvc, AnnRunningConditionReason, reason)
×
1066
                AddAnnotation(pvc, AnnRunningConditionMessage, msg)
×
1067
        }
×
UNCOV
1068

×
1069
        AddAnnotation(pvc, AnnPodPhase, string(corev1.PodFailed))
×
1070
        if err := c.Update(context.TODO(), pvc); err != nil {
×
1071
                return err
1072
        }
×
UNCOV
1073

×
1074
        return err
×
UNCOV
1075
}
×
1076

UNCOV
1077
// GetSource returns the source string which determines the type of source. If no source or invalid source found, default to http
×
1078
func GetSource(pvc *corev1.PersistentVolumeClaim) string {
1079
        source, found := pvc.Annotations[AnnSource]
1080
        if !found {
1081
                source = ""
×
1082
        }
×
1083
        switch source {
×
UNCOV
1084
        case
×
UNCOV
1085
                SourceHTTP,
×
UNCOV
1086
                SourceS3,
×
1087
                SourceGCS,
1088
                SourceGlance,
1089
                SourceNone,
1090
                SourceRegistry,
1091
                SourceImageio,
1092
                SourceVDDK:
1093
        default:
1094
                source = SourceHTTP
UNCOV
1095
        }
×
1096
        return source
×
UNCOV
1097
}
×
1098

UNCOV
1099
// GetEndpoint returns the endpoint string which contains the full path URI of the target object to be copied.
×
1100
func GetEndpoint(pvc *corev1.PersistentVolumeClaim) (string, error) {
1101
        ep, found := pvc.Annotations[AnnEndpoint]
1102
        if !found || ep == "" {
1103
                verb := "empty"
×
1104
                if !found {
×
1105
                        verb = "missing"
×
1106
                }
×
1107
                return ep, errors.Errorf("annotation %q in pvc \"%s/%s\" is %s", AnnEndpoint, pvc.Namespace, pvc.Name, verb)
×
UNCOV
1108
        }
×
1109
        return ep, nil
×
UNCOV
1110
}
×
1111

UNCOV
1112
// AddImportVolumeMounts is being called for pods using PV with filesystem volume mode
×
1113
func AddImportVolumeMounts() []corev1.VolumeMount {
1114
        volumeMounts := []corev1.VolumeMount{
1115
                {
1116
                        Name:      DataVolName,
×
1117
                        MountPath: common.ImporterDataDir,
×
1118
                },
×
1119
        }
×
1120
        return volumeMounts
×
1121
}
×
UNCOV
1122

×
UNCOV
1123
// GetEffectiveStorageResources returns the maximum of the passed storageResources and the storageProfile minimumSupportedPVCSize.
×
UNCOV
1124
// If the passed storageResources has no size, it is returned as-is.
×
1125
func GetEffectiveStorageResources(ctx context.Context, client client.Client, storageResources corev1.VolumeResourceRequirements,
1126
        storageClassName *string, contentType cdiv1.DataVolumeContentType, log logr.Logger) (corev1.VolumeResourceRequirements, error) {
1127
        sc, err := GetStorageClassByNameWithVirtFallback(ctx, client, storageClassName, contentType)
1128
        if err != nil || sc == nil {
1129
                return storageResources, err
×
1130
        }
×
UNCOV
1131

×
1132
        requestedSize, hasSize := storageResources.Requests[corev1.ResourceStorage]
×
1133
        if !hasSize {
×
1134
                return storageResources, nil
1135
        }
×
UNCOV
1136

×
1137
        if requestedSize, err = GetEffectiveVolumeSize(ctx, client, requestedSize, sc.Name, &log); err != nil {
×
1138
                return storageResources, err
×
1139
        }
UNCOV
1140

×
1141
        return corev1.VolumeResourceRequirements{
×
1142
                Requests: corev1.ResourceList{
×
1143
                        corev1.ResourceStorage: requestedSize,
1144
                },
×
1145
        }, nil
×
UNCOV
1146
}
×
UNCOV
1147

×
UNCOV
1148
// GetEffectiveVolumeSize returns the maximum of the passed requestedSize and the storageProfile minimumSupportedPVCSize.
×
1149
func GetEffectiveVolumeSize(ctx context.Context, client client.Client, requestedSize resource.Quantity, storageClassName string, log *logr.Logger) (resource.Quantity, error) {
1150
        storageProfile := &cdiv1.StorageProfile{}
1151
        if err := client.Get(ctx, types.NamespacedName{Name: storageClassName}, storageProfile); err != nil {
1152
                return requestedSize, IgnoreNotFound(err)
×
1153
        }
×
UNCOV
1154

×
1155
        if val, exists := storageProfile.Annotations[AnnMinimumSupportedPVCSize]; exists {
×
1156
                if minSize, err := resource.ParseQuantity(val); err == nil {
×
1157
                        if requestedSize.Cmp(minSize) == -1 {
1158
                                return minSize, nil
×
1159
                        }
×
1160
                } else if log != nil {
×
1161
                        log.V(1).Info("Invalid minimum PVC size in annotation", "value", val, "error", err)
×
1162
                }
×
UNCOV
1163
        }
×
UNCOV
1164

×
1165
        return requestedSize, nil
×
1166
}
1167

UNCOV
1168
// ValidateRequestedCloneSize validates the clone size requirements on block
×
1169
func ValidateRequestedCloneSize(sourceResources, targetResources corev1.VolumeResourceRequirements) error {
1170
        sourceRequest, hasSource := sourceResources.Requests[corev1.ResourceStorage]
1171
        targetRequest, hasTarget := targetResources.Requests[corev1.ResourceStorage]
1172
        if !hasSource || !hasTarget {
×
1173
                return errors.New("source/target missing storage resource requests")
×
1174
        }
×
UNCOV
1175

×
UNCOV
1176
        // Verify that the target PVC size is equal or larger than the source.
×
1177
        if sourceRequest.Value() > targetRequest.Value() {
×
1178
                return errors.Errorf("target resources requests storage size is smaller than the source %d < %d", targetRequest.Value(), sourceRequest.Value())
1179
        }
1180
        return nil
×
UNCOV
1181
}
×
UNCOV
1182

×
UNCOV
1183
// CreateCloneSourcePodName creates clone source pod name
×
1184
func CreateCloneSourcePodName(targetPvc *corev1.PersistentVolumeClaim) string {
1185
        return string(targetPvc.GetUID()) + common.ClonerSourcePodNameSuffix
1186
}
UNCOV
1187

×
UNCOV
1188
// IsPVCComplete returns true if a PVC is in 'Succeeded' phase, false if not
×
1189
func IsPVCComplete(pvc *corev1.PersistentVolumeClaim) bool {
×
1190
        if pvc != nil {
1191
                phase, exists := pvc.ObjectMeta.Annotations[AnnPodPhase]
1192
                return exists && (phase == string(corev1.PodSucceeded))
×
1193
        }
×
1194
        return false
×
UNCOV
1195
}
×
UNCOV
1196

×
UNCOV
1197
// IsMultiStageImportInProgress returns true when a PVC is being part of an ongoing multi-stage import
×
1198
func IsMultiStageImportInProgress(pvc *corev1.PersistentVolumeClaim) bool {
1199
        if pvc != nil {
1200
                multiStageImport := metav1.HasAnnotation(pvc.ObjectMeta, AnnCurrentCheckpoint)
1201
                multiStageAlreadyDone := metav1.HasAnnotation(pvc.ObjectMeta, AnnMultiStageImportDone)
×
1202
                return multiStageImport && !multiStageAlreadyDone
×
1203
        }
×
1204
        return false
×
UNCOV
1205
}
×
UNCOV
1206

×
UNCOV
1207
// SetRestrictedSecurityContext sets the pod security params to be compatible with restricted PSA
×
1208
func SetRestrictedSecurityContext(podSpec *corev1.PodSpec) {
1209
        hasVolumeMounts := false
1210
        for _, containers := range [][]corev1.Container{podSpec.InitContainers, podSpec.Containers} {
1211
                for i := range containers {
×
1212
                        container := &containers[i]
×
1213
                        if container.SecurityContext == nil {
×
1214
                                container.SecurityContext = &corev1.SecurityContext{}
×
1215
                        }
×
1216
                        container.SecurityContext.Capabilities = &corev1.Capabilities{
×
1217
                                Drop: []corev1.Capability{
×
1218
                                        "ALL",
×
1219
                                },
×
1220
                        }
×
1221
                        container.SecurityContext.SeccompProfile = &corev1.SeccompProfile{
×
1222
                                Type: corev1.SeccompProfileTypeRuntimeDefault,
×
1223
                        }
×
1224
                        container.SecurityContext.AllowPrivilegeEscalation = ptr.To[bool](false)
×
1225
                        container.SecurityContext.RunAsNonRoot = ptr.To[bool](true)
×
1226
                        container.SecurityContext.RunAsUser = ptr.To[int64](common.QemuSubGid)
×
1227
                        if len(container.VolumeMounts) > 0 {
×
1228
                                hasVolumeMounts = true
×
1229
                        }
×
UNCOV
1230
                }
×
UNCOV
1231
        }
×
UNCOV
1232

×
1233
        if podSpec.SecurityContext == nil {
1234
                podSpec.SecurityContext = &corev1.PodSecurityContext{}
1235
        }
UNCOV
1236
        // Some tools like istio inject containers and thus rely on a pod level seccomp profile being specified
×
1237
        podSpec.SecurityContext.SeccompProfile = &corev1.SeccompProfile{
×
1238
                Type: corev1.SeccompProfileTypeRuntimeDefault,
×
1239
        }
1240
        if hasVolumeMounts {
×
1241
                podSpec.SecurityContext.FSGroup = ptr.To[int64](common.QemuSubGid)
×
1242
        }
×
UNCOV
1243
}
×
UNCOV
1244

×
UNCOV
1245
// SetNodeNameIfPopulator sets NodeName in a pod spec when the PVC is being handled by a CDI volume populator
×
1246
func SetNodeNameIfPopulator(pvc *corev1.PersistentVolumeClaim, podSpec *corev1.PodSpec) {
1247
        _, isPopulator := pvc.Annotations[AnnPopulatorKind]
1248
        nodeName := pvc.Annotations[AnnSelectedNode]
1249
        if isPopulator && nodeName != "" {
×
1250
                podSpec.NodeName = nodeName
×
1251
        }
×
UNCOV
1252
}
×
UNCOV
1253

×
UNCOV
1254
// CreatePvc creates PVC
×
1255
func CreatePvc(name, ns string, annotations, labels map[string]string) *corev1.PersistentVolumeClaim {
1256
        return CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1257
}
1258

1✔
1259
// CreatePvcInStorageClass creates PVC with storgae class
1✔
1260
func CreatePvcInStorageClass(name, ns string, storageClassName *string, annotations, labels map[string]string, phase corev1.PersistentVolumeClaimPhase) *corev1.PersistentVolumeClaim {
1✔
1261
        pvc := &corev1.PersistentVolumeClaim{
1262
                ObjectMeta: metav1.ObjectMeta{
1263
                        Name:        name,
1✔
1264
                        Namespace:   ns,
1✔
1265
                        Annotations: annotations,
1✔
1266
                        Labels:      labels,
1✔
1267
                        UID:         types.UID(ns + "-" + name),
1✔
1268
                },
1✔
1269
                Spec: corev1.PersistentVolumeClaimSpec{
1✔
1270
                        AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadOnlyMany, corev1.ReadWriteOnce},
1✔
1271
                        Resources: corev1.VolumeResourceRequirements{
1✔
1272
                                Requests: corev1.ResourceList{
1✔
1273
                                        corev1.ResourceStorage: resource.MustParse("1G"),
1✔
1274
                                },
1✔
1275
                        },
1✔
1276
                        StorageClassName: storageClassName,
1✔
1277
                },
1✔
1278
                Status: corev1.PersistentVolumeClaimStatus{
1✔
1279
                        Phase: phase,
1✔
1280
                },
1✔
1281
        }
1✔
1282
        pvc.Status.Capacity = pvc.Spec.Resources.Requests.DeepCopy()
1✔
1283
        if pvc.Status.Phase == corev1.ClaimBound {
1✔
1284
                pvc.Spec.VolumeName = "pv-" + string(pvc.UID)
1✔
1285
        }
1✔
1286
        return pvc
2✔
1287
}
1✔
1288

1✔
1289
// GetAPIServerKey returns API server RSA key
1✔
1290
func GetAPIServerKey() *rsa.PrivateKey {
1291
        apiServerKeyOnce.Do(func() {
1292
                apiServerKey, _ = rsa.GenerateKey(rand.Reader, 2048)
1293
        })
×
1294
        return apiServerKey
×
UNCOV
1295
}
×
UNCOV
1296

×
UNCOV
1297
// CreateStorageClass creates storage class CR
×
1298
func CreateStorageClass(name string, annotations map[string]string) *storagev1.StorageClass {
1299
        return &storagev1.StorageClass{
1300
                ObjectMeta: metav1.ObjectMeta{
1301
                        Name:        name,
1✔
1302
                        Annotations: annotations,
1✔
1303
                },
1✔
1304
        }
1✔
1305
}
1✔
1306

1✔
1307
// CreateImporterTestPod creates importer test pod CR
1✔
1308
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
1✔
1309
        // importer pod name contains the pvc name
1310
        podName := fmt.Sprintf("%s-%s", common.ImporterPodName, pvc.Name)
1311

×
1312
        blockOwnerDeletion := true
×
1313
        isController := true
×
1314

×
1315
        volumes := []corev1.Volume{
×
1316
                {
×
1317
                        Name: dvname,
×
1318
                        VolumeSource: corev1.VolumeSource{
×
1319
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1320
                                        ClaimName: pvc.Name,
×
1321
                                        ReadOnly:  false,
×
1322
                                },
×
1323
                        },
×
1324
                },
×
1325
        }
×
1326

×
1327
        if scratchPvc != nil {
×
1328
                volumes = append(volumes, corev1.Volume{
×
1329
                        Name: ScratchVolName,
×
1330
                        VolumeSource: corev1.VolumeSource{
×
1331
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1332
                                        ClaimName: scratchPvc.Name,
×
1333
                                        ReadOnly:  false,
×
1334
                                },
×
1335
                        },
×
1336
                })
×
1337
        }
×
UNCOV
1338

×
1339
        pod := &corev1.Pod{
×
1340
                TypeMeta: metav1.TypeMeta{
×
1341
                        Kind:       "Pod",
1342
                        APIVersion: "v1",
×
1343
                },
×
1344
                ObjectMeta: metav1.ObjectMeta{
×
1345
                        Name:      podName,
×
1346
                        Namespace: pvc.Namespace,
×
1347
                        Annotations: map[string]string{
×
1348
                                AnnCreatedBy: "yes",
×
1349
                        },
×
1350
                        Labels: map[string]string{
×
1351
                                common.CDILabelKey:        common.CDILabelValue,
×
1352
                                common.CDIComponentLabel:  common.ImporterPodName,
×
1353
                                common.PrometheusLabelKey: common.PrometheusLabelValue,
×
1354
                        },
×
1355
                        OwnerReferences: []metav1.OwnerReference{
×
1356
                                {
×
1357
                                        APIVersion:         "v1",
×
1358
                                        Kind:               "PersistentVolumeClaim",
×
1359
                                        Name:               pvc.Name,
×
1360
                                        UID:                pvc.GetUID(),
×
1361
                                        BlockOwnerDeletion: &blockOwnerDeletion,
×
1362
                                        Controller:         &isController,
×
1363
                                },
×
1364
                        },
×
1365
                },
×
1366
                Spec: corev1.PodSpec{
×
1367
                        Containers: []corev1.Container{
×
1368
                                {
×
1369
                                        Name:            common.ImporterPodName,
×
1370
                                        Image:           "test/myimage",
×
1371
                                        ImagePullPolicy: corev1.PullPolicy("Always"),
×
1372
                                        Args:            []string{"-v=5"},
×
1373
                                        Ports: []corev1.ContainerPort{
×
1374
                                                {
×
1375
                                                        Name:          "metrics",
×
1376
                                                        ContainerPort: 8443,
×
1377
                                                        Protocol:      corev1.ProtocolTCP,
×
1378
                                                },
×
1379
                                        },
×
1380
                                },
×
1381
                        },
×
1382
                        RestartPolicy:      corev1.RestartPolicyOnFailure,
×
1383
                        Volumes:            volumes,
×
1384
                        EnableServiceLinks: ptr.To(false),
×
1385
                },
×
1386
        }
×
1387

×
1388
        ep, _ := GetEndpoint(pvc)
×
1389
        source := GetSource(pvc)
×
1390
        contentType := GetPVCContentType(pvc)
×
1391
        imageSize, _ := GetRequestedImageSize(pvc)
×
1392
        volumeMode := GetVolumeMode(pvc)
×
1393

×
1394
        env := []corev1.EnvVar{
×
1395
                {
×
1396
                        Name:  common.ImporterSource,
×
1397
                        Value: source,
×
1398
                },
×
1399
                {
×
1400
                        Name:  common.ImporterEndpoint,
×
1401
                        Value: ep,
×
1402
                },
×
1403
                {
×
1404
                        Name:  common.ImporterContentType,
×
1405
                        Value: string(contentType),
×
1406
                },
×
1407
                {
×
1408
                        Name:  common.ImporterImageSize,
×
1409
                        Value: imageSize,
×
1410
                },
×
1411
                {
×
1412
                        Name:  common.OwnerUID,
×
1413
                        Value: string(pvc.UID),
×
1414
                },
×
1415
                {
×
1416
                        Name:  common.InsecureTLSVar,
×
1417
                        Value: "false",
×
1418
                },
×
1419
        }
×
1420
        pod.Spec.Containers[0].Env = env
×
1421
        if volumeMode == corev1.PersistentVolumeBlock {
×
1422
                pod.Spec.Containers[0].VolumeDevices = AddVolumeDevices()
×
1423
        } else {
×
1424
                pod.Spec.Containers[0].VolumeMounts = AddImportVolumeMounts()
×
1425
        }
×
UNCOV
1426

×
1427
        if scratchPvc != nil {
×
1428
                pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
×
1429
                        Name:      ScratchVolName,
1430
                        MountPath: common.ScratchDataDir,
×
1431
                })
×
1432
        }
×
UNCOV
1433

×
1434
        return pod
×
UNCOV
1435
}
×
1436

UNCOV
1437
// CreateStorageClassWithProvisioner creates CR of storage class with provisioner
×
1438
func CreateStorageClassWithProvisioner(name string, annotations, labels map[string]string, provisioner string) *storagev1.StorageClass {
1439
        return &storagev1.StorageClass{
1440
                Provisioner: provisioner,
1441
                ObjectMeta: metav1.ObjectMeta{
×
1442
                        Name:        name,
×
1443
                        Annotations: annotations,
×
1444
                        Labels:      labels,
×
1445
                },
×
1446
        }
×
1447
}
×
UNCOV
1448

×
UNCOV
1449
// CreateClient creates a fake client
×
UNCOV
1450
func CreateClient(objs ...runtime.Object) client.Client {
×
1451
        s := scheme.Scheme
1452
        _ = cdiv1.AddToScheme(s)
1453
        _ = corev1.AddToScheme(s)
1✔
1454
        _ = storagev1.AddToScheme(s)
1✔
1455
        _ = ocpconfigv1.Install(s)
1✔
1456

1✔
1457
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1458
}
1✔
1459

1✔
1460
// ErrQuotaExceeded checked is the error is of exceeded quota
1✔
1461
func ErrQuotaExceeded(err error) bool {
1✔
1462
        return strings.Contains(err.Error(), "exceeded quota:")
1463
}
UNCOV
1464

×
UNCOV
1465
// GetContentType returns the content type. If invalid or not set, default to kubevirt
×
UNCOV
1466
func GetContentType(contentType cdiv1.DataVolumeContentType) cdiv1.DataVolumeContentType {
×
1467
        switch contentType {
1468
        case
1469
                cdiv1.DataVolumeKubeVirt,
1✔
1470
                cdiv1.DataVolumeArchive:
1✔
1471
        default:
1472
                // TODO - shouldn't archive be the default?
1473
                contentType = cdiv1.DataVolumeKubeVirt
1✔
UNCOV
1474
        }
×
UNCOV
1475
        return contentType
×
UNCOV
1476
}
×
1477

1478
// GetPVCContentType returns the content type of the source image. If invalid or not set, default to kubevirt
1✔
1479
func GetPVCContentType(pvc *corev1.PersistentVolumeClaim) cdiv1.DataVolumeContentType {
1480
        contentType, found := pvc.Annotations[AnnContentType]
1481
        if !found {
1482
                // TODO - shouldn't archive be the default?
×
1483
                return cdiv1.DataVolumeKubeVirt
×
1484
        }
×
UNCOV
1485

×
1486
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
UNCOV
1487
}
×
1488

UNCOV
1489
// GetNamespace returns the given namespace if not empty, otherwise the default namespace
×
1490
func GetNamespace(namespace, defaultNamespace string) string {
1491
        if namespace == "" {
1492
                return defaultNamespace
1493
        }
×
1494
        return namespace
×
UNCOV
1495
}
×
UNCOV
1496

×
UNCOV
1497
// IsErrCacheNotStarted checked is the error is of cache not started
×
1498
func IsErrCacheNotStarted(err error) bool {
1499
        target := &runtimecache.ErrCacheNotStarted{}
1500
        return errors.As(err, &target)
1501
}
×
UNCOV
1502

×
UNCOV
1503
// NewImportDataVolume returns new import DataVolume CR
×
1504
func NewImportDataVolume(name string) *cdiv1.DataVolume {
×
1505
        return &cdiv1.DataVolume{
1506
                TypeMeta: metav1.TypeMeta{APIVersion: cdiv1.SchemeGroupVersion.String()},
1507
                ObjectMeta: metav1.ObjectMeta{
×
1508
                        Name:      name,
×
1509
                        Namespace: metav1.NamespaceDefault,
×
1510
                        UID:       types.UID(metav1.NamespaceDefault + "-" + name),
×
1511
                },
×
1512
                Spec: cdiv1.DataVolumeSpec{
×
1513
                        Source: &cdiv1.DataVolumeSource{
×
1514
                                HTTP: &cdiv1.DataVolumeSourceHTTP{
×
1515
                                        URL: "http://example.com/data",
×
1516
                                },
×
1517
                        },
×
1518
                        PVC: &corev1.PersistentVolumeClaimSpec{
×
1519
                                AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
×
1520
                        },
×
1521
                        PriorityClassName: "p0",
×
1522
                },
×
1523
        }
×
1524
}
×
UNCOV
1525

×
UNCOV
1526
// GetCloneSourceInfo returns the type, name and namespace of the cloning source
×
1527
func GetCloneSourceInfo(dv *cdiv1.DataVolume) (sourceType, sourceName, sourceNamespace string) {
×
1528
        // Cloning sources are mutually exclusive
1529
        if dv.Spec.Source.PVC != nil {
1530
                return "pvc", dv.Spec.Source.PVC.Name, dv.Spec.Source.PVC.Namespace
×
1531
        }
×
UNCOV
1532

×
1533
        if dv.Spec.Source.Snapshot != nil {
×
1534
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
×
1535
        }
UNCOV
1536

×
1537
        return "", "", ""
×
UNCOV
1538
}
×
1539

UNCOV
1540
// IsWaitForFirstConsumerEnabled tells us if we should respect "real" WFFC behavior or just let our worker pods randomly spawn
×
1541
func IsWaitForFirstConsumerEnabled(obj metav1.Object, gates featuregates.FeatureGates) (bool, error) {
1542
        // when PVC requests immediateBinding it cannot honor wffc logic
1543
        isImmediateBindingRequested := ImmediateBindingRequested(obj)
1544
        pvcHonorWaitForFirstConsumer := !isImmediateBindingRequested
×
1545
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
×
1546
        if err != nil {
×
1547
                return false, err
×
1548
        }
×
UNCOV
1549

×
1550
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
UNCOV
1551
}
×
1552

UNCOV
1553
// AddImmediateBindingAnnotationIfWFFCDisabled adds the immediateBinding annotation if wffc feature gate is disabled
×
1554
func AddImmediateBindingAnnotationIfWFFCDisabled(obj metav1.Object, gates featuregates.FeatureGates) error {
1555
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
1556
        if err != nil {
1557
                return err
×
1558
        }
×
1559
        if !globalHonorWaitForFirstConsumer {
×
1560
                AddAnnotation(obj, AnnImmediateBinding, "")
×
1561
        }
×
1562
        return nil
×
UNCOV
1563
}
×
UNCOV
1564

×
UNCOV
1565
// InflateSizeWithOverhead inflates a storage size with proper overhead calculations
×
1566
func InflateSizeWithOverhead(ctx context.Context, c client.Client, imgSize int64, pvcSpec *corev1.PersistentVolumeClaimSpec) (resource.Quantity, error) {
1567
        var returnSize resource.Quantity
1568

1569
        if util.ResolveVolumeMode(pvcSpec.VolumeMode) == corev1.PersistentVolumeFilesystem {
×
1570
                fsOverhead, err := GetFilesystemOverheadForStorageClass(ctx, c, pvcSpec.StorageClassName)
×
1571
                if err != nil {
×
1572
                        return resource.Quantity{}, err
×
1573
                }
×
UNCOV
1574
                // Parse filesystem overhead (percentage) into a 64-bit float
×
1575
                fsOverheadFloat, _ := strconv.ParseFloat(string(fsOverhead), 64)
×
1576

×
1577
                // Merge the previous values into a 'resource.Quantity' struct
1578
                requiredSpace := util.GetRequiredSpace(fsOverheadFloat, imgSize)
×
1579
                returnSize = *resource.NewScaledQuantity(requiredSpace, 0)
×
1580
        } else {
×
1581
                // Inflation is not needed with 'Block' mode
×
1582
                returnSize = *resource.NewScaledQuantity(imgSize, 0)
×
1583
        }
×
UNCOV
1584

×
1585
        return returnSize, nil
×
UNCOV
1586
}
×
1587

UNCOV
1588
// GetSnapshotContentFromSnapshot returns the VolumeSnapshotContent of a given VolumeSnapshot
×
1589
func GetSnapshotContentFromSnapshot(ctx context.Context, c client.Client, snapshot *snapshotv1.VolumeSnapshot) (*snapshotv1.VolumeSnapshotContent, error) {
1590
        if snapshot.Status == nil || snapshot.Status.BoundVolumeSnapshotContentName == nil {
1591
                return nil, fmt.Errorf("volumeSnapshotContent name not found")
1592
        }
×
1593
        vsc := &snapshotv1.VolumeSnapshotContent{}
×
1594
        if err := c.Get(ctx, types.NamespacedName{Name: *snapshot.Status.BoundVolumeSnapshotContentName}, vsc); err != nil {
×
1595
                return nil, err
×
1596
        }
×
1597
        return vsc, nil
×
UNCOV
1598
}
×
UNCOV
1599

×
UNCOV
1600
// GetSnapshotSourceVolumeMode determines the volume mode of the PVC that was
×
1601
// used to create the given snapshot. It checks (in order):
1602
// 1. VolumeSnapshotContent.Spec.SourceVolumeMode (available since k8s 1.29)
1603
// 2. AnnSourceVolumeMode annotation on the snapshot
1604
// 3. Falls back to the provided fallback volume mode
1605
func GetSnapshotSourceVolumeMode(log logr.Logger, snapshot *snapshotv1.VolumeSnapshot, vsc *snapshotv1.VolumeSnapshotContent, fallback *corev1.PersistentVolumeMode) *corev1.PersistentVolumeMode {
1606
        if vsc != nil && vsc.Spec.SourceVolumeMode != nil {
1607
                return vsc.Spec.SourceVolumeMode
1608
        }
×
UNCOV
1609

×
1610
        if v, ok := snapshot.Annotations[AnnSourceVolumeMode]; ok {
×
1611
                mode := corev1.PersistentVolumeMode(v)
×
1612
                return &mode
1613
        }
×
UNCOV
1614

×
1615
        log.V(1).Info("Could not infer source volume mode of snapshot, assuming same as target")
×
1616
        return fallback
×
1617
}
UNCOV
1618

×
UNCOV
1619
// IsBound returns if the pvc is bound
×
1620
func IsBound(pvc *corev1.PersistentVolumeClaim) bool {
1621
        return pvc != nil && pvc.Status.Phase == corev1.ClaimBound
1622
}
UNCOV
1623

×
UNCOV
1624
// IsUnbound returns if the pvc is not bound yet
×
1625
func IsUnbound(pvc *corev1.PersistentVolumeClaim) bool {
×
1626
        return !IsBound(pvc)
1627
}
UNCOV
1628

×
UNCOV
1629
// IsLost returns if the pvc is lost
×
1630
func IsLost(pvc *corev1.PersistentVolumeClaim) bool {
×
1631
        return pvc != nil && pvc.Status.Phase == corev1.ClaimLost
1632
}
UNCOV
1633

×
UNCOV
1634
// IsImageStream returns true if registry source is ImageStream
×
1635
func IsImageStream(pvc *corev1.PersistentVolumeClaim) bool {
×
1636
        return pvc.Annotations[AnnRegistryImageStream] == "true"
1637
}
UNCOV
1638

×
UNCOV
1639
// ShouldIgnorePod checks if a pod should be ignored.
×
UNCOV
1640
// If this is a completed pod that was used for one checkpoint of a multi-stage import, it
×
1641
// should be ignored by pod lookups as long as the retainAfterCompletion annotation is set.
1642
func ShouldIgnorePod(pod *corev1.Pod, pvc *corev1.PersistentVolumeClaim) bool {
1643
        retain := pvc.ObjectMeta.Annotations[AnnPodRetainAfterCompletion]
1644
        checkpoint := pvc.ObjectMeta.Annotations[AnnCurrentCheckpoint]
1645
        if checkpoint != "" && pod.Status.Phase == corev1.PodSucceeded {
×
1646
                return retain == "true"
×
1647
        }
×
1648
        return false
×
UNCOV
1649
}
×
UNCOV
1650

×
UNCOV
1651
// BuildHTTPClient generates an http client that accepts any certificate, since we are using
×
1652
// it to get prometheus data it doesn't matter if someone can intercept the data. Once we have
1653
// a mechanism to properly sign the server, we can update this method to get a proper client.
1654
func BuildHTTPClient(httpClient *http.Client) *http.Client {
1655
        if httpClient == nil {
1656
                defaultTransport := http.DefaultTransport.(*http.Transport)
1657
                // Create new Transport that ignores self-signed SSL
×
1658
                //nolint:gosec
×
1659
                tr := &http.Transport{
×
1660
                        Proxy:                 defaultTransport.Proxy,
×
1661
                        DialContext:           defaultTransport.DialContext,
×
1662
                        MaxIdleConns:          defaultTransport.MaxIdleConns,
×
1663
                        IdleConnTimeout:       defaultTransport.IdleConnTimeout,
×
1664
                        ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
×
1665
                        TLSHandshakeTimeout:   defaultTransport.TLSHandshakeTimeout,
×
1666
                        TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
×
1667
                }
×
1668
                httpClient = &http.Client{
×
1669
                        Transport: tr,
×
1670
                }
×
1671
        }
×
1672
        return httpClient
×
UNCOV
1673
}
×
UNCOV
1674

×
UNCOV
1675
// ErrConnectionRefused checks for connection refused errors
×
1676
func ErrConnectionRefused(err error) bool {
1677
        return strings.Contains(err.Error(), "connection refused")
1678
}
UNCOV
1679

×
UNCOV
1680
// GetPodMetricsPort returns, if exists, the metrics port from the passed pod
×
UNCOV
1681
func GetPodMetricsPort(pod *corev1.Pod) (int, error) {
×
1682
        for _, container := range pod.Spec.Containers {
1683
                for _, port := range container.Ports {
1684
                        if port.Name == "metrics" {
1✔
1685
                                return int(port.ContainerPort), nil
2✔
1686
                        }
2✔
1687
                }
2✔
1688
        }
1✔
1689
        return 0, errors.New("Metrics port not found in pod")
1✔
1690
}
1691

1692
// GetMetricsURL builds the metrics URL according to the specified pod
1✔
1693
func GetMetricsURL(pod *corev1.Pod) (string, error) {
1694
        if pod == nil {
1695
                return "", nil
1696
        }
1✔
1697
        port, err := GetPodMetricsPort(pod)
1✔
UNCOV
1698
        if err != nil || pod.Status.PodIP == "" {
×
UNCOV
1699
                return "", err
×
1700
        }
1✔
1701
        domain := net.JoinHostPort(pod.Status.PodIP, fmt.Sprint(port))
2✔
1702
        url := fmt.Sprintf("https://%s/metrics", domain)
1✔
1703
        return url, nil
1✔
1704
}
1✔
1705

1✔
1706
// GetProgressReportFromURL fetches the progress report from the passed URL according to an specific metric expression and ownerUID
1✔
1707
func GetProgressReportFromURL(ctx context.Context, url string, httpClient *http.Client, metricExp, ownerUID string) (string, error) {
1708
        regExp := regexp.MustCompile(fmt.Sprintf("(%s)\\{ownerUID\\=%q\\} (\\d{1,3}\\.?\\d*)", metricExp, ownerUID))
1709
        // pod could be gone, don't block an entire thread for 30 seconds
1710
        // just to get back an i/o timeout
×
1711
        ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
×
1712
        defer cancel()
×
1713
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
×
1714
        if err != nil {
×
1715
                return "", err
×
1716
        }
×
1717
        resp, err := httpClient.Do(req)
×
1718
        if err != nil {
×
1719
                if ErrConnectionRefused(err) {
×
1720
                        return "", nil
×
1721
                }
×
1722
                return "", err
×
UNCOV
1723
        }
×
1724
        defer resp.Body.Close()
×
1725
        body, err := io.ReadAll(resp.Body)
×
1726
        if err != nil {
1727
                return "", err
×
1728
        }
×
UNCOV
1729

×
UNCOV
1730
        // Parse the progress from the body
×
1731
        progressReport := ""
×
1732
        match := regExp.FindStringSubmatch(string(body))
1733
        if match != nil {
1734
                progressReport = match[len(match)-1]
×
1735
        }
×
1736
        return progressReport, nil
×
UNCOV
1737
}
×
UNCOV
1738

×
UNCOV
1739
// UpdateHTTPAnnotations updates the passed annotations for proper http import
×
1740
func UpdateHTTPAnnotations(annotations map[string]string, http *cdiv1.DataVolumeSourceHTTP) {
1741
        annotations[AnnEndpoint] = http.URL
1742
        annotations[AnnSource] = SourceHTTP
1743

×
1744
        if http.SecretRef != "" {
×
1745
                annotations[AnnSecret] = http.SecretRef
×
1746
        }
×
1747
        if http.CertConfigMap != "" {
×
1748
                annotations[AnnCertConfigMap] = http.CertConfigMap
×
1749
        }
×
1750
        if http.Checksum != "" {
×
1751
                annotations[AnnChecksum] = http.Checksum
×
1752
        }
×
1753
        for index, header := range http.ExtraHeaders {
×
1754
                annotations[fmt.Sprintf("%s.%d", AnnExtraHeaders, index)] = header
×
1755
        }
×
1756
        for index, header := range http.SecretExtraHeaders {
×
1757
                annotations[fmt.Sprintf("%s.%d", AnnSecretExtraHeaders, index)] = header
×
1758
        }
×
UNCOV
1759
}
×
UNCOV
1760

×
UNCOV
1761
// UpdateS3Annotations updates the passed annotations for proper S3 import
×
1762
func UpdateS3Annotations(annotations map[string]string, s3 *cdiv1.DataVolumeSourceS3) {
1763
        annotations[AnnEndpoint] = s3.URL
1764
        annotations[AnnSource] = SourceS3
1765
        if s3.SecretRef != "" {
×
1766
                annotations[AnnSecret] = s3.SecretRef
×
1767
        }
×
1768
        if s3.CertConfigMap != "" {
×
1769
                annotations[AnnCertConfigMap] = s3.CertConfigMap
×
1770
        }
×
UNCOV
1771
}
×
UNCOV
1772

×
UNCOV
1773
// UpdateGCSAnnotations updates the passed annotations for proper GCS import
×
1774
func UpdateGCSAnnotations(annotations map[string]string, gcs *cdiv1.DataVolumeSourceGCS) {
1775
        annotations[AnnEndpoint] = gcs.URL
1776
        annotations[AnnSource] = SourceGCS
1777
        if gcs.SecretRef != "" {
×
1778
                annotations[AnnSecret] = gcs.SecretRef
×
1779
        }
×
UNCOV
1780
}
×
UNCOV
1781

×
UNCOV
1782
// UpdateRegistryAnnotations updates the passed annotations for proper registry import
×
1783
func UpdateRegistryAnnotations(annotations map[string]string, registry *cdiv1.DataVolumeSourceRegistry) {
1784
        annotations[AnnSource] = SourceRegistry
1785
        pullMethod := registry.PullMethod
1786
        if pullMethod != nil && *pullMethod != "" {
×
1787
                annotations[AnnRegistryImportMethod] = string(*pullMethod)
×
1788
        }
×
1789
        url := registry.URL
×
1790
        if url != nil && *url != "" {
×
1791
                annotations[AnnEndpoint] = *url
×
1792
        } else {
×
1793
                imageStream := registry.ImageStream
×
1794
                if imageStream != nil && *imageStream != "" {
×
1795
                        annotations[AnnEndpoint] = *imageStream
×
1796
                        annotations[AnnRegistryImageStream] = "true"
×
1797
                }
×
UNCOV
1798
        }
×
1799
        secretRef := registry.SecretRef
×
1800
        if secretRef != nil && *secretRef != "" {
×
1801
                annotations[AnnSecret] = *secretRef
1802
        }
×
1803
        certConfigMap := registry.CertConfigMap
×
1804
        if certConfigMap != nil && *certConfigMap != "" {
×
1805
                annotations[AnnCertConfigMap] = *certConfigMap
×
1806
        }
×
UNCOV
1807

×
1808
        if registry.Platform != nil && registry.Platform.Architecture != "" {
×
1809
                annotations[AnnRegistryImageArchitecture] = registry.Platform.Architecture
×
1810
        }
UNCOV
1811
}
×
UNCOV
1812

×
UNCOV
1813
// UpdateVDDKAnnotations updates the passed annotations for proper VDDK import
×
1814
func UpdateVDDKAnnotations(annotations map[string]string, vddk *cdiv1.DataVolumeSourceVDDK) {
1815
        annotations[AnnEndpoint] = vddk.URL
1816
        annotations[AnnSource] = SourceVDDK
1817
        annotations[AnnSecret] = vddk.SecretRef
×
1818
        annotations[AnnBackingFile] = vddk.BackingFile
×
1819
        annotations[AnnUUID] = vddk.UUID
×
1820
        annotations[AnnThumbprint] = vddk.Thumbprint
×
1821
        if vddk.InitImageURL != "" {
×
1822
                annotations[AnnVddkInitImageURL] = vddk.InitImageURL
×
1823
        }
×
1824
        if vddk.ExtraArgs != "" {
×
1825
                annotations[AnnVddkExtraArgs] = vddk.ExtraArgs
×
1826
        }
×
UNCOV
1827
}
×
UNCOV
1828

×
UNCOV
1829
// UpdateImageIOAnnotations updates the passed annotations for proper imageIO import
×
1830
func UpdateImageIOAnnotations(annotations map[string]string, imageio *cdiv1.DataVolumeSourceImageIO) {
1831
        annotations[AnnEndpoint] = imageio.URL
1832
        annotations[AnnSource] = SourceImageio
1833
        annotations[AnnSecret] = imageio.SecretRef
×
1834
        annotations[AnnCertConfigMap] = imageio.CertConfigMap
×
1835
        annotations[AnnDiskID] = imageio.DiskID
×
1836
        if imageio.InsecureSkipVerify != nil && *imageio.InsecureSkipVerify {
×
1837
                annotations[AnnInsecureSkipVerify] = "true"
×
1838
        }
×
UNCOV
1839
}
×
UNCOV
1840

×
UNCOV
1841
// IsPVBoundToPVC checks if a PV is bound to a specific PVC
×
1842
func IsPVBoundToPVC(pv *corev1.PersistentVolume, pvc *corev1.PersistentVolumeClaim) bool {
1843
        claimRef := pv.Spec.ClaimRef
1844
        return claimRef != nil && claimRef.Name == pvc.Name && claimRef.Namespace == pvc.Namespace && claimRef.UID == pvc.UID
1845
}
1✔
1846

1✔
1847
// Rebind binds the PV of source to target
1✔
1848
func Rebind(ctx context.Context, c client.Client, source, target *corev1.PersistentVolumeClaim) error {
1✔
1849
        pv := &corev1.PersistentVolume{
1850
                ObjectMeta: metav1.ObjectMeta{
1851
                        Name: source.Spec.VolumeName,
1✔
1852
                },
1✔
1853
        }
1✔
1854

1✔
1855
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
1✔
1856
                return err
1✔
1857
        }
1✔
1858

2✔
1859
        // Examine the claimref for the PV and see if it's still bound to PVC'
1✔
1860
        if pv.Spec.ClaimRef == nil {
1✔
1861
                return fmt.Errorf("PV %s claimRef is nil", pv.Name)
1862
        }
1863

1✔
UNCOV
1864
        if !IsPVBoundToPVC(pv, source) {
×
UNCOV
1865
                // Something is not right if the PV is neither bound to PVC' nor target PVC
×
1866
                if !IsPVBoundToPVC(pv, target) {
1867
                        klog.Errorf("PV bound to unexpected PVC: Could not rebind to target PVC '%s'", target.Name)
2✔
1868
                        return fmt.Errorf("PV %s bound to unexpected claim %s", pv.Name, pv.Spec.ClaimRef.Name)
1✔
1869
                }
2✔
1870
                // our work is done
1✔
1871
                return nil
1✔
1872
        }
1✔
1873

1874
        // Rebind PVC to target PVC
1✔
1875
        pv.Spec.ClaimRef = &corev1.ObjectReference{
1876
                Namespace:       target.Namespace,
1877
                Name:            target.Name,
1878
                UID:             target.UID,
1✔
1879
                ResourceVersion: target.ResourceVersion,
1✔
1880
        }
1✔
1881
        klog.V(3).Info("Rebinding PV to target PVC", "PVC", target.Name)
1✔
1882
        if err := c.Update(context.TODO(), pv); err != nil {
1✔
1883
                return err
1✔
1884
        }
1✔
1885

1✔
UNCOV
1886
        return nil
×
UNCOV
1887
}
×
1888

1889
// BulkDeleteResources deletes a bunch of resources
1✔
1890
func BulkDeleteResources(ctx context.Context, c client.Client, obj client.ObjectList, lo client.ListOption) error {
1891
        if err := c.List(ctx, obj, lo); err != nil {
1892
                if meta.IsNoMatchError(err) {
1893
                        return nil
×
1894
                }
×
1895
                return err
×
UNCOV
1896
        }
×
UNCOV
1897

×
1898
        sv := reflect.ValueOf(obj).Elem()
×
1899
        iv := sv.FieldByName("Items")
1900

1901
        for i := 0; i < iv.Len(); i++ {
×
1902
                obj := iv.Index(i).Addr().Interface().(client.Object)
×
1903
                if obj.GetDeletionTimestamp().IsZero() {
×
1904
                        klog.V(3).Infof("Deleting type %+v %+v", reflect.TypeOf(obj), obj)
×
1905
                        if err := c.Delete(ctx, obj); err != nil {
×
1906
                                return err
×
1907
                        }
×
UNCOV
1908
                }
×
UNCOV
1909
        }
×
UNCOV
1910

×
1911
        return nil
1912
}
1913

UNCOV
1914
// ValidateSnapshotCloneSize does proper size validation when doing a clone from snapshot operation
×
1915
func ValidateSnapshotCloneSize(snapshot *snapshotv1.VolumeSnapshot, pvcSpec *corev1.PersistentVolumeClaimSpec, targetSC *storagev1.StorageClass, log logr.Logger) (bool, error) {
1916
        restoreSize := snapshot.Status.RestoreSize
1917
        if restoreSize == nil {
1918
                return false, fmt.Errorf("snapshot has no RestoreSize")
×
1919
        }
×
1920
        targetRequest, hasTargetRequest := pvcSpec.Resources.Requests[corev1.ResourceStorage]
×
1921
        allowExpansion := targetSC.AllowVolumeExpansion != nil && *targetSC.AllowVolumeExpansion
×
1922
        if hasTargetRequest {
×
1923
                // otherwise will just use restoreSize
×
1924
                if restoreSize.Cmp(targetRequest) < 0 && !allowExpansion {
×
1925
                        log.V(3).Info("Can't expand restored PVC because SC does not allow expansion, need to fall back to host assisted")
×
1926
                        return false, nil
×
1927
                }
×
UNCOV
1928
        }
×
1929
        return true, nil
×
UNCOV
1930
}
×
1931

UNCOV
1932
// ValidateSnapshotCloneProvisioners validates the target PVC storage class against the snapshot class provisioner
×
1933
func ValidateSnapshotCloneProvisioners(vsc *snapshotv1.VolumeSnapshotContent, storageClass *storagev1.StorageClass) (bool, error) {
1934
        // Do snapshot and storage class validation
1935
        if storageClass == nil {
1936
                return false, fmt.Errorf("target storage class not found")
×
1937
        }
×
1938
        if storageClass.Provisioner != vsc.Spec.Driver {
×
1939
                return false, nil
×
1940
        }
×
UNCOV
1941
        // TODO: get sourceVolumeMode from volumesnapshotcontent and validate against target spec
×
UNCOV
1942
        // currently don't have CRDs in CI with sourceVolumeMode which is pretty new
×
UNCOV
1943
        // converting volume mode is possible but has security implications
×
1944
        return true, nil
1945
}
1946

UNCOV
1947
// GetSnapshotClassForSmartClone looks up the snapshot class based on the storage class
×
1948
func GetSnapshotClassForSmartClone(pvc *corev1.PersistentVolumeClaim, targetPvcStorageClassName, snapshotClassName *string, log logr.Logger, client client.Client, recorder record.EventRecorder) (string, error) {
1949
        logger := log.WithName("GetSnapshotClassForSmartClone").V(3)
1950
        // Check if relevant CRDs are available
1951
        if !isCsiCrdsDeployed(client, log) {
×
1952
                logger.Info("Missing CSI snapshotter CRDs, falling back to host assisted clone")
×
1953
                return "", nil
×
1954
        }
×
UNCOV
1955

×
1956
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(context.TODO(), client, targetPvcStorageClassName)
×
1957
        if err != nil {
×
1958
                return "", err
1959
        }
×
1960
        if targetStorageClass == nil {
×
1961
                logger.Info("Target PVC's Storage Class not found")
×
1962
                return "", nil
×
1963
        }
×
UNCOV
1964

×
1965
        vscName, err := GetVolumeSnapshotClass(context.TODO(), client, pvc, targetStorageClass.Provisioner, snapshotClassName, logger, recorder)
×
1966
        if err != nil {
×
1967
                return "", err
1968
        }
×
1969
        if vscName != nil {
×
1970
                if pvc != nil {
×
1971
                        logger.Info("smart-clone is applicable for datavolume", "datavolume",
×
1972
                                pvc.Name, "snapshot class", *vscName)
×
1973
                }
×
1974
                return *vscName, nil
×
UNCOV
1975
        }
×
UNCOV
1976

×
1977
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
1978
        return "", nil
1979
}
UNCOV
1980

×
UNCOV
1981
// GetVolumeSnapshotClass looks up the snapshot class based on the driver and an optional specific name
×
1982
// In case of multiple candidates, it returns the default-annotated one, or the sorted list first one if no such default
1983
func GetVolumeSnapshotClass(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim, driver string, snapshotClassName *string, log logr.Logger, recorder record.EventRecorder) (*string, error) {
1984
        logger := log.WithName("GetVolumeSnapshotClass").V(3)
1985

1986
        logEvent := func(message, vscName string) {
×
1987
                logger.Info(message, "name", vscName)
×
1988
                if pvc != nil {
×
1989
                        msg := fmt.Sprintf("%s %s", message, vscName)
×
1990
                        recorder.Event(pvc, corev1.EventTypeNormal, VolumeSnapshotClassSelected, msg)
×
1991
                }
×
UNCOV
1992
        }
×
UNCOV
1993

×
1994
        if snapshotClassName != nil {
×
1995
                vsc := &snapshotv1.VolumeSnapshotClass{}
1996
                if err := c.Get(context.TODO(), types.NamespacedName{Name: *snapshotClassName}, vsc); err != nil {
1997
                        return nil, err
×
1998
                }
×
1999
                if vsc.Driver == driver {
×
2000
                        logEvent(MessageStorageProfileVolumeSnapshotClassSelected, vsc.Name)
×
2001
                        return snapshotClassName, nil
×
2002
                }
×
2003
                return nil, nil
×
UNCOV
2004
        }
×
UNCOV
2005

×
2006
        vscList := &snapshotv1.VolumeSnapshotClassList{}
×
2007
        if err := c.List(ctx, vscList); err != nil {
2008
                if meta.IsNoMatchError(err) {
2009
                        return nil, nil
×
2010
                }
×
2011
                return nil, err
×
UNCOV
2012
        }
×
UNCOV
2013

×
2014
        var candidates []string
×
2015
        for _, vsc := range vscList.Items {
2016
                if vsc.Driver == driver {
2017
                        if vsc.Annotations[AnnDefaultSnapshotClass] == "true" {
×
2018
                                logEvent(MessageDefaultVolumeSnapshotClassSelected, vsc.Name)
×
2019
                                vscName := vsc.Name
×
2020
                                return &vscName, nil
×
2021
                        }
×
2022
                        candidates = append(candidates, vsc.Name)
×
UNCOV
2023
                }
×
UNCOV
2024
        }
×
UNCOV
2025

×
2026
        if len(candidates) > 0 {
2027
                sort.Strings(candidates)
2028
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
2029
                return &candidates[0], nil
×
2030
        }
×
UNCOV
2031

×
2032
        return nil, nil
×
UNCOV
2033
}
×
2034

UNCOV
2035
// isCsiCrdsDeployed checks whether the CSI snapshotter CRD are deployed
×
2036
func isCsiCrdsDeployed(c client.Client, log logr.Logger) bool {
2037
        version := "v1"
2038
        vsClass := "volumesnapshotclasses." + snapshotv1.GroupName
2039
        vsContent := "volumesnapshotcontents." + snapshotv1.GroupName
×
2040
        vs := "volumesnapshots." + snapshotv1.GroupName
×
2041

×
2042
        return isCrdDeployed(c, vsClass, version, log) &&
×
2043
                isCrdDeployed(c, vsContent, version, log) &&
×
2044
                isCrdDeployed(c, vs, version, log)
×
2045
}
×
UNCOV
2046

×
UNCOV
2047
// isCrdDeployed checks whether a CRD is deployed
×
2048
func isCrdDeployed(c client.Client, name, version string, log logr.Logger) bool {
×
2049
        crd := &extv1.CustomResourceDefinition{}
2050
        err := c.Get(context.TODO(), types.NamespacedName{Name: name}, crd)
2051
        if err != nil {
×
2052
                if !k8serrors.IsNotFound(err) {
×
2053
                        log.Info("Error looking up CRD", "crd name", name, "version", version, "error", err)
×
2054
                }
×
2055
                return false
×
UNCOV
2056
        }
×
UNCOV
2057

×
2058
        for _, v := range crd.Spec.Versions {
×
2059
                if v.Name == version && v.Served {
2060
                        return true
2061
                }
×
UNCOV
2062
        }
×
UNCOV
2063

×
2064
        return false
×
2065
}
2066

UNCOV
2067
// IsSnapshotReady indicates if a volume snapshot is ready to be used
×
2068
func IsSnapshotReady(snapshot *snapshotv1.VolumeSnapshot) bool {
2069
        return snapshot.Status != nil && snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse
2070
}
UNCOV
2071

×
UNCOV
2072
// GetResource updates given obj with the data of the object with the same name and namespace
×
2073
func GetResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
×
2074
        obj.SetNamespace(namespace)
2075
        obj.SetName(name)
2076

×
2077
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
2078
        if err != nil {
×
2079
                if k8serrors.IsNotFound(err) {
×
2080
                        return false, nil
×
2081
                }
×
UNCOV
2082

×
2083
                return false, err
×
UNCOV
2084
        }
×
2085

2086
        return true, nil
×
2087
}
2088

UNCOV
2089
// PatchArgs are the args for Patch
×
2090
type PatchArgs struct {
2091
        Client client.Client
2092
        Log    logr.Logger
2093
        Obj    client.Object
2094
        OldObj client.Object
2095
}
2096

2097
// GetAnnotatedEventSource returns resource referenced by AnnEventSource annotations
2098
func GetAnnotatedEventSource(ctx context.Context, c client.Client, obj client.Object) (client.Object, error) {
2099
        esk, ok := obj.GetAnnotations()[AnnEventSourceKind]
2100
        if !ok {
2101
                return obj, nil
×
2102
        }
×
2103
        if esk != "PersistentVolumeClaim" {
×
2104
                return obj, nil
×
2105
        }
×
2106
        es, ok := obj.GetAnnotations()[AnnEventSource]
×
2107
        if !ok {
×
2108
                return obj, nil
×
2109
        }
×
2110
        namespace, name, err := cache.SplitMetaNamespaceKey(es)
×
2111
        if err != nil {
×
2112
                return nil, err
×
2113
        }
×
2114
        pvc := &corev1.PersistentVolumeClaim{
×
2115
                ObjectMeta: metav1.ObjectMeta{
×
2116
                        Namespace: namespace,
×
2117
                        Name:      name,
×
2118
                },
×
2119
        }
×
2120
        if err := c.Get(ctx, client.ObjectKeyFromObject(pvc), pvc); err != nil {
×
2121
                return nil, err
×
2122
        }
×
2123
        return pvc, nil
×
UNCOV
2124
}
×
UNCOV
2125

×
UNCOV
2126
// OwnedByDataVolume returns true if the object is owned by a DataVolume
×
2127
func OwnedByDataVolume(obj metav1.Object) bool {
2128
        owner := metav1.GetControllerOf(obj)
2129
        return owner != nil && owner.Kind == "DataVolume"
2130
}
×
UNCOV
2131

×
UNCOV
2132
// CopyAllowedAnnotations copies the allowed annotations from the source object
×
UNCOV
2133
// to the destination object
×
2134
func CopyAllowedAnnotations(srcObj, dstObj metav1.Object) {
2135
        for ann, def := range allowedAnnotations {
2136
                val, ok := srcObj.GetAnnotations()[ann]
2137
                if !ok && def != "" {
×
2138
                        val = def
×
2139
                }
×
2140
                if val != "" {
×
2141
                        klog.V(1).Info("Applying annotation", "Name", dstObj.GetName(), ann, val)
×
2142
                        AddAnnotation(dstObj, ann, val)
×
2143
                }
×
UNCOV
2144
        }
×
UNCOV
2145
}
×
UNCOV
2146

×
2147
// CopyAllowedLabels copies allowed labels matching the validLabelsMatch regexp from the
2148
// source map to the destination object allowing overwrites
2149
func CopyAllowedLabels(srcLabels map[string]string, dstObj metav1.Object, overwrite bool) {
2150
        for label, value := range srcLabels {
2151
                if _, found := dstObj.GetLabels()[label]; (!found || overwrite) && validLabelsMatch.MatchString(label) {
2152
                        AddLabel(dstObj, label, value)
1✔
2153
                }
2✔
2154
        }
2✔
2155
}
1✔
2156

1✔
2157
// ClaimMayExistBeforeDataVolume returns true if the PVC may exist before the DataVolume
2158
func ClaimMayExistBeforeDataVolume(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
2159
        if ClaimIsPopulatedForDataVolume(pvc, dv) {
2160
                return true, nil
2161
        }
×
2162
        return AllowClaimAdoption(c, pvc, dv)
×
UNCOV
2163
}
×
UNCOV
2164

×
UNCOV
2165
// ClaimIsPopulatedForDataVolume returns true if the PVC is populated for the given DataVolume
×
2166
func ClaimIsPopulatedForDataVolume(pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) bool {
2167
        return pvc != nil && dv != nil && pvc.Annotations[AnnPopulatedFor] == dv.Name
2168
}
UNCOV
2169

×
UNCOV
2170
// AllowClaimAdoption returns true if the PVC may be adopted
×
2171
func AllowClaimAdoption(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2172
        if pvc == nil || dv == nil {
2173
                return false, nil
2174
        }
×
2175
        anno, ok := pvc.Annotations[AnnCreatedForDataVolume]
×
2176
        if ok && anno == string(dv.UID) {
×
2177
                return false, nil
×
2178
        }
×
2179
        anno, ok = dv.Annotations[AnnAllowClaimAdoption]
×
2180
        // if annotation exists, go with that regardless of featuregate
×
2181
        if ok {
×
2182
                val, _ := strconv.ParseBool(anno)
×
2183
                return val, nil
×
2184
        }
×
2185
        return featuregates.NewFeatureGates(c).ClaimAdoptionEnabled()
×
UNCOV
2186
}
×
UNCOV
2187

×
UNCOV
2188
// ResolveDataSourceChain resolves a DataSource reference.
×
2189
// Returns an error if DataSource reference is not found or
2190
// DataSource reference points to another DataSource
2191
func ResolveDataSourceChain(ctx context.Context, client client.Client, dataSource *cdiv1.DataSource) (*cdiv1.DataSource, error) {
2192
        if dataSource.Spec.Source.DataSource == nil {
2193
                return dataSource, nil
2194
        }
×
UNCOV
2195

×
2196
        ref := dataSource.Spec.Source.DataSource
×
2197
        refNs := GetNamespace(ref.Namespace, dataSource.Namespace)
×
2198
        if dataSource.Namespace != refNs {
2199
                return dataSource, ErrDataSourceCrossNamespace
×
2200
        }
×
2201
        if ref.Name == dataSource.Name && refNs == dataSource.Namespace {
×
2202
                return nil, ErrDataSourceSelfReference
×
2203
        }
×
UNCOV
2204

×
2205
        resolved := &cdiv1.DataSource{}
×
2206
        if err := client.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: refNs}, resolved); err != nil {
×
2207
                return nil, err
2208
        }
×
UNCOV
2209

×
2210
        if resolved.Spec.Source.DataSource != nil {
×
2211
                return nil, ErrDataSourceMaxDepthReached
×
2212
        }
UNCOV
2213

×
2214
        return resolved, nil
×
UNCOV
2215
}
×
2216

UNCOV
2217
func sortEvents(events *corev1.EventList, usingPopulator bool, pvcPrimeName string) {
×
2218
        // Sort event lists by containing primeName substring and most recent timestamp
2219
        sort.Slice(events.Items, func(i, j int) bool {
2220
                if usingPopulator {
1✔
2221
                        firstContainsPrime := strings.Contains(events.Items[i].Message, pvcPrimeName)
1✔
2222
                        secondContainsPrime := strings.Contains(events.Items[j].Message, pvcPrimeName)
2✔
2223

2✔
2224
                        if firstContainsPrime && !secondContainsPrime {
1✔
2225
                                return true
1✔
2226
                        }
1✔
2227
                        if !firstContainsPrime && secondContainsPrime {
2✔
2228
                                return false
1✔
2229
                        }
1✔
2230
                }
2✔
2231

1✔
2232
                // if the timestamps are the same, prioritze longer messages to make sure our sorting is deterministic
1✔
2233
                if events.Items[i].LastTimestamp.Time.Equal(events.Items[j].LastTimestamp.Time) {
2234
                        return len(events.Items[i].Message) > len(events.Items[j].Message)
2235
                }
2236

1✔
UNCOV
2237
                // if both contains primeName substring or neither, just sort on timestamp
×
UNCOV
2238
                return events.Items[i].LastTimestamp.Time.After(events.Items[j].LastTimestamp.Time)
×
2239
        })
2240
}
2241

1✔
2242
// UpdatePVCBoundContionFromEvents updates the bound condition annotations on the PVC based on recent events
2243
// This function can be used by both controller and populator packages to update PVC bound condition information
2244
func UpdatePVCBoundContionFromEvents(pvc *corev1.PersistentVolumeClaim, c client.Client, log logr.Logger) error {
2245
        currentPvcCopy := pvc.DeepCopy()
2246

2247
        anno := pvc.GetAnnotations()
×
2248
        if anno == nil {
×
2249
                return nil
×
2250
        }
×
UNCOV
2251

×
2252
        if IsBound(pvc) {
×
2253
                anno := pvc.GetAnnotations()
×
2254
                delete(anno, AnnBoundCondition)
2255
                delete(anno, AnnBoundConditionReason)
×
2256
                delete(anno, AnnBoundConditionMessage)
×
2257

×
2258
                if !reflect.DeepEqual(currentPvcCopy, pvc) {
×
2259
                        patch := client.MergeFrom(currentPvcCopy)
×
2260
                        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2261
                                return err
×
2262
                        }
×
UNCOV
2263
                }
×
UNCOV
2264

×
2265
                return nil
×
2266
        }
2267

2268
        if pvc.Status.Phase != corev1.ClaimPending {
×
2269
                return nil
2270
        }
UNCOV
2271

×
UNCOV
2272
        // set bound condition by getting the latest event
×
2273
        events := &corev1.EventList{}
×
2274

2275
        err := c.List(context.TODO(), events,
2276
                client.InNamespace(pvc.GetNamespace()),
×
2277
                client.MatchingFields{"involvedObject.name": pvc.GetName(),
×
2278
                        "involvedObject.uid": string(pvc.GetUID())},
×
2279
        )
×
2280

×
2281
        if err != nil {
×
2282
                // Log the error but don't fail the reconciliation
×
2283
                log.Error(err, "Unable to list events for PVC bound condition update", "pvc", pvc.Name)
×
2284
                return nil
×
2285
        }
×
UNCOV
2286

×
2287
        if len(events.Items) == 0 {
×
2288
                return nil
×
2289
        }
UNCOV
2290

×
2291
        pvcPrime, usingPopulator := anno[AnnPVCPrimeName]
×
2292

×
2293
        // Sort event lists by containing primeName substring and most recent timestamp
2294
        sortEvents(events, usingPopulator, pvcPrime)
×
2295

×
2296
        boundMessage := ""
×
2297
        // check if prime name annotation exists
×
2298
        if usingPopulator {
×
2299
                // if we are using populators get the latest event from prime pvc
×
2300
                pvcPrime = fmt.Sprintf("[%s] : ", pvcPrime)
×
2301

×
2302
                // if the first event does not contain a prime message, none will so return
×
2303
                primeIdx := strings.Index(events.Items[0].Message, pvcPrime)
×
2304
                if primeIdx == -1 {
×
2305
                        log.V(1).Info("No bound message found, skipping bound condition update", "pvc", pvc.Name)
×
2306
                        return nil
×
2307
                }
×
2308
                boundMessage = events.Items[0].Message[primeIdx+len(pvcPrime):]
×
2309
        } else {
×
2310
                // if not using populators just get the latest event
×
2311
                boundMessage = events.Items[0].Message
×
2312
        }
×
UNCOV
2313

×
UNCOV
2314
        // since we checked status of phase above, we know this is pending
×
2315
        anno[AnnBoundCondition] = "false"
×
2316
        anno[AnnBoundConditionReason] = "Pending"
2317
        anno[AnnBoundConditionMessage] = boundMessage
2318

×
2319
        patch := client.MergeFrom(currentPvcCopy)
×
2320
        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2321
                return err
×
2322
        }
×
UNCOV
2323

×
2324
        return nil
×
UNCOV
2325
}
×
2326

UNCOV
2327
// CopyEvents gets srcPvc events and re-emits them on the target PVC with the src name prefix
×
2328
func CopyEvents(srcPVC, targetPVC client.Object, c client.Client, recorder record.EventRecorder) {
2329
        srcPrefixMsg := fmt.Sprintf("[%s] : ", srcPVC.GetName())
2330

2331
        newEvents := &corev1.EventList{}
×
2332
        err := c.List(context.TODO(), newEvents,
×
2333
                client.InNamespace(srcPVC.GetNamespace()),
×
2334
                client.MatchingFields{"involvedObject.name": srcPVC.GetName(),
×
2335
                        "involvedObject.uid": string(srcPVC.GetUID())},
×
2336
        )
×
2337

×
2338
        if err != nil {
×
2339
                klog.Error(err, "Could not retrieve srcPVC list of Events")
×
2340
        }
×
UNCOV
2341

×
2342
        currEvents := &corev1.EventList{}
×
2343
        err = c.List(context.TODO(), currEvents,
×
2344
                client.InNamespace(targetPVC.GetNamespace()),
2345
                client.MatchingFields{"involvedObject.name": targetPVC.GetName(),
×
2346
                        "involvedObject.uid": string(targetPVC.GetUID())},
×
2347
        )
×
2348

×
2349
        if err != nil {
×
2350
                klog.Error(err, "Could not retrieve targetPVC list of Events")
×
2351
        }
×
UNCOV
2352

×
UNCOV
2353
        // use this to hash each message for quick lookup, value is unused
×
2354
        eventMap := map[string]struct{}{}
×
2355

2356
        for _, event := range currEvents.Items {
2357
                eventMap[event.Message] = struct{}{}
×
2358
        }
×
UNCOV
2359

×
2360
        for _, newEvent := range newEvents.Items {
×
2361
                msg := newEvent.Message
×
2362

2363
                // check if target PVC already has this equivalent event
×
2364
                if _, exists := eventMap[msg]; exists {
×
2365
                        continue
×
UNCOV
2366
                }
×
UNCOV
2367

×
2368
                formattedMsg := srcPrefixMsg + msg
×
2369
                // check if we already emitted this event with the src prefix
2370
                if _, exists := eventMap[formattedMsg]; exists {
2371
                        continue
×
UNCOV
2372
                }
×
2373
                recorder.Event(targetPVC, newEvent.Type, newEvent.Reason, formattedMsg)
×
UNCOV
2374
        }
×
2375
}
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