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

kubevirt / containerized-data-importer / #5510

30 Jul 2025 08:25AM UTC coverage: 59.346% (+0.01%) from 59.336%
#5510

push

travis-ci

web-flow
fix: add DataSource reference handling to authorize utils (#3831)

* fix: add DataSource reference to authorize_utils

A previous PR[1] introduced the concept of DataSource references as a way
for a DataSource to reference another DataSource.

A DV that has its sourceRef set to a DataSource reference that resides
in a different namespace would fail as authorize utils did not include
the necessary changes to the clone source handler to accomodate
DataSource references.

This commit fixes that and introduces a functional test for it.

[1] https://github.com/kubevirt/containerized-data-importer/pull/3760

Signed-off-by: Adi Aloni <aaloni@redhat.com>

* 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>

---------

Signed-off-by: Adi Aloni <aaloni@redhat.com>

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

7 existing lines in 1 file now uncovered.

17173 of 28937 relevant lines covered (59.35%)

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
        // AnnPVCPrimeName annotation is the name of the PVC' that is used to populate the PV which is then rebound to the target PVC
355
        AnnPVCPrimeName = AnnAPIGroup + "/storage.populator.pvcPrime"
356
)
357

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

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

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

382
        apiServerKeyOnce sync.Once
383
        apiServerKey     *rsa.PrivateKey
384

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

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

397
        ErrDataSourceMaxDepthReached = errors.New("DataSource reference chain exceeds maximum depth of 1")
398
        ErrDataSourceSelfReference   = errors.New("DataSource cannot self-reference")
399
        ErrDataSourceCrossNamespace  = errors.New("DataSource cannot reference a DataSource in another namespace")
400
)
401

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

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

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

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

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

447
        tok, v := mtv.getTokenAndValidator(pvc)
×
448

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

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

×
463
        return validateTokenData(tokenData, vcs.Namespace, srcName, pvc.Namespace, pvc.Name, string(pvc.UID), tokenResourceName)
×
464
}
465

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

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

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

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

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

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

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

515
        if dv.Spec.Storage != nil {
×
516
                return dv.Spec.Storage.StorageClassName
×
517
        }
×
518

519
        return nil
×
520
}
521

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

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

540
        return storageClass, nil
×
541
}
542

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

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

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

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

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

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

586
        if len(defaultClasses) == 0 {
2✔
587
                return nil
1✔
588
        }
1✔
589

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

604
        return &defaultClasses[0]
1✔
605
}
606

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

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

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

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

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

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

×
645
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
×
646

×
647
        storageClassOverhead, found := perStorageConfig[targetStorageClass.GetName()]
×
648
        if found {
×
649
                return storageClassOverhead, nil
×
650
        }
×
651

652
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
653
}
654

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

663
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
664
}
665

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

674
        return cdiconfig.Status.ImagePullSecrets, nil
×
675
}
676

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

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

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

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

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

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

776
        return pods, nil
×
777
}
778

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

786
        if cr == nil {
×
787
                return nil, fmt.Errorf("no active CDI")
×
788
        }
×
789

790
        return &cr.Spec.Workloads, nil
×
791
}
792

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

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

804
        if len(crList.Items) == 1 {
2✔
805
                return &crList.Items[0], nil
1✔
806
        }
1✔
807

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

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

819
        return &activeResources[0], nil
1✔
820
}
821

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

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

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

844
        return cdiconfig.Status.Preallocation
×
845
}
846

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

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

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

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

870
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
871
}
872

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

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

886
        obj.SetFinalizers(finalizers)
×
887
}
888

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

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

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

910
        tokenResourceName := getTokenResourceNamePvc(source)
×
911
        srcName := getSourceNamePvc(source)
×
912

×
913
        return validateTokenData(tokenData, source.Namespace, srcName, target.Namespace, target.Name, string(target.UID), tokenResourceName)
×
914
}
915

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

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

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

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

938
        return validateTokenData(tokenData, sourceNamespace, sourceName, dv.Namespace, dv.Name, "", tokenResourceName)
×
939
}
940

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

948
        return ""
×
949
}
950

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

956
        return "persistentvolumeclaims"
×
957
}
958

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

966
        return sourcePvc.Name
×
967
}
968

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

981
        return nil
×
982
}
983

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

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

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

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

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

1036
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1037

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

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

1053
        return err
×
1054
}
1055

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
1246
        blockOwnerDeletion := true
×
1247
        isController := true
×
1248

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

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

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

×
1321
        ep, _ := GetEndpoint(pvc)
×
1322
        source := GetSource(pvc)
×
1323
        contentType := GetPVCContentType(pvc)
×
1324
        imageSize, _ := GetRequestedImageSize(pvc)
×
1325
        volumeMode := GetVolumeMode(pvc)
×
1326

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

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

1367
        return pod
×
1368
}
1369

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

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

1✔
1390
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1391
}
1✔
1392

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

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

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

1419
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1420
}
1421

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

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

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

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

1466
        if dv.Spec.Source.Snapshot != nil {
×
1467
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
×
1468
        }
×
1469

1470
        return "", "", ""
×
1471
}
1472

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

1483
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1484
}
1485

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

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

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

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

1518
        return returnSize, nil
×
1519
}
1520

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1707
        if registry.Platform != nil && registry.Platform.Architecture != "" {
×
1708
                annotations[AnnRegistryImageArchitecture] = registry.Platform.Architecture
×
1709
        }
×
1710
}
1711

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

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

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

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

1✔
1751
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
2✔
1752
                return err
1✔
1753
        }
1✔
1754

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

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

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

1782
        return nil
1✔
1783
}
1784

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

1794
        sv := reflect.ValueOf(obj).Elem()
×
1795
        iv := sv.FieldByName("Items")
×
1796

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

1807
        return nil
×
1808
}
1809

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

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

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

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

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

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

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

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

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

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

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

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

1928
        return nil, nil
×
1929
}
1930

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

×
1938
        return isCrdDeployed(c, vsClass, version, log) &&
×
1939
                isCrdDeployed(c, vsContent, version, log) &&
×
1940
                isCrdDeployed(c, vs, version, log)
×
1941
}
×
1942

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

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

1960
        return false
×
1961
}
1962

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

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

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

1979
                return false, err
×
1980
        }
1981

1982
        return true, nil
×
1983
}
1984

1985
// PatchArgs are the args for Patch
1986
type PatchArgs struct {
1987
        Client client.Client
1988
        Log    logr.Logger
1989
        Obj    client.Object
1990
        OldObj client.Object
1991
}
1992

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

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

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

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

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

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

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

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

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

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

2106
        if resolved.Spec.Source.DataSource != nil {
×
2107
                return nil, ErrDataSourceMaxDepthReached
×
2108
        }
×
2109

2110
        return resolved, nil
×
2111
}
2112

2113
func sortEvents(events *corev1.EventList, usingPopulator bool, pvcPrimeName string) {
1✔
2114
        // Sort event lists by containing primeName substring and most recent timestamp
1✔
2115
        sort.Slice(events.Items, func(i, j int) bool {
2✔
2116
                if usingPopulator {
2✔
2117
                        firstContainsPrime := strings.Contains(events.Items[i].Message, pvcPrimeName)
1✔
2118
                        secondContainsPrime := strings.Contains(events.Items[j].Message, pvcPrimeName)
1✔
2119

1✔
2120
                        if firstContainsPrime && !secondContainsPrime {
2✔
2121
                                return true
1✔
2122
                        }
1✔
2123
                        if !firstContainsPrime && secondContainsPrime {
2✔
2124
                                return false
1✔
2125
                        }
1✔
2126
                }
2127

2128
                // if the timestamps are the same, prioritze longer messages to make sure our sorting is deterministic
2129
                if events.Items[i].LastTimestamp.Time.Equal(events.Items[j].LastTimestamp.Time) {
1✔
2130
                        return len(events.Items[i].Message) > len(events.Items[j].Message)
×
2131
                }
×
2132

2133
                // if both contains primeName substring or neither, just sort on timestamp
2134
                return events.Items[i].LastTimestamp.Time.After(events.Items[j].LastTimestamp.Time)
1✔
2135
        })
2136
}
2137

2138
// UpdatePVCBoundContionFromEvents updates the bound condition annotations on the PVC based on recent events
2139
// This function can be used by both controller and populator packages to update PVC bound condition information
2140
func UpdatePVCBoundContionFromEvents(pvc *corev1.PersistentVolumeClaim, c client.Client, log logr.Logger) error {
×
2141
        currentPvcCopy := pvc.DeepCopy()
×
2142

×
2143
        anno := pvc.GetAnnotations()
×
2144
        if anno == nil {
×
2145
                return nil
×
2146
        }
×
2147

2148
        if IsBound(pvc) {
×
2149
                anno := pvc.GetAnnotations()
×
2150
                delete(anno, AnnBoundCondition)
×
2151
                delete(anno, AnnBoundConditionReason)
×
2152
                delete(anno, AnnBoundConditionMessage)
×
2153

×
2154
                if !reflect.DeepEqual(currentPvcCopy, pvc) {
×
2155
                        patch := client.MergeFrom(currentPvcCopy)
×
2156
                        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2157
                                return err
×
2158
                        }
×
2159
                }
2160

2161
                return nil
×
2162
        }
2163

2164
        if pvc.Status.Phase != corev1.ClaimPending {
×
2165
                return nil
×
2166
        }
×
2167

2168
        // set bound condition by getting the latest event
2169
        events := &corev1.EventList{}
×
2170

×
2171
        err := c.List(context.TODO(), events,
×
2172
                client.InNamespace(pvc.GetNamespace()),
×
2173
                client.MatchingFields{"involvedObject.name": pvc.GetName(),
×
2174
                        "involvedObject.uid": string(pvc.GetUID())},
×
2175
        )
×
2176

×
2177
        if err != nil {
×
2178
                // Log the error but don't fail the reconciliation
×
2179
                log.Error(err, "Unable to list events for PVC bound condition update", "pvc", pvc.Name)
×
2180
                return nil
×
2181
        }
×
2182

2183
        if len(events.Items) == 0 {
×
2184
                return nil
×
2185
        }
×
2186

2187
        pvcPrime, usingPopulator := anno[AnnPVCPrimeName]
×
2188

×
2189
        // Sort event lists by containing primeName substring and most recent timestamp
×
2190
        sortEvents(events, usingPopulator, pvcPrime)
×
2191

×
2192
        boundMessage := ""
×
2193
        // check if prime name annotation exists
×
2194
        if usingPopulator {
×
2195
                // if we are using populators get the latest event from prime pvc
×
2196
                pvcPrime = fmt.Sprintf("[%s] : ", pvcPrime)
×
2197

×
2198
                // if the first event does not contain a prime message, none will so return
×
2199
                primeIdx := strings.Index(events.Items[0].Message, pvcPrime)
×
2200
                if primeIdx == -1 {
×
2201
                        log.V(1).Info("No bound message found, skipping bound condition update", "pvc", pvc.Name)
×
2202
                        return nil
×
2203
                }
×
2204
                boundMessage = events.Items[0].Message[primeIdx+len(pvcPrime):]
×
2205
        } else {
×
2206
                // if not using populators just get the latest event
×
2207
                boundMessage = events.Items[0].Message
×
2208
        }
×
2209

2210
        // since we checked status of phase above, we know this is pending
2211
        anno[AnnBoundCondition] = "false"
×
2212
        anno[AnnBoundConditionReason] = "Pending"
×
2213
        anno[AnnBoundConditionMessage] = boundMessage
×
2214

×
2215
        patch := client.MergeFrom(currentPvcCopy)
×
2216
        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2217
                return err
×
2218
        }
×
2219

2220
        return nil
×
2221
}
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