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

kubevirt / containerized-data-importer / #5509

28 Jul 2025 07:11AM UTC coverage: 59.319% (-0.02%) from 59.336%
#5509

Pull #3831

travis-ci

Acedus
datasource-controller: prohibit cross-namespace references

Previously a DataSource could reference a DataSource in a namespace
different from its own. This is unwanted behavior and with this commit
we now error when we create such DataSources.

Signed-off-by: Adi Aloni <aaloni@redhat.com>
Pull Request #3831: fix: add DataSource reference handling to authorize utils

2 of 5 new or added lines in 2 files covered. (40.0%)

4 existing lines in 1 file now uncovered.

17165 of 28937 relevant lines covered (59.32%)

0.66 hits per line

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

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

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

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

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

17
package common
18

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

274
        cloneTokenLeeway = 10 * time.Second
275

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

394
        ErrDataSourceMaxDepthReached = errors.New("DataSource reference chain exceeds maximum depth of 1")
395
        ErrDataSourceSelfReference   = errors.New("DataSource cannot self-reference")
396
        ErrDataSourceCrossNamespace  = errors.New("DataSource cannot reference a DataSource in another namespace")
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) {
×
489
        pvcSize, found := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
490
        if !found {
491
                return "", errors.Errorf("storage request is missing in pvc \"%s/%s\"", pvc.Namespace, pvc.Name)
1✔
492
        }
1✔
493
        return pvcSize.String(), nil
2✔
494
}
1✔
495

1✔
496
// GetVolumeMode returns the volumeMode from PVC handling default empty value
1✔
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) {
523
        if name == nil {
524
                return getFallbackStorageClass(ctx, client, contentType)
525
        }
1✔
526

2✔
527
        // look up storage class by name
1✔
528
        storageClass := &storagev1.StorageClass{}
1✔
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) {
544
        return getStorageClassByName(ctx, client, name, cdiv1.DataVolumeArchive)
545
}
546

1✔
547
// GetStorageClassByNameWithVirtFallback looks up the storage class based on the name
1✔
548
// If name is nil, it looks for the following, in this order:
1✔
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) {
553
        return getStorageClassByName(ctx, client, name, contentType)
554
}
555

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

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

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

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

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

2✔
587
        // Primary sort by creation timestamp, newest first
1✔
588
        // Secondary sort by class name, ascending order
1✔
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 {
592
                if defaultClasses[i].CreationTimestamp.UnixNano() == defaultClasses[j].CreationTimestamp.UnixNano() {
593
                        return defaultClasses[i].Name < defaultClasses[j].Name
594
                }
2✔
595
                return defaultClasses[i].CreationTimestamp.UnixNano() > defaultClasses[j].CreationTimestamp.UnixNano()
2✔
596
        })
1✔
597
        if len(defaultClasses) > 1 {
1✔
598
                klog.V(3).Infof("%d default StorageClasses were found, choosing: %s", len(defaultClasses), defaultClasses[0].Name)
1✔
599
        }
600

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

604
// GetFilesystemOverheadForStorageClass determines the filesystem overhead defined in CDIConfig for the storageClass.
1✔
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) {
792
        crList := &cdiv1.CDIList{}
793
        if err := c.List(ctx, crList, &client.ListOptions{}); err != nil {
794
                return nil, err
1✔
795
        }
1✔
796

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

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

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

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

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

819
// IsPopulated returns if the passed in PVC has been populated according to the rules outlined in pkg/apis/core/<version>/utils.go
1✔
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) {
1005
        if obj.GetAnnotations() == nil {
1006
                obj.SetAnnotations(make(map[string]string))
1007
        }
1✔
1008
        obj.GetAnnotations()[key] = value
2✔
1009
}
1✔
1010

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

1✔
1019
// HandleFailedPod handles pod-creation errors and updates the pod's PVC without providing sensitive information
1✔
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 {
1187
        return CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1188
}
1189

1✔
1190
// CreatePvcInStorageClass creates PVC with storgae class
1✔
1191
func CreatePvcInStorageClass(name, ns string, storageClassName *string, annotations, labels map[string]string, phase corev1.PersistentVolumeClaimPhase) *corev1.PersistentVolumeClaim {
1✔
1192
        pvc := &corev1.PersistentVolumeClaim{
1193
                ObjectMeta: metav1.ObjectMeta{
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 {
1✔
1215
                pvc.Spec.VolumeName = "pv-" + string(pvc.UID)
1✔
1216
        }
1✔
1217
        return pvc
2✔
1218
}
1✔
1219

1✔
1220
// GetAPIServerKey returns API server RSA key
1✔
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 {
1230
        return &storagev1.StorageClass{
1231
                ObjectMeta: metav1.ObjectMeta{
1232
                        Name:        name,
1✔
1233
                        Annotations: annotations,
1✔
1234
                },
1✔
1235
        }
1✔
1236
}
1✔
1237

1✔
1238
// CreateImporterTestPod creates importer test pod CR
1✔
1239
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
1✔
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 {
×
1381
        s := scheme.Scheme
1382
        _ = cdiv1.AddToScheme(s)
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

1✔
1390
// ErrQuotaExceeded checked is the error is of exceeded quota
1✔
1391
func ErrQuotaExceeded(err error) bool {
1✔
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 {
×
1397
        switch contentType {
1398
        case
1399
                cdiv1.DataVolumeKubeVirt,
1✔
1400
                cdiv1.DataVolumeArchive:
1✔
1401
        default:
1402
                // TODO - shouldn't archive be the default?
1403
                contentType = cdiv1.DataVolumeKubeVirt
1✔
1404
        }
×
1405
        return contentType
×
1406
}
×
1407

1408
// GetPVCContentType returns the content type of the source image. If invalid or not set, default to kubevirt
1✔
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
// InflateSizeWithOverhead inflates a storage size with proper overhead calculations
×
1496
func InflateSizeWithOverhead(ctx context.Context, c client.Client, imgSize int64, pvcSpec *corev1.PersistentVolumeClaimSpec) (resource.Quantity, error) {
1497
        var returnSize resource.Quantity
1498

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

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

×
1515
        return returnSize, nil
×
1516
}
×
1517

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
1779
        return nil
×
1780
}
×
1781

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

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

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

×
1804
        return nil
1805
}
1806

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

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

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

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

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

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

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

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

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

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

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

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

×
1925
        return nil, nil
×
1926
}
×
1927

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

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

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

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

×
1957
        return false
×
1958
}
1959

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

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

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

×
1976
                return false, err
×
1977
        }
×
1978

1979
        return true, nil
×
1980
}
1981

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

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

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

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

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

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

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

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

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

×
2089
        ref := dataSource.Spec.Source.DataSource
×
2090
        refNs := GetNamespace(ref.Namespace, dataSource.Namespace)
×
2091
        if dataSource.Namespace != refNs {
NEW
2092
                return dataSource, ErrDataSourceCrossNamespace
×
NEW
2093
        }
×
2094
        if ref.Name == dataSource.Name && refNs == dataSource.Namespace {
×
2095
                return nil, ErrDataSourceSelfReference
×
2096
        }
×
NEW
2097

×
2098
        resolved := &cdiv1.DataSource{}
×
2099
        if err := client.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: refNs}, resolved); err != nil {
×
2100
                return nil, err
2101
        }
×
2102

×
2103
        if resolved.Spec.Source.DataSource != nil {
×
2104
                return nil, ErrDataSourceMaxDepthReached
×
2105
        }
2106

×
2107
        return resolved, nil
×
2108
}
×
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