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

kubevirt / containerized-data-importer / #6133

21 Jul 2026 04:38AM UTC coverage: 49.775% (-0.04%) from 49.813%
#6133

Pull #3947

travis-ci

gwencasey96
Fix VDDK TLS cert application and E2E cert validation tests

Apply CA certificates before connecting to vCenter so cert validation
works, and align E2E assertions with standard DV/PVC wait patterns and
importer failure detection instead of expecting a Failed DV phase.

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Gwen Casey <gcasey@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Pull Request #3947: Add certificate validation support for VDDK importer

39 of 76 new or added lines in 3 files covered. (51.32%)

44 existing lines in 1 file now uncovered.

15158 of 30453 relevant lines covered (49.78%)

0.56 hits per line

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

14.54
/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
        // AnnOpenDefaultPorts allows incoming traffic on specific ports through the
264
        // pod's default network interface in OVN-Kubernetes managed clusters.
265
        AnnOpenDefaultPorts = "k8s.ovn.org/open-default-ports"
266
        // AnnPodSidecarInjectionIstio is used for enabling/disabling Pod istio/AspenMesh sidecar injection
267
        AnnPodSidecarInjectionIstio = "sidecar.istio.io/inject"
268
        // AnnPodSidecarInjectionIstioDefault is the default value passed for AnnPodSidecarInjection
269
        AnnPodSidecarInjectionIstioDefault = "false"
270
        // AnnPodSidecarInjectionLinkerd is used to enable/disable linkerd sidecar injection
271
        AnnPodSidecarInjectionLinkerd = "linkerd.io/inject"
272
        // AnnPodSidecarInjectionLinkerdDefault is the default value passed for AnnPodSidecarInjectionLinkerd
273
        AnnPodSidecarInjectionLinkerdDefault = "disabled"
274

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

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

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

285
        // CloneSourceInUse is reason for event created when clone source pvc is in use
286
        CloneSourceInUse = "CloneSourceInUse"
287

288
        // CloneComplete message
289
        CloneComplete = "Clone Complete"
290

291
        cloneTokenLeeway = 10 * time.Second
292

293
        // Default value for preallocation option if not defined in DV or CDIConfig
294
        defaultPreallocation = false
295

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

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

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

333
        // ClaimLost reason const
334
        ClaimLost = "ClaimLost"
335
        // NotFound reason const
336
        NotFound = "NotFound"
337

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

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

351
        // LabelExcludeFromVeleroBackup provides a const to indicate whether an object should be excluded from velero backup
352
        LabelExcludeFromVeleroBackup = "velero.io/exclude-from-backup"
353

354
        // ProgressDone this means we are DONE
355
        ProgressDone = "100.0%"
356

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

362
        // AnnAllowClaimAdoption is the annotation that allows a claim to be adopted by a DataVolume
363
        AnnAllowClaimAdoption = AnnAPIGroup + "/allowClaimAdoption"
364

365
        // AnnCdiCustomizeComponentHash annotation is a hash of all customizations that live under spec.CustomizeComponents
366
        AnnCdiCustomizeComponentHash = AnnAPIGroup + "/customizer-identifier"
367

368
        // AnnCreatedForDataVolume stores the UID of the datavolume that the PVC was created for
369
        AnnCreatedForDataVolume = AnnAPIGroup + "/createdForDataVolume"
370

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

374
        // ClaimMisbound event reason
375
        EventReasonClaimMisbound = "ClaimMisbound"
376
)
377

378
// Size-detection pod error codes
379
const (
380
        NoErr int = iota
381
        ErrBadArguments
382
        ErrInvalidFile
383
        ErrInvalidPath
384
        ErrBadTermFile
385
        ErrUnknown
386
)
387

388
var (
389
        // BlockMode is raw block device mode
390
        BlockMode = corev1.PersistentVolumeBlock
391
        // FilesystemMode is filesystem device mode
392
        FilesystemMode = corev1.PersistentVolumeFilesystem
393

394
        // DefaultInstanceTypeLabels is a list of currently supported default instance type labels
395
        DefaultInstanceTypeLabels = []string{
396
                LabelDefaultInstancetype,
397
                LabelDefaultInstancetypeKind,
398
                LabelDefaultPreference,
399
                LabelDefaultPreferenceKind,
400
        }
401

402
        apiServerKeyOnce sync.Once
403
        apiServerKey     *rsa.PrivateKey
404

405
        // allowedAnnotations is a list of annotations
406
        // that can be propagated from the pvc/dv to a pod
407
        allowedAnnotations = map[string]string{
408
                AnnPodNetwork:                 "",
409
                AnnPodSidecarInjectionIstio:   AnnPodSidecarInjectionIstioDefault,
410
                AnnPodSidecarInjectionLinkerd: AnnPodSidecarInjectionLinkerdDefault,
411
                AnnPriorityClassName:          "",
412
                AnnPodServiceAccount:          "",
413
                AnnPodMultusDefaultNetwork:    "",
414
        }
415

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

418
        ErrDataSourceMaxDepthReached = errors.New("DataSource reference chain exceeds maximum depth of 1")
419
        ErrDataSourceSelfReference   = errors.New("DataSource cannot self-reference")
420
        ErrDataSourceCrossNamespace  = errors.New("DataSource cannot reference a DataSource in another namespace")
421
)
422

423
// FakeValidator is a fake token validator
424
type FakeValidator struct {
425
        Match     string
426
        Operation token.Operation
427
        Name      string
428
        Namespace string
429
        Resource  metav1.GroupVersionResource
430
        Params    map[string]string
431
}
432

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

450
// MultiTokenValidator is a token validator that can validate both short and long tokens
451
type MultiTokenValidator struct {
452
        ShortTokenValidator token.Validator
453
        LongTokenValidator  token.Validator
454
}
455

456
// ValidatePVC validates a PVC
457
func (mtv *MultiTokenValidator) ValidatePVC(source, target *corev1.PersistentVolumeClaim) error {
×
458
        tok, v := mtv.getTokenAndValidator(target)
×
459
        return ValidateCloneTokenPVC(tok, v, source, target)
×
460
}
×
461

462
// ValidatePopulator valades a token for a populator
463
func (mtv *MultiTokenValidator) ValidatePopulator(vcs *cdiv1.VolumeCloneSource, pvc *corev1.PersistentVolumeClaim) error {
×
464
        if vcs.Namespace == pvc.Namespace {
×
465
                return nil
×
466
        }
×
467

468
        tok, v := mtv.getTokenAndValidator(pvc)
×
469

×
470
        tokenData, err := v.Validate(tok)
×
471
        if err != nil {
×
472
                return errors.Wrap(err, "error verifying token")
×
473
        }
×
474

475
        var tokenResourceName string
×
476
        switch vcs.Spec.Source.Kind {
×
477
        case "PersistentVolumeClaim":
×
478
                tokenResourceName = "persistentvolumeclaims"
×
479
        case "VolumeSnapshot":
×
480
                tokenResourceName = "volumesnapshots"
×
481
        }
482
        srcName := vcs.Spec.Source.Name
×
483

×
484
        return validateTokenData(tokenData, vcs.Namespace, srcName, pvc.Namespace, pvc.Name, string(pvc.UID), tokenResourceName)
×
485
}
486

487
func (mtv *MultiTokenValidator) getTokenAndValidator(pvc *corev1.PersistentVolumeClaim) (string, token.Validator) {
×
488
        v := mtv.LongTokenValidator
×
489
        tok, ok := pvc.Annotations[AnnExtendedCloneToken]
×
490
        if !ok {
×
491
                // if token doesn't exist, no prob for same namespace
×
492
                tok = pvc.Annotations[AnnCloneToken]
×
493
                v = mtv.ShortTokenValidator
×
494
        }
×
495
        return tok, v
×
496
}
497

498
// NewMultiTokenValidator returns a new multi token validator
499
func NewMultiTokenValidator(key *rsa.PublicKey) *MultiTokenValidator {
×
500
        return &MultiTokenValidator{
×
501
                ShortTokenValidator: NewCloneTokenValidator(common.CloneTokenIssuer, key),
×
502
                LongTokenValidator:  NewCloneTokenValidator(common.ExtendedCloneTokenIssuer, key),
×
503
        }
×
504
}
×
505

506
// NewCloneTokenValidator returns a new token validator
507
func NewCloneTokenValidator(issuer string, key *rsa.PublicKey) token.Validator {
×
508
        return token.NewValidator(issuer, key, cloneTokenLeeway)
×
509
}
×
510

511
// GetRequestedImageSize returns the PVC requested size
512
func GetRequestedImageSize(pvc *corev1.PersistentVolumeClaim) (string, error) {
1✔
513
        pvcSize, found := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
1✔
514
        if !found {
2✔
515
                return "", errors.Errorf("storage request is missing in pvc \"%s/%s\"", pvc.Namespace, pvc.Name)
1✔
516
        }
1✔
517
        return pvcSize.String(), nil
1✔
518
}
519

520
// GetVolumeMode returns the volumeMode from PVC handling default empty value
521
func GetVolumeMode(pvc *corev1.PersistentVolumeClaim) corev1.PersistentVolumeMode {
×
522
        return util.ResolveVolumeMode(pvc.Spec.VolumeMode)
×
523
}
×
524

525
// IsDataVolumeUsingDefaultStorageClass checks if the DataVolume is using the default StorageClass
526
func IsDataVolumeUsingDefaultStorageClass(dv *cdiv1.DataVolume) bool {
×
527
        return GetStorageClassFromDVSpec(dv) == nil
×
528
}
×
529

530
// GetStorageClassFromDVSpec returns the StorageClassName from DataVolume PVC or Storage spec
531
func GetStorageClassFromDVSpec(dv *cdiv1.DataVolume) *string {
×
532
        if dv.Spec.PVC != nil {
×
533
                return dv.Spec.PVC.StorageClassName
×
534
        }
×
535

536
        if dv.Spec.Storage != nil {
×
537
                return dv.Spec.Storage.StorageClassName
×
538
        }
×
539

540
        return nil
×
541
}
542

543
// getStorageClassByName looks up the storage class based on the name.
544
// If name is nil, it performs fallback to default according to the provided content type
545
// If no storage class is found, returns nil
546
func getStorageClassByName(ctx context.Context, client client.Client, name *string, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
1✔
547
        if name == nil {
2✔
548
                return getFallbackStorageClass(ctx, client, contentType)
1✔
549
        }
1✔
550

551
        // look up storage class by name
552
        storageClass := &storagev1.StorageClass{}
×
553
        if err := client.Get(ctx, types.NamespacedName{Name: *name}, storageClass); err != nil {
×
554
                if k8serrors.IsNotFound(err) {
×
555
                        return nil, nil
×
556
                }
×
557
                klog.V(3).Info("Unable to retrieve storage class", "storage class name", *name)
×
558
                return nil, errors.Errorf("unable to retrieve storage class %s", *name)
×
559
        }
560

561
        return storageClass, nil
×
562
}
563

564
// GetStorageClassByNameWithK8sFallback looks up the storage class based on the name
565
// If name is nil, it looks for the default k8s storage class storageclass.kubernetes.io/is-default-class
566
// If no storage class is found, returns nil
567
func GetStorageClassByNameWithK8sFallback(ctx context.Context, client client.Client, name *string) (*storagev1.StorageClass, error) {
1✔
568
        return getStorageClassByName(ctx, client, name, cdiv1.DataVolumeArchive)
1✔
569
}
1✔
570

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

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

589
        if GetContentType(contentType) == cdiv1.DataVolumeKubeVirt {
2✔
590
                if virtSc := GetPlatformDefaultStorageClass(storageClasses, AnnDefaultVirtStorageClass); virtSc != nil {
2✔
591
                        return virtSc, nil
1✔
592
                }
1✔
593
        }
594
        return GetPlatformDefaultStorageClass(storageClasses, AnnDefaultStorageClass), nil
1✔
595
}
596

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

1✔
601
        for _, storageClass := range storageClasses.Items {
2✔
602
                if storageClass.Annotations[defaultAnnotationKey] == "true" {
2✔
603
                        defaultClasses = append(defaultClasses, storageClass)
1✔
604
                }
1✔
605
        }
606

607
        if len(defaultClasses) == 0 {
2✔
608
                return nil
1✔
609
        }
1✔
610

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

625
        return &defaultClasses[0]
1✔
626
}
627

628
// GetFilesystemOverheadForStorageClass determines the filesystem overhead defined in CDIConfig for the storageClass.
629
func GetFilesystemOverheadForStorageClass(ctx context.Context, client client.Client, storageClassName *string) (cdiv1.Percent, error) {
×
630
        if storageClassName != nil && *storageClassName == "" {
×
631
                klog.V(3).Info("No storage class name passed")
×
632
                return "0", nil
×
633
        }
×
634

635
        cdiConfig := &cdiv1.CDIConfig{}
×
636
        if err := client.Get(ctx, types.NamespacedName{Name: common.ConfigName}, cdiConfig); err != nil {
×
637
                if k8serrors.IsNotFound(err) {
×
638
                        klog.V(1).Info("CDIConfig does not exist, pod will not start until it does")
×
639
                        return "0", nil
×
640
                }
×
641
                return "0", err
×
642
        }
643

644
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(ctx, client, storageClassName)
×
645
        if err != nil || targetStorageClass == nil {
×
646
                klog.V(3).Info("Storage class", storageClassName, "not found, trying default storage class")
×
647
                targetStorageClass, err = GetStorageClassByNameWithK8sFallback(ctx, client, nil)
×
648
                if err != nil {
×
649
                        klog.V(3).Info("No default storage class found, continuing with global overhead")
×
650
                        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
651
                }
×
652
        }
653

654
        if cdiConfig.Status.FilesystemOverhead == nil {
×
655
                klog.Errorf("CDIConfig filesystemOverhead used before config controller ran reconcile. Hopefully this only happens during unit testing.")
×
656
                return "0", nil
×
657
        }
×
658

659
        if targetStorageClass == nil {
×
660
                klog.V(3).Info("Storage class", storageClassName, "not found, continuing with global overhead")
×
661
                return cdiConfig.Status.FilesystemOverhead.Global, nil
×
662
        }
×
663

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

×
666
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
×
667

×
668
        storageClassOverhead, found := perStorageConfig[targetStorageClass.GetName()]
×
669
        if found {
×
670
                return storageClassOverhead, nil
×
671
        }
×
672

673
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
674
}
675

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

684
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
685
}
686

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

695
        return cdiconfig.Status.ImagePullSecrets, nil
×
696
}
697

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

709
        pvcUID := pvc.GetUID()
×
710
        for _, pod := range pods.Items {
×
711
                if ShouldIgnorePod(&pod, pvc) {
×
712
                        continue
×
713
                }
714
                for _, or := range pod.OwnerReferences {
×
715
                        if or.UID == pvcUID {
×
716
                                return &pod, nil
×
717
                        }
×
718
                }
719

720
                // TODO: check this
721
                val, exists := pod.Labels[CloneUniqueID]
×
722
                if exists && val == string(pvcUID)+common.ClonerSourcePodNameSuffix {
×
723
                        return &pod, nil
×
724
                }
×
725
        }
726
        return nil, errors.Errorf("Unable to find pod owned by UID: %s, in namespace: %s", string(pvcUID), namespace)
×
727
}
728

729
// AddVolumeDevices returns VolumeDevice slice with one block device for pods using PV with block volume mode
730
func AddVolumeDevices() []corev1.VolumeDevice {
×
731
        volumeDevices := []corev1.VolumeDevice{
×
732
                {
×
733
                        Name:       DataVolName,
×
734
                        DevicePath: common.WriteBlockPath,
×
735
                },
×
736
        }
×
737
        return volumeDevices
×
738
}
×
739

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

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

797
        return pods, nil
×
798
}
799

800
// GetWorkloadNodePlacement extracts the workload-specific nodeplacement values from the CDI CR
801
func GetWorkloadNodePlacement(ctx context.Context, c client.Client) (*sdkapi.NodePlacement, error) {
×
802
        cr, err := GetActiveCDI(ctx, c)
×
803
        if err != nil {
×
804
                return nil, err
×
805
        }
×
806

807
        if cr == nil {
×
808
                return nil, fmt.Errorf("no active CDI")
×
809
        }
×
810

811
        return &cr.Spec.Workloads, nil
×
812
}
813

814
// GetActiveCDI returns the active CDI CR
815
func GetActiveCDI(ctx context.Context, c client.Client) (*cdiv1.CDI, error) {
1✔
816
        crList := &cdiv1.CDIList{}
1✔
817
        if err := c.List(ctx, crList, &client.ListOptions{}); err != nil {
1✔
818
                return nil, err
×
819
        }
×
820

821
        if len(crList.Items) == 0 {
2✔
822
                return nil, nil
1✔
823
        }
1✔
824

825
        if len(crList.Items) == 1 {
2✔
826
                return &crList.Items[0], nil
1✔
827
        }
1✔
828

829
        var activeResources []cdiv1.CDI
1✔
830
        for _, cr := range crList.Items {
2✔
831
                if cr.Status.Phase != sdkapi.PhaseError {
2✔
832
                        activeResources = append(activeResources, cr)
1✔
833
                }
1✔
834
        }
835

836
        if len(activeResources) != 1 {
2✔
837
                return nil, fmt.Errorf("invalid number of active CDI resources: %d", len(activeResources))
1✔
838
        }
1✔
839

840
        return &activeResources[0], nil
1✔
841
}
842

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

852
// GetPreallocation returns the preallocation setting for the specified object (DV or VolumeImportSource), falling back to StorageClass and global setting (in this order)
853
func GetPreallocation(ctx context.Context, client client.Client, preallocation *bool) bool {
×
854
        // First, the DV's preallocation
×
855
        if preallocation != nil {
×
856
                return *preallocation
×
857
        }
×
858

859
        cdiconfig := &cdiv1.CDIConfig{}
×
860
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
×
861
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
862
                return defaultPreallocation
×
863
        }
×
864

865
        return cdiconfig.Status.Preallocation
×
866
}
867

868
// ImmediateBindingRequested returns if an object has the ImmediateBinding annotation
869
func ImmediateBindingRequested(obj metav1.Object) bool {
×
870
        _, isImmediateBindingRequested := obj.GetAnnotations()[AnnImmediateBinding]
×
871
        return isImmediateBindingRequested
×
872
}
×
873

874
// GetPriorityClass gets PVC priority class
875
func GetPriorityClass(pvc *corev1.PersistentVolumeClaim) string {
×
876
        anno := pvc.GetAnnotations()
×
877
        return anno[AnnPriorityClassName]
×
878
}
×
879

880
// GetPodServiceAccount gets PVC service account name
881
func GetPodServiceAccount(pvc *corev1.PersistentVolumeClaim) string {
×
882
        anno := pvc.GetAnnotations()
×
883
        return anno[AnnPodServiceAccount]
×
884
}
×
885

886
// ShouldDeletePod returns whether the PVC workload pod should be deleted
887
func ShouldDeletePod(pvc *corev1.PersistentVolumeClaim) bool {
×
888
        return pvc.GetAnnotations()[AnnPodRetainAfterCompletion] != "true" || pvc.GetAnnotations()[AnnRequiresScratch] == "true" || pvc.GetAnnotations()[AnnRequiresDirectIO] == "true" || pvc.DeletionTimestamp != nil
×
889
}
×
890

891
// AddFinalizer adds a finalizer to a resource
892
func AddFinalizer(obj metav1.Object, name string) {
×
893
        if HasFinalizer(obj, name) {
×
894
                return
×
895
        }
×
896

897
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
898
}
899

900
// RemoveFinalizer removes a finalizer from a resource
901
func RemoveFinalizer(obj metav1.Object, name string) {
×
902
        if !HasFinalizer(obj, name) {
×
903
                return
×
904
        }
×
905

906
        var finalizers []string
×
907
        for _, f := range obj.GetFinalizers() {
×
908
                if f != name {
×
909
                        finalizers = append(finalizers, f)
×
910
                }
×
911
        }
912

913
        obj.SetFinalizers(finalizers)
×
914
}
915

916
// HasFinalizer returns true if a resource has a specific finalizer
917
func HasFinalizer(object metav1.Object, value string) bool {
×
918
        for _, f := range object.GetFinalizers() {
×
919
                if f == value {
×
920
                        return true
×
921
                }
×
922
        }
923
        return false
×
924
}
925

926
// ValidateCloneTokenPVC validates clone token for source and target PVCs
927
func ValidateCloneTokenPVC(t string, v token.Validator, source, target *corev1.PersistentVolumeClaim) error {
×
928
        if source.Namespace == target.Namespace {
×
929
                return nil
×
930
        }
×
931

932
        tokenData, err := v.Validate(t)
×
933
        if err != nil {
×
934
                return errors.Wrap(err, "error verifying token")
×
935
        }
×
936

937
        tokenResourceName := getTokenResourceNamePvc(source)
×
938
        srcName := getSourceNamePvc(source)
×
939

×
940
        return validateTokenData(tokenData, source.Namespace, srcName, target.Namespace, target.Name, string(target.UID), tokenResourceName)
×
941
}
942

943
// ValidateCloneTokenDV validates clone token for DV
944
func ValidateCloneTokenDV(validator token.Validator, dv *cdiv1.DataVolume) error {
×
945
        _, sourceName, sourceNamespace := GetCloneSourceInfo(dv)
×
946
        if sourceNamespace == "" || sourceNamespace == dv.Namespace {
×
947
                return nil
×
948
        }
×
949

950
        tok, ok := dv.Annotations[AnnCloneToken]
×
951
        if !ok {
×
952
                return errors.New("clone token missing")
×
953
        }
×
954

955
        tokenData, err := validator.Validate(tok)
×
956
        if err != nil {
×
957
                return errors.Wrap(err, "error verifying token")
×
958
        }
×
959

960
        tokenResourceName := getTokenResourceNameDataVolume(dv.Spec.Source)
×
961
        if tokenResourceName == "" {
×
962
                return errors.New("token resource name empty, can't verify properly")
×
963
        }
×
964

965
        return validateTokenData(tokenData, sourceNamespace, sourceName, dv.Namespace, dv.Name, "", tokenResourceName)
×
966
}
967

968
func getTokenResourceNameDataVolume(source *cdiv1.DataVolumeSource) string {
×
969
        if source.PVC != nil {
×
970
                return "persistentvolumeclaims"
×
971
        } else if source.Snapshot != nil {
×
972
                return "volumesnapshots"
×
973
        }
×
974

975
        return ""
×
976
}
977

978
func getTokenResourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
979
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
×
980
                return "volumesnapshots"
×
981
        }
×
982

983
        return "persistentvolumeclaims"
×
984
}
985

986
func getSourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
987
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
×
988
                if sourcePvc.Spec.DataSourceRef != nil {
×
989
                        return sourcePvc.Spec.DataSourceRef.Name
×
990
                }
×
991
        }
992

993
        return sourcePvc.Name
×
994
}
995

996
func validateTokenData(tokenData *token.Payload, srcNamespace, srcName, targetNamespace, targetName, targetUID, tokenResourceName string) error {
×
997
        uid := tokenData.Params["uid"]
×
998
        if tokenData.Operation != token.OperationClone ||
×
999
                tokenData.Name != srcName ||
×
1000
                tokenData.Namespace != srcNamespace ||
×
1001
                tokenData.Resource.Resource != tokenResourceName ||
×
1002
                tokenData.Params["targetNamespace"] != targetNamespace ||
×
1003
                tokenData.Params["targetName"] != targetName ||
×
1004
                (uid != "" && uid != targetUID) {
×
1005
                return errors.New("invalid token")
×
1006
        }
×
1007

1008
        return nil
×
1009
}
1010

1011
// IsSnapshotValidForClone returns an error if the passed snapshot is not valid for cloning
1012
func IsSnapshotValidForClone(sourceSnapshot *snapshotv1.VolumeSnapshot) error {
×
1013
        if sourceSnapshot.Status == nil {
×
1014
                return fmt.Errorf("no status on source snapshot yet")
×
1015
        }
×
1016
        if !IsSnapshotReady(sourceSnapshot) {
×
1017
                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)
×
1018
        }
×
1019
        if sourceSnapshot.Status.Error != nil {
×
1020
                errMessage := "no details"
×
1021
                if msg := sourceSnapshot.Status.Error.Message; msg != nil {
×
1022
                        errMessage = *msg
×
1023
                }
×
1024
                return fmt.Errorf("snapshot in error state with msg: %s", errMessage)
×
1025
        }
1026
        if sourceSnapshot.Spec.VolumeSnapshotClassName == nil ||
×
1027
                *sourceSnapshot.Spec.VolumeSnapshotClassName == "" {
×
1028
                return fmt.Errorf("snapshot %s/%s does not have volume snap class populated, can't clone", sourceSnapshot.Name, sourceSnapshot.Namespace)
×
1029
        }
×
1030
        return nil
×
1031
}
1032

1033
// AddAnnotation adds an annotation to an object
1034
func AddAnnotation(obj metav1.Object, key, value string) {
1✔
1035
        if obj.GetAnnotations() == nil {
2✔
1036
                obj.SetAnnotations(make(map[string]string))
1✔
1037
        }
1✔
1038
        obj.GetAnnotations()[key] = value
1✔
1039
}
1040

1041
// AddLabel adds a label to an object
1042
func AddLabel(obj metav1.Object, key, value string) {
1✔
1043
        if obj.GetLabels() == nil {
2✔
1044
                obj.SetLabels(make(map[string]string))
1✔
1045
        }
1✔
1046
        obj.GetLabels()[key] = value
1✔
1047
}
1048

1049
// HandleFailedPod handles pod-creation errors and updates the pod's PVC without providing sensitive information
1050
func HandleFailedPod(err error, podName string, pvc *corev1.PersistentVolumeClaim, recorder record.EventRecorder, c client.Client) error {
×
1051
        if err == nil {
×
1052
                return nil
×
1053
        }
×
1054
        // Generic reason and msg to avoid providing sensitive information
1055
        reason := ErrStartingPod
×
1056
        msg := fmt.Sprintf(MessageErrStartingPod, podName)
×
1057

×
1058
        // Error handling to fine-tune the event with pertinent info
×
1059
        if ErrQuotaExceeded(err) {
×
1060
                reason = ErrExceededQuota
×
1061
        }
×
1062

1063
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1064

×
1065
        if isCloneSourcePod := CreateCloneSourcePodName(pvc) == podName; isCloneSourcePod {
×
1066
                AddAnnotation(pvc, AnnSourceRunningCondition, "false")
×
1067
                AddAnnotation(pvc, AnnSourceRunningConditionReason, reason)
×
1068
                AddAnnotation(pvc, AnnSourceRunningConditionMessage, msg)
×
1069
        } else {
×
1070
                AddAnnotation(pvc, AnnRunningCondition, "false")
×
1071
                AddAnnotation(pvc, AnnRunningConditionReason, reason)
×
1072
                AddAnnotation(pvc, AnnRunningConditionMessage, msg)
×
1073
        }
×
1074

1075
        AddAnnotation(pvc, AnnPodPhase, string(corev1.PodFailed))
×
1076
        if err := c.Update(context.TODO(), pvc); err != nil {
×
1077
                return err
×
1078
        }
×
1079

1080
        return err
×
1081
}
1082

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

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

1118
// AddImportVolumeMounts is being called for pods using PV with filesystem volume mode
1119
func AddImportVolumeMounts() []corev1.VolumeMount {
×
1120
        volumeMounts := []corev1.VolumeMount{
×
1121
                {
×
1122
                        Name:      DataVolName,
×
1123
                        MountPath: common.ImporterDataDir,
×
1124
                },
×
1125
        }
×
1126
        return volumeMounts
×
1127
}
×
1128

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

1138
        requestedSize, hasSize := storageResources.Requests[corev1.ResourceStorage]
×
1139
        if !hasSize {
×
1140
                return storageResources, nil
×
1141
        }
×
1142

1143
        if requestedSize, err = GetEffectiveVolumeSize(ctx, client, requestedSize, sc.Name, &log); err != nil {
×
1144
                return storageResources, err
×
1145
        }
×
1146

1147
        return corev1.VolumeResourceRequirements{
×
1148
                Requests: corev1.ResourceList{
×
1149
                        corev1.ResourceStorage: requestedSize,
×
1150
                },
×
1151
        }, nil
×
1152
}
1153

1154
// GetEffectiveVolumeSize returns the maximum of the passed requestedSize and the storageProfile minimumSupportedPVCSize.
1155
func GetEffectiveVolumeSize(ctx context.Context, client client.Client, requestedSize resource.Quantity, storageClassName string, log *logr.Logger) (resource.Quantity, error) {
×
1156
        storageProfile := &cdiv1.StorageProfile{}
×
1157
        if err := client.Get(ctx, types.NamespacedName{Name: storageClassName}, storageProfile); err != nil {
×
1158
                return requestedSize, IgnoreNotFound(err)
×
1159
        }
×
1160

1161
        if val, exists := storageProfile.Annotations[AnnMinimumSupportedPVCSize]; exists {
×
1162
                if minSize, err := resource.ParseQuantity(val); err == nil {
×
1163
                        if requestedSize.Cmp(minSize) == -1 {
×
1164
                                return minSize, nil
×
1165
                        }
×
1166
                } else if log != nil {
×
1167
                        log.V(1).Info("Invalid minimum PVC size in annotation", "value", val, "error", err)
×
1168
                }
×
1169
        }
1170

1171
        return requestedSize, nil
×
1172
}
1173

1174
// ValidateRequestedCloneSize validates the clone size requirements on block
1175
func ValidateRequestedCloneSize(sourceResources, targetResources corev1.VolumeResourceRequirements) error {
×
1176
        sourceRequest, hasSource := sourceResources.Requests[corev1.ResourceStorage]
×
1177
        targetRequest, hasTarget := targetResources.Requests[corev1.ResourceStorage]
×
1178
        if !hasSource || !hasTarget {
×
1179
                return errors.New("source/target missing storage resource requests")
×
1180
        }
×
1181

1182
        // Verify that the target PVC size is equal or larger than the source.
1183
        if sourceRequest.Value() > targetRequest.Value() {
×
1184
                return errors.Errorf("target resources requests storage size is smaller than the source %d < %d", targetRequest.Value(), sourceRequest.Value())
×
1185
        }
×
1186
        return nil
×
1187
}
1188

1189
// CreateCloneSourcePodName creates clone source pod name
1190
func CreateCloneSourcePodName(targetPvc *corev1.PersistentVolumeClaim) string {
×
1191
        return string(targetPvc.GetUID()) + common.ClonerSourcePodNameSuffix
×
1192
}
×
1193

1194
// IsPVCComplete returns true if a PVC is in 'Succeeded' phase, false if not
1195
func IsPVCComplete(pvc *corev1.PersistentVolumeClaim) bool {
×
1196
        if pvc != nil {
×
1197
                phase, exists := pvc.ObjectMeta.Annotations[AnnPodPhase]
×
1198
                return exists && (phase == string(corev1.PodSucceeded))
×
1199
        }
×
1200
        return false
×
1201
}
1202

1203
// IsMultiStageImportInProgress returns true when a PVC is being part of an ongoing multi-stage import
1204
func IsMultiStageImportInProgress(pvc *corev1.PersistentVolumeClaim) bool {
×
1205
        if pvc != nil {
×
1206
                multiStageImport := metav1.HasAnnotation(pvc.ObjectMeta, AnnCurrentCheckpoint)
×
1207
                multiStageAlreadyDone := metav1.HasAnnotation(pvc.ObjectMeta, AnnMultiStageImportDone)
×
1208
                return multiStageImport && !multiStageAlreadyDone
×
1209
        }
×
1210
        return false
×
1211
}
1212

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

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

1251
// SetNodeNameIfPopulator sets NodeName in a pod spec when the PVC is being handled by a CDI volume populator
1252
func SetNodeNameIfPopulator(pvc *corev1.PersistentVolumeClaim, podSpec *corev1.PodSpec) {
×
1253
        _, isPopulator := pvc.Annotations[AnnPopulatorKind]
×
1254
        nodeName := pvc.Annotations[AnnSelectedNode]
×
1255
        if isPopulator && nodeName != "" {
×
1256
                podSpec.NodeName = nodeName
×
1257
        }
×
1258
}
1259

1260
// CreatePvc creates PVC
1261
func CreatePvc(name, ns string, annotations, labels map[string]string) *corev1.PersistentVolumeClaim {
1✔
1262
        return CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1✔
1263
}
1✔
1264

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

1295
// GetAPIServerKey returns API server RSA key
1296
func GetAPIServerKey() *rsa.PrivateKey {
×
1297
        apiServerKeyOnce.Do(func() {
×
1298
                apiServerKey, _ = rsa.GenerateKey(rand.Reader, 2048)
×
1299
        })
×
1300
        return apiServerKey
×
1301
}
1302

1303
// CreateStorageClass creates storage class CR
1304
func CreateStorageClass(name string, annotations map[string]string) *storagev1.StorageClass {
1✔
1305
        return &storagev1.StorageClass{
1✔
1306
                ObjectMeta: metav1.ObjectMeta{
1✔
1307
                        Name:        name,
1✔
1308
                        Annotations: annotations,
1✔
1309
                },
1✔
1310
        }
1✔
1311
}
1✔
1312

1313
// CreateImporterTestPod creates importer test pod CR
1314
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
×
1315
        // importer pod name contains the pvc name
×
1316
        podName := fmt.Sprintf("%s-%s", common.ImporterPodName, pvc.Name)
×
1317

×
1318
        blockOwnerDeletion := true
×
1319
        isController := true
×
1320

×
1321
        volumes := []corev1.Volume{
×
1322
                {
×
1323
                        Name: dvname,
×
1324
                        VolumeSource: corev1.VolumeSource{
×
1325
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1326
                                        ClaimName: pvc.Name,
×
1327
                                        ReadOnly:  false,
×
1328
                                },
×
1329
                        },
×
1330
                },
×
1331
        }
×
1332

×
1333
        if scratchPvc != nil {
×
1334
                volumes = append(volumes, corev1.Volume{
×
1335
                        Name: ScratchVolName,
×
1336
                        VolumeSource: corev1.VolumeSource{
×
1337
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1338
                                        ClaimName: scratchPvc.Name,
×
1339
                                        ReadOnly:  false,
×
1340
                                },
×
1341
                        },
×
1342
                })
×
1343
        }
×
1344

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

×
1394
        ep, _ := GetEndpoint(pvc)
×
1395
        source := GetSource(pvc)
×
1396
        contentType := GetPVCContentType(pvc)
×
1397
        imageSize, _ := GetRequestedImageSize(pvc)
×
1398
        volumeMode := GetVolumeMode(pvc)
×
1399

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

1433
        if scratchPvc != nil {
×
1434
                pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
×
1435
                        Name:      ScratchVolName,
×
1436
                        MountPath: common.ScratchDataDir,
×
1437
                })
×
1438
        }
×
1439

1440
        return pod
×
1441
}
1442

1443
// CreateStorageClassWithProvisioner creates CR of storage class with provisioner
1444
func CreateStorageClassWithProvisioner(name string, annotations, labels map[string]string, provisioner string) *storagev1.StorageClass {
×
1445
        return &storagev1.StorageClass{
×
1446
                Provisioner: provisioner,
×
1447
                ObjectMeta: metav1.ObjectMeta{
×
1448
                        Name:        name,
×
1449
                        Annotations: annotations,
×
1450
                        Labels:      labels,
×
1451
                },
×
1452
        }
×
1453
}
×
1454

1455
// CreateClient creates a fake client
1456
func CreateClient(objs ...runtime.Object) client.Client {
1✔
1457
        s := scheme.Scheme
1✔
1458
        _ = cdiv1.AddToScheme(s)
1✔
1459
        _ = corev1.AddToScheme(s)
1✔
1460
        _ = storagev1.AddToScheme(s)
1✔
1461
        _ = ocpconfigv1.Install(s)
1✔
1462

1✔
1463
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1464
}
1✔
1465

1466
// ErrQuotaExceeded checked is the error is of exceeded quota
1467
func ErrQuotaExceeded(err error) bool {
×
1468
        return strings.Contains(err.Error(), "exceeded quota:")
×
1469
}
×
1470

1471
// GetContentType returns the content type. If invalid or not set, default to kubevirt
1472
func GetContentType(contentType cdiv1.DataVolumeContentType) cdiv1.DataVolumeContentType {
1✔
1473
        switch contentType {
1✔
1474
        case
1475
                cdiv1.DataVolumeKubeVirt,
1476
                cdiv1.DataVolumeArchive:
1✔
1477
        default:
×
1478
                // TODO - shouldn't archive be the default?
×
1479
                contentType = cdiv1.DataVolumeKubeVirt
×
1480
        }
1481
        return contentType
1✔
1482
}
1483

1484
// GetPVCContentType returns the content type of the source image. If invalid or not set, default to kubevirt
1485
func GetPVCContentType(pvc *corev1.PersistentVolumeClaim) cdiv1.DataVolumeContentType {
×
1486
        contentType, found := pvc.Annotations[AnnContentType]
×
1487
        if !found {
×
1488
                // TODO - shouldn't archive be the default?
×
1489
                return cdiv1.DataVolumeKubeVirt
×
1490
        }
×
1491

1492
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1493
}
1494

1495
// GetNamespace returns the given namespace if not empty, otherwise the default namespace
1496
func GetNamespace(namespace, defaultNamespace string) string {
×
1497
        if namespace == "" {
×
1498
                return defaultNamespace
×
1499
        }
×
1500
        return namespace
×
1501
}
1502

1503
// IsErrCacheNotStarted checked is the error is of cache not started
1504
func IsErrCacheNotStarted(err error) bool {
×
1505
        target := &runtimecache.ErrCacheNotStarted{}
×
1506
        return errors.As(err, &target)
×
1507
}
×
1508

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

1532
// GetCloneSourceInfo returns the type, name and namespace of the cloning source
1533
func GetCloneSourceInfo(dv *cdiv1.DataVolume) (sourceType, sourceName, sourceNamespace string) {
×
1534
        // Cloning sources are mutually exclusive
×
1535
        if dv.Spec.Source.PVC != nil {
×
1536
                return "pvc", dv.Spec.Source.PVC.Name, dv.Spec.Source.PVC.Namespace
×
1537
        }
×
1538

1539
        if dv.Spec.Source.Snapshot != nil {
×
1540
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
×
1541
        }
×
1542

1543
        return "", "", ""
×
1544
}
1545

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

1556
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1557
}
1558

1559
// AddImmediateBindingAnnotationIfWFFCDisabled adds the immediateBinding annotation if wffc feature gate is disabled
1560
func AddImmediateBindingAnnotationIfWFFCDisabled(obj metav1.Object, gates featuregates.FeatureGates) error {
×
1561
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
×
1562
        if err != nil {
×
1563
                return err
×
1564
        }
×
1565
        if !globalHonorWaitForFirstConsumer {
×
1566
                AddAnnotation(obj, AnnImmediateBinding, "")
×
1567
        }
×
1568
        return nil
×
1569
}
1570

1571
// InflateSizeWithOverhead inflates a storage size with proper overhead calculations
1572
func InflateSizeWithOverhead(ctx context.Context, c client.Client, imgSize int64, pvcSpec *corev1.PersistentVolumeClaimSpec) (resource.Quantity, error) {
×
1573
        var returnSize resource.Quantity
×
1574

×
1575
        if util.ResolveVolumeMode(pvcSpec.VolumeMode) == corev1.PersistentVolumeFilesystem {
×
1576
                fsOverhead, err := GetFilesystemOverheadForStorageClass(ctx, c, pvcSpec.StorageClassName)
×
1577
                if err != nil {
×
1578
                        return resource.Quantity{}, err
×
1579
                }
×
1580
                // Parse filesystem overhead (percentage) into a 64-bit float
1581
                fsOverheadFloat, _ := strconv.ParseFloat(string(fsOverhead), 64)
×
1582

×
1583
                // Merge the previous values into a 'resource.Quantity' struct
×
1584
                requiredSpace := util.GetRequiredSpace(fsOverheadFloat, imgSize)
×
1585
                returnSize = *resource.NewScaledQuantity(requiredSpace, 0)
×
1586
        } else {
×
1587
                // Inflation is not needed with 'Block' mode
×
1588
                returnSize = *resource.NewScaledQuantity(imgSize, 0)
×
1589
        }
×
1590

1591
        return returnSize, nil
×
1592
}
1593

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

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

1616
        if v, ok := snapshot.Annotations[AnnSourceVolumeMode]; ok {
×
1617
                mode := corev1.PersistentVolumeMode(v)
×
1618
                return &mode
×
1619
        }
×
1620

1621
        log.V(1).Info("Could not infer source volume mode of snapshot, assuming same as target")
×
1622
        return fallback
×
1623
}
1624

1625
// IsBound returns if the pvc is bound
1626
func IsBound(pvc *corev1.PersistentVolumeClaim) bool {
×
1627
        return pvc != nil && pvc.Status.Phase == corev1.ClaimBound
×
1628
}
×
1629

1630
// IsUnbound returns if the pvc is not bound yet
1631
func IsUnbound(pvc *corev1.PersistentVolumeClaim) bool {
×
1632
        return !IsBound(pvc)
×
1633
}
×
1634

1635
// IsLost returns if the pvc is lost
1636
func IsLost(pvc *corev1.PersistentVolumeClaim) bool {
×
1637
        return pvc != nil && pvc.Status.Phase == corev1.ClaimLost
×
1638
}
×
1639

1640
// IsImageStream returns true if registry source is ImageStream
1641
func IsImageStream(pvc *corev1.PersistentVolumeClaim) bool {
×
1642
        return pvc.Annotations[AnnRegistryImageStream] == "true"
×
1643
}
×
1644

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

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

1681
// ErrConnectionRefused checks for connection refused errors
1682
func ErrConnectionRefused(err error) bool {
×
1683
        return strings.Contains(err.Error(), "connection refused")
×
1684
}
×
1685

1686
// GetPodMetricsPort returns, if exists, the metrics port from the passed pod
1687
func GetPodMetricsPort(pod *corev1.Pod) (int, error) {
1✔
1688
        for _, container := range pod.Spec.Containers {
2✔
1689
                for _, port := range container.Ports {
2✔
1690
                        if port.Name == "metrics" {
2✔
1691
                                return int(port.ContainerPort), nil
1✔
1692
                        }
1✔
1693
                }
1694
        }
1695
        return 0, errors.New("Metrics port not found in pod")
1✔
1696
}
1697

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

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

1736
        // Parse the progress from the body
1737
        progressReport := ""
×
1738
        match := regExp.FindStringSubmatch(string(body))
×
1739
        if match != nil {
×
1740
                progressReport = match[len(match)-1]
×
1741
        }
×
1742
        return progressReport, nil
×
1743
}
1744

1745
// UpdateHTTPAnnotations updates the passed annotations for proper http import
1746
func UpdateHTTPAnnotations(annotations map[string]string, http *cdiv1.DataVolumeSourceHTTP) {
1✔
1747
        annotations[AnnEndpoint] = http.URL
1✔
1748
        annotations[AnnSource] = SourceHTTP
1✔
1749

1✔
1750
        if http.SecretRef != "" {
1✔
1751
                annotations[AnnSecret] = http.SecretRef
×
1752
        }
×
1753
        if http.CertConfigMap != "" {
1✔
1754
                annotations[AnnCertConfigMap] = http.CertConfigMap
×
1755
        }
×
1756
        if http.Checksum != "" {
1✔
1757
                annotations[AnnChecksum] = http.Checksum
×
1758
        }
×
1759
        if http.InsecureSkipVerify != nil && *http.InsecureSkipVerify {
2✔
1760
                annotations[AnnInsecureSkipVerify] = "true"
1✔
1761
        }
1✔
1762
        for index, header := range http.ExtraHeaders {
1✔
1763
                annotations[fmt.Sprintf("%s.%d", AnnExtraHeaders, index)] = header
×
1764
        }
×
1765
        for index, header := range http.SecretExtraHeaders {
1✔
1766
                annotations[fmt.Sprintf("%s.%d", AnnSecretExtraHeaders, index)] = header
×
1767
        }
×
1768
}
1769

1770
// UpdateS3Annotations updates the passed annotations for proper S3 import
1771
func UpdateS3Annotations(annotations map[string]string, s3 *cdiv1.DataVolumeSourceS3) {
×
1772
        annotations[AnnEndpoint] = s3.URL
×
1773
        annotations[AnnSource] = SourceS3
×
1774
        if s3.SecretRef != "" {
×
1775
                annotations[AnnSecret] = s3.SecretRef
×
1776
        }
×
1777
        if s3.CertConfigMap != "" {
×
1778
                annotations[AnnCertConfigMap] = s3.CertConfigMap
×
1779
        }
×
1780
}
1781

1782
// UpdateGCSAnnotations updates the passed annotations for proper GCS import
1783
func UpdateGCSAnnotations(annotations map[string]string, gcs *cdiv1.DataVolumeSourceGCS) {
×
1784
        annotations[AnnEndpoint] = gcs.URL
×
1785
        annotations[AnnSource] = SourceGCS
×
1786
        if gcs.SecretRef != "" {
×
1787
                annotations[AnnSecret] = gcs.SecretRef
×
1788
        }
×
1789
}
1790

1791
// UpdateRegistryAnnotations updates the passed annotations for proper registry import
1792
func UpdateRegistryAnnotations(annotations map[string]string, registry *cdiv1.DataVolumeSourceRegistry) {
×
1793
        annotations[AnnSource] = SourceRegistry
×
1794
        pullMethod := registry.PullMethod
×
1795
        if pullMethod != nil && *pullMethod != "" {
×
1796
                annotations[AnnRegistryImportMethod] = string(*pullMethod)
×
1797
        }
×
1798
        url := registry.URL
×
1799
        if url != nil && *url != "" {
×
1800
                annotations[AnnEndpoint] = *url
×
1801
        } else {
×
1802
                imageStream := registry.ImageStream
×
1803
                if imageStream != nil && *imageStream != "" {
×
1804
                        annotations[AnnEndpoint] = *imageStream
×
1805
                        annotations[AnnRegistryImageStream] = "true"
×
1806
                }
×
1807
        }
1808
        secretRef := registry.SecretRef
×
1809
        if secretRef != nil && *secretRef != "" {
×
1810
                annotations[AnnSecret] = *secretRef
×
1811
        }
×
1812
        certConfigMap := registry.CertConfigMap
×
1813
        if certConfigMap != nil && *certConfigMap != "" {
×
1814
                annotations[AnnCertConfigMap] = *certConfigMap
×
1815
        }
×
1816

1817
        if registry.Platform != nil && registry.Platform.Architecture != "" {
×
1818
                annotations[AnnRegistryImageArchitecture] = registry.Platform.Architecture
×
1819
        }
×
1820
}
1821

1822
// UpdateVDDKAnnotations updates the passed annotations for proper VDDK import
1823
func UpdateVDDKAnnotations(annotations map[string]string, vddk *cdiv1.DataVolumeSourceVDDK) {
×
1824
        annotations[AnnEndpoint] = vddk.URL
×
1825
        annotations[AnnSource] = SourceVDDK
×
1826
        annotations[AnnSecret] = vddk.SecretRef
×
1827
        annotations[AnnBackingFile] = vddk.BackingFile
×
1828
        annotations[AnnUUID] = vddk.UUID
×
1829
        annotations[AnnThumbprint] = vddk.Thumbprint
×
NEW
1830
        if vddk.CertConfigMap != "" {
×
NEW
1831
                annotations[AnnCertConfigMap] = vddk.CertConfigMap
×
NEW
1832
        }
×
1833
        if vddk.InitImageURL != "" {
×
1834
                annotations[AnnVddkInitImageURL] = vddk.InitImageURL
×
1835
        }
×
1836
        if vddk.ExtraArgs != "" {
×
1837
                annotations[AnnVddkExtraArgs] = vddk.ExtraArgs
×
1838
        }
×
1839
}
1840

1841
// UpdateImageIOAnnotations updates the passed annotations for proper imageIO import
1842
func UpdateImageIOAnnotations(annotations map[string]string, imageio *cdiv1.DataVolumeSourceImageIO) {
×
1843
        annotations[AnnEndpoint] = imageio.URL
×
1844
        annotations[AnnSource] = SourceImageio
×
1845
        annotations[AnnSecret] = imageio.SecretRef
×
1846
        annotations[AnnCertConfigMap] = imageio.CertConfigMap
×
1847
        annotations[AnnDiskID] = imageio.DiskID
×
1848
        if imageio.InsecureSkipVerify != nil && *imageio.InsecureSkipVerify {
×
1849
                annotations[AnnInsecureSkipVerify] = "true"
×
1850
        }
×
1851
}
1852

1853
// IsPVBoundToPVC checks if a PV is bound to a specific PVC
1854
func IsPVBoundToPVC(pv *corev1.PersistentVolume, pvc *corev1.PersistentVolumeClaim) bool {
1✔
1855
        claimRef := pv.Spec.ClaimRef
1✔
1856
        return claimRef != nil && claimRef.Name == pvc.Name && claimRef.Namespace == pvc.Namespace && claimRef.UID == pvc.UID
1✔
1857
}
1✔
1858

1859
// Rebind binds the PV of source to target
1860
func Rebind(ctx context.Context, c client.Client, source, target *corev1.PersistentVolumeClaim) error {
1✔
1861
        pv := &corev1.PersistentVolume{
1✔
1862
                ObjectMeta: metav1.ObjectMeta{
1✔
1863
                        Name: source.Spec.VolumeName,
1✔
1864
                },
1✔
1865
        }
1✔
1866

1✔
1867
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
2✔
1868
                return err
1✔
1869
        }
1✔
1870

1871
        // Examine the claimref for the PV and see if it's still bound to PVC'
1872
        if pv.Spec.ClaimRef == nil {
1✔
1873
                return fmt.Errorf("PV %s claimRef is nil", pv.Name)
×
1874
        }
×
1875

1876
        if !IsPVBoundToPVC(pv, source) {
2✔
1877
                // Something is not right if the PV is neither bound to PVC' nor target PVC
1✔
1878
                if !IsPVBoundToPVC(pv, target) {
2✔
1879
                        klog.Errorf("PV bound to unexpected PVC: Could not rebind to target PVC '%s'", target.Name)
1✔
1880
                        return fmt.Errorf("PV %s bound to unexpected claim %s", pv.Name, pv.Spec.ClaimRef.Name)
1✔
1881
                }
1✔
1882
                // our work is done
1883
                return nil
1✔
1884
        }
1885

1886
        // Rebind PVC to target PVC
1887
        pv.Spec.ClaimRef = &corev1.ObjectReference{
1✔
1888
                Namespace:       target.Namespace,
1✔
1889
                Name:            target.Name,
1✔
1890
                UID:             target.UID,
1✔
1891
                ResourceVersion: target.ResourceVersion,
1✔
1892
        }
1✔
1893
        klog.V(3).Info("Rebinding PV to target PVC", "PVC", target.Name)
1✔
1894
        if err := c.Update(context.TODO(), pv); err != nil {
1✔
1895
                return err
×
1896
        }
×
1897

1898
        return nil
1✔
1899
}
1900

1901
// BulkDeleteResources deletes a bunch of resources
1902
func BulkDeleteResources(ctx context.Context, c client.Client, obj client.ObjectList, lo client.ListOption) error {
×
1903
        if err := c.List(ctx, obj, lo); err != nil {
×
1904
                if meta.IsNoMatchError(err) {
×
1905
                        return nil
×
1906
                }
×
1907
                return err
×
1908
        }
1909

1910
        sv := reflect.ValueOf(obj).Elem()
×
1911
        iv := sv.FieldByName("Items")
×
1912

×
1913
        for i := 0; i < iv.Len(); i++ {
×
1914
                obj := iv.Index(i).Addr().Interface().(client.Object)
×
1915
                if obj.GetDeletionTimestamp().IsZero() {
×
1916
                        klog.V(3).Infof("Deleting type %+v %+v", reflect.TypeOf(obj), obj)
×
1917
                        if err := c.Delete(ctx, obj); err != nil {
×
1918
                                return err
×
1919
                        }
×
1920
                }
1921
        }
1922

1923
        return nil
×
1924
}
1925

1926
// ValidateSnapshotCloneSize does proper size validation when doing a clone from snapshot operation
1927
func ValidateSnapshotCloneSize(snapshot *snapshotv1.VolumeSnapshot, pvcSpec *corev1.PersistentVolumeClaimSpec, targetSC *storagev1.StorageClass, log logr.Logger) (bool, error) {
×
1928
        restoreSize := snapshot.Status.RestoreSize
×
1929
        if restoreSize == nil {
×
1930
                return false, fmt.Errorf("snapshot has no RestoreSize")
×
1931
        }
×
1932
        targetRequest, hasTargetRequest := pvcSpec.Resources.Requests[corev1.ResourceStorage]
×
1933
        allowExpansion := targetSC.AllowVolumeExpansion != nil && *targetSC.AllowVolumeExpansion
×
1934
        if hasTargetRequest {
×
1935
                // otherwise will just use restoreSize
×
1936
                if restoreSize.Cmp(targetRequest) < 0 && !allowExpansion {
×
1937
                        log.V(3).Info("Can't expand restored PVC because SC does not allow expansion, need to fall back to host assisted")
×
1938
                        return false, nil
×
1939
                }
×
1940
        }
1941
        return true, nil
×
1942
}
1943

1944
// ValidateSnapshotCloneProvisioners validates the target PVC storage class against the snapshot class provisioner
1945
func ValidateSnapshotCloneProvisioners(vsc *snapshotv1.VolumeSnapshotContent, storageClass *storagev1.StorageClass) (bool, error) {
×
1946
        // Do snapshot and storage class validation
×
1947
        if storageClass == nil {
×
1948
                return false, fmt.Errorf("target storage class not found")
×
1949
        }
×
1950
        if storageClass.Provisioner != vsc.Spec.Driver {
×
1951
                return false, nil
×
1952
        }
×
1953
        // TODO: get sourceVolumeMode from volumesnapshotcontent and validate against target spec
1954
        // currently don't have CRDs in CI with sourceVolumeMode which is pretty new
1955
        // converting volume mode is possible but has security implications
1956
        return true, nil
×
1957
}
1958

1959
// GetSnapshotClassForSmartClone looks up the snapshot class based on the storage class
1960
func GetSnapshotClassForSmartClone(pvc *corev1.PersistentVolumeClaim, targetPvcStorageClassName, snapshotClassName *string, log logr.Logger, client client.Client, recorder record.EventRecorder) (string, error) {
×
1961
        logger := log.WithName("GetSnapshotClassForSmartClone").V(3)
×
1962
        // Check if relevant CRDs are available
×
1963
        if !isCsiCrdsDeployed(client, log) {
×
1964
                logger.Info("Missing CSI snapshotter CRDs, falling back to host assisted clone")
×
1965
                return "", nil
×
1966
        }
×
1967

1968
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(context.TODO(), client, targetPvcStorageClassName)
×
1969
        if err != nil {
×
1970
                return "", err
×
1971
        }
×
1972
        if targetStorageClass == nil {
×
1973
                logger.Info("Target PVC's Storage Class not found")
×
1974
                return "", nil
×
1975
        }
×
1976

1977
        vscName, err := GetVolumeSnapshotClass(context.TODO(), client, pvc, targetStorageClass.Provisioner, snapshotClassName, logger, recorder)
×
1978
        if err != nil {
×
1979
                return "", err
×
1980
        }
×
1981
        if vscName != nil {
×
1982
                if pvc != nil {
×
1983
                        logger.Info("smart-clone is applicable for datavolume", "datavolume",
×
1984
                                pvc.Name, "snapshot class", *vscName)
×
1985
                }
×
1986
                return *vscName, nil
×
1987
        }
1988

1989
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
1990
        return "", nil
×
1991
}
1992

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

×
1998
        logEvent := func(message, vscName string) {
×
1999
                logger.Info(message, "name", vscName)
×
2000
                if pvc != nil {
×
2001
                        msg := fmt.Sprintf("%s %s", message, vscName)
×
2002
                        recorder.Event(pvc, corev1.EventTypeNormal, VolumeSnapshotClassSelected, msg)
×
2003
                }
×
2004
        }
2005

2006
        if snapshotClassName != nil {
×
2007
                vsc := &snapshotv1.VolumeSnapshotClass{}
×
2008
                if err := c.Get(context.TODO(), types.NamespacedName{Name: *snapshotClassName}, vsc); err != nil {
×
2009
                        return nil, err
×
2010
                }
×
2011
                if vsc.Driver == driver {
×
2012
                        logEvent(MessageStorageProfileVolumeSnapshotClassSelected, vsc.Name)
×
2013
                        return snapshotClassName, nil
×
2014
                }
×
2015
                return nil, nil
×
2016
        }
2017

2018
        vscList := &snapshotv1.VolumeSnapshotClassList{}
×
2019
        if err := c.List(ctx, vscList); err != nil {
×
2020
                if meta.IsNoMatchError(err) {
×
2021
                        return nil, nil
×
2022
                }
×
2023
                return nil, err
×
2024
        }
2025

2026
        var candidates []string
×
2027
        for _, vsc := range vscList.Items {
×
2028
                if vsc.Driver == driver {
×
2029
                        if vsc.Annotations[AnnDefaultSnapshotClass] == "true" {
×
2030
                                logEvent(MessageDefaultVolumeSnapshotClassSelected, vsc.Name)
×
2031
                                vscName := vsc.Name
×
2032
                                return &vscName, nil
×
2033
                        }
×
2034
                        candidates = append(candidates, vsc.Name)
×
2035
                }
2036
        }
2037

2038
        if len(candidates) > 0 {
×
2039
                sort.Strings(candidates)
×
2040
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
×
2041
                return &candidates[0], nil
×
2042
        }
×
2043

2044
        return nil, nil
×
2045
}
2046

2047
// isCsiCrdsDeployed checks whether the CSI snapshotter CRD are deployed
2048
func isCsiCrdsDeployed(c client.Client, log logr.Logger) bool {
×
2049
        version := "v1"
×
2050
        vsClass := "volumesnapshotclasses." + snapshotv1.GroupName
×
2051
        vsContent := "volumesnapshotcontents." + snapshotv1.GroupName
×
2052
        vs := "volumesnapshots." + snapshotv1.GroupName
×
2053

×
2054
        return isCrdDeployed(c, vsClass, version, log) &&
×
2055
                isCrdDeployed(c, vsContent, version, log) &&
×
2056
                isCrdDeployed(c, vs, version, log)
×
2057
}
×
2058

2059
// isCrdDeployed checks whether a CRD is deployed
2060
func isCrdDeployed(c client.Client, name, version string, log logr.Logger) bool {
×
2061
        crd := &extv1.CustomResourceDefinition{}
×
2062
        err := c.Get(context.TODO(), types.NamespacedName{Name: name}, crd)
×
2063
        if err != nil {
×
2064
                if !k8serrors.IsNotFound(err) {
×
2065
                        log.Info("Error looking up CRD", "crd name", name, "version", version, "error", err)
×
2066
                }
×
2067
                return false
×
2068
        }
2069

2070
        for _, v := range crd.Spec.Versions {
×
2071
                if v.Name == version && v.Served {
×
2072
                        return true
×
2073
                }
×
2074
        }
2075

2076
        return false
×
2077
}
2078

2079
// IsSnapshotReady indicates if a volume snapshot is ready to be used
2080
func IsSnapshotReady(snapshot *snapshotv1.VolumeSnapshot) bool {
×
2081
        return snapshot.Status != nil && snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse
×
2082
}
×
2083

2084
// GetResource updates given obj with the data of the object with the same name and namespace
2085
func GetResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
×
2086
        obj.SetNamespace(namespace)
×
2087
        obj.SetName(name)
×
2088

×
2089
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
2090
        if err != nil {
×
2091
                if k8serrors.IsNotFound(err) {
×
2092
                        return false, nil
×
2093
                }
×
2094

2095
                return false, err
×
2096
        }
2097

2098
        return true, nil
×
2099
}
2100

2101
// PatchArgs are the args for Patch
2102
type PatchArgs struct {
2103
        Client client.Client
2104
        Log    logr.Logger
2105
        Obj    client.Object
2106
        OldObj client.Object
2107
}
2108

2109
// GetAnnotatedEventSource returns resource referenced by AnnEventSource annotations
2110
func GetAnnotatedEventSource(ctx context.Context, c client.Client, obj client.Object) (client.Object, error) {
×
2111
        esk, ok := obj.GetAnnotations()[AnnEventSourceKind]
×
2112
        if !ok {
×
2113
                return obj, nil
×
2114
        }
×
2115
        if esk != "PersistentVolumeClaim" {
×
2116
                return obj, nil
×
2117
        }
×
2118
        es, ok := obj.GetAnnotations()[AnnEventSource]
×
2119
        if !ok {
×
2120
                return obj, nil
×
2121
        }
×
2122
        namespace, name, err := cache.SplitMetaNamespaceKey(es)
×
2123
        if err != nil {
×
2124
                return nil, err
×
2125
        }
×
2126
        pvc := &corev1.PersistentVolumeClaim{
×
2127
                ObjectMeta: metav1.ObjectMeta{
×
2128
                        Namespace: namespace,
×
2129
                        Name:      name,
×
2130
                },
×
2131
        }
×
2132
        if err := c.Get(ctx, client.ObjectKeyFromObject(pvc), pvc); err != nil {
×
2133
                return nil, err
×
2134
        }
×
2135
        return pvc, nil
×
2136
}
2137

2138
// OwnedByDataVolume returns true if the object is owned by a DataVolume
2139
func OwnedByDataVolume(obj metav1.Object) bool {
×
2140
        owner := metav1.GetControllerOf(obj)
×
2141
        return owner != nil && owner.Kind == "DataVolume"
×
2142
}
×
2143

2144
// CopyAllowedAnnotations copies the allowed annotations from the source object
2145
// to the destination object
2146
func CopyAllowedAnnotations(srcObj, dstObj metav1.Object) {
×
2147
        for ann, def := range allowedAnnotations {
×
2148
                val, ok := srcObj.GetAnnotations()[ann]
×
2149
                if !ok && def != "" {
×
2150
                        val = def
×
2151
                }
×
2152
                if val != "" {
×
2153
                        klog.V(1).Info("Applying annotation", "Name", dstObj.GetName(), ann, val)
×
2154
                        AddAnnotation(dstObj, ann, val)
×
2155
                }
×
2156
        }
2157
}
2158

2159
// CopyAllowedLabels copies allowed labels matching the validLabelsMatch regexp from the
2160
// source map to the destination object allowing overwrites
2161
func CopyAllowedLabels(srcLabels map[string]string, dstObj metav1.Object, overwrite bool) {
1✔
2162
        for label, value := range srcLabels {
2✔
2163
                if _, found := dstObj.GetLabels()[label]; (!found || overwrite) && validLabelsMatch.MatchString(label) {
2✔
2164
                        AddLabel(dstObj, label, value)
1✔
2165
                }
1✔
2166
        }
2167
}
2168

2169
// ClaimMayExistBeforeDataVolume returns true if the PVC may exist before the DataVolume
2170
func ClaimMayExistBeforeDataVolume(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2171
        if ClaimIsPopulatedForDataVolume(pvc, dv) {
×
2172
                return true, nil
×
2173
        }
×
2174
        return AllowClaimAdoption(c, pvc, dv)
×
2175
}
2176

2177
// ClaimIsPopulatedForDataVolume returns true if the PVC is populated for the given DataVolume
2178
func ClaimIsPopulatedForDataVolume(pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) bool {
×
2179
        return pvc != nil && dv != nil && pvc.Annotations[AnnPopulatedFor] == dv.Name
×
2180
}
×
2181

2182
// AllowClaimAdoption returns true if the PVC may be adopted
2183
func AllowClaimAdoption(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2184
        if pvc == nil || dv == nil {
×
2185
                return false, nil
×
2186
        }
×
2187
        anno, ok := pvc.Annotations[AnnCreatedForDataVolume]
×
2188
        if ok && anno == string(dv.UID) {
×
2189
                return false, nil
×
2190
        }
×
2191
        anno, ok = dv.Annotations[AnnAllowClaimAdoption]
×
2192
        // if annotation exists, go with that regardless of featuregate
×
2193
        if ok {
×
2194
                val, _ := strconv.ParseBool(anno)
×
2195
                return val, nil
×
2196
        }
×
2197
        return featuregates.NewFeatureGates(c).ClaimAdoptionEnabled()
×
2198
}
2199

2200
// ResolveDataSourceChain resolves a DataSource reference.
2201
// Returns an error if DataSource reference is not found or
2202
// DataSource reference points to another DataSource
2203
func ResolveDataSourceChain(ctx context.Context, client client.Client, dataSource *cdiv1.DataSource) (*cdiv1.DataSource, error) {
×
2204
        if dataSource.Spec.Source.DataSource == nil {
×
2205
                return dataSource, nil
×
2206
        }
×
2207

2208
        ref := dataSource.Spec.Source.DataSource
×
2209
        refNs := GetNamespace(ref.Namespace, dataSource.Namespace)
×
2210
        if dataSource.Namespace != refNs {
×
2211
                return dataSource, ErrDataSourceCrossNamespace
×
2212
        }
×
2213
        if ref.Name == dataSource.Name && refNs == dataSource.Namespace {
×
2214
                return nil, ErrDataSourceSelfReference
×
2215
        }
×
2216

2217
        resolved := &cdiv1.DataSource{}
×
2218
        if err := client.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: refNs}, resolved); err != nil {
×
2219
                return nil, err
×
2220
        }
×
2221

2222
        if resolved.Spec.Source.DataSource != nil {
×
2223
                return nil, ErrDataSourceMaxDepthReached
×
2224
        }
×
2225

2226
        return resolved, nil
×
2227
}
2228

2229
func sortEvents(events *corev1.EventList, usingPopulator bool, pvcPrimeName string) {
1✔
2230
        // Sort event lists by containing primeName substring and most recent timestamp
1✔
2231
        sort.Slice(events.Items, func(i, j int) bool {
2✔
2232
                if usingPopulator {
2✔
2233
                        firstContainsPrime := strings.Contains(events.Items[i].Message, pvcPrimeName)
1✔
2234
                        secondContainsPrime := strings.Contains(events.Items[j].Message, pvcPrimeName)
1✔
2235

1✔
2236
                        if firstContainsPrime && !secondContainsPrime {
2✔
2237
                                return true
1✔
2238
                        }
1✔
2239
                        if !firstContainsPrime && secondContainsPrime {
2✔
2240
                                return false
1✔
2241
                        }
1✔
2242
                }
2243

2244
                // if the timestamps are the same, prioritze longer messages to make sure our sorting is deterministic
2245
                if events.Items[i].LastTimestamp.Time.Equal(events.Items[j].LastTimestamp.Time) {
1✔
2246
                        return len(events.Items[i].Message) > len(events.Items[j].Message)
×
2247
                }
×
2248

2249
                // if both contains primeName substring or neither, just sort on timestamp
2250
                return events.Items[i].LastTimestamp.Time.After(events.Items[j].LastTimestamp.Time)
1✔
2251
        })
2252
}
2253

2254
// UpdatePVCBoundConditionFromEvents updates the bound condition annotations on the PVC based on recent events
2255
// This function can be used by both controller and populator packages to update PVC bound condition information
2256
func UpdatePVCBoundConditionFromEvents(pvc *corev1.PersistentVolumeClaim, c client.Client, log logr.Logger) error {
×
2257
        currentPvcCopy := pvc.DeepCopy()
×
2258

×
2259
        anno := pvc.GetAnnotations()
×
2260
        if anno == nil {
×
2261
                return nil
×
2262
        }
×
2263

2264
        if IsBound(pvc) {
×
2265
                anno := pvc.GetAnnotations()
×
2266
                delete(anno, AnnBoundCondition)
×
2267
                delete(anno, AnnBoundConditionReason)
×
2268
                delete(anno, AnnBoundConditionMessage)
×
2269

×
2270
                if !reflect.DeepEqual(currentPvcCopy, pvc) {
×
2271
                        patch := client.MergeFrom(currentPvcCopy)
×
2272
                        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2273
                                return err
×
2274
                        }
×
2275
                }
2276

2277
                return nil
×
2278
        }
2279

2280
        if pvc.Status.Phase != corev1.ClaimPending {
×
2281
                return nil
×
2282
        }
×
2283

2284
        // set bound condition by getting the latest event
2285
        events := &corev1.EventList{}
×
2286

×
2287
        err := c.List(context.TODO(), events,
×
2288
                client.InNamespace(pvc.GetNamespace()),
×
2289
                client.MatchingFields{"involvedObject.name": pvc.GetName(),
×
2290
                        "involvedObject.uid": string(pvc.GetUID())},
×
2291
        )
×
2292

×
2293
        if err != nil {
×
2294
                // Log the error but don't fail the reconciliation
×
2295
                log.Error(err, "Unable to list events for PVC bound condition update", "pvc", pvc.Name)
×
2296
                return nil
×
2297
        }
×
2298

2299
        if len(events.Items) == 0 {
×
2300
                return nil
×
2301
        }
×
2302

2303
        pvcPrime, usingPopulator := anno[AnnPVCPrimeName]
×
2304

×
2305
        // Sort event lists by containing primeName substring and most recent timestamp
×
2306
        sortEvents(events, usingPopulator, pvcPrime)
×
2307

×
2308
        boundMessage := ""
×
2309
        // check if prime name annotation exists
×
2310
        if usingPopulator {
×
2311
                // if we are using populators get the latest event from prime pvc
×
2312
                pvcPrime = fmt.Sprintf("[%s] : ", pvcPrime)
×
2313

×
2314
                // if the first event does not contain a prime message, none will so return
×
2315
                primeIdx := strings.Index(events.Items[0].Message, pvcPrime)
×
2316
                if primeIdx == -1 {
×
2317
                        log.V(1).Info("No bound message found, skipping bound condition update", "pvc", pvc.Name)
×
2318
                        return nil
×
2319
                }
×
2320
                boundMessage = events.Items[0].Message[primeIdx+len(pvcPrime):]
×
2321
        } else {
×
2322
                // if not using populators just get the latest event
×
2323
                boundMessage = events.Items[0].Message
×
2324
        }
×
2325

2326
        // since we checked status of phase above, we know this is pending
2327
        anno[AnnBoundCondition] = "false"
×
2328
        anno[AnnBoundConditionReason] = "Pending"
×
2329
        anno[AnnBoundConditionMessage] = boundMessage
×
2330

×
2331
        patch := client.MergeFrom(currentPvcCopy)
×
2332
        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2333
                return err
×
2334
        }
×
2335

2336
        return nil
×
2337
}
2338

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

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

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

2354
        currEvents := &corev1.EventList{}
×
2355
        err = c.List(context.TODO(), currEvents,
×
2356
                client.InNamespace(targetPVC.GetNamespace()),
×
2357
                client.MatchingFields{"involvedObject.name": targetPVC.GetName(),
×
2358
                        "involvedObject.uid": string(targetPVC.GetUID())},
×
2359
        )
×
2360

×
2361
        if err != nil {
×
2362
                klog.Error(err, "Could not retrieve targetPVC list of Events")
×
2363
        }
×
2364

2365
        // use this to hash each message for quick lookup, value is unused
2366
        eventMap := map[string]struct{}{}
×
2367

×
2368
        for _, event := range currEvents.Items {
×
2369
                eventMap[event.Message] = struct{}{}
×
2370
        }
×
2371

2372
        for _, newEvent := range newEvents.Items {
×
2373
                msg := newEvent.Message
×
2374

×
2375
                // check if target PVC already has this equivalent event
×
2376
                if _, exists := eventMap[msg]; exists {
×
2377
                        continue
×
2378
                }
2379

2380
                formattedMsg := srcPrefixMsg + msg
×
2381
                // check if we already emitted this event with the src prefix
×
2382
                if _, exists := eventMap[formattedMsg]; exists {
×
2383
                        continue
×
2384
                }
2385

2386
                // simply check if event is a ClaimMisbound event, if so, skip it
2387
                if newEvent.Reason == EventReasonClaimMisbound {
×
2388
                        continue
×
2389
                }
2390

2391
                recorder.Event(targetPVC, newEvent.Type, newEvent.Reason, formattedMsg)
×
2392
        }
2393
}
2394

2395
// IsWebhookPvcRenderingEnabled returns true unless
2396
// CDIConfigSpec.WebhookPvcRendering is explicitly set to "Disabled"
2397
func IsWebhookPvcRenderingEnabled(c client.Client) (bool, error) {
1✔
2398
        config := &cdiv1.CDIConfig{}
1✔
2399
        if err := c.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, config); err != nil {
2✔
2400
                return false, errors.Wrap(err, "error getting CDIConfig")
1✔
2401
        }
1✔
2402
        if config.Spec.WebhookPvcRendering == cdiv1.WebhookPvcRenderingDisabled {
2✔
2403
                return false, nil
1✔
2404
        }
1✔
2405
        return true, nil
1✔
2406
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc