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

kubevirt / containerized-data-importer / #5434

03 Jul 2025 03:14PM UTC coverage: 59.425% (+0.07%) from 59.355%
#5434

push

travis-ci

web-flow
Bugfix: Fix math in overhead size calculations  (#3779)

* Fix GetUsableSpace calculation

GetUsableSpace had wrong assumptions about undoing overhead calculations. This commit fixes the math and updates the appropriate unit tests.

Signed-off-by: Alvaro Romero <alromero@redhat.com>

* Fix GetRequiredSpace calculations

GetRequiredSpace uses wrong math to calculate overhead expansion, which usually causes inflated space to be significantly larger than it should.

This commit fixes this calculation and removes the redundant CalculateOverheadSpace.

Signed-off-by: Alvaro Romero <alromero@redhat.com>

* Fix functional tests that assume bad fs overhead inflation

Signed-off-by: Alvaro Romero <alromero@redhat.com>

* Increase image size in upload test to avoid overhead inflation issue

There's a known issue where the default overhead inflation is insufficient to account for the fs overhead in smaller images.

This commit increases the image size in a test so the overhead inflation is enough for the upload to succeed.

Signed-off-by: Alvaro Romero <alromero@redhat.com>

---------

Signed-off-by: Alvaro Romero <alromero@redhat.com>

20 of 21 new or added lines in 3 files covered. (95.24%)

2 existing lines in 1 file now uncovered.

16982 of 28577 relevant lines covered (59.43%)

0.66 hits per line

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

14.04
/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
        // AnnPopulatedFor is a PVC annotation telling the datavolume controller that the PVC is already populated
91
        AnnPopulatedFor = AnnAPIGroup + "/storage.populatedFor"
92
        // AnnPrePopulated is a PVC annotation telling the datavolume controller that the PVC is already populated
93
        AnnPrePopulated = AnnAPIGroup + "/storage.prePopulated"
94
        // AnnPriorityClassName is PVC annotation to indicate the priority class name for importer, cloner and uploader pod
95
        AnnPriorityClassName = AnnAPIGroup + "/storage.pod.priorityclassname"
96
        // AnnExternalPopulation annotation marks a PVC as "externally populated", allowing the import-controller to skip it
97
        AnnExternalPopulation = AnnAPIGroup + "/externalPopulation"
98

99
        // AnnPodRetainAfterCompletion is PVC annotation for retaining transfer pods after completion
100
        AnnPodRetainAfterCompletion = AnnAPIGroup + "/storage.pod.retainAfterCompletion"
101

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

111
        // AnnCurrentPodID keeps track of the latest pod servicing this PVC
112
        AnnCurrentPodID = AnnAPIGroup + "/storage.checkpoint.pod.id"
113
        // AnnMultiStageImportDone marks a multi-stage import as totally finished
114
        AnnMultiStageImportDone = AnnAPIGroup + "/storage.checkpoint.done"
115

116
        // AnnPopulatorProgress is a standard annotation that can be used progress reporting
117
        AnnPopulatorProgress = AnnAPIGroup + "/storage.populator.progress"
118

119
        // AnnPreallocationRequested provides a const to indicate whether preallocation should be performed on the PV
120
        AnnPreallocationRequested = AnnAPIGroup + "/storage.preallocation.requested"
121
        // AnnPreallocationApplied provides a const for PVC preallocation annotation
122
        AnnPreallocationApplied = AnnAPIGroup + "/storage.preallocation"
123

124
        // AnnRunningCondition provides a const for the running condition
125
        AnnRunningCondition = AnnAPIGroup + "/storage.condition.running"
126
        // AnnRunningConditionMessage provides a const for the running condition
127
        AnnRunningConditionMessage = AnnAPIGroup + "/storage.condition.running.message"
128
        // AnnRunningConditionReason provides a const for the running condition
129
        AnnRunningConditionReason = AnnAPIGroup + "/storage.condition.running.reason"
130

131
        // AnnBoundCondition provides a const for the running condition
132
        AnnBoundCondition = AnnAPIGroup + "/storage.condition.bound"
133
        // AnnBoundConditionMessage provides a const for the running condition
134
        AnnBoundConditionMessage = AnnAPIGroup + "/storage.condition.bound.message"
135
        // AnnBoundConditionReason provides a const for the running condition
136
        AnnBoundConditionReason = AnnAPIGroup + "/storage.condition.bound.reason"
137

138
        // AnnSourceRunningCondition provides a const for the running condition
139
        AnnSourceRunningCondition = AnnAPIGroup + "/storage.condition.source.running"
140
        // AnnSourceRunningConditionMessage provides a const for the running condition
141
        AnnSourceRunningConditionMessage = AnnAPIGroup + "/storage.condition.source.running.message"
142
        // AnnSourceRunningConditionReason provides a const for the running condition
143
        AnnSourceRunningConditionReason = AnnAPIGroup + "/storage.condition.source.running.reason"
144

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

154
        // AnnRequiresScratch provides a const for our PVC requiring scratch annotation
155
        AnnRequiresScratch = AnnAPIGroup + "/storage.import.requiresScratch"
156

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

162
        // AnnContentType provides a const for the PVC content-type
163
        AnnContentType = AnnAPIGroup + "/storage.contentType"
164

165
        // AnnSource provide a const for our PVC import source annotation
166
        AnnSource = AnnAPIGroup + "/storage.import.source"
167
        // AnnEndpoint provides a const for our PVC endpoint annotation
168
        AnnEndpoint = AnnAPIGroup + "/storage.import.endpoint"
169

170
        // AnnSecret provides a const for our PVC secretName annotation
171
        AnnSecret = AnnAPIGroup + "/storage.import.secretName"
172
        // AnnCertConfigMap is the name of a configmap containing tls certs
173
        AnnCertConfigMap = AnnAPIGroup + "/storage.import.certConfigMap"
174
        // AnnRegistryImportMethod provides a const for registry import method annotation
175
        AnnRegistryImportMethod = AnnAPIGroup + "/storage.import.registryImportMethod"
176
        // AnnRegistryImageStream provides a const for registry image stream annotation
177
        AnnRegistryImageStream = AnnAPIGroup + "/storage.import.registryImageStream"
178
        // AnnImportPod provides a const for our PVC importPodName annotation
179
        AnnImportPod = AnnAPIGroup + "/storage.import.importPodName"
180
        // AnnDiskID provides a const for our PVC diskId annotation
181
        AnnDiskID = AnnAPIGroup + "/storage.import.diskId"
182
        // AnnUUID provides a const for our PVC uuid annotation
183
        AnnUUID = AnnAPIGroup + "/storage.import.uuid"
184
        // AnnBackingFile provides a const for our PVC backing file annotation
185
        AnnBackingFile = AnnAPIGroup + "/storage.import.backingFile"
186
        // AnnThumbprint provides a const for our PVC backing thumbprint annotation
187
        AnnThumbprint = AnnAPIGroup + "/storage.import.vddk.thumbprint"
188
        // AnnExtraHeaders provides a const for our PVC extraHeaders annotation
189
        AnnExtraHeaders = AnnAPIGroup + "/storage.import.extraHeaders"
190
        // AnnSecretExtraHeaders provides a const for our PVC secretExtraHeaders annotation
191
        AnnSecretExtraHeaders = AnnAPIGroup + "/storage.import.secretExtraHeaders"
192
        // AnnRegistryImageArchitecture provides a const for our PVC registryImageArchitecture annotation
193
        AnnRegistryImageArchitecture = AnnAPIGroup + "/storage.import.registryImageArchitecture"
194

195
        // AnnCloneToken is the annotation containing the clone token
196
        AnnCloneToken = AnnAPIGroup + "/storage.clone.token"
197
        // AnnExtendedCloneToken is the annotation containing the long term clone token
198
        AnnExtendedCloneToken = AnnAPIGroup + "/storage.extended.clone.token"
199
        // AnnPermissiveClone annotation allows the clone-controller to skip the clone size validation
200
        AnnPermissiveClone = AnnAPIGroup + "/permissiveClone"
201
        // AnnOwnerUID annotation has the owner UID
202
        AnnOwnerUID = AnnAPIGroup + "/ownerUID"
203
        // AnnCloneType is the comuuted/requested clone type
204
        AnnCloneType = AnnAPIGroup + "/cloneType"
205
        // AnnCloneSourcePod name of the source clone pod
206
        AnnCloneSourcePod = AnnAPIGroup + "/storage.sourceClonePodName"
207

208
        // AnnUploadRequest marks that a PVC should be made available for upload
209
        AnnUploadRequest = AnnAPIGroup + "/storage.upload.target"
210

211
        // AnnCheckStaticVolume checks if a statically allocated PV exists before creating the target PVC.
212
        // If so, PVC is still created but population is skipped
213
        AnnCheckStaticVolume = AnnAPIGroup + "/storage.checkStaticVolume"
214

215
        // AnnPersistentVolumeList is an annotation storing a list of PV names
216
        AnnPersistentVolumeList = AnnAPIGroup + "/storage.persistentVolumeList"
217

218
        // AnnPopulatorKind annotation is added to a PVC' to specify the population kind, so it's later
219
        // checked by the common populator watches.
220
        AnnPopulatorKind = AnnAPIGroup + "/storage.populator.kind"
221
        // AnnUsePopulator annotation indicates if the datavolume population will use populators
222
        AnnUsePopulator = AnnAPIGroup + "/storage.usePopulator"
223

224
        // AnnMinimumSupportedPVCSize annotation on a StorageProfile specifies its minimum supported PVC size
225
        AnnMinimumSupportedPVCSize = AnnAPIGroup + "/minimumSupportedPvcSize"
226

227
        // AnnDefaultStorageClass is the annotation indicating that a storage class is the default one
228
        AnnDefaultStorageClass = "storageclass.kubernetes.io/is-default-class"
229
        // AnnDefaultVirtStorageClass is the annotation indicating that a storage class is the default one for virtualization purposes
230
        AnnDefaultVirtStorageClass = "storageclass.kubevirt.io/is-default-virt-class"
231
        // AnnDefaultSnapshotClass is the annotation indicating that a snapshot class is the default one
232
        AnnDefaultSnapshotClass = "snapshot.storage.kubernetes.io/is-default-class"
233

234
        // AnnSourceVolumeMode is the volume mode of the source PVC specified as an annotation on snapshots
235
        AnnSourceVolumeMode = AnnAPIGroup + "/storage.import.sourceVolumeMode"
236

237
        // AnnOpenShiftImageLookup is the annotation for OpenShift image stream lookup
238
        AnnOpenShiftImageLookup = "alpha.image.policy.openshift.io/resolve-names"
239

240
        // AnnCloneRequest sets our expected annotation for a CloneRequest
241
        AnnCloneRequest = "k8s.io/CloneRequest"
242
        // AnnCloneOf is used to indicate that cloning was complete
243
        AnnCloneOf = "k8s.io/CloneOf"
244

245
        // AnnPodNetwork is used for specifying Pod Network
246
        AnnPodNetwork = "k8s.v1.cni.cncf.io/networks"
247
        // AnnPodMultusDefaultNetwork is used for specifying default Pod Network
248
        AnnPodMultusDefaultNetwork = "v1.multus-cni.io/default-network"
249
        // AnnPodSidecarInjectionIstio is used for enabling/disabling Pod istio/AspenMesh sidecar injection
250
        AnnPodSidecarInjectionIstio = "sidecar.istio.io/inject"
251
        // AnnPodSidecarInjectionIstioDefault is the default value passed for AnnPodSidecarInjection
252
        AnnPodSidecarInjectionIstioDefault = "false"
253
        // AnnPodSidecarInjectionLinkerd is used to enable/disable linkerd sidecar injection
254
        AnnPodSidecarInjectionLinkerd = "linkerd.io/inject"
255
        // AnnPodSidecarInjectionLinkerdDefault is the default value passed for AnnPodSidecarInjectionLinkerd
256
        AnnPodSidecarInjectionLinkerdDefault = "disabled"
257

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

261
        // AnnSelectedNode annotation is added to a PVC that has been triggered by scheduler to
262
        // be dynamically provisioned. Its value is the name of the selected node.
263
        AnnSelectedNode = "volume.kubernetes.io/selected-node"
264

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

268
        // CloneSourceInUse is reason for event created when clone source pvc is in use
269
        CloneSourceInUse = "CloneSourceInUse"
270

271
        // CloneComplete message
272
        CloneComplete = "Clone Complete"
273

274
        cloneTokenLeeway = 10 * time.Second
275

276
        // Default value for preallocation option if not defined in DV or CDIConfig
277
        defaultPreallocation = false
278

279
        // ErrStartingPod provides a const to indicate that a pod wasn't able to start without providing sensitive information (reason)
280
        ErrStartingPod = "ErrStartingPod"
281
        // MessageErrStartingPod provides a const to indicate that a pod wasn't able to start without providing sensitive information (message)
282
        MessageErrStartingPod = "Error starting pod '%s': For more information, request access to cdi-deploy logs from your sysadmin"
283
        // ErrClaimNotValid provides a const to indicate a claim is not valid
284
        ErrClaimNotValid = "ErrClaimNotValid"
285
        // ErrExceededQuota provides a const to indicate the claim has exceeded the quota
286
        ErrExceededQuota = "ErrExceededQuota"
287
        // ErrIncompatiblePVC provides a const to indicate a clone is not possible due to an incompatible PVC
288
        ErrIncompatiblePVC = "ErrIncompatiblePVC"
289

290
        // SourceHTTP is the source type HTTP, if unspecified or invalid, it defaults to SourceHTTP
291
        SourceHTTP = "http"
292
        // SourceS3 is the source type S3
293
        SourceS3 = "s3"
294
        // SourceGCS is the source type GCS
295
        SourceGCS = "gcs"
296
        // SourceGlance is the source type of glance
297
        SourceGlance = "glance"
298
        // SourceNone means there is no source.
299
        SourceNone = "none"
300
        // SourceRegistry is the source type of Registry
301
        SourceRegistry = "registry"
302
        // SourceImageio is the source type ovirt-imageio
303
        SourceImageio = "imageio"
304
        // SourceVDDK is the source type of VDDK
305
        SourceVDDK = "vddk"
306

307
        // VolumeSnapshotClassSelected reports that a VolumeSnapshotClass was selected
308
        VolumeSnapshotClassSelected = "VolumeSnapshotClassSelected"
309
        // MessageStorageProfileVolumeSnapshotClassSelected reports that a VolumeSnapshotClass was selected according to StorageProfile
310
        MessageStorageProfileVolumeSnapshotClassSelected = "VolumeSnapshotClass selected according to StorageProfile"
311
        // MessageDefaultVolumeSnapshotClassSelected reports that the default VolumeSnapshotClass was selected
312
        MessageDefaultVolumeSnapshotClassSelected = "Default VolumeSnapshotClass selected"
313
        // MessageFirstVolumeSnapshotClassSelected reports that the first VolumeSnapshotClass was selected
314
        MessageFirstVolumeSnapshotClassSelected = "First VolumeSnapshotClass selected"
315

316
        // ClaimLost reason const
317
        ClaimLost = "ClaimLost"
318
        // NotFound reason const
319
        NotFound = "NotFound"
320

321
        // LabelDefaultInstancetype provides a default VirtualMachine{ClusterInstancetype,Instancetype} that can be used by a VirtualMachine booting from a given PVC
322
        LabelDefaultInstancetype = "instancetype.kubevirt.io/default-instancetype"
323
        // LabelDefaultInstancetypeKind provides a default kind of either VirtualMachineClusterInstancetype or VirtualMachineInstancetype
324
        LabelDefaultInstancetypeKind = "instancetype.kubevirt.io/default-instancetype-kind"
325
        // LabelDefaultPreference provides a default VirtualMachine{ClusterPreference,Preference} that can be used by a VirtualMachine booting from a given PVC
326
        LabelDefaultPreference = "instancetype.kubevirt.io/default-preference"
327
        // LabelDefaultPreferenceKind provides a default kind of either VirtualMachineClusterPreference or VirtualMachinePreference
328
        LabelDefaultPreferenceKind = "instancetype.kubevirt.io/default-preference-kind"
329

330
        // LabelDynamicCredentialSupport specifies if the OS supports updating credentials at runtime.
331
        //nolint:gosec // These are not credentials
332
        LabelDynamicCredentialSupport = "kubevirt.io/dynamic-credentials-support"
333

334
        // LabelExcludeFromVeleroBackup provides a const to indicate whether an object should be excluded from velero backup
335
        LabelExcludeFromVeleroBackup = "velero.io/exclude-from-backup"
336

337
        // ProgressDone this means we are DONE
338
        ProgressDone = "100.0%"
339

340
        // AnnEventSourceKind is the source kind that should be related to events
341
        AnnEventSourceKind = AnnAPIGroup + "/events.source.kind"
342
        // AnnEventSource is the source that should be related to events (namespace/name)
343
        AnnEventSource = AnnAPIGroup + "/events.source"
344

345
        // AnnAllowClaimAdoption is the annotation that allows a claim to be adopted by a DataVolume
346
        AnnAllowClaimAdoption = AnnAPIGroup + "/allowClaimAdoption"
347

348
        // AnnCdiCustomizeComponentHash annotation is a hash of all customizations that live under spec.CustomizeComponents
349
        AnnCdiCustomizeComponentHash = AnnAPIGroup + "/customizer-identifier"
350

351
        // AnnCreatedForDataVolume stores the UID of the datavolume that the PVC was created for
352
        AnnCreatedForDataVolume = AnnAPIGroup + "/createdForDataVolume"
353
)
354

355
// Size-detection pod error codes
356
const (
357
        NoErr int = iota
358
        ErrBadArguments
359
        ErrInvalidFile
360
        ErrInvalidPath
361
        ErrBadTermFile
362
        ErrUnknown
363
)
364

365
var (
366
        // BlockMode is raw block device mode
367
        BlockMode = corev1.PersistentVolumeBlock
368
        // FilesystemMode is filesystem device mode
369
        FilesystemMode = corev1.PersistentVolumeFilesystem
370

371
        // DefaultInstanceTypeLabels is a list of currently supported default instance type labels
372
        DefaultInstanceTypeLabels = []string{
373
                LabelDefaultInstancetype,
374
                LabelDefaultInstancetypeKind,
375
                LabelDefaultPreference,
376
                LabelDefaultPreferenceKind,
377
        }
378

379
        apiServerKeyOnce sync.Once
380
        apiServerKey     *rsa.PrivateKey
381

382
        // allowedAnnotations is a list of annotations
383
        // that can be propagated from the pvc/dv to a pod
384
        allowedAnnotations = map[string]string{
385
                AnnPodNetwork:                 "",
386
                AnnPodSidecarInjectionIstio:   AnnPodSidecarInjectionIstioDefault,
387
                AnnPodSidecarInjectionLinkerd: AnnPodSidecarInjectionLinkerdDefault,
388
                AnnPriorityClassName:          "",
389
                AnnPodMultusDefaultNetwork:    "",
390
        }
391

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

394
        ErrDataSourceMaxDepthReached = errors.New("DataSource reference chain exceeds maximum depth of 1")
395
        ErrDataSourceSelfReference   = errors.New("DataSource cannot self-reference")
396
)
397

398
// FakeValidator is a fake token validator
399
type FakeValidator struct {
400
        Match     string
401
        Operation token.Operation
402
        Name      string
403
        Namespace string
404
        Resource  metav1.GroupVersionResource
405
        Params    map[string]string
406
}
407

408
// Validate is a fake token validation
409
func (v *FakeValidator) Validate(value string) (*token.Payload, error) {
×
410
        if value != v.Match {
×
411
                return nil, fmt.Errorf("token does not match expected")
×
412
        }
×
413
        resource := metav1.GroupVersionResource{
×
414
                Resource: "persistentvolumeclaims",
×
415
        }
×
416
        return &token.Payload{
×
417
                Name:      v.Name,
×
418
                Namespace: v.Namespace,
×
419
                Operation: token.OperationClone,
×
420
                Resource:  resource,
×
421
                Params:    v.Params,
×
422
        }, nil
×
423
}
424

425
// MultiTokenValidator is a token validator that can validate both short and long tokens
426
type MultiTokenValidator struct {
427
        ShortTokenValidator token.Validator
428
        LongTokenValidator  token.Validator
429
}
430

431
// ValidatePVC validates a PVC
432
func (mtv *MultiTokenValidator) ValidatePVC(source, target *corev1.PersistentVolumeClaim) error {
×
433
        tok, v := mtv.getTokenAndValidator(target)
×
434
        return ValidateCloneTokenPVC(tok, v, source, target)
×
435
}
×
436

437
// ValidatePopulator valades a token for a populator
438
func (mtv *MultiTokenValidator) ValidatePopulator(vcs *cdiv1.VolumeCloneSource, pvc *corev1.PersistentVolumeClaim) error {
×
439
        if vcs.Namespace == pvc.Namespace {
×
440
                return nil
×
441
        }
×
442

443
        tok, v := mtv.getTokenAndValidator(pvc)
×
444

×
445
        tokenData, err := v.Validate(tok)
×
446
        if err != nil {
×
447
                return errors.Wrap(err, "error verifying token")
×
448
        }
×
449

450
        var tokenResourceName string
×
451
        switch vcs.Spec.Source.Kind {
×
452
        case "PersistentVolumeClaim":
×
453
                tokenResourceName = "persistentvolumeclaims"
×
454
        case "VolumeSnapshot":
×
455
                tokenResourceName = "volumesnapshots"
×
456
        }
457
        srcName := vcs.Spec.Source.Name
×
458

×
459
        return validateTokenData(tokenData, vcs.Namespace, srcName, pvc.Namespace, pvc.Name, string(pvc.UID), tokenResourceName)
×
460
}
461

462
func (mtv *MultiTokenValidator) getTokenAndValidator(pvc *corev1.PersistentVolumeClaim) (string, token.Validator) {
×
463
        v := mtv.LongTokenValidator
×
464
        tok, ok := pvc.Annotations[AnnExtendedCloneToken]
×
465
        if !ok {
×
466
                // if token doesn't exist, no prob for same namespace
×
467
                tok = pvc.Annotations[AnnCloneToken]
×
468
                v = mtv.ShortTokenValidator
×
469
        }
×
470
        return tok, v
×
471
}
472

473
// NewMultiTokenValidator returns a new multi token validator
474
func NewMultiTokenValidator(key *rsa.PublicKey) *MultiTokenValidator {
×
475
        return &MultiTokenValidator{
×
476
                ShortTokenValidator: NewCloneTokenValidator(common.CloneTokenIssuer, key),
×
477
                LongTokenValidator:  NewCloneTokenValidator(common.ExtendedCloneTokenIssuer, key),
×
478
        }
×
479
}
×
480

481
// NewCloneTokenValidator returns a new token validator
482
func NewCloneTokenValidator(issuer string, key *rsa.PublicKey) token.Validator {
×
483
        return token.NewValidator(issuer, key, cloneTokenLeeway)
×
484
}
×
485

486
// GetRequestedImageSize returns the PVC requested size
487
func GetRequestedImageSize(pvc *corev1.PersistentVolumeClaim) (string, error) {
1✔
488
        pvcSize, found := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
1✔
489
        if !found {
2✔
490
                return "", errors.Errorf("storage request is missing in pvc \"%s/%s\"", pvc.Namespace, pvc.Name)
1✔
491
        }
1✔
492
        return pvcSize.String(), nil
1✔
493
}
494

495
// GetVolumeMode returns the volumeMode from PVC handling default empty value
496
func GetVolumeMode(pvc *corev1.PersistentVolumeClaim) corev1.PersistentVolumeMode {
×
497
        return util.ResolveVolumeMode(pvc.Spec.VolumeMode)
×
498
}
×
499

500
// IsDataVolumeUsingDefaultStorageClass checks if the DataVolume is using the default StorageClass
501
func IsDataVolumeUsingDefaultStorageClass(dv *cdiv1.DataVolume) bool {
×
502
        return GetStorageClassFromDVSpec(dv) == nil
×
503
}
×
504

505
// GetStorageClassFromDVSpec returns the StorageClassName from DataVolume PVC or Storage spec
506
func GetStorageClassFromDVSpec(dv *cdiv1.DataVolume) *string {
×
507
        if dv.Spec.PVC != nil {
×
508
                return dv.Spec.PVC.StorageClassName
×
509
        }
×
510

511
        if dv.Spec.Storage != nil {
×
512
                return dv.Spec.Storage.StorageClassName
×
513
        }
×
514

515
        return nil
×
516
}
517

518
// getStorageClassByName looks up the storage class based on the name.
519
// If name is nil, it performs fallback to default according to the provided content type
520
// If no storage class is found, returns nil
521
func getStorageClassByName(ctx context.Context, client client.Client, name *string, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
1✔
522
        if name == nil {
2✔
523
                return getFallbackStorageClass(ctx, client, contentType)
1✔
524
        }
1✔
525

526
        // look up storage class by name
527
        storageClass := &storagev1.StorageClass{}
×
528
        if err := client.Get(ctx, types.NamespacedName{Name: *name}, storageClass); err != nil {
×
529
                if k8serrors.IsNotFound(err) {
×
530
                        return nil, nil
×
531
                }
×
532
                klog.V(3).Info("Unable to retrieve storage class", "storage class name", *name)
×
533
                return nil, errors.Errorf("unable to retrieve storage class %s", *name)
×
534
        }
535

536
        return storageClass, nil
×
537
}
538

539
// GetStorageClassByNameWithK8sFallback looks up the storage class based on the name
540
// If name is nil, it looks for the default k8s storage class storageclass.kubernetes.io/is-default-class
541
// If no storage class is found, returns nil
542
func GetStorageClassByNameWithK8sFallback(ctx context.Context, client client.Client, name *string) (*storagev1.StorageClass, error) {
1✔
543
        return getStorageClassByName(ctx, client, name, cdiv1.DataVolumeArchive)
1✔
544
}
1✔
545

546
// GetStorageClassByNameWithVirtFallback looks up the storage class based on the name
547
// If name is nil, it looks for the following, in this order:
548
// default kubevirt storage class (if the caller is interested) storageclass.kubevirt.io/is-default-class
549
// default k8s storage class storageclass.kubernetes.io/is-default-class
550
// If no storage class is found, returns nil
551
func GetStorageClassByNameWithVirtFallback(ctx context.Context, client client.Client, name *string, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
1✔
552
        return getStorageClassByName(ctx, client, name, contentType)
1✔
553
}
1✔
554

555
// getFallbackStorageClass looks for a default virt/k8s storage class according to the content type
556
// If no storage class is found, returns nil
557
func getFallbackStorageClass(ctx context.Context, client client.Client, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
1✔
558
        storageClasses := &storagev1.StorageClassList{}
1✔
559
        if err := client.List(ctx, storageClasses); err != nil {
1✔
560
                klog.V(3).Info("Unable to retrieve available storage classes")
×
561
                return nil, errors.New("unable to retrieve storage classes")
×
562
        }
×
563

564
        if GetContentType(contentType) == cdiv1.DataVolumeKubeVirt {
2✔
565
                if virtSc := GetPlatformDefaultStorageClass(storageClasses, AnnDefaultVirtStorageClass); virtSc != nil {
2✔
566
                        return virtSc, nil
1✔
567
                }
1✔
568
        }
569
        return GetPlatformDefaultStorageClass(storageClasses, AnnDefaultStorageClass), nil
1✔
570
}
571

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

1✔
576
        for _, storageClass := range storageClasses.Items {
2✔
577
                if storageClass.Annotations[defaultAnnotationKey] == "true" {
2✔
578
                        defaultClasses = append(defaultClasses, storageClass)
1✔
579
                }
1✔
580
        }
581

582
        if len(defaultClasses) == 0 {
2✔
583
                return nil
1✔
584
        }
1✔
585

586
        // Primary sort by creation timestamp, newest first
587
        // Secondary sort by class name, ascending order
588
        // Follows k8s behavior
589
        // https://github.com/kubernetes/kubernetes/blob/731068288e112c8b5af70f676296cc44661e84f4/pkg/volume/util/storageclass.go#L58-L59
590
        sort.Slice(defaultClasses, func(i, j int) bool {
2✔
591
                if defaultClasses[i].CreationTimestamp.UnixNano() == defaultClasses[j].CreationTimestamp.UnixNano() {
2✔
592
                        return defaultClasses[i].Name < defaultClasses[j].Name
1✔
593
                }
1✔
594
                return defaultClasses[i].CreationTimestamp.UnixNano() > defaultClasses[j].CreationTimestamp.UnixNano()
1✔
595
        })
596
        if len(defaultClasses) > 1 {
2✔
597
                klog.V(3).Infof("%d default StorageClasses were found, choosing: %s", len(defaultClasses), defaultClasses[0].Name)
1✔
598
        }
1✔
599

600
        return &defaultClasses[0]
1✔
601
}
602

603
// GetFilesystemOverheadForStorageClass determines the filesystem overhead defined in CDIConfig for the storageClass.
604
func GetFilesystemOverheadForStorageClass(ctx context.Context, client client.Client, storageClassName *string) (cdiv1.Percent, error) {
×
605
        if storageClassName != nil && *storageClassName == "" {
×
606
                klog.V(3).Info("No storage class name passed")
×
607
                return "0", nil
×
608
        }
×
609

610
        cdiConfig := &cdiv1.CDIConfig{}
×
611
        if err := client.Get(ctx, types.NamespacedName{Name: common.ConfigName}, cdiConfig); err != nil {
×
612
                if k8serrors.IsNotFound(err) {
×
613
                        klog.V(1).Info("CDIConfig does not exist, pod will not start until it does")
×
614
                        return "0", nil
×
615
                }
×
616
                return "0", err
×
617
        }
618

619
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(ctx, client, storageClassName)
×
620
        if err != nil || targetStorageClass == nil {
×
621
                klog.V(3).Info("Storage class", storageClassName, "not found, trying default storage class")
×
622
                targetStorageClass, err = GetStorageClassByNameWithK8sFallback(ctx, client, nil)
×
623
                if err != nil {
×
624
                        klog.V(3).Info("No default storage class found, continuing with global overhead")
×
625
                        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
626
                }
×
627
        }
628

629
        if cdiConfig.Status.FilesystemOverhead == nil {
×
630
                klog.Errorf("CDIConfig filesystemOverhead used before config controller ran reconcile. Hopefully this only happens during unit testing.")
×
631
                return "0", nil
×
632
        }
×
633

634
        if targetStorageClass == nil {
×
635
                klog.V(3).Info("Storage class", storageClassName, "not found, continuing with global overhead")
×
636
                return cdiConfig.Status.FilesystemOverhead.Global, nil
×
637
        }
×
638

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

×
641
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
×
642

×
643
        storageClassOverhead, found := perStorageConfig[targetStorageClass.GetName()]
×
644
        if found {
×
645
                return storageClassOverhead, nil
×
646
        }
×
647

648
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
649
}
650

651
// GetDefaultPodResourceRequirements gets default pod resource requirements from cdi config status
652
func GetDefaultPodResourceRequirements(client client.Client) (*corev1.ResourceRequirements, error) {
×
653
        cdiconfig := &cdiv1.CDIConfig{}
×
654
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
×
655
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
656
                return nil, err
×
657
        }
×
658

659
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
660
}
661

662
// GetImagePullSecrets gets the imagePullSecrets needed to pull images from the cdi config
663
func GetImagePullSecrets(client client.Client) ([]corev1.LocalObjectReference, error) {
×
664
        cdiconfig := &cdiv1.CDIConfig{}
×
665
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
×
666
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
667
                return nil, err
×
668
        }
×
669

670
        return cdiconfig.Status.ImagePullSecrets, nil
×
671
}
672

673
// GetPodFromPvc determines the pod associated with the pvc passed in.
674
func GetPodFromPvc(c client.Client, namespace string, pvc *corev1.PersistentVolumeClaim) (*corev1.Pod, error) {
×
675
        l, _ := labels.Parse(common.PrometheusLabelKey)
×
676
        pods := &corev1.PodList{}
×
677
        listOptions := client.ListOptions{
×
678
                LabelSelector: l,
×
679
        }
×
680
        if err := c.List(context.TODO(), pods, &listOptions); err != nil {
×
681
                return nil, err
×
682
        }
×
683

684
        pvcUID := pvc.GetUID()
×
685
        for _, pod := range pods.Items {
×
686
                if ShouldIgnorePod(&pod, pvc) {
×
687
                        continue
×
688
                }
689
                for _, or := range pod.OwnerReferences {
×
690
                        if or.UID == pvcUID {
×
691
                                return &pod, nil
×
692
                        }
×
693
                }
694

695
                // TODO: check this
696
                val, exists := pod.Labels[CloneUniqueID]
×
697
                if exists && val == string(pvcUID)+common.ClonerSourcePodNameSuffix {
×
698
                        return &pod, nil
×
699
                }
×
700
        }
701
        return nil, errors.Errorf("Unable to find pod owned by UID: %s, in namespace: %s", string(pvcUID), namespace)
×
702
}
703

704
// AddVolumeDevices returns VolumeDevice slice with one block device for pods using PV with block volume mode
705
func AddVolumeDevices() []corev1.VolumeDevice {
×
706
        volumeDevices := []corev1.VolumeDevice{
×
707
                {
×
708
                        Name:       DataVolName,
×
709
                        DevicePath: common.WriteBlockPath,
×
710
                },
×
711
        }
×
712
        return volumeDevices
×
713
}
×
714

715
// GetPodsUsingPVCs returns Pods currently using PVCs
716
func GetPodsUsingPVCs(ctx context.Context, c client.Client, namespace string, names sets.Set[string], allowReadOnly bool) ([]corev1.Pod, error) {
×
717
        pl := &corev1.PodList{}
×
718
        // hopefully using cached client here
×
719
        err := c.List(ctx, pl, &client.ListOptions{Namespace: namespace})
×
720
        if err != nil {
×
721
                return nil, err
×
722
        }
×
723

724
        var pods []corev1.Pod
×
725
        for _, pod := range pl.Items {
×
726
                if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
×
727
                        continue
×
728
                }
729
                for _, volume := range pod.Spec.Volumes {
×
730
                        if volume.VolumeSource.PersistentVolumeClaim != nil &&
×
731
                                names.Has(volume.PersistentVolumeClaim.ClaimName) {
×
732
                                addPod := true
×
733
                                if allowReadOnly {
×
734
                                        if !volume.VolumeSource.PersistentVolumeClaim.ReadOnly {
×
735
                                                onlyReadOnly := true
×
736
                                                for _, c := range pod.Spec.Containers {
×
737
                                                        for _, vm := range c.VolumeMounts {
×
738
                                                                if vm.Name == volume.Name && !vm.ReadOnly {
×
739
                                                                        onlyReadOnly = false
×
740
                                                                }
×
741
                                                        }
742
                                                        for _, vm := range c.VolumeDevices {
×
743
                                                                if vm.Name == volume.Name {
×
744
                                                                        // Node level rw mount and container can't mount block device ro
×
745
                                                                        onlyReadOnly = false
×
746
                                                                }
×
747
                                                        }
748
                                                }
749
                                                if onlyReadOnly {
×
750
                                                        // no rw mounts
×
751
                                                        addPod = false
×
752
                                                }
×
753
                                        } else {
×
754
                                                // all mounts must be ro
×
755
                                                addPod = false
×
756
                                        }
×
757
                                        if strings.HasSuffix(pod.Name, common.ClonerSourcePodNameSuffix) && pod.Labels != nil &&
×
758
                                                pod.Labels[common.CDIComponentLabel] == common.ClonerSourcePodName {
×
759
                                                // Host assisted clone source pod only reads from source
×
760
                                                // But some drivers disallow mounting a block PVC ReadOnly
×
761
                                                addPod = false
×
762
                                        }
×
763
                                }
764
                                if addPod {
×
765
                                        pods = append(pods, pod)
×
766
                                        break
×
767
                                }
768
                        }
769
                }
770
        }
771

772
        return pods, nil
×
773
}
774

775
// GetWorkloadNodePlacement extracts the workload-specific nodeplacement values from the CDI CR
776
func GetWorkloadNodePlacement(ctx context.Context, c client.Client) (*sdkapi.NodePlacement, error) {
×
777
        cr, err := GetActiveCDI(ctx, c)
×
778
        if err != nil {
×
779
                return nil, err
×
780
        }
×
781

782
        if cr == nil {
×
783
                return nil, fmt.Errorf("no active CDI")
×
784
        }
×
785

786
        return &cr.Spec.Workloads, nil
×
787
}
788

789
// GetActiveCDI returns the active CDI CR
790
func GetActiveCDI(ctx context.Context, c client.Client) (*cdiv1.CDI, error) {
1✔
791
        crList := &cdiv1.CDIList{}
1✔
792
        if err := c.List(ctx, crList, &client.ListOptions{}); err != nil {
1✔
793
                return nil, err
×
794
        }
×
795

796
        if len(crList.Items) == 0 {
2✔
797
                return nil, nil
1✔
798
        }
1✔
799

800
        if len(crList.Items) == 1 {
2✔
801
                return &crList.Items[0], nil
1✔
802
        }
1✔
803

804
        var activeResources []cdiv1.CDI
1✔
805
        for _, cr := range crList.Items {
2✔
806
                if cr.Status.Phase != sdkapi.PhaseError {
2✔
807
                        activeResources = append(activeResources, cr)
1✔
808
                }
1✔
809
        }
810

811
        if len(activeResources) != 1 {
2✔
812
                return nil, fmt.Errorf("invalid number of active CDI resources: %d", len(activeResources))
1✔
813
        }
1✔
814

815
        return &activeResources[0], nil
1✔
816
}
817

818
// IsPopulated returns if the passed in PVC has been populated according to the rules outlined in pkg/apis/core/<version>/utils.go
819
func IsPopulated(pvc *corev1.PersistentVolumeClaim, c client.Client) (bool, error) {
×
820
        return cdiv1utils.IsPopulated(pvc, func(name, namespace string) (*cdiv1.DataVolume, error) {
×
821
                dv := &cdiv1.DataVolume{}
×
822
                err := c.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, dv)
×
823
                return dv, err
×
824
        })
×
825
}
826

827
// GetPreallocation returns the preallocation setting for the specified object (DV or VolumeImportSource), falling back to StorageClass and global setting (in this order)
828
func GetPreallocation(ctx context.Context, client client.Client, preallocation *bool) bool {
×
829
        // First, the DV's preallocation
×
830
        if preallocation != nil {
×
831
                return *preallocation
×
832
        }
×
833

834
        cdiconfig := &cdiv1.CDIConfig{}
×
835
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
×
836
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
837
                return defaultPreallocation
×
838
        }
×
839

840
        return cdiconfig.Status.Preallocation
×
841
}
842

843
// ImmediateBindingRequested returns if an object has the ImmediateBinding annotation
844
func ImmediateBindingRequested(obj metav1.Object) bool {
×
845
        _, isImmediateBindingRequested := obj.GetAnnotations()[AnnImmediateBinding]
×
846
        return isImmediateBindingRequested
×
847
}
×
848

849
// GetPriorityClass gets PVC priority class
850
func GetPriorityClass(pvc *corev1.PersistentVolumeClaim) string {
×
851
        anno := pvc.GetAnnotations()
×
852
        return anno[AnnPriorityClassName]
×
853
}
×
854

855
// ShouldDeletePod returns whether the PVC workload pod should be deleted
856
func ShouldDeletePod(pvc *corev1.PersistentVolumeClaim) bool {
×
857
        return pvc.GetAnnotations()[AnnPodRetainAfterCompletion] != "true" || pvc.GetAnnotations()[AnnRequiresScratch] == "true" || pvc.GetAnnotations()[AnnRequiresDirectIO] == "true" || pvc.DeletionTimestamp != nil
×
858
}
×
859

860
// AddFinalizer adds a finalizer to a resource
861
func AddFinalizer(obj metav1.Object, name string) {
×
862
        if HasFinalizer(obj, name) {
×
863
                return
×
864
        }
×
865

866
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
867
}
868

869
// RemoveFinalizer removes a finalizer from a resource
870
func RemoveFinalizer(obj metav1.Object, name string) {
×
871
        if !HasFinalizer(obj, name) {
×
872
                return
×
873
        }
×
874

875
        var finalizers []string
×
876
        for _, f := range obj.GetFinalizers() {
×
877
                if f != name {
×
878
                        finalizers = append(finalizers, f)
×
879
                }
×
880
        }
881

882
        obj.SetFinalizers(finalizers)
×
883
}
884

885
// HasFinalizer returns true if a resource has a specific finalizer
886
func HasFinalizer(object metav1.Object, value string) bool {
×
887
        for _, f := range object.GetFinalizers() {
×
888
                if f == value {
×
889
                        return true
×
890
                }
×
891
        }
892
        return false
×
893
}
894

895
// ValidateCloneTokenPVC validates clone token for source and target PVCs
896
func ValidateCloneTokenPVC(t string, v token.Validator, source, target *corev1.PersistentVolumeClaim) error {
×
897
        if source.Namespace == target.Namespace {
×
898
                return nil
×
899
        }
×
900

901
        tokenData, err := v.Validate(t)
×
902
        if err != nil {
×
903
                return errors.Wrap(err, "error verifying token")
×
904
        }
×
905

906
        tokenResourceName := getTokenResourceNamePvc(source)
×
907
        srcName := getSourceNamePvc(source)
×
908

×
909
        return validateTokenData(tokenData, source.Namespace, srcName, target.Namespace, target.Name, string(target.UID), tokenResourceName)
×
910
}
911

912
// ValidateCloneTokenDV validates clone token for DV
913
func ValidateCloneTokenDV(validator token.Validator, dv *cdiv1.DataVolume) error {
×
914
        _, sourceName, sourceNamespace := GetCloneSourceInfo(dv)
×
915
        if sourceNamespace == "" || sourceNamespace == dv.Namespace {
×
916
                return nil
×
917
        }
×
918

919
        tok, ok := dv.Annotations[AnnCloneToken]
×
920
        if !ok {
×
921
                return errors.New("clone token missing")
×
922
        }
×
923

924
        tokenData, err := validator.Validate(tok)
×
925
        if err != nil {
×
926
                return errors.Wrap(err, "error verifying token")
×
927
        }
×
928

929
        tokenResourceName := getTokenResourceNameDataVolume(dv.Spec.Source)
×
930
        if tokenResourceName == "" {
×
931
                return errors.New("token resource name empty, can't verify properly")
×
932
        }
×
933

934
        return validateTokenData(tokenData, sourceNamespace, sourceName, dv.Namespace, dv.Name, "", tokenResourceName)
×
935
}
936

937
func getTokenResourceNameDataVolume(source *cdiv1.DataVolumeSource) string {
×
938
        if source.PVC != nil {
×
939
                return "persistentvolumeclaims"
×
940
        } else if source.Snapshot != nil {
×
941
                return "volumesnapshots"
×
942
        }
×
943

944
        return ""
×
945
}
946

947
func getTokenResourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
948
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
×
949
                return "volumesnapshots"
×
950
        }
×
951

952
        return "persistentvolumeclaims"
×
953
}
954

955
func getSourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
956
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
×
957
                if sourcePvc.Spec.DataSourceRef != nil {
×
958
                        return sourcePvc.Spec.DataSourceRef.Name
×
959
                }
×
960
        }
961

962
        return sourcePvc.Name
×
963
}
964

965
func validateTokenData(tokenData *token.Payload, srcNamespace, srcName, targetNamespace, targetName, targetUID, tokenResourceName string) error {
×
966
        uid := tokenData.Params["uid"]
×
967
        if tokenData.Operation != token.OperationClone ||
×
968
                tokenData.Name != srcName ||
×
969
                tokenData.Namespace != srcNamespace ||
×
970
                tokenData.Resource.Resource != tokenResourceName ||
×
971
                tokenData.Params["targetNamespace"] != targetNamespace ||
×
972
                tokenData.Params["targetName"] != targetName ||
×
973
                (uid != "" && uid != targetUID) {
×
974
                return errors.New("invalid token")
×
975
        }
×
976

977
        return nil
×
978
}
979

980
// IsSnapshotValidForClone returns an error if the passed snapshot is not valid for cloning
981
func IsSnapshotValidForClone(sourceSnapshot *snapshotv1.VolumeSnapshot) error {
×
982
        if sourceSnapshot.Status == nil {
×
983
                return fmt.Errorf("no status on source snapshot yet")
×
984
        }
×
985
        if !IsSnapshotReady(sourceSnapshot) {
×
986
                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)
×
987
        }
×
988
        if sourceSnapshot.Status.Error != nil {
×
989
                errMessage := "no details"
×
990
                if msg := sourceSnapshot.Status.Error.Message; msg != nil {
×
991
                        errMessage = *msg
×
992
                }
×
993
                return fmt.Errorf("snapshot in error state with msg: %s", errMessage)
×
994
        }
995
        if sourceSnapshot.Spec.VolumeSnapshotClassName == nil ||
×
996
                *sourceSnapshot.Spec.VolumeSnapshotClassName == "" {
×
997
                return fmt.Errorf("snapshot %s/%s does not have volume snap class populated, can't clone", sourceSnapshot.Name, sourceSnapshot.Namespace)
×
998
        }
×
999
        return nil
×
1000
}
1001

1002
// AddAnnotation adds an annotation to an object
1003
func AddAnnotation(obj metav1.Object, key, value string) {
1✔
1004
        if obj.GetAnnotations() == nil {
2✔
1005
                obj.SetAnnotations(make(map[string]string))
1✔
1006
        }
1✔
1007
        obj.GetAnnotations()[key] = value
1✔
1008
}
1009

1010
// AddLabel adds a label to an object
1011
func AddLabel(obj metav1.Object, key, value string) {
1✔
1012
        if obj.GetLabels() == nil {
2✔
1013
                obj.SetLabels(make(map[string]string))
1✔
1014
        }
1✔
1015
        obj.GetLabels()[key] = value
1✔
1016
}
1017

1018
// HandleFailedPod handles pod-creation errors and updates the pod's PVC without providing sensitive information
1019
func HandleFailedPod(err error, podName string, pvc *corev1.PersistentVolumeClaim, recorder record.EventRecorder, c client.Client) error {
×
1020
        if err == nil {
×
1021
                return nil
×
1022
        }
×
1023
        // Generic reason and msg to avoid providing sensitive information
1024
        reason := ErrStartingPod
×
1025
        msg := fmt.Sprintf(MessageErrStartingPod, podName)
×
1026

×
1027
        // Error handling to fine-tune the event with pertinent info
×
1028
        if ErrQuotaExceeded(err) {
×
1029
                reason = ErrExceededQuota
×
1030
        }
×
1031

1032
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1033

×
1034
        if isCloneSourcePod := CreateCloneSourcePodName(pvc) == podName; isCloneSourcePod {
×
1035
                AddAnnotation(pvc, AnnSourceRunningCondition, "false")
×
1036
                AddAnnotation(pvc, AnnSourceRunningConditionReason, reason)
×
1037
                AddAnnotation(pvc, AnnSourceRunningConditionMessage, msg)
×
1038
        } else {
×
1039
                AddAnnotation(pvc, AnnRunningCondition, "false")
×
1040
                AddAnnotation(pvc, AnnRunningConditionReason, reason)
×
1041
                AddAnnotation(pvc, AnnRunningConditionMessage, msg)
×
1042
        }
×
1043

1044
        AddAnnotation(pvc, AnnPodPhase, string(corev1.PodFailed))
×
1045
        if err := c.Update(context.TODO(), pvc); err != nil {
×
1046
                return err
×
1047
        }
×
1048

1049
        return err
×
1050
}
1051

1052
// GetSource returns the source string which determines the type of source. If no source or invalid source found, default to http
1053
func GetSource(pvc *corev1.PersistentVolumeClaim) string {
×
1054
        source, found := pvc.Annotations[AnnSource]
×
1055
        if !found {
×
1056
                source = ""
×
1057
        }
×
1058
        switch source {
×
1059
        case
1060
                SourceHTTP,
1061
                SourceS3,
1062
                SourceGCS,
1063
                SourceGlance,
1064
                SourceNone,
1065
                SourceRegistry,
1066
                SourceImageio,
1067
                SourceVDDK:
×
1068
        default:
×
1069
                source = SourceHTTP
×
1070
        }
1071
        return source
×
1072
}
1073

1074
// GetEndpoint returns the endpoint string which contains the full path URI of the target object to be copied.
1075
func GetEndpoint(pvc *corev1.PersistentVolumeClaim) (string, error) {
×
1076
        ep, found := pvc.Annotations[AnnEndpoint]
×
1077
        if !found || ep == "" {
×
1078
                verb := "empty"
×
1079
                if !found {
×
1080
                        verb = "missing"
×
1081
                }
×
1082
                return ep, errors.Errorf("annotation %q in pvc \"%s/%s\" is %s", AnnEndpoint, pvc.Namespace, pvc.Name, verb)
×
1083
        }
1084
        return ep, nil
×
1085
}
1086

1087
// AddImportVolumeMounts is being called for pods using PV with filesystem volume mode
1088
func AddImportVolumeMounts() []corev1.VolumeMount {
×
1089
        volumeMounts := []corev1.VolumeMount{
×
1090
                {
×
1091
                        Name:      DataVolName,
×
1092
                        MountPath: common.ImporterDataDir,
×
1093
                },
×
1094
        }
×
1095
        return volumeMounts
×
1096
}
×
1097

1098
// ValidateRequestedCloneSize validates the clone size requirements on block
1099
func ValidateRequestedCloneSize(sourceResources, targetResources corev1.VolumeResourceRequirements) error {
×
1100
        sourceRequest, hasSource := sourceResources.Requests[corev1.ResourceStorage]
×
1101
        targetRequest, hasTarget := targetResources.Requests[corev1.ResourceStorage]
×
1102
        if !hasSource || !hasTarget {
×
1103
                return errors.New("source/target missing storage resource requests")
×
1104
        }
×
1105

1106
        // Verify that the target PVC size is equal or larger than the source.
1107
        if sourceRequest.Value() > targetRequest.Value() {
×
1108
                return errors.Errorf("target resources requests storage size is smaller than the source %d < %d", targetRequest.Value(), sourceRequest.Value())
×
1109
        }
×
1110
        return nil
×
1111
}
1112

1113
// CreateCloneSourcePodName creates clone source pod name
1114
func CreateCloneSourcePodName(targetPvc *corev1.PersistentVolumeClaim) string {
×
1115
        return string(targetPvc.GetUID()) + common.ClonerSourcePodNameSuffix
×
1116
}
×
1117

1118
// IsPVCComplete returns true if a PVC is in 'Succeeded' phase, false if not
1119
func IsPVCComplete(pvc *corev1.PersistentVolumeClaim) bool {
×
1120
        if pvc != nil {
×
1121
                phase, exists := pvc.ObjectMeta.Annotations[AnnPodPhase]
×
1122
                return exists && (phase == string(corev1.PodSucceeded))
×
1123
        }
×
1124
        return false
×
1125
}
1126

1127
// IsMultiStageImportInProgress returns true when a PVC is being part of an ongoing multi-stage import
1128
func IsMultiStageImportInProgress(pvc *corev1.PersistentVolumeClaim) bool {
×
1129
        if pvc != nil {
×
1130
                multiStageImport := metav1.HasAnnotation(pvc.ObjectMeta, AnnCurrentCheckpoint)
×
1131
                multiStageAlreadyDone := metav1.HasAnnotation(pvc.ObjectMeta, AnnMultiStageImportDone)
×
1132
                return multiStageImport && !multiStageAlreadyDone
×
1133
        }
×
1134
        return false
×
1135
}
1136

1137
// SetRestrictedSecurityContext sets the pod security params to be compatible with restricted PSA
1138
func SetRestrictedSecurityContext(podSpec *corev1.PodSpec) {
×
1139
        hasVolumeMounts := false
×
1140
        for _, containers := range [][]corev1.Container{podSpec.InitContainers, podSpec.Containers} {
×
1141
                for i := range containers {
×
1142
                        container := &containers[i]
×
1143
                        if container.SecurityContext == nil {
×
1144
                                container.SecurityContext = &corev1.SecurityContext{}
×
1145
                        }
×
1146
                        container.SecurityContext.Capabilities = &corev1.Capabilities{
×
1147
                                Drop: []corev1.Capability{
×
1148
                                        "ALL",
×
1149
                                },
×
1150
                        }
×
1151
                        container.SecurityContext.SeccompProfile = &corev1.SeccompProfile{
×
1152
                                Type: corev1.SeccompProfileTypeRuntimeDefault,
×
1153
                        }
×
1154
                        container.SecurityContext.AllowPrivilegeEscalation = ptr.To[bool](false)
×
1155
                        container.SecurityContext.RunAsNonRoot = ptr.To[bool](true)
×
1156
                        container.SecurityContext.RunAsUser = ptr.To[int64](common.QemuSubGid)
×
1157
                        if len(container.VolumeMounts) > 0 {
×
1158
                                hasVolumeMounts = true
×
1159
                        }
×
1160
                }
1161
        }
1162

1163
        if podSpec.SecurityContext == nil {
×
1164
                podSpec.SecurityContext = &corev1.PodSecurityContext{}
×
1165
        }
×
1166
        // Some tools like istio inject containers and thus rely on a pod level seccomp profile being specified
1167
        podSpec.SecurityContext.SeccompProfile = &corev1.SeccompProfile{
×
1168
                Type: corev1.SeccompProfileTypeRuntimeDefault,
×
1169
        }
×
1170
        if hasVolumeMounts {
×
1171
                podSpec.SecurityContext.FSGroup = ptr.To[int64](common.QemuSubGid)
×
1172
        }
×
1173
}
1174

1175
// SetNodeNameIfPopulator sets NodeName in a pod spec when the PVC is being handled by a CDI volume populator
1176
func SetNodeNameIfPopulator(pvc *corev1.PersistentVolumeClaim, podSpec *corev1.PodSpec) {
×
1177
        _, isPopulator := pvc.Annotations[AnnPopulatorKind]
×
1178
        nodeName := pvc.Annotations[AnnSelectedNode]
×
1179
        if isPopulator && nodeName != "" {
×
1180
                podSpec.NodeName = nodeName
×
1181
        }
×
1182
}
1183

1184
// CreatePvc creates PVC
1185
func CreatePvc(name, ns string, annotations, labels map[string]string) *corev1.PersistentVolumeClaim {
1✔
1186
        return CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1✔
1187
}
1✔
1188

1189
// CreatePvcInStorageClass creates PVC with storgae class
1190
func CreatePvcInStorageClass(name, ns string, storageClassName *string, annotations, labels map[string]string, phase corev1.PersistentVolumeClaimPhase) *corev1.PersistentVolumeClaim {
1✔
1191
        pvc := &corev1.PersistentVolumeClaim{
1✔
1192
                ObjectMeta: metav1.ObjectMeta{
1✔
1193
                        Name:        name,
1✔
1194
                        Namespace:   ns,
1✔
1195
                        Annotations: annotations,
1✔
1196
                        Labels:      labels,
1✔
1197
                        UID:         types.UID(ns + "-" + name),
1✔
1198
                },
1✔
1199
                Spec: corev1.PersistentVolumeClaimSpec{
1✔
1200
                        AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadOnlyMany, corev1.ReadWriteOnce},
1✔
1201
                        Resources: corev1.VolumeResourceRequirements{
1✔
1202
                                Requests: corev1.ResourceList{
1✔
1203
                                        corev1.ResourceStorage: resource.MustParse("1G"),
1✔
1204
                                },
1✔
1205
                        },
1✔
1206
                        StorageClassName: storageClassName,
1✔
1207
                },
1✔
1208
                Status: corev1.PersistentVolumeClaimStatus{
1✔
1209
                        Phase: phase,
1✔
1210
                },
1✔
1211
        }
1✔
1212
        pvc.Status.Capacity = pvc.Spec.Resources.Requests.DeepCopy()
1✔
1213
        if pvc.Status.Phase == corev1.ClaimBound {
2✔
1214
                pvc.Spec.VolumeName = "pv-" + string(pvc.UID)
1✔
1215
        }
1✔
1216
        return pvc
1✔
1217
}
1218

1219
// GetAPIServerKey returns API server RSA key
1220
func GetAPIServerKey() *rsa.PrivateKey {
×
1221
        apiServerKeyOnce.Do(func() {
×
1222
                apiServerKey, _ = rsa.GenerateKey(rand.Reader, 2048)
×
1223
        })
×
1224
        return apiServerKey
×
1225
}
1226

1227
// CreateStorageClass creates storage class CR
1228
func CreateStorageClass(name string, annotations map[string]string) *storagev1.StorageClass {
1✔
1229
        return &storagev1.StorageClass{
1✔
1230
                ObjectMeta: metav1.ObjectMeta{
1✔
1231
                        Name:        name,
1✔
1232
                        Annotations: annotations,
1✔
1233
                },
1✔
1234
        }
1✔
1235
}
1✔
1236

1237
// CreateImporterTestPod creates importer test pod CR
1238
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
×
1239
        // importer pod name contains the pvc name
×
1240
        podName := fmt.Sprintf("%s-%s", common.ImporterPodName, pvc.Name)
×
1241

×
1242
        blockOwnerDeletion := true
×
1243
        isController := true
×
1244

×
1245
        volumes := []corev1.Volume{
×
1246
                {
×
1247
                        Name: dvname,
×
1248
                        VolumeSource: corev1.VolumeSource{
×
1249
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1250
                                        ClaimName: pvc.Name,
×
1251
                                        ReadOnly:  false,
×
1252
                                },
×
1253
                        },
×
1254
                },
×
1255
        }
×
1256

×
1257
        if scratchPvc != nil {
×
1258
                volumes = append(volumes, corev1.Volume{
×
1259
                        Name: ScratchVolName,
×
1260
                        VolumeSource: corev1.VolumeSource{
×
1261
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1262
                                        ClaimName: scratchPvc.Name,
×
1263
                                        ReadOnly:  false,
×
1264
                                },
×
1265
                        },
×
1266
                })
×
1267
        }
×
1268

1269
        pod := &corev1.Pod{
×
1270
                TypeMeta: metav1.TypeMeta{
×
1271
                        Kind:       "Pod",
×
1272
                        APIVersion: "v1",
×
1273
                },
×
1274
                ObjectMeta: metav1.ObjectMeta{
×
1275
                        Name:      podName,
×
1276
                        Namespace: pvc.Namespace,
×
1277
                        Annotations: map[string]string{
×
1278
                                AnnCreatedBy: "yes",
×
1279
                        },
×
1280
                        Labels: map[string]string{
×
1281
                                common.CDILabelKey:        common.CDILabelValue,
×
1282
                                common.CDIComponentLabel:  common.ImporterPodName,
×
1283
                                common.PrometheusLabelKey: common.PrometheusLabelValue,
×
1284
                        },
×
1285
                        OwnerReferences: []metav1.OwnerReference{
×
1286
                                {
×
1287
                                        APIVersion:         "v1",
×
1288
                                        Kind:               "PersistentVolumeClaim",
×
1289
                                        Name:               pvc.Name,
×
1290
                                        UID:                pvc.GetUID(),
×
1291
                                        BlockOwnerDeletion: &blockOwnerDeletion,
×
1292
                                        Controller:         &isController,
×
1293
                                },
×
1294
                        },
×
1295
                },
×
1296
                Spec: corev1.PodSpec{
×
1297
                        Containers: []corev1.Container{
×
1298
                                {
×
1299
                                        Name:            common.ImporterPodName,
×
1300
                                        Image:           "test/myimage",
×
1301
                                        ImagePullPolicy: corev1.PullPolicy("Always"),
×
1302
                                        Args:            []string{"-v=5"},
×
1303
                                        Ports: []corev1.ContainerPort{
×
1304
                                                {
×
1305
                                                        Name:          "metrics",
×
1306
                                                        ContainerPort: 8443,
×
1307
                                                        Protocol:      corev1.ProtocolTCP,
×
1308
                                                },
×
1309
                                        },
×
1310
                                },
×
1311
                        },
×
1312
                        RestartPolicy: corev1.RestartPolicyOnFailure,
×
1313
                        Volumes:       volumes,
×
1314
                },
×
1315
        }
×
1316

×
1317
        ep, _ := GetEndpoint(pvc)
×
1318
        source := GetSource(pvc)
×
1319
        contentType := GetPVCContentType(pvc)
×
1320
        imageSize, _ := GetRequestedImageSize(pvc)
×
1321
        volumeMode := GetVolumeMode(pvc)
×
1322

×
1323
        env := []corev1.EnvVar{
×
1324
                {
×
1325
                        Name:  common.ImporterSource,
×
1326
                        Value: source,
×
1327
                },
×
1328
                {
×
1329
                        Name:  common.ImporterEndpoint,
×
1330
                        Value: ep,
×
1331
                },
×
1332
                {
×
1333
                        Name:  common.ImporterContentType,
×
1334
                        Value: string(contentType),
×
1335
                },
×
1336
                {
×
1337
                        Name:  common.ImporterImageSize,
×
1338
                        Value: imageSize,
×
1339
                },
×
1340
                {
×
1341
                        Name:  common.OwnerUID,
×
1342
                        Value: string(pvc.UID),
×
1343
                },
×
1344
                {
×
1345
                        Name:  common.InsecureTLSVar,
×
1346
                        Value: "false",
×
1347
                },
×
1348
        }
×
1349
        pod.Spec.Containers[0].Env = env
×
1350
        if volumeMode == corev1.PersistentVolumeBlock {
×
1351
                pod.Spec.Containers[0].VolumeDevices = AddVolumeDevices()
×
1352
        } else {
×
1353
                pod.Spec.Containers[0].VolumeMounts = AddImportVolumeMounts()
×
1354
        }
×
1355

1356
        if scratchPvc != nil {
×
1357
                pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
×
1358
                        Name:      ScratchVolName,
×
1359
                        MountPath: common.ScratchDataDir,
×
1360
                })
×
1361
        }
×
1362

1363
        return pod
×
1364
}
1365

1366
// CreateStorageClassWithProvisioner creates CR of storage class with provisioner
1367
func CreateStorageClassWithProvisioner(name string, annotations, labels map[string]string, provisioner string) *storagev1.StorageClass {
×
1368
        return &storagev1.StorageClass{
×
1369
                Provisioner: provisioner,
×
1370
                ObjectMeta: metav1.ObjectMeta{
×
1371
                        Name:        name,
×
1372
                        Annotations: annotations,
×
1373
                        Labels:      labels,
×
1374
                },
×
1375
        }
×
1376
}
×
1377

1378
// CreateClient creates a fake client
1379
func CreateClient(objs ...runtime.Object) client.Client {
1✔
1380
        s := scheme.Scheme
1✔
1381
        _ = cdiv1.AddToScheme(s)
1✔
1382
        _ = corev1.AddToScheme(s)
1✔
1383
        _ = storagev1.AddToScheme(s)
1✔
1384
        _ = ocpconfigv1.Install(s)
1✔
1385

1✔
1386
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1387
}
1✔
1388

1389
// ErrQuotaExceeded checked is the error is of exceeded quota
1390
func ErrQuotaExceeded(err error) bool {
×
1391
        return strings.Contains(err.Error(), "exceeded quota:")
×
1392
}
×
1393

1394
// GetContentType returns the content type. If invalid or not set, default to kubevirt
1395
func GetContentType(contentType cdiv1.DataVolumeContentType) cdiv1.DataVolumeContentType {
1✔
1396
        switch contentType {
1✔
1397
        case
1398
                cdiv1.DataVolumeKubeVirt,
1399
                cdiv1.DataVolumeArchive:
1✔
1400
        default:
×
1401
                // TODO - shouldn't archive be the default?
×
1402
                contentType = cdiv1.DataVolumeKubeVirt
×
1403
        }
1404
        return contentType
1✔
1405
}
1406

1407
// GetPVCContentType returns the content type of the source image. If invalid or not set, default to kubevirt
1408
func GetPVCContentType(pvc *corev1.PersistentVolumeClaim) cdiv1.DataVolumeContentType {
×
1409
        contentType, found := pvc.Annotations[AnnContentType]
×
1410
        if !found {
×
1411
                // TODO - shouldn't archive be the default?
×
1412
                return cdiv1.DataVolumeKubeVirt
×
1413
        }
×
1414

1415
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1416
}
1417

1418
// GetNamespace returns the given namespace if not empty, otherwise the default namespace
1419
func GetNamespace(namespace, defaultNamespace string) string {
×
1420
        if namespace == "" {
×
1421
                return defaultNamespace
×
1422
        }
×
1423
        return namespace
×
1424
}
1425

1426
// IsErrCacheNotStarted checked is the error is of cache not started
1427
func IsErrCacheNotStarted(err error) bool {
×
1428
        target := &runtimecache.ErrCacheNotStarted{}
×
1429
        return errors.As(err, &target)
×
1430
}
×
1431

1432
// NewImportDataVolume returns new import DataVolume CR
1433
func NewImportDataVolume(name string) *cdiv1.DataVolume {
×
1434
        return &cdiv1.DataVolume{
×
1435
                TypeMeta: metav1.TypeMeta{APIVersion: cdiv1.SchemeGroupVersion.String()},
×
1436
                ObjectMeta: metav1.ObjectMeta{
×
1437
                        Name:      name,
×
1438
                        Namespace: metav1.NamespaceDefault,
×
1439
                        UID:       types.UID(metav1.NamespaceDefault + "-" + name),
×
1440
                },
×
1441
                Spec: cdiv1.DataVolumeSpec{
×
1442
                        Source: &cdiv1.DataVolumeSource{
×
1443
                                HTTP: &cdiv1.DataVolumeSourceHTTP{
×
1444
                                        URL: "http://example.com/data",
×
1445
                                },
×
1446
                        },
×
1447
                        PVC: &corev1.PersistentVolumeClaimSpec{
×
1448
                                AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
×
1449
                        },
×
1450
                        PriorityClassName: "p0",
×
1451
                },
×
1452
        }
×
1453
}
×
1454

1455
// GetCloneSourceInfo returns the type, name and namespace of the cloning source
1456
func GetCloneSourceInfo(dv *cdiv1.DataVolume) (sourceType, sourceName, sourceNamespace string) {
×
1457
        // Cloning sources are mutually exclusive
×
1458
        if dv.Spec.Source.PVC != nil {
×
1459
                return "pvc", dv.Spec.Source.PVC.Name, dv.Spec.Source.PVC.Namespace
×
1460
        }
×
1461

1462
        if dv.Spec.Source.Snapshot != nil {
×
1463
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
×
1464
        }
×
1465

1466
        return "", "", ""
×
1467
}
1468

1469
// IsWaitForFirstConsumerEnabled tells us if we should respect "real" WFFC behavior or just let our worker pods randomly spawn
1470
func IsWaitForFirstConsumerEnabled(obj metav1.Object, gates featuregates.FeatureGates) (bool, error) {
×
1471
        // when PVC requests immediateBinding it cannot honor wffc logic
×
1472
        isImmediateBindingRequested := ImmediateBindingRequested(obj)
×
1473
        pvcHonorWaitForFirstConsumer := !isImmediateBindingRequested
×
1474
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
×
1475
        if err != nil {
×
1476
                return false, err
×
1477
        }
×
1478

1479
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1480
}
1481

1482
// AddImmediateBindingAnnotationIfWFFCDisabled adds the immediateBinding annotation if wffc feature gate is disabled
1483
func AddImmediateBindingAnnotationIfWFFCDisabled(obj metav1.Object, gates featuregates.FeatureGates) error {
×
1484
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
×
1485
        if err != nil {
×
1486
                return err
×
1487
        }
×
1488
        if !globalHonorWaitForFirstConsumer {
×
1489
                AddAnnotation(obj, AnnImmediateBinding, "")
×
1490
        }
×
1491
        return nil
×
1492
}
1493

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

×
1498
        if util.ResolveVolumeMode(pvcSpec.VolumeMode) == corev1.PersistentVolumeFilesystem {
×
1499
                fsOverhead, err := GetFilesystemOverheadForStorageClass(ctx, c, pvcSpec.StorageClassName)
×
1500
                if err != nil {
×
1501
                        return resource.Quantity{}, err
×
1502
                }
×
1503
                // Parse filesystem overhead (percentage) into a 64-bit float
1504
                fsOverheadFloat, _ := strconv.ParseFloat(string(fsOverhead), 64)
×
1505

×
1506
                // Merge the previous values into a 'resource.Quantity' struct
×
NEW
1507
                requiredSpace := util.GetRequiredSpace(fsOverheadFloat, imgSize)
×
1508
                returnSize = *resource.NewScaledQuantity(requiredSpace, 0)
×
1509
        } else {
×
1510
                // Inflation is not needed with 'Block' mode
×
1511
                returnSize = *resource.NewScaledQuantity(imgSize, 0)
×
1512
        }
×
1513

1514
        return returnSize, nil
×
1515
}
1516

1517
// IsBound returns if the pvc is bound
1518
func IsBound(pvc *corev1.PersistentVolumeClaim) bool {
×
1519
        return pvc != nil && pvc.Status.Phase == corev1.ClaimBound
×
1520
}
×
1521

1522
// IsUnbound returns if the pvc is not bound yet
1523
func IsUnbound(pvc *corev1.PersistentVolumeClaim) bool {
×
1524
        return !IsBound(pvc)
×
1525
}
×
1526

1527
// IsLost returns if the pvc is lost
1528
func IsLost(pvc *corev1.PersistentVolumeClaim) bool {
×
1529
        return pvc != nil && pvc.Status.Phase == corev1.ClaimLost
×
1530
}
×
1531

1532
// IsImageStream returns true if registry source is ImageStream
1533
func IsImageStream(pvc *corev1.PersistentVolumeClaim) bool {
×
1534
        return pvc.Annotations[AnnRegistryImageStream] == "true"
×
1535
}
×
1536

1537
// ShouldIgnorePod checks if a pod should be ignored.
1538
// If this is a completed pod that was used for one checkpoint of a multi-stage import, it
1539
// should be ignored by pod lookups as long as the retainAfterCompletion annotation is set.
1540
func ShouldIgnorePod(pod *corev1.Pod, pvc *corev1.PersistentVolumeClaim) bool {
×
1541
        retain := pvc.ObjectMeta.Annotations[AnnPodRetainAfterCompletion]
×
1542
        checkpoint := pvc.ObjectMeta.Annotations[AnnCurrentCheckpoint]
×
1543
        if checkpoint != "" && pod.Status.Phase == corev1.PodSucceeded {
×
1544
                return retain == "true"
×
1545
        }
×
1546
        return false
×
1547
}
1548

1549
// BuildHTTPClient generates an http client that accepts any certificate, since we are using
1550
// it to get prometheus data it doesn't matter if someone can intercept the data. Once we have
1551
// a mechanism to properly sign the server, we can update this method to get a proper client.
1552
func BuildHTTPClient(httpClient *http.Client) *http.Client {
×
1553
        if httpClient == nil {
×
1554
                defaultTransport := http.DefaultTransport.(*http.Transport)
×
1555
                // Create new Transport that ignores self-signed SSL
×
1556
                //nolint:gosec
×
1557
                tr := &http.Transport{
×
1558
                        Proxy:                 defaultTransport.Proxy,
×
1559
                        DialContext:           defaultTransport.DialContext,
×
1560
                        MaxIdleConns:          defaultTransport.MaxIdleConns,
×
1561
                        IdleConnTimeout:       defaultTransport.IdleConnTimeout,
×
1562
                        ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
×
1563
                        TLSHandshakeTimeout:   defaultTransport.TLSHandshakeTimeout,
×
1564
                        TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
×
1565
                }
×
1566
                httpClient = &http.Client{
×
1567
                        Transport: tr,
×
1568
                }
×
1569
        }
×
1570
        return httpClient
×
1571
}
1572

1573
// ErrConnectionRefused checks for connection refused errors
1574
func ErrConnectionRefused(err error) bool {
×
1575
        return strings.Contains(err.Error(), "connection refused")
×
1576
}
×
1577

1578
// GetPodMetricsPort returns, if exists, the metrics port from the passed pod
1579
func GetPodMetricsPort(pod *corev1.Pod) (int, error) {
1✔
1580
        for _, container := range pod.Spec.Containers {
2✔
1581
                for _, port := range container.Ports {
2✔
1582
                        if port.Name == "metrics" {
2✔
1583
                                return int(port.ContainerPort), nil
1✔
1584
                        }
1✔
1585
                }
1586
        }
1587
        return 0, errors.New("Metrics port not found in pod")
1✔
1588
}
1589

1590
// GetMetricsURL builds the metrics URL according to the specified pod
1591
func GetMetricsURL(pod *corev1.Pod) (string, error) {
1✔
1592
        if pod == nil {
1✔
1593
                return "", nil
×
1594
        }
×
1595
        port, err := GetPodMetricsPort(pod)
1✔
1596
        if err != nil || pod.Status.PodIP == "" {
2✔
1597
                return "", err
1✔
1598
        }
1✔
1599
        domain := net.JoinHostPort(pod.Status.PodIP, fmt.Sprint(port))
1✔
1600
        url := fmt.Sprintf("https://%s/metrics", domain)
1✔
1601
        return url, nil
1✔
1602
}
1603

1604
// GetProgressReportFromURL fetches the progress report from the passed URL according to an specific metric expression and ownerUID
1605
func GetProgressReportFromURL(ctx context.Context, url string, httpClient *http.Client, metricExp, ownerUID string) (string, error) {
×
1606
        regExp := regexp.MustCompile(fmt.Sprintf("(%s)\\{ownerUID\\=%q\\} (\\d{1,3}\\.?\\d*)", metricExp, ownerUID))
×
1607
        // pod could be gone, don't block an entire thread for 30 seconds
×
1608
        // just to get back an i/o timeout
×
1609
        ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
×
1610
        defer cancel()
×
1611
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
×
1612
        if err != nil {
×
1613
                return "", err
×
1614
        }
×
1615
        resp, err := httpClient.Do(req)
×
1616
        if err != nil {
×
1617
                if ErrConnectionRefused(err) {
×
1618
                        return "", nil
×
1619
                }
×
1620
                return "", err
×
1621
        }
1622
        defer resp.Body.Close()
×
1623
        body, err := io.ReadAll(resp.Body)
×
1624
        if err != nil {
×
1625
                return "", err
×
1626
        }
×
1627

1628
        // Parse the progress from the body
1629
        progressReport := ""
×
1630
        match := regExp.FindStringSubmatch(string(body))
×
1631
        if match != nil {
×
1632
                progressReport = match[len(match)-1]
×
1633
        }
×
1634
        return progressReport, nil
×
1635
}
1636

1637
// UpdateHTTPAnnotations updates the passed annotations for proper http import
1638
func UpdateHTTPAnnotations(annotations map[string]string, http *cdiv1.DataVolumeSourceHTTP) {
×
1639
        annotations[AnnEndpoint] = http.URL
×
1640
        annotations[AnnSource] = SourceHTTP
×
1641

×
1642
        if http.SecretRef != "" {
×
1643
                annotations[AnnSecret] = http.SecretRef
×
1644
        }
×
1645
        if http.CertConfigMap != "" {
×
1646
                annotations[AnnCertConfigMap] = http.CertConfigMap
×
1647
        }
×
1648
        for index, header := range http.ExtraHeaders {
×
1649
                annotations[fmt.Sprintf("%s.%d", AnnExtraHeaders, index)] = header
×
1650
        }
×
1651
        for index, header := range http.SecretExtraHeaders {
×
1652
                annotations[fmt.Sprintf("%s.%d", AnnSecretExtraHeaders, index)] = header
×
1653
        }
×
1654
}
1655

1656
// UpdateS3Annotations updates the passed annotations for proper S3 import
1657
func UpdateS3Annotations(annotations map[string]string, s3 *cdiv1.DataVolumeSourceS3) {
×
1658
        annotations[AnnEndpoint] = s3.URL
×
1659
        annotations[AnnSource] = SourceS3
×
1660
        if s3.SecretRef != "" {
×
1661
                annotations[AnnSecret] = s3.SecretRef
×
1662
        }
×
1663
        if s3.CertConfigMap != "" {
×
1664
                annotations[AnnCertConfigMap] = s3.CertConfigMap
×
1665
        }
×
1666
}
1667

1668
// UpdateGCSAnnotations updates the passed annotations for proper GCS import
1669
func UpdateGCSAnnotations(annotations map[string]string, gcs *cdiv1.DataVolumeSourceGCS) {
×
1670
        annotations[AnnEndpoint] = gcs.URL
×
1671
        annotations[AnnSource] = SourceGCS
×
1672
        if gcs.SecretRef != "" {
×
1673
                annotations[AnnSecret] = gcs.SecretRef
×
1674
        }
×
1675
}
1676

1677
// UpdateRegistryAnnotations updates the passed annotations for proper registry import
1678
func UpdateRegistryAnnotations(annotations map[string]string, registry *cdiv1.DataVolumeSourceRegistry) {
×
1679
        annotations[AnnSource] = SourceRegistry
×
1680
        pullMethod := registry.PullMethod
×
1681
        if pullMethod != nil && *pullMethod != "" {
×
1682
                annotations[AnnRegistryImportMethod] = string(*pullMethod)
×
1683
        }
×
1684
        url := registry.URL
×
1685
        if url != nil && *url != "" {
×
1686
                annotations[AnnEndpoint] = *url
×
1687
        } else {
×
1688
                imageStream := registry.ImageStream
×
1689
                if imageStream != nil && *imageStream != "" {
×
1690
                        annotations[AnnEndpoint] = *imageStream
×
1691
                        annotations[AnnRegistryImageStream] = "true"
×
1692
                }
×
1693
        }
1694
        secretRef := registry.SecretRef
×
1695
        if secretRef != nil && *secretRef != "" {
×
1696
                annotations[AnnSecret] = *secretRef
×
1697
        }
×
1698
        certConfigMap := registry.CertConfigMap
×
1699
        if certConfigMap != nil && *certConfigMap != "" {
×
1700
                annotations[AnnCertConfigMap] = *certConfigMap
×
1701
        }
×
1702

1703
        if registry.Platform != nil && registry.Platform.Architecture != "" {
×
1704
                annotations[AnnRegistryImageArchitecture] = registry.Platform.Architecture
×
1705
        }
×
1706
}
1707

1708
// UpdateVDDKAnnotations updates the passed annotations for proper VDDK import
1709
func UpdateVDDKAnnotations(annotations map[string]string, vddk *cdiv1.DataVolumeSourceVDDK) {
×
1710
        annotations[AnnEndpoint] = vddk.URL
×
1711
        annotations[AnnSource] = SourceVDDK
×
1712
        annotations[AnnSecret] = vddk.SecretRef
×
1713
        annotations[AnnBackingFile] = vddk.BackingFile
×
1714
        annotations[AnnUUID] = vddk.UUID
×
1715
        annotations[AnnThumbprint] = vddk.Thumbprint
×
1716
        if vddk.InitImageURL != "" {
×
1717
                annotations[AnnVddkInitImageURL] = vddk.InitImageURL
×
1718
        }
×
1719
        if vddk.ExtraArgs != "" {
×
1720
                annotations[AnnVddkExtraArgs] = vddk.ExtraArgs
×
1721
        }
×
1722
}
1723

1724
// UpdateImageIOAnnotations updates the passed annotations for proper imageIO import
1725
func UpdateImageIOAnnotations(annotations map[string]string, imageio *cdiv1.DataVolumeSourceImageIO) {
×
1726
        annotations[AnnEndpoint] = imageio.URL
×
1727
        annotations[AnnSource] = SourceImageio
×
1728
        annotations[AnnSecret] = imageio.SecretRef
×
1729
        annotations[AnnCertConfigMap] = imageio.CertConfigMap
×
1730
        annotations[AnnDiskID] = imageio.DiskID
×
1731
}
×
1732

1733
// IsPVBoundToPVC checks if a PV is bound to a specific PVC
1734
func IsPVBoundToPVC(pv *corev1.PersistentVolume, pvc *corev1.PersistentVolumeClaim) bool {
1✔
1735
        claimRef := pv.Spec.ClaimRef
1✔
1736
        return claimRef != nil && claimRef.Name == pvc.Name && claimRef.Namespace == pvc.Namespace && claimRef.UID == pvc.UID
1✔
1737
}
1✔
1738

1739
// Rebind binds the PV of source to target
1740
func Rebind(ctx context.Context, c client.Client, source, target *corev1.PersistentVolumeClaim) error {
1✔
1741
        pv := &corev1.PersistentVolume{
1✔
1742
                ObjectMeta: metav1.ObjectMeta{
1✔
1743
                        Name: source.Spec.VolumeName,
1✔
1744
                },
1✔
1745
        }
1✔
1746

1✔
1747
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
2✔
1748
                return err
1✔
1749
        }
1✔
1750

1751
        // Examine the claimref for the PV and see if it's still bound to PVC'
1752
        if pv.Spec.ClaimRef == nil {
1✔
1753
                return fmt.Errorf("PV %s claimRef is nil", pv.Name)
×
1754
        }
×
1755

1756
        if !IsPVBoundToPVC(pv, source) {
2✔
1757
                // Something is not right if the PV is neither bound to PVC' nor target PVC
1✔
1758
                if !IsPVBoundToPVC(pv, target) {
2✔
1759
                        klog.Errorf("PV bound to unexpected PVC: Could not rebind to target PVC '%s'", target.Name)
1✔
1760
                        return fmt.Errorf("PV %s bound to unexpected claim %s", pv.Name, pv.Spec.ClaimRef.Name)
1✔
1761
                }
1✔
1762
                // our work is done
1763
                return nil
1✔
1764
        }
1765

1766
        // Rebind PVC to target PVC
1767
        pv.Spec.ClaimRef = &corev1.ObjectReference{
1✔
1768
                Namespace:       target.Namespace,
1✔
1769
                Name:            target.Name,
1✔
1770
                UID:             target.UID,
1✔
1771
                ResourceVersion: target.ResourceVersion,
1✔
1772
        }
1✔
1773
        klog.V(3).Info("Rebinding PV to target PVC", "PVC", target.Name)
1✔
1774
        if err := c.Update(context.TODO(), pv); err != nil {
1✔
1775
                return err
×
1776
        }
×
1777

1778
        return nil
1✔
1779
}
1780

1781
// BulkDeleteResources deletes a bunch of resources
1782
func BulkDeleteResources(ctx context.Context, c client.Client, obj client.ObjectList, lo client.ListOption) error {
×
1783
        if err := c.List(ctx, obj, lo); err != nil {
×
1784
                if meta.IsNoMatchError(err) {
×
1785
                        return nil
×
1786
                }
×
1787
                return err
×
1788
        }
1789

1790
        sv := reflect.ValueOf(obj).Elem()
×
1791
        iv := sv.FieldByName("Items")
×
1792

×
1793
        for i := 0; i < iv.Len(); i++ {
×
1794
                obj := iv.Index(i).Addr().Interface().(client.Object)
×
1795
                if obj.GetDeletionTimestamp().IsZero() {
×
1796
                        klog.V(3).Infof("Deleting type %+v %+v", reflect.TypeOf(obj), obj)
×
1797
                        if err := c.Delete(ctx, obj); err != nil {
×
1798
                                return err
×
1799
                        }
×
1800
                }
1801
        }
1802

1803
        return nil
×
1804
}
1805

1806
// ValidateSnapshotCloneSize does proper size validation when doing a clone from snapshot operation
1807
func ValidateSnapshotCloneSize(snapshot *snapshotv1.VolumeSnapshot, pvcSpec *corev1.PersistentVolumeClaimSpec, targetSC *storagev1.StorageClass, log logr.Logger) (bool, error) {
×
1808
        restoreSize := snapshot.Status.RestoreSize
×
1809
        if restoreSize == nil {
×
1810
                return false, fmt.Errorf("snapshot has no RestoreSize")
×
1811
        }
×
1812
        targetRequest, hasTargetRequest := pvcSpec.Resources.Requests[corev1.ResourceStorage]
×
1813
        allowExpansion := targetSC.AllowVolumeExpansion != nil && *targetSC.AllowVolumeExpansion
×
1814
        if hasTargetRequest {
×
1815
                // otherwise will just use restoreSize
×
1816
                if restoreSize.Cmp(targetRequest) < 0 && !allowExpansion {
×
1817
                        log.V(3).Info("Can't expand restored PVC because SC does not allow expansion, need to fall back to host assisted")
×
1818
                        return false, nil
×
1819
                }
×
1820
        }
1821
        return true, nil
×
1822
}
1823

1824
// ValidateSnapshotCloneProvisioners validates the target PVC storage class against the snapshot class provisioner
1825
func ValidateSnapshotCloneProvisioners(vsc *snapshotv1.VolumeSnapshotContent, storageClass *storagev1.StorageClass) (bool, error) {
×
1826
        // Do snapshot and storage class validation
×
1827
        if storageClass == nil {
×
1828
                return false, fmt.Errorf("target storage class not found")
×
1829
        }
×
1830
        if storageClass.Provisioner != vsc.Spec.Driver {
×
1831
                return false, nil
×
1832
        }
×
1833
        // TODO: get sourceVolumeMode from volumesnapshotcontent and validate against target spec
1834
        // currently don't have CRDs in CI with sourceVolumeMode which is pretty new
1835
        // converting volume mode is possible but has security implications
1836
        return true, nil
×
1837
}
1838

1839
// GetSnapshotClassForSmartClone looks up the snapshot class based on the storage class
1840
func GetSnapshotClassForSmartClone(pvc *corev1.PersistentVolumeClaim, targetPvcStorageClassName, snapshotClassName *string, log logr.Logger, client client.Client, recorder record.EventRecorder) (string, error) {
×
1841
        logger := log.WithName("GetSnapshotClassForSmartClone").V(3)
×
1842
        // Check if relevant CRDs are available
×
1843
        if !isCsiCrdsDeployed(client, log) {
×
1844
                logger.Info("Missing CSI snapshotter CRDs, falling back to host assisted clone")
×
1845
                return "", nil
×
1846
        }
×
1847

1848
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(context.TODO(), client, targetPvcStorageClassName)
×
1849
        if err != nil {
×
1850
                return "", err
×
1851
        }
×
1852
        if targetStorageClass == nil {
×
1853
                logger.Info("Target PVC's Storage Class not found")
×
1854
                return "", nil
×
1855
        }
×
1856

1857
        vscName, err := GetVolumeSnapshotClass(context.TODO(), client, pvc, targetStorageClass.Provisioner, snapshotClassName, logger, recorder)
×
1858
        if err != nil {
×
1859
                return "", err
×
1860
        }
×
1861
        if vscName != nil {
×
1862
                if pvc != nil {
×
1863
                        logger.Info("smart-clone is applicable for datavolume", "datavolume",
×
1864
                                pvc.Name, "snapshot class", *vscName)
×
1865
                }
×
1866
                return *vscName, nil
×
1867
        }
1868

1869
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
1870
        return "", nil
×
1871
}
1872

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

×
1878
        logEvent := func(message, vscName string) {
×
1879
                logger.Info(message, "name", vscName)
×
1880
                if pvc != nil {
×
1881
                        msg := fmt.Sprintf("%s %s", message, vscName)
×
1882
                        recorder.Event(pvc, corev1.EventTypeNormal, VolumeSnapshotClassSelected, msg)
×
1883
                }
×
1884
        }
1885

1886
        if snapshotClassName != nil {
×
1887
                vsc := &snapshotv1.VolumeSnapshotClass{}
×
1888
                if err := c.Get(context.TODO(), types.NamespacedName{Name: *snapshotClassName}, vsc); err != nil {
×
1889
                        return nil, err
×
1890
                }
×
1891
                if vsc.Driver == driver {
×
1892
                        logEvent(MessageStorageProfileVolumeSnapshotClassSelected, vsc.Name)
×
1893
                        return snapshotClassName, nil
×
1894
                }
×
1895
                return nil, nil
×
1896
        }
1897

1898
        vscList := &snapshotv1.VolumeSnapshotClassList{}
×
1899
        if err := c.List(ctx, vscList); err != nil {
×
1900
                if meta.IsNoMatchError(err) {
×
1901
                        return nil, nil
×
1902
                }
×
1903
                return nil, err
×
1904
        }
1905

1906
        var candidates []string
×
1907
        for _, vsc := range vscList.Items {
×
1908
                if vsc.Driver == driver {
×
1909
                        if vsc.Annotations[AnnDefaultSnapshotClass] == "true" {
×
1910
                                logEvent(MessageDefaultVolumeSnapshotClassSelected, vsc.Name)
×
1911
                                vscName := vsc.Name
×
1912
                                return &vscName, nil
×
1913
                        }
×
1914
                        candidates = append(candidates, vsc.Name)
×
1915
                }
1916
        }
1917

1918
        if len(candidates) > 0 {
×
1919
                sort.Strings(candidates)
×
1920
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
×
1921
                return &candidates[0], nil
×
1922
        }
×
1923

1924
        return nil, nil
×
1925
}
1926

1927
// isCsiCrdsDeployed checks whether the CSI snapshotter CRD are deployed
1928
func isCsiCrdsDeployed(c client.Client, log logr.Logger) bool {
×
1929
        version := "v1"
×
1930
        vsClass := "volumesnapshotclasses." + snapshotv1.GroupName
×
1931
        vsContent := "volumesnapshotcontents." + snapshotv1.GroupName
×
1932
        vs := "volumesnapshots." + snapshotv1.GroupName
×
1933

×
1934
        return isCrdDeployed(c, vsClass, version, log) &&
×
1935
                isCrdDeployed(c, vsContent, version, log) &&
×
1936
                isCrdDeployed(c, vs, version, log)
×
1937
}
×
1938

1939
// isCrdDeployed checks whether a CRD is deployed
1940
func isCrdDeployed(c client.Client, name, version string, log logr.Logger) bool {
×
1941
        crd := &extv1.CustomResourceDefinition{}
×
1942
        err := c.Get(context.TODO(), types.NamespacedName{Name: name}, crd)
×
1943
        if err != nil {
×
1944
                if !k8serrors.IsNotFound(err) {
×
1945
                        log.Info("Error looking up CRD", "crd name", name, "version", version, "error", err)
×
1946
                }
×
1947
                return false
×
1948
        }
1949

1950
        for _, v := range crd.Spec.Versions {
×
1951
                if v.Name == version && v.Served {
×
1952
                        return true
×
1953
                }
×
1954
        }
1955

1956
        return false
×
1957
}
1958

1959
// IsSnapshotReady indicates if a volume snapshot is ready to be used
1960
func IsSnapshotReady(snapshot *snapshotv1.VolumeSnapshot) bool {
×
1961
        return snapshot.Status != nil && snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse
×
1962
}
×
1963

1964
// GetResource updates given obj with the data of the object with the same name and namespace
1965
func GetResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
×
1966
        obj.SetNamespace(namespace)
×
1967
        obj.SetName(name)
×
1968

×
1969
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
1970
        if err != nil {
×
1971
                if k8serrors.IsNotFound(err) {
×
1972
                        return false, nil
×
1973
                }
×
1974

1975
                return false, err
×
1976
        }
1977

1978
        return true, nil
×
1979
}
1980

1981
// PatchArgs are the args for Patch
1982
type PatchArgs struct {
1983
        Client client.Client
1984
        Log    logr.Logger
1985
        Obj    client.Object
1986
        OldObj client.Object
1987
}
1988

1989
// GetAnnotatedEventSource returns resource referenced by AnnEventSource annotations
1990
func GetAnnotatedEventSource(ctx context.Context, c client.Client, obj client.Object) (client.Object, error) {
×
1991
        esk, ok := obj.GetAnnotations()[AnnEventSourceKind]
×
1992
        if !ok {
×
1993
                return obj, nil
×
1994
        }
×
1995
        if esk != "PersistentVolumeClaim" {
×
1996
                return obj, nil
×
1997
        }
×
1998
        es, ok := obj.GetAnnotations()[AnnEventSource]
×
1999
        if !ok {
×
2000
                return obj, nil
×
2001
        }
×
2002
        namespace, name, err := cache.SplitMetaNamespaceKey(es)
×
2003
        if err != nil {
×
2004
                return nil, err
×
2005
        }
×
2006
        pvc := &corev1.PersistentVolumeClaim{
×
2007
                ObjectMeta: metav1.ObjectMeta{
×
2008
                        Namespace: namespace,
×
2009
                        Name:      name,
×
2010
                },
×
2011
        }
×
2012
        if err := c.Get(ctx, client.ObjectKeyFromObject(pvc), pvc); err != nil {
×
2013
                return nil, err
×
2014
        }
×
2015
        return pvc, nil
×
2016
}
2017

2018
// OwnedByDataVolume returns true if the object is owned by a DataVolume
2019
func OwnedByDataVolume(obj metav1.Object) bool {
×
2020
        owner := metav1.GetControllerOf(obj)
×
2021
        return owner != nil && owner.Kind == "DataVolume"
×
2022
}
×
2023

2024
// CopyAllowedAnnotations copies the allowed annotations from the source object
2025
// to the destination object
2026
func CopyAllowedAnnotations(srcObj, dstObj metav1.Object) {
×
2027
        for ann, def := range allowedAnnotations {
×
2028
                val, ok := srcObj.GetAnnotations()[ann]
×
2029
                if !ok && def != "" {
×
2030
                        val = def
×
2031
                }
×
2032
                if val != "" {
×
2033
                        klog.V(1).Info("Applying annotation", "Name", dstObj.GetName(), ann, val)
×
2034
                        AddAnnotation(dstObj, ann, val)
×
2035
                }
×
2036
        }
2037
}
2038

2039
// CopyAllowedLabels copies allowed labels matching the validLabelsMatch regexp from the
2040
// source map to the destination object allowing overwrites
2041
func CopyAllowedLabels(srcLabels map[string]string, dstObj metav1.Object, overwrite bool) {
1✔
2042
        for label, value := range srcLabels {
2✔
2043
                if _, found := dstObj.GetLabels()[label]; (!found || overwrite) && validLabelsMatch.MatchString(label) {
2✔
2044
                        AddLabel(dstObj, label, value)
1✔
2045
                }
1✔
2046
        }
2047
}
2048

2049
// ClaimMayExistBeforeDataVolume returns true if the PVC may exist before the DataVolume
2050
func ClaimMayExistBeforeDataVolume(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2051
        if ClaimIsPopulatedForDataVolume(pvc, dv) {
×
2052
                return true, nil
×
2053
        }
×
2054
        return AllowClaimAdoption(c, pvc, dv)
×
2055
}
2056

2057
// ClaimIsPopulatedForDataVolume returns true if the PVC is populated for the given DataVolume
2058
func ClaimIsPopulatedForDataVolume(pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) bool {
×
2059
        return pvc != nil && dv != nil && pvc.Annotations[AnnPopulatedFor] == dv.Name
×
2060
}
×
2061

2062
// AllowClaimAdoption returns true if the PVC may be adopted
2063
func AllowClaimAdoption(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2064
        if pvc == nil || dv == nil {
×
2065
                return false, nil
×
2066
        }
×
2067
        anno, ok := pvc.Annotations[AnnCreatedForDataVolume]
×
2068
        if ok && anno == string(dv.UID) {
×
2069
                return false, nil
×
2070
        }
×
2071
        anno, ok = dv.Annotations[AnnAllowClaimAdoption]
×
2072
        // if annotation exists, go with that regardless of featuregate
×
2073
        if ok {
×
2074
                val, _ := strconv.ParseBool(anno)
×
2075
                return val, nil
×
2076
        }
×
2077
        return featuregates.NewFeatureGates(c).ClaimAdoptionEnabled()
×
2078
}
2079

2080
// ResolveDataSourceChain resolves a DataSource reference.
2081
// Returns an error if DataSource reference is not found or
2082
// DataSource reference points to another DataSource
2083
func ResolveDataSourceChain(ctx context.Context, client client.Client, dataSource *cdiv1.DataSource) (*cdiv1.DataSource, error) {
×
2084
        if dataSource.Spec.Source.DataSource == nil {
×
2085
                return dataSource, nil
×
2086
        }
×
2087

2088
        ref := dataSource.Spec.Source.DataSource
×
2089
        refNs := GetNamespace(ref.Namespace, dataSource.Namespace)
×
2090
        if ref.Name == dataSource.Name && refNs == dataSource.Namespace {
×
2091
                return nil, ErrDataSourceSelfReference
×
2092
        }
×
2093
        resolved := &cdiv1.DataSource{}
×
2094
        if err := client.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: refNs}, resolved); err != nil {
×
2095
                return nil, err
×
2096
        }
×
2097

2098
        if resolved.Spec.Source.DataSource != nil {
×
2099
                return nil, ErrDataSourceMaxDepthReached
×
2100
        }
×
2101

2102
        return resolved, nil
×
2103
}
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