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

kubevirt / containerized-data-importer / #5400

24 Jun 2025 07:02AM UTC coverage: 59.418% (+0.003%) from 59.415%
#5400

Pull #3760

travis-ci

Acedus
testing: test pullMethod: node multi-arch import flake

Signed-off-by: Adi Aloni <aaloni@redhat.com>
Pull Request #3760: VEP48: Introduce DataSource Source DataSource (DataSource Pointers)

27 of 59 new or added lines in 5 files covered. (45.76%)

83 existing lines in 2 files now uncovered.

16980 of 28577 relevant lines covered (59.42%)

0.66 hits per line

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

13.93
/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
        "math"
27
        "net"
28
        "net/http"
29
        "reflect"
30
        "regexp"
31
        "sort"
32
        "strconv"
33
        "strings"
34
        "sync"
35
        "time"
36

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

275
        cloneTokenLeeway = 10 * time.Second
276

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

516
        return nil
×
517
}
518

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

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

537
        return storageClass, nil
×
538
}
539

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

773
        return pods, nil
×
774
}
775

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

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

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

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

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

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

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

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

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

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

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

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

841
        return cdiconfig.Status.Preallocation
×
842
}
843

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

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

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

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

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

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

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

883
        obj.SetFinalizers(finalizers)
×
884
}
885

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

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

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

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

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

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

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

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

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

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

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

945
        return ""
×
946
}
947

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

953
        return "persistentvolumeclaims"
×
954
}
955

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

963
        return sourcePvc.Name
×
964
}
965

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

978
        return nil
×
979
}
980

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

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

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

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

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

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

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

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

1050
        return err
×
1051
}
1052

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1364
        return pod
×
1365
}
1366

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

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

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

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

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

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

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

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

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

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

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

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

1467
        return "", "", ""
×
1468
}
1469

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

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

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

1495
// GetRequiredSpace calculates space required taking file system overhead into account
1496
func GetRequiredSpace(filesystemOverhead float64, requestedSpace int64) int64 {
×
1497
        // the `image` has to be aligned correctly, so the space requested has to be aligned to
×
1498
        // next value that is a multiple of a block size
×
1499
        alignedSize := util.RoundUp(requestedSpace, util.DefaultAlignBlockSize)
×
1500

×
1501
        // count overhead as a percentage of the whole/new size, including aligned image
×
1502
        // and the space required by filesystem metadata
×
1503
        spaceWithOverhead := int64(math.Ceil(float64(alignedSize) / (1 - filesystemOverhead)))
×
1504
        return spaceWithOverhead
×
1505
}
×
1506

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

×
1511
        if util.ResolveVolumeMode(pvcSpec.VolumeMode) == corev1.PersistentVolumeFilesystem {
×
1512
                fsOverhead, err := GetFilesystemOverheadForStorageClass(ctx, c, pvcSpec.StorageClassName)
×
1513
                if err != nil {
×
1514
                        return resource.Quantity{}, err
×
1515
                }
×
1516
                // Parse filesystem overhead (percentage) into a 64-bit float
1517
                fsOverheadFloat, _ := strconv.ParseFloat(string(fsOverhead), 64)
×
1518

×
1519
                // Merge the previous values into a 'resource.Quantity' struct
×
1520
                requiredSpace := GetRequiredSpace(fsOverheadFloat, imgSize)
×
1521
                returnSize = *resource.NewScaledQuantity(requiredSpace, 0)
×
1522
        } else {
×
1523
                // Inflation is not needed with 'Block' mode
×
1524
                returnSize = *resource.NewScaledQuantity(imgSize, 0)
×
1525
        }
×
1526

1527
        return returnSize, nil
×
1528
}
1529

1530
// IsBound returns if the pvc is bound
1531
func IsBound(pvc *corev1.PersistentVolumeClaim) bool {
×
1532
        return pvc != nil && pvc.Status.Phase == corev1.ClaimBound
×
1533
}
×
1534

1535
// IsUnbound returns if the pvc is not bound yet
1536
func IsUnbound(pvc *corev1.PersistentVolumeClaim) bool {
×
1537
        return !IsBound(pvc)
×
1538
}
×
1539

1540
// IsLost returns if the pvc is lost
1541
func IsLost(pvc *corev1.PersistentVolumeClaim) bool {
×
1542
        return pvc != nil && pvc.Status.Phase == corev1.ClaimLost
×
1543
}
×
1544

1545
// IsImageStream returns true if registry source is ImageStream
1546
func IsImageStream(pvc *corev1.PersistentVolumeClaim) bool {
×
1547
        return pvc.Annotations[AnnRegistryImageStream] == "true"
×
1548
}
×
1549

1550
// ShouldIgnorePod checks if a pod should be ignored.
1551
// If this is a completed pod that was used for one checkpoint of a multi-stage import, it
1552
// should be ignored by pod lookups as long as the retainAfterCompletion annotation is set.
1553
func ShouldIgnorePod(pod *corev1.Pod, pvc *corev1.PersistentVolumeClaim) bool {
×
1554
        retain := pvc.ObjectMeta.Annotations[AnnPodRetainAfterCompletion]
×
1555
        checkpoint := pvc.ObjectMeta.Annotations[AnnCurrentCheckpoint]
×
1556
        if checkpoint != "" && pod.Status.Phase == corev1.PodSucceeded {
×
1557
                return retain == "true"
×
1558
        }
×
1559
        return false
×
1560
}
1561

1562
// BuildHTTPClient generates an http client that accepts any certificate, since we are using
1563
// it to get prometheus data it doesn't matter if someone can intercept the data. Once we have
1564
// a mechanism to properly sign the server, we can update this method to get a proper client.
1565
func BuildHTTPClient(httpClient *http.Client) *http.Client {
×
1566
        if httpClient == nil {
×
1567
                defaultTransport := http.DefaultTransport.(*http.Transport)
×
1568
                // Create new Transport that ignores self-signed SSL
×
1569
                //nolint:gosec
×
1570
                tr := &http.Transport{
×
1571
                        Proxy:                 defaultTransport.Proxy,
×
1572
                        DialContext:           defaultTransport.DialContext,
×
1573
                        MaxIdleConns:          defaultTransport.MaxIdleConns,
×
1574
                        IdleConnTimeout:       defaultTransport.IdleConnTimeout,
×
1575
                        ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
×
1576
                        TLSHandshakeTimeout:   defaultTransport.TLSHandshakeTimeout,
×
1577
                        TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
×
1578
                }
×
1579
                httpClient = &http.Client{
×
1580
                        Transport: tr,
×
1581
                }
×
1582
        }
×
1583
        return httpClient
×
1584
}
1585

1586
// ErrConnectionRefused checks for connection refused errors
1587
func ErrConnectionRefused(err error) bool {
×
1588
        return strings.Contains(err.Error(), "connection refused")
×
1589
}
×
1590

1591
// GetPodMetricsPort returns, if exists, the metrics port from the passed pod
1592
func GetPodMetricsPort(pod *corev1.Pod) (int, error) {
1✔
1593
        for _, container := range pod.Spec.Containers {
2✔
1594
                for _, port := range container.Ports {
2✔
1595
                        if port.Name == "metrics" {
2✔
1596
                                return int(port.ContainerPort), nil
1✔
1597
                        }
1✔
1598
                }
1599
        }
1600
        return 0, errors.New("Metrics port not found in pod")
1✔
1601
}
1602

1603
// GetMetricsURL builds the metrics URL according to the specified pod
1604
func GetMetricsURL(pod *corev1.Pod) (string, error) {
1✔
1605
        if pod == nil {
1✔
1606
                return "", nil
×
1607
        }
×
1608
        port, err := GetPodMetricsPort(pod)
1✔
1609
        if err != nil || pod.Status.PodIP == "" {
2✔
1610
                return "", err
1✔
1611
        }
1✔
1612
        domain := net.JoinHostPort(pod.Status.PodIP, fmt.Sprint(port))
1✔
1613
        url := fmt.Sprintf("https://%s/metrics", domain)
1✔
1614
        return url, nil
1✔
1615
}
1616

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

1641
        // Parse the progress from the body
1642
        progressReport := ""
×
1643
        match := regExp.FindStringSubmatch(string(body))
×
1644
        if match != nil {
×
1645
                progressReport = match[len(match)-1]
×
1646
        }
×
1647
        return progressReport, nil
×
1648
}
1649

1650
// UpdateHTTPAnnotations updates the passed annotations for proper http import
1651
func UpdateHTTPAnnotations(annotations map[string]string, http *cdiv1.DataVolumeSourceHTTP) {
×
1652
        annotations[AnnEndpoint] = http.URL
×
1653
        annotations[AnnSource] = SourceHTTP
×
1654

×
1655
        if http.SecretRef != "" {
×
1656
                annotations[AnnSecret] = http.SecretRef
×
1657
        }
×
1658
        if http.CertConfigMap != "" {
×
1659
                annotations[AnnCertConfigMap] = http.CertConfigMap
×
1660
        }
×
1661
        for index, header := range http.ExtraHeaders {
×
1662
                annotations[fmt.Sprintf("%s.%d", AnnExtraHeaders, index)] = header
×
1663
        }
×
1664
        for index, header := range http.SecretExtraHeaders {
×
1665
                annotations[fmt.Sprintf("%s.%d", AnnSecretExtraHeaders, index)] = header
×
1666
        }
×
1667
}
1668

1669
// UpdateS3Annotations updates the passed annotations for proper S3 import
1670
func UpdateS3Annotations(annotations map[string]string, s3 *cdiv1.DataVolumeSourceS3) {
×
1671
        annotations[AnnEndpoint] = s3.URL
×
1672
        annotations[AnnSource] = SourceS3
×
1673
        if s3.SecretRef != "" {
×
1674
                annotations[AnnSecret] = s3.SecretRef
×
1675
        }
×
1676
        if s3.CertConfigMap != "" {
×
1677
                annotations[AnnCertConfigMap] = s3.CertConfigMap
×
1678
        }
×
1679
}
1680

1681
// UpdateGCSAnnotations updates the passed annotations for proper GCS import
1682
func UpdateGCSAnnotations(annotations map[string]string, gcs *cdiv1.DataVolumeSourceGCS) {
×
1683
        annotations[AnnEndpoint] = gcs.URL
×
1684
        annotations[AnnSource] = SourceGCS
×
1685
        if gcs.SecretRef != "" {
×
1686
                annotations[AnnSecret] = gcs.SecretRef
×
1687
        }
×
1688
}
1689

1690
// UpdateRegistryAnnotations updates the passed annotations for proper registry import
1691
func UpdateRegistryAnnotations(annotations map[string]string, registry *cdiv1.DataVolumeSourceRegistry) {
×
1692
        annotations[AnnSource] = SourceRegistry
×
1693
        pullMethod := registry.PullMethod
×
1694
        if pullMethod != nil && *pullMethod != "" {
×
1695
                annotations[AnnRegistryImportMethod] = string(*pullMethod)
×
1696
        }
×
1697
        url := registry.URL
×
1698
        if url != nil && *url != "" {
×
1699
                annotations[AnnEndpoint] = *url
×
1700
        } else {
×
1701
                imageStream := registry.ImageStream
×
1702
                if imageStream != nil && *imageStream != "" {
×
1703
                        annotations[AnnEndpoint] = *imageStream
×
1704
                        annotations[AnnRegistryImageStream] = "true"
×
1705
                }
×
1706
        }
1707
        secretRef := registry.SecretRef
×
1708
        if secretRef != nil && *secretRef != "" {
×
1709
                annotations[AnnSecret] = *secretRef
×
1710
        }
×
1711
        certConfigMap := registry.CertConfigMap
×
1712
        if certConfigMap != nil && *certConfigMap != "" {
×
1713
                annotations[AnnCertConfigMap] = *certConfigMap
×
1714
        }
×
1715

1716
        if registry.Platform != nil && registry.Platform.Architecture != "" {
×
1717
                annotations[AnnRegistryImageArchitecture] = registry.Platform.Architecture
×
1718
        }
×
1719
}
1720

1721
// UpdateVDDKAnnotations updates the passed annotations for proper VDDK import
1722
func UpdateVDDKAnnotations(annotations map[string]string, vddk *cdiv1.DataVolumeSourceVDDK) {
×
1723
        annotations[AnnEndpoint] = vddk.URL
×
1724
        annotations[AnnSource] = SourceVDDK
×
1725
        annotations[AnnSecret] = vddk.SecretRef
×
1726
        annotations[AnnBackingFile] = vddk.BackingFile
×
1727
        annotations[AnnUUID] = vddk.UUID
×
1728
        annotations[AnnThumbprint] = vddk.Thumbprint
×
1729
        if vddk.InitImageURL != "" {
×
1730
                annotations[AnnVddkInitImageURL] = vddk.InitImageURL
×
1731
        }
×
1732
        if vddk.ExtraArgs != "" {
×
1733
                annotations[AnnVddkExtraArgs] = vddk.ExtraArgs
×
1734
        }
×
1735
}
1736

1737
// UpdateImageIOAnnotations updates the passed annotations for proper imageIO import
1738
func UpdateImageIOAnnotations(annotations map[string]string, imageio *cdiv1.DataVolumeSourceImageIO) {
×
1739
        annotations[AnnEndpoint] = imageio.URL
×
1740
        annotations[AnnSource] = SourceImageio
×
1741
        annotations[AnnSecret] = imageio.SecretRef
×
1742
        annotations[AnnCertConfigMap] = imageio.CertConfigMap
×
1743
        annotations[AnnDiskID] = imageio.DiskID
×
1744
}
×
1745

1746
// IsPVBoundToPVC checks if a PV is bound to a specific PVC
1747
func IsPVBoundToPVC(pv *corev1.PersistentVolume, pvc *corev1.PersistentVolumeClaim) bool {
1✔
1748
        claimRef := pv.Spec.ClaimRef
1✔
1749
        return claimRef != nil && claimRef.Name == pvc.Name && claimRef.Namespace == pvc.Namespace && claimRef.UID == pvc.UID
1✔
1750
}
1✔
1751

1752
// Rebind binds the PV of source to target
1753
func Rebind(ctx context.Context, c client.Client, source, target *corev1.PersistentVolumeClaim) error {
1✔
1754
        pv := &corev1.PersistentVolume{
1✔
1755
                ObjectMeta: metav1.ObjectMeta{
1✔
1756
                        Name: source.Spec.VolumeName,
1✔
1757
                },
1✔
1758
        }
1✔
1759

1✔
1760
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
2✔
1761
                return err
1✔
1762
        }
1✔
1763

1764
        // Examine the claimref for the PV and see if it's still bound to PVC'
1765
        if pv.Spec.ClaimRef == nil {
1✔
1766
                return fmt.Errorf("PV %s claimRef is nil", pv.Name)
×
1767
        }
×
1768

1769
        if !IsPVBoundToPVC(pv, source) {
2✔
1770
                // Something is not right if the PV is neither bound to PVC' nor target PVC
1✔
1771
                if !IsPVBoundToPVC(pv, target) {
2✔
1772
                        klog.Errorf("PV bound to unexpected PVC: Could not rebind to target PVC '%s'", target.Name)
1✔
1773
                        return fmt.Errorf("PV %s bound to unexpected claim %s", pv.Name, pv.Spec.ClaimRef.Name)
1✔
1774
                }
1✔
1775
                // our work is done
1776
                return nil
1✔
1777
        }
1778

1779
        // Rebind PVC to target PVC
1780
        pv.Spec.ClaimRef = &corev1.ObjectReference{
1✔
1781
                Namespace:       target.Namespace,
1✔
1782
                Name:            target.Name,
1✔
1783
                UID:             target.UID,
1✔
1784
                ResourceVersion: target.ResourceVersion,
1✔
1785
        }
1✔
1786
        klog.V(3).Info("Rebinding PV to target PVC", "PVC", target.Name)
1✔
1787
        if err := c.Update(context.TODO(), pv); err != nil {
1✔
1788
                return err
×
1789
        }
×
1790

1791
        return nil
1✔
1792
}
1793

1794
// BulkDeleteResources deletes a bunch of resources
1795
func BulkDeleteResources(ctx context.Context, c client.Client, obj client.ObjectList, lo client.ListOption) error {
×
1796
        if err := c.List(ctx, obj, lo); err != nil {
×
1797
                if meta.IsNoMatchError(err) {
×
1798
                        return nil
×
1799
                }
×
1800
                return err
×
1801
        }
1802

1803
        sv := reflect.ValueOf(obj).Elem()
×
1804
        iv := sv.FieldByName("Items")
×
1805

×
1806
        for i := 0; i < iv.Len(); i++ {
×
1807
                obj := iv.Index(i).Addr().Interface().(client.Object)
×
1808
                if obj.GetDeletionTimestamp().IsZero() {
×
1809
                        klog.V(3).Infof("Deleting type %+v %+v", reflect.TypeOf(obj), obj)
×
1810
                        if err := c.Delete(ctx, obj); err != nil {
×
1811
                                return err
×
1812
                        }
×
1813
                }
1814
        }
1815

1816
        return nil
×
1817
}
1818

1819
// ValidateSnapshotCloneSize does proper size validation when doing a clone from snapshot operation
1820
func ValidateSnapshotCloneSize(snapshot *snapshotv1.VolumeSnapshot, pvcSpec *corev1.PersistentVolumeClaimSpec, targetSC *storagev1.StorageClass, log logr.Logger) (bool, error) {
×
1821
        restoreSize := snapshot.Status.RestoreSize
×
1822
        if restoreSize == nil {
×
1823
                return false, fmt.Errorf("snapshot has no RestoreSize")
×
1824
        }
×
1825
        targetRequest, hasTargetRequest := pvcSpec.Resources.Requests[corev1.ResourceStorage]
×
1826
        allowExpansion := targetSC.AllowVolumeExpansion != nil && *targetSC.AllowVolumeExpansion
×
1827
        if hasTargetRequest {
×
1828
                // otherwise will just use restoreSize
×
1829
                if restoreSize.Cmp(targetRequest) < 0 && !allowExpansion {
×
1830
                        log.V(3).Info("Can't expand restored PVC because SC does not allow expansion, need to fall back to host assisted")
×
1831
                        return false, nil
×
1832
                }
×
1833
        }
1834
        return true, nil
×
1835
}
1836

1837
// ValidateSnapshotCloneProvisioners validates the target PVC storage class against the snapshot class provisioner
1838
func ValidateSnapshotCloneProvisioners(vsc *snapshotv1.VolumeSnapshotContent, storageClass *storagev1.StorageClass) (bool, error) {
×
1839
        // Do snapshot and storage class validation
×
1840
        if storageClass == nil {
×
1841
                return false, fmt.Errorf("target storage class not found")
×
1842
        }
×
1843
        if storageClass.Provisioner != vsc.Spec.Driver {
×
1844
                return false, nil
×
1845
        }
×
1846
        // TODO: get sourceVolumeMode from volumesnapshotcontent and validate against target spec
1847
        // currently don't have CRDs in CI with sourceVolumeMode which is pretty new
1848
        // converting volume mode is possible but has security implications
1849
        return true, nil
×
1850
}
1851

1852
// GetSnapshotClassForSmartClone looks up the snapshot class based on the storage class
1853
func GetSnapshotClassForSmartClone(pvc *corev1.PersistentVolumeClaim, targetPvcStorageClassName, snapshotClassName *string, log logr.Logger, client client.Client, recorder record.EventRecorder) (string, error) {
×
1854
        logger := log.WithName("GetSnapshotClassForSmartClone").V(3)
×
1855
        // Check if relevant CRDs are available
×
1856
        if !isCsiCrdsDeployed(client, log) {
×
1857
                logger.Info("Missing CSI snapshotter CRDs, falling back to host assisted clone")
×
1858
                return "", nil
×
1859
        }
×
1860

1861
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(context.TODO(), client, targetPvcStorageClassName)
×
1862
        if err != nil {
×
1863
                return "", err
×
1864
        }
×
1865
        if targetStorageClass == nil {
×
1866
                logger.Info("Target PVC's Storage Class not found")
×
1867
                return "", nil
×
1868
        }
×
1869

1870
        vscName, err := GetVolumeSnapshotClass(context.TODO(), client, pvc, targetStorageClass.Provisioner, snapshotClassName, logger, recorder)
×
1871
        if err != nil {
×
1872
                return "", err
×
1873
        }
×
1874
        if vscName != nil {
×
1875
                if pvc != nil {
×
1876
                        logger.Info("smart-clone is applicable for datavolume", "datavolume",
×
1877
                                pvc.Name, "snapshot class", *vscName)
×
1878
                }
×
1879
                return *vscName, nil
×
1880
        }
1881

1882
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
1883
        return "", nil
×
1884
}
1885

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

×
1891
        logEvent := func(message, vscName string) {
×
1892
                logger.Info(message, "name", vscName)
×
1893
                if pvc != nil {
×
1894
                        msg := fmt.Sprintf("%s %s", message, vscName)
×
1895
                        recorder.Event(pvc, corev1.EventTypeNormal, VolumeSnapshotClassSelected, msg)
×
1896
                }
×
1897
        }
1898

1899
        if snapshotClassName != nil {
×
1900
                vsc := &snapshotv1.VolumeSnapshotClass{}
×
1901
                if err := c.Get(context.TODO(), types.NamespacedName{Name: *snapshotClassName}, vsc); err != nil {
×
1902
                        return nil, err
×
1903
                }
×
1904
                if vsc.Driver == driver {
×
1905
                        logEvent(MessageStorageProfileVolumeSnapshotClassSelected, vsc.Name)
×
1906
                        return snapshotClassName, nil
×
1907
                }
×
1908
                return nil, nil
×
1909
        }
1910

1911
        vscList := &snapshotv1.VolumeSnapshotClassList{}
×
1912
        if err := c.List(ctx, vscList); err != nil {
×
1913
                if meta.IsNoMatchError(err) {
×
1914
                        return nil, nil
×
1915
                }
×
1916
                return nil, err
×
1917
        }
1918

1919
        var candidates []string
×
1920
        for _, vsc := range vscList.Items {
×
1921
                if vsc.Driver == driver {
×
1922
                        if vsc.Annotations[AnnDefaultSnapshotClass] == "true" {
×
1923
                                logEvent(MessageDefaultVolumeSnapshotClassSelected, vsc.Name)
×
1924
                                vscName := vsc.Name
×
1925
                                return &vscName, nil
×
1926
                        }
×
1927
                        candidates = append(candidates, vsc.Name)
×
1928
                }
1929
        }
1930

1931
        if len(candidates) > 0 {
×
1932
                sort.Strings(candidates)
×
1933
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
×
1934
                return &candidates[0], nil
×
1935
        }
×
1936

1937
        return nil, nil
×
1938
}
1939

1940
// isCsiCrdsDeployed checks whether the CSI snapshotter CRD are deployed
1941
func isCsiCrdsDeployed(c client.Client, log logr.Logger) bool {
×
1942
        version := "v1"
×
1943
        vsClass := "volumesnapshotclasses." + snapshotv1.GroupName
×
1944
        vsContent := "volumesnapshotcontents." + snapshotv1.GroupName
×
1945
        vs := "volumesnapshots." + snapshotv1.GroupName
×
1946

×
1947
        return isCrdDeployed(c, vsClass, version, log) &&
×
1948
                isCrdDeployed(c, vsContent, version, log) &&
×
1949
                isCrdDeployed(c, vs, version, log)
×
1950
}
×
1951

1952
// isCrdDeployed checks whether a CRD is deployed
1953
func isCrdDeployed(c client.Client, name, version string, log logr.Logger) bool {
×
1954
        crd := &extv1.CustomResourceDefinition{}
×
1955
        err := c.Get(context.TODO(), types.NamespacedName{Name: name}, crd)
×
1956
        if err != nil {
×
1957
                if !k8serrors.IsNotFound(err) {
×
1958
                        log.Info("Error looking up CRD", "crd name", name, "version", version, "error", err)
×
1959
                }
×
1960
                return false
×
1961
        }
1962

1963
        for _, v := range crd.Spec.Versions {
×
1964
                if v.Name == version && v.Served {
×
1965
                        return true
×
1966
                }
×
1967
        }
1968

1969
        return false
×
1970
}
1971

1972
// IsSnapshotReady indicates if a volume snapshot is ready to be used
1973
func IsSnapshotReady(snapshot *snapshotv1.VolumeSnapshot) bool {
×
1974
        return snapshot.Status != nil && snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse
×
1975
}
×
1976

1977
// GetResource updates given obj with the data of the object with the same name and namespace
1978
func GetResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
×
1979
        obj.SetNamespace(namespace)
×
1980
        obj.SetName(name)
×
1981

×
1982
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
1983
        if err != nil {
×
1984
                if k8serrors.IsNotFound(err) {
×
1985
                        return false, nil
×
1986
                }
×
1987

1988
                return false, err
×
1989
        }
1990

1991
        return true, nil
×
1992
}
1993

1994
// PatchArgs are the args for Patch
1995
type PatchArgs struct {
1996
        Client client.Client
1997
        Log    logr.Logger
1998
        Obj    client.Object
1999
        OldObj client.Object
2000
}
2001

2002
// GetAnnotatedEventSource returns resource referenced by AnnEventSource annotations
2003
func GetAnnotatedEventSource(ctx context.Context, c client.Client, obj client.Object) (client.Object, error) {
×
2004
        esk, ok := obj.GetAnnotations()[AnnEventSourceKind]
×
2005
        if !ok {
×
2006
                return obj, nil
×
2007
        }
×
2008
        if esk != "PersistentVolumeClaim" {
×
2009
                return obj, nil
×
2010
        }
×
2011
        es, ok := obj.GetAnnotations()[AnnEventSource]
×
2012
        if !ok {
×
2013
                return obj, nil
×
2014
        }
×
2015
        namespace, name, err := cache.SplitMetaNamespaceKey(es)
×
2016
        if err != nil {
×
2017
                return nil, err
×
2018
        }
×
2019
        pvc := &corev1.PersistentVolumeClaim{
×
2020
                ObjectMeta: metav1.ObjectMeta{
×
2021
                        Namespace: namespace,
×
2022
                        Name:      name,
×
2023
                },
×
2024
        }
×
2025
        if err := c.Get(ctx, client.ObjectKeyFromObject(pvc), pvc); err != nil {
×
2026
                return nil, err
×
2027
        }
×
2028
        return pvc, nil
×
2029
}
2030

2031
// OwnedByDataVolume returns true if the object is owned by a DataVolume
2032
func OwnedByDataVolume(obj metav1.Object) bool {
×
2033
        owner := metav1.GetControllerOf(obj)
×
2034
        return owner != nil && owner.Kind == "DataVolume"
×
2035
}
×
2036

2037
// CopyAllowedAnnotations copies the allowed annotations from the source object
2038
// to the destination object
2039
func CopyAllowedAnnotations(srcObj, dstObj metav1.Object) {
×
2040
        for ann, def := range allowedAnnotations {
×
2041
                val, ok := srcObj.GetAnnotations()[ann]
×
2042
                if !ok && def != "" {
×
2043
                        val = def
×
2044
                }
×
2045
                if val != "" {
×
2046
                        klog.V(1).Info("Applying annotation", "Name", dstObj.GetName(), ann, val)
×
2047
                        AddAnnotation(dstObj, ann, val)
×
2048
                }
×
2049
        }
2050
}
2051

2052
// CopyAllowedLabels copies allowed labels matching the validLabelsMatch regexp from the
2053
// source map to the destination object allowing overwrites
2054
func CopyAllowedLabels(srcLabels map[string]string, dstObj metav1.Object, overwrite bool) {
1✔
2055
        for label, value := range srcLabels {
2✔
2056
                if _, found := dstObj.GetLabels()[label]; (!found || overwrite) && validLabelsMatch.MatchString(label) {
2✔
2057
                        AddLabel(dstObj, label, value)
1✔
2058
                }
1✔
2059
        }
2060
}
2061

2062
// ClaimMayExistBeforeDataVolume returns true if the PVC may exist before the DataVolume
2063
func ClaimMayExistBeforeDataVolume(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2064
        if ClaimIsPopulatedForDataVolume(pvc, dv) {
×
2065
                return true, nil
×
2066
        }
×
2067
        return AllowClaimAdoption(c, pvc, dv)
×
2068
}
2069

2070
// ClaimIsPopulatedForDataVolume returns true if the PVC is populated for the given DataVolume
2071
func ClaimIsPopulatedForDataVolume(pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) bool {
×
2072
        return pvc != nil && dv != nil && pvc.Annotations[AnnPopulatedFor] == dv.Name
×
2073
}
×
2074

2075
// AllowClaimAdoption returns true if the PVC may be adopted
2076
func AllowClaimAdoption(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2077
        if pvc == nil || dv == nil {
×
2078
                return false, nil
×
2079
        }
×
2080
        anno, ok := pvc.Annotations[AnnCreatedForDataVolume]
×
2081
        if ok && anno == string(dv.UID) {
×
2082
                return false, nil
×
2083
        }
×
2084
        anno, ok = dv.Annotations[AnnAllowClaimAdoption]
×
2085
        // if annotation exists, go with that regardless of featuregate
×
2086
        if ok {
×
2087
                val, _ := strconv.ParseBool(anno)
×
2088
                return val, nil
×
2089
        }
×
2090
        return featuregates.NewFeatureGates(c).ClaimAdoptionEnabled()
×
2091
}
2092

2093
// ResolveDataSourceChain resolves a DataSource reference.
2094
// Returns an error if DataSource reference is not found or
2095
// DataSource reference points to another DataSource.
NEW
2096
func ResolveDataSourceChain(ctx context.Context, client client.Client, dataSource *cdiv1.DataSource) (*cdiv1.DataSource, error) {
×
NEW
2097
        if dataSource.Spec.Source.DataSource == nil {
×
NEW
2098
                return dataSource, nil
×
NEW
2099
        }
×
2100

NEW
2101
        ref := dataSource.Spec.Source.DataSource
×
NEW
2102
        refNs := GetNamespace(ref.Namespace, dataSource.Namespace)
×
NEW
2103
        if ref.Name == dataSource.Name && refNs == dataSource.Namespace {
×
NEW
2104
                return nil, ErrDataSourceSelfReference
×
NEW
2105
        }
×
NEW
2106
        resolved := &cdiv1.DataSource{}
×
NEW
2107
        if err := client.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: refNs}, resolved); err != nil {
×
NEW
2108
                return nil, err
×
NEW
2109
        }
×
2110

NEW
2111
        if resolved.Spec.Source.DataSource != nil {
×
NEW
2112
                return nil, ErrDataSourceMaxDepthReached
×
NEW
2113
        }
×
2114

NEW
2115
        return resolved, nil
×
2116
}
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