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

kubevirt / containerized-data-importer / #6106

06 Jul 2026 08:12PM UTC coverage: 49.85% (+0.07%) from 49.783%
#6106

Pull #4167

travis-ci

xphyr
adding in additional tests from @akalenyu

Signed-off-by: xphyr <xphyr@users.noreply.github.com>
Pull Request #4167: add insecureSkipVerify to http source type

7 of 10 new or added lines in 4 files covered. (70.0%)

2 existing lines in 1 file now uncovered.

15162 of 30415 relevant lines covered (49.85%)

0.56 hits per line

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

14.57
/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

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

385
var (
386
        // BlockMode is raw block device mode
387
        BlockMode = corev1.PersistentVolumeBlock
388
        // FilesystemMode is filesystem device mode
389
        FilesystemMode = corev1.PersistentVolumeFilesystem
390

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

399
        apiServerKeyOnce sync.Once
400
        apiServerKey     *rsa.PrivateKey
401

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

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

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

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

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

×
447
// MultiTokenValidator is a token validator that can validate both short and long tokens
×
448
type MultiTokenValidator struct {
449
        ShortTokenValidator token.Validator
450
        LongTokenValidator  token.Validator
451
}
452

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

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

×
465
        tok, v := mtv.getTokenAndValidator(pvc)
×
466

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

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

×
481
        return validateTokenData(tokenData, vcs.Namespace, srcName, pvc.Namespace, pvc.Name, string(pvc.UID), tokenResourceName)
482
}
×
483

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

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

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

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

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

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

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

×
533
        if dv.Spec.Storage != nil {
×
534
                return dv.Spec.Storage.StorageClassName
×
535
        }
536

×
537
        return nil
×
538
}
×
539

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

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

×
558
        return storageClass, nil
×
559
}
560

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

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

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

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

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

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

1✔
604
        if len(defaultClasses) == 0 {
1✔
605
                return nil
606
        }
607

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

2✔
622
        return &defaultClasses[0]
1✔
623
}
1✔
624

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

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

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

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

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

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

×
663
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
664

×
665
        storageClassOverhead, found := perStorageConfig[targetStorageClass.GetName()]
×
666
        if found {
×
667
                return storageClassOverhead, nil
×
668
        }
×
669

×
670
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
671
}
×
672

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

×
681
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
682
}
×
683

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

×
692
        return cdiconfig.Status.ImagePullSecrets, nil
×
693
}
×
694

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

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

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

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

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

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

794
        return pods, nil
795
}
796

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

×
804
        if cr == nil {
×
805
                return nil, fmt.Errorf("no active CDI")
×
806
        }
807

×
808
        return &cr.Spec.Workloads, nil
×
809
}
×
810

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

1✔
818
        if len(crList.Items) == 0 {
×
819
                return nil, nil
×
820
        }
821

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

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

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

2✔
837
        return &activeResources[0], nil
1✔
838
}
1✔
839

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

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

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

×
862
        return cdiconfig.Status.Preallocation
×
863
}
×
864

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

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

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

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

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

×
894
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
895
}
×
896

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

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

×
910
        obj.SetFinalizers(finalizers)
×
911
}
912

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

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

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

×
934
        tokenResourceName := getTokenResourceNamePvc(source)
×
935
        srcName := getSourceNamePvc(source)
×
936

937
        return validateTokenData(tokenData, source.Namespace, srcName, target.Namespace, target.Name, string(target.UID), tokenResourceName)
×
938
}
×
939

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

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

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

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

×
962
        return validateTokenData(tokenData, sourceNamespace, sourceName, dv.Namespace, dv.Name, "", tokenResourceName)
×
963
}
×
964

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

×
972
        return ""
×
973
}
×
974

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

×
980
        return "persistentvolumeclaims"
×
981
}
×
982

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

×
990
        return sourcePvc.Name
×
991
}
992

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

×
1005
        return nil
×
1006
}
×
1007

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

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

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

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

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

×
1060
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1061

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

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

×
1077
        return err
×
1078
}
×
1079

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

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

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

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

×
1135
        requestedSize, hasSize := storageResources.Requests[corev1.ResourceStorage]
×
1136
        if !hasSize {
×
1137
                return storageResources, nil
1138
        }
×
1139

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

×
1144
        return corev1.VolumeResourceRequirements{
×
1145
                Requests: corev1.ResourceList{
×
1146
                        corev1.ResourceStorage: requestedSize,
1147
                },
×
1148
        }, nil
×
1149
}
×
1150

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

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

×
1168
        return requestedSize, nil
×
1169
}
1170

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

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

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

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

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

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

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

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

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

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

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

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

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

×
1315
        blockOwnerDeletion := true
×
1316
        isController := true
×
1317

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

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

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

×
1391
        ep, _ := GetEndpoint(pvc)
×
1392
        source := GetSource(pvc)
×
1393
        contentType := GetPVCContentType(pvc)
×
1394
        imageSize, _ := GetRequestedImageSize(pvc)
×
1395
        volumeMode := GetVolumeMode(pvc)
×
1396

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

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

×
1437
        return pod
×
1438
}
×
1439

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

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

1✔
1460
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1461
}
1✔
1462

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

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

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

×
1489
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1490
}
×
1491

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

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

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

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

×
1536
        if dv.Spec.Source.Snapshot != nil {
×
1537
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
×
1538
        }
1539

×
1540
        return "", "", ""
×
1541
}
×
1542

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

×
1553
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1554
}
×
1555

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

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

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

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

×
1588
        return returnSize, nil
×
1589
}
×
1590

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
1814
        if registry.Platform != nil && registry.Platform.Architecture != "" {
×
1815
                annotations[AnnRegistryImageArchitecture] = registry.Platform.Architecture
×
1816
        }
1817
}
×
1818

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

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

×
1847
// IsPVBoundToPVC checks if a PV is bound to a specific PVC
×
1848
func IsPVBoundToPVC(pv *corev1.PersistentVolume, pvc *corev1.PersistentVolumeClaim) bool {
1849
        claimRef := pv.Spec.ClaimRef
1850
        return claimRef != nil && claimRef.Name == pvc.Name && claimRef.Namespace == pvc.Namespace && claimRef.UID == pvc.UID
1851
}
1✔
1852

1✔
1853
// Rebind binds the PV of source to target
1✔
1854
func Rebind(ctx context.Context, c client.Client, source, target *corev1.PersistentVolumeClaim) error {
1✔
1855
        pv := &corev1.PersistentVolume{
1856
                ObjectMeta: metav1.ObjectMeta{
1857
                        Name: source.Spec.VolumeName,
1✔
1858
                },
1✔
1859
        }
1✔
1860

1✔
1861
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
1✔
1862
                return err
1✔
1863
        }
1✔
1864

2✔
1865
        // Examine the claimref for the PV and see if it's still bound to PVC'
1✔
1866
        if pv.Spec.ClaimRef == nil {
1✔
1867
                return fmt.Errorf("PV %s claimRef is nil", pv.Name)
1868
        }
1869

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

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

1✔
1892
        return nil
×
1893
}
×
1894

1895
// BulkDeleteResources deletes a bunch of resources
1✔
1896
func BulkDeleteResources(ctx context.Context, c client.Client, obj client.ObjectList, lo client.ListOption) error {
1897
        if err := c.List(ctx, obj, lo); err != nil {
1898
                if meta.IsNoMatchError(err) {
1899
                        return nil
×
1900
                }
×
1901
                return err
×
1902
        }
×
1903

×
1904
        sv := reflect.ValueOf(obj).Elem()
×
1905
        iv := sv.FieldByName("Items")
1906

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

×
1917
        return nil
1918
}
1919

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

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

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

×
1962
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(context.TODO(), client, targetPvcStorageClassName)
×
1963
        if err != nil {
×
1964
                return "", err
1965
        }
×
1966
        if targetStorageClass == nil {
×
1967
                logger.Info("Target PVC's Storage Class not found")
×
1968
                return "", nil
×
1969
        }
×
1970

×
1971
        vscName, err := GetVolumeSnapshotClass(context.TODO(), client, pvc, targetStorageClass.Provisioner, snapshotClassName, logger, recorder)
×
1972
        if err != nil {
×
1973
                return "", err
1974
        }
×
1975
        if vscName != nil {
×
1976
                if pvc != nil {
×
1977
                        logger.Info("smart-clone is applicable for datavolume", "datavolume",
×
1978
                                pvc.Name, "snapshot class", *vscName)
×
1979
                }
×
1980
                return *vscName, nil
×
1981
        }
×
1982

×
1983
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
1984
        return "", nil
1985
}
1986

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

1992
        logEvent := func(message, vscName string) {
×
1993
                logger.Info(message, "name", vscName)
×
1994
                if pvc != nil {
×
1995
                        msg := fmt.Sprintf("%s %s", message, vscName)
×
1996
                        recorder.Event(pvc, corev1.EventTypeNormal, VolumeSnapshotClassSelected, msg)
×
1997
                }
×
1998
        }
×
1999

×
2000
        if snapshotClassName != nil {
×
2001
                vsc := &snapshotv1.VolumeSnapshotClass{}
2002
                if err := c.Get(context.TODO(), types.NamespacedName{Name: *snapshotClassName}, vsc); err != nil {
2003
                        return nil, err
×
2004
                }
×
2005
                if vsc.Driver == driver {
×
2006
                        logEvent(MessageStorageProfileVolumeSnapshotClassSelected, vsc.Name)
×
2007
                        return snapshotClassName, nil
×
2008
                }
×
2009
                return nil, nil
×
2010
        }
×
2011

×
2012
        vscList := &snapshotv1.VolumeSnapshotClassList{}
×
2013
        if err := c.List(ctx, vscList); err != nil {
2014
                if meta.IsNoMatchError(err) {
2015
                        return nil, nil
×
2016
                }
×
2017
                return nil, err
×
2018
        }
×
2019

×
2020
        var candidates []string
×
2021
        for _, vsc := range vscList.Items {
2022
                if vsc.Driver == driver {
2023
                        if vsc.Annotations[AnnDefaultSnapshotClass] == "true" {
×
2024
                                logEvent(MessageDefaultVolumeSnapshotClassSelected, vsc.Name)
×
2025
                                vscName := vsc.Name
×
2026
                                return &vscName, nil
×
2027
                        }
×
2028
                        candidates = append(candidates, vsc.Name)
×
2029
                }
×
2030
        }
×
2031

×
2032
        if len(candidates) > 0 {
2033
                sort.Strings(candidates)
2034
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
2035
                return &candidates[0], nil
×
2036
        }
×
2037

×
2038
        return nil, nil
×
2039
}
×
2040

2041
// isCsiCrdsDeployed checks whether the CSI snapshotter CRD are deployed
×
2042
func isCsiCrdsDeployed(c client.Client, log logr.Logger) bool {
2043
        version := "v1"
2044
        vsClass := "volumesnapshotclasses." + snapshotv1.GroupName
2045
        vsContent := "volumesnapshotcontents." + snapshotv1.GroupName
×
2046
        vs := "volumesnapshots." + snapshotv1.GroupName
×
2047

×
2048
        return isCrdDeployed(c, vsClass, version, log) &&
×
2049
                isCrdDeployed(c, vsContent, version, log) &&
×
2050
                isCrdDeployed(c, vs, version, log)
×
2051
}
×
2052

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

×
2064
        for _, v := range crd.Spec.Versions {
×
2065
                if v.Name == version && v.Served {
2066
                        return true
2067
                }
×
2068
        }
×
2069

×
2070
        return false
×
2071
}
2072

2073
// IsSnapshotReady indicates if a volume snapshot is ready to be used
×
2074
func IsSnapshotReady(snapshot *snapshotv1.VolumeSnapshot) bool {
2075
        return snapshot.Status != nil && snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse
2076
}
2077

×
2078
// GetResource updates given obj with the data of the object with the same name and namespace
×
2079
func GetResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
×
2080
        obj.SetNamespace(namespace)
2081
        obj.SetName(name)
2082

×
2083
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
2084
        if err != nil {
×
2085
                if k8serrors.IsNotFound(err) {
×
2086
                        return false, nil
×
2087
                }
×
2088

×
2089
                return false, err
×
2090
        }
×
2091

2092
        return true, nil
×
2093
}
2094

2095
// PatchArgs are the args for Patch
×
2096
type PatchArgs struct {
2097
        Client client.Client
2098
        Log    logr.Logger
2099
        Obj    client.Object
2100
        OldObj client.Object
2101
}
2102

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

×
2132
// OwnedByDataVolume returns true if the object is owned by a DataVolume
×
2133
func OwnedByDataVolume(obj metav1.Object) bool {
2134
        owner := metav1.GetControllerOf(obj)
2135
        return owner != nil && owner.Kind == "DataVolume"
2136
}
×
2137

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

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

1✔
2163
// ClaimMayExistBeforeDataVolume returns true if the PVC may exist before the DataVolume
2164
func ClaimMayExistBeforeDataVolume(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
2165
        if ClaimIsPopulatedForDataVolume(pvc, dv) {
2166
                return true, nil
2167
        }
×
2168
        return AllowClaimAdoption(c, pvc, dv)
×
2169
}
×
2170

×
2171
// ClaimIsPopulatedForDataVolume returns true if the PVC is populated for the given DataVolume
×
2172
func ClaimIsPopulatedForDataVolume(pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) bool {
2173
        return pvc != nil && dv != nil && pvc.Annotations[AnnPopulatedFor] == dv.Name
2174
}
2175

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

×
2194
// ResolveDataSourceChain resolves a DataSource reference.
×
2195
// Returns an error if DataSource reference is not found or
2196
// DataSource reference points to another DataSource
2197
func ResolveDataSourceChain(ctx context.Context, client client.Client, dataSource *cdiv1.DataSource) (*cdiv1.DataSource, error) {
2198
        if dataSource.Spec.Source.DataSource == nil {
2199
                return dataSource, nil
2200
        }
×
2201

×
2202
        ref := dataSource.Spec.Source.DataSource
×
2203
        refNs := GetNamespace(ref.Namespace, dataSource.Namespace)
×
2204
        if dataSource.Namespace != refNs {
2205
                return dataSource, ErrDataSourceCrossNamespace
×
2206
        }
×
2207
        if ref.Name == dataSource.Name && refNs == dataSource.Namespace {
×
2208
                return nil, ErrDataSourceSelfReference
×
2209
        }
×
2210

×
2211
        resolved := &cdiv1.DataSource{}
×
2212
        if err := client.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: refNs}, resolved); err != nil {
×
2213
                return nil, err
2214
        }
×
2215

×
2216
        if resolved.Spec.Source.DataSource != nil {
×
2217
                return nil, ErrDataSourceMaxDepthReached
×
2218
        }
2219

×
2220
        return resolved, nil
×
2221
}
×
2222

2223
func sortEvents(events *corev1.EventList, usingPopulator bool, pvcPrimeName string) {
×
2224
        // Sort event lists by containing primeName substring and most recent timestamp
2225
        sort.Slice(events.Items, func(i, j int) bool {
2226
                if usingPopulator {
1✔
2227
                        firstContainsPrime := strings.Contains(events.Items[i].Message, pvcPrimeName)
1✔
2228
                        secondContainsPrime := strings.Contains(events.Items[j].Message, pvcPrimeName)
2✔
2229

2✔
2230
                        if firstContainsPrime && !secondContainsPrime {
1✔
2231
                                return true
1✔
2232
                        }
1✔
2233
                        if !firstContainsPrime && secondContainsPrime {
2✔
2234
                                return false
1✔
2235
                        }
1✔
2236
                }
2✔
2237

1✔
2238
                // if the timestamps are the same, prioritze longer messages to make sure our sorting is deterministic
1✔
2239
                if events.Items[i].LastTimestamp.Time.Equal(events.Items[j].LastTimestamp.Time) {
2240
                        return len(events.Items[i].Message) > len(events.Items[j].Message)
2241
                }
2242

1✔
2243
                // if both contains primeName substring or neither, just sort on timestamp
×
2244
                return events.Items[i].LastTimestamp.Time.After(events.Items[j].LastTimestamp.Time)
×
2245
        })
2246
}
2247

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

2253
        anno := pvc.GetAnnotations()
×
2254
        if anno == nil {
×
2255
                return nil
×
2256
        }
×
2257

×
2258
        if IsBound(pvc) {
×
2259
                anno := pvc.GetAnnotations()
×
2260
                delete(anno, AnnBoundCondition)
2261
                delete(anno, AnnBoundConditionReason)
×
2262
                delete(anno, AnnBoundConditionMessage)
×
2263

×
2264
                if !reflect.DeepEqual(currentPvcCopy, pvc) {
×
2265
                        patch := client.MergeFrom(currentPvcCopy)
×
2266
                        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2267
                                return err
×
2268
                        }
×
2269
                }
×
2270

×
2271
                return nil
×
2272
        }
2273

2274
        if pvc.Status.Phase != corev1.ClaimPending {
×
2275
                return nil
2276
        }
2277

×
2278
        // set bound condition by getting the latest event
×
2279
        events := &corev1.EventList{}
×
2280

2281
        err := c.List(context.TODO(), events,
2282
                client.InNamespace(pvc.GetNamespace()),
×
2283
                client.MatchingFields{"involvedObject.name": pvc.GetName(),
×
2284
                        "involvedObject.uid": string(pvc.GetUID())},
×
2285
        )
×
2286

×
2287
        if err != nil {
×
2288
                // Log the error but don't fail the reconciliation
×
2289
                log.Error(err, "Unable to list events for PVC bound condition update", "pvc", pvc.Name)
×
2290
                return nil
×
2291
        }
×
2292

×
2293
        if len(events.Items) == 0 {
×
2294
                return nil
×
2295
        }
2296

×
2297
        pvcPrime, usingPopulator := anno[AnnPVCPrimeName]
×
2298

×
2299
        // Sort event lists by containing primeName substring and most recent timestamp
2300
        sortEvents(events, usingPopulator, pvcPrime)
×
2301

×
2302
        boundMessage := ""
×
2303
        // check if prime name annotation exists
×
2304
        if usingPopulator {
×
2305
                // if we are using populators get the latest event from prime pvc
×
2306
                pvcPrime = fmt.Sprintf("[%s] : ", pvcPrime)
×
2307

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

×
2320
        // since we checked status of phase above, we know this is pending
×
2321
        anno[AnnBoundCondition] = "false"
×
2322
        anno[AnnBoundConditionReason] = "Pending"
2323
        anno[AnnBoundConditionMessage] = boundMessage
2324

×
2325
        patch := client.MergeFrom(currentPvcCopy)
×
2326
        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2327
                return err
×
2328
        }
×
2329

×
2330
        return nil
×
2331
}
×
2332

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

2337
        newEvents := &corev1.EventList{}
×
2338
        err := c.List(context.TODO(), newEvents,
×
2339
                client.InNamespace(srcPVC.GetNamespace()),
×
2340
                client.MatchingFields{"involvedObject.name": srcPVC.GetName(),
×
2341
                        "involvedObject.uid": string(srcPVC.GetUID())},
×
2342
        )
×
2343

×
2344
        if err != nil {
×
2345
                klog.Error(err, "Could not retrieve srcPVC list of Events")
×
2346
        }
×
2347

×
2348
        currEvents := &corev1.EventList{}
×
2349
        err = c.List(context.TODO(), currEvents,
×
2350
                client.InNamespace(targetPVC.GetNamespace()),
2351
                client.MatchingFields{"involvedObject.name": targetPVC.GetName(),
×
2352
                        "involvedObject.uid": string(targetPVC.GetUID())},
×
2353
        )
×
2354

×
2355
        if err != nil {
×
2356
                klog.Error(err, "Could not retrieve targetPVC list of Events")
×
2357
        }
×
2358

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

2362
        for _, event := range currEvents.Items {
2363
                eventMap[event.Message] = struct{}{}
×
2364
        }
×
2365

×
2366
        for _, newEvent := range newEvents.Items {
×
2367
                msg := newEvent.Message
×
2368

2369
                // check if target PVC already has this equivalent event
×
2370
                if _, exists := eventMap[msg]; exists {
×
2371
                        continue
×
2372
                }
×
2373

×
2374
                formattedMsg := srcPrefixMsg + msg
×
2375
                // check if we already emitted this event with the src prefix
2376
                if _, exists := eventMap[formattedMsg]; exists {
2377
                        continue
×
2378
                }
×
2379
                recorder.Event(targetPVC, newEvent.Type, newEvent.Reason, formattedMsg)
×
2380
        }
×
2381
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc