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

kubevirt / containerized-data-importer / #5529

18 Aug 2025 02:57PM UTC coverage: 59.233% (-0.09%) from 59.319%
#5529

Pull #3852

travis-ci

arnongilboa
Support storageProfile minimumSupportedPVCSize in clone

When the target DataVolume storage requests a size smaller than the
source PVC. For target without size it already worked correctly.

Signed-off-by: Arnon Gilboa <agilboa@redhat.com>
Pull Request #3852: Support storageProfile minimumSupportedPVCSize in clone

21 of 56 new or added lines in 5 files covered. (37.5%)

3 existing lines in 3 files now uncovered.

17155 of 28962 relevant lines covered (59.23%)

0.65 hits per line

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

13.91
/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
// GetEffectiveStorageResources returns the maximum of the passed storageResources and the storageProfile minimumSupportedPVCSize.
1103
// If the passed storageResources has no size, it is returned as-is.
1104
func GetEffectiveStorageResources(ctx context.Context, client client.Client, storageResources corev1.VolumeResourceRequirements,
NEW
1105
        storageClassName *string, contentType cdiv1.DataVolumeContentType, log logr.Logger) (corev1.VolumeResourceRequirements, error) {
×
NEW
1106
        sc, err := GetStorageClassByNameWithVirtFallback(ctx, client, storageClassName, contentType)
×
NEW
1107
        if err != nil || sc == nil {
×
NEW
1108
                return storageResources, err
×
NEW
1109
        }
×
1110

NEW
1111
        requestedSize, hasSize := storageResources.Requests[corev1.ResourceStorage]
×
NEW
1112
        if !hasSize {
×
NEW
1113
                return storageResources, nil
×
NEW
1114
        }
×
1115

NEW
1116
        if requestedSize, err = GetEffectiveVolumeSize(ctx, client, requestedSize, sc.Name, &log); err != nil {
×
NEW
1117
                return storageResources, err
×
NEW
1118
        }
×
1119

NEW
1120
        return corev1.VolumeResourceRequirements{
×
NEW
1121
                Requests: corev1.ResourceList{
×
NEW
1122
                        corev1.ResourceStorage: requestedSize,
×
NEW
1123
                },
×
NEW
1124
        }, nil
×
1125
}
1126

1127
// GetEffectiveVolumeSize returns the maximum of the passed requestedSize and the storageProfile minimumSupportedPVCSize.
NEW
1128
func GetEffectiveVolumeSize(ctx context.Context, client client.Client, requestedSize resource.Quantity, storageClassName string, log *logr.Logger) (resource.Quantity, error) {
×
NEW
1129
        storageProfile := &cdiv1.StorageProfile{}
×
NEW
1130
        if err := client.Get(ctx, types.NamespacedName{Name: storageClassName}, storageProfile); err != nil {
×
NEW
1131
                return requestedSize, IgnoreNotFound(err)
×
NEW
1132
        }
×
1133

NEW
1134
        if val, exists := storageProfile.Annotations[AnnMinimumSupportedPVCSize]; exists {
×
NEW
1135
                if minSize, err := resource.ParseQuantity(val); err == nil {
×
NEW
1136
                        if requestedSize.Cmp(minSize) == -1 {
×
NEW
1137
                                return minSize, nil
×
NEW
1138
                        }
×
NEW
1139
                } else if log != nil {
×
NEW
1140
                        log.V(1).Info("Invalid minimum PVC size in annotation", "value", val, "error", err)
×
NEW
1141
                }
×
1142
        }
1143

NEW
1144
        return requestedSize, nil
×
1145
}
1146

1147
// ValidateRequestedCloneSize validates the clone size requirements on block
1148
func ValidateRequestedCloneSize(sourceResources, targetResources corev1.VolumeResourceRequirements) error {
×
1149
        sourceRequest, hasSource := sourceResources.Requests[corev1.ResourceStorage]
×
1150
        targetRequest, hasTarget := targetResources.Requests[corev1.ResourceStorage]
×
1151
        if !hasSource || !hasTarget {
×
1152
                return errors.New("source/target missing storage resource requests")
×
1153
        }
×
1154

1155
        // Verify that the target PVC size is equal or larger than the source.
1156
        if sourceRequest.Value() > targetRequest.Value() {
×
1157
                return errors.Errorf("target resources requests storage size is smaller than the source %d < %d", targetRequest.Value(), sourceRequest.Value())
×
1158
        }
×
1159
        return nil
×
1160
}
1161

1162
// CreateCloneSourcePodName creates clone source pod name
1163
func CreateCloneSourcePodName(targetPvc *corev1.PersistentVolumeClaim) string {
×
1164
        return string(targetPvc.GetUID()) + common.ClonerSourcePodNameSuffix
×
1165
}
×
1166

1167
// IsPVCComplete returns true if a PVC is in 'Succeeded' phase, false if not
1168
func IsPVCComplete(pvc *corev1.PersistentVolumeClaim) bool {
×
1169
        if pvc != nil {
×
1170
                phase, exists := pvc.ObjectMeta.Annotations[AnnPodPhase]
×
1171
                return exists && (phase == string(corev1.PodSucceeded))
×
1172
        }
×
1173
        return false
×
1174
}
1175

1176
// IsMultiStageImportInProgress returns true when a PVC is being part of an ongoing multi-stage import
1177
func IsMultiStageImportInProgress(pvc *corev1.PersistentVolumeClaim) bool {
×
1178
        if pvc != nil {
×
1179
                multiStageImport := metav1.HasAnnotation(pvc.ObjectMeta, AnnCurrentCheckpoint)
×
1180
                multiStageAlreadyDone := metav1.HasAnnotation(pvc.ObjectMeta, AnnMultiStageImportDone)
×
1181
                return multiStageImport && !multiStageAlreadyDone
×
1182
        }
×
1183
        return false
×
1184
}
1185

1186
// SetRestrictedSecurityContext sets the pod security params to be compatible with restricted PSA
1187
func SetRestrictedSecurityContext(podSpec *corev1.PodSpec) {
×
1188
        hasVolumeMounts := false
×
1189
        for _, containers := range [][]corev1.Container{podSpec.InitContainers, podSpec.Containers} {
×
1190
                for i := range containers {
×
1191
                        container := &containers[i]
×
1192
                        if container.SecurityContext == nil {
×
1193
                                container.SecurityContext = &corev1.SecurityContext{}
×
1194
                        }
×
1195
                        container.SecurityContext.Capabilities = &corev1.Capabilities{
×
1196
                                Drop: []corev1.Capability{
×
1197
                                        "ALL",
×
1198
                                },
×
1199
                        }
×
1200
                        container.SecurityContext.SeccompProfile = &corev1.SeccompProfile{
×
1201
                                Type: corev1.SeccompProfileTypeRuntimeDefault,
×
1202
                        }
×
1203
                        container.SecurityContext.AllowPrivilegeEscalation = ptr.To[bool](false)
×
1204
                        container.SecurityContext.RunAsNonRoot = ptr.To[bool](true)
×
1205
                        container.SecurityContext.RunAsUser = ptr.To[int64](common.QemuSubGid)
×
1206
                        if len(container.VolumeMounts) > 0 {
×
1207
                                hasVolumeMounts = true
×
1208
                        }
×
1209
                }
1210
        }
1211

1212
        if podSpec.SecurityContext == nil {
×
1213
                podSpec.SecurityContext = &corev1.PodSecurityContext{}
×
1214
        }
×
1215
        // Some tools like istio inject containers and thus rely on a pod level seccomp profile being specified
1216
        podSpec.SecurityContext.SeccompProfile = &corev1.SeccompProfile{
×
1217
                Type: corev1.SeccompProfileTypeRuntimeDefault,
×
1218
        }
×
1219
        if hasVolumeMounts {
×
1220
                podSpec.SecurityContext.FSGroup = ptr.To[int64](common.QemuSubGid)
×
1221
        }
×
1222
}
1223

1224
// SetNodeNameIfPopulator sets NodeName in a pod spec when the PVC is being handled by a CDI volume populator
1225
func SetNodeNameIfPopulator(pvc *corev1.PersistentVolumeClaim, podSpec *corev1.PodSpec) {
×
1226
        _, isPopulator := pvc.Annotations[AnnPopulatorKind]
×
1227
        nodeName := pvc.Annotations[AnnSelectedNode]
×
1228
        if isPopulator && nodeName != "" {
×
1229
                podSpec.NodeName = nodeName
×
1230
        }
×
1231
}
1232

1233
// CreatePvc creates PVC
1234
func CreatePvc(name, ns string, annotations, labels map[string]string) *corev1.PersistentVolumeClaim {
1✔
1235
        return CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1✔
1236
}
1✔
1237

1238
// CreatePvcInStorageClass creates PVC with storgae class
1239
func CreatePvcInStorageClass(name, ns string, storageClassName *string, annotations, labels map[string]string, phase corev1.PersistentVolumeClaimPhase) *corev1.PersistentVolumeClaim {
1✔
1240
        pvc := &corev1.PersistentVolumeClaim{
1✔
1241
                ObjectMeta: metav1.ObjectMeta{
1✔
1242
                        Name:        name,
1✔
1243
                        Namespace:   ns,
1✔
1244
                        Annotations: annotations,
1✔
1245
                        Labels:      labels,
1✔
1246
                        UID:         types.UID(ns + "-" + name),
1✔
1247
                },
1✔
1248
                Spec: corev1.PersistentVolumeClaimSpec{
1✔
1249
                        AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadOnlyMany, corev1.ReadWriteOnce},
1✔
1250
                        Resources: corev1.VolumeResourceRequirements{
1✔
1251
                                Requests: corev1.ResourceList{
1✔
1252
                                        corev1.ResourceStorage: resource.MustParse("1G"),
1✔
1253
                                },
1✔
1254
                        },
1✔
1255
                        StorageClassName: storageClassName,
1✔
1256
                },
1✔
1257
                Status: corev1.PersistentVolumeClaimStatus{
1✔
1258
                        Phase: phase,
1✔
1259
                },
1✔
1260
        }
1✔
1261
        pvc.Status.Capacity = pvc.Spec.Resources.Requests.DeepCopy()
1✔
1262
        if pvc.Status.Phase == corev1.ClaimBound {
2✔
1263
                pvc.Spec.VolumeName = "pv-" + string(pvc.UID)
1✔
1264
        }
1✔
1265
        return pvc
1✔
1266
}
1267

1268
// GetAPIServerKey returns API server RSA key
1269
func GetAPIServerKey() *rsa.PrivateKey {
×
1270
        apiServerKeyOnce.Do(func() {
×
1271
                apiServerKey, _ = rsa.GenerateKey(rand.Reader, 2048)
×
1272
        })
×
1273
        return apiServerKey
×
1274
}
1275

1276
// CreateStorageClass creates storage class CR
1277
func CreateStorageClass(name string, annotations map[string]string) *storagev1.StorageClass {
1✔
1278
        return &storagev1.StorageClass{
1✔
1279
                ObjectMeta: metav1.ObjectMeta{
1✔
1280
                        Name:        name,
1✔
1281
                        Annotations: annotations,
1✔
1282
                },
1✔
1283
        }
1✔
1284
}
1✔
1285

1286
// CreateImporterTestPod creates importer test pod CR
1287
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
×
1288
        // importer pod name contains the pvc name
×
1289
        podName := fmt.Sprintf("%s-%s", common.ImporterPodName, pvc.Name)
×
1290

×
1291
        blockOwnerDeletion := true
×
1292
        isController := true
×
1293

×
1294
        volumes := []corev1.Volume{
×
1295
                {
×
1296
                        Name: dvname,
×
1297
                        VolumeSource: corev1.VolumeSource{
×
1298
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1299
                                        ClaimName: pvc.Name,
×
1300
                                        ReadOnly:  false,
×
1301
                                },
×
1302
                        },
×
1303
                },
×
1304
        }
×
1305

×
1306
        if scratchPvc != nil {
×
1307
                volumes = append(volumes, corev1.Volume{
×
1308
                        Name: ScratchVolName,
×
1309
                        VolumeSource: corev1.VolumeSource{
×
1310
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1311
                                        ClaimName: scratchPvc.Name,
×
1312
                                        ReadOnly:  false,
×
1313
                                },
×
1314
                        },
×
1315
                })
×
1316
        }
×
1317

1318
        pod := &corev1.Pod{
×
1319
                TypeMeta: metav1.TypeMeta{
×
1320
                        Kind:       "Pod",
×
1321
                        APIVersion: "v1",
×
1322
                },
×
1323
                ObjectMeta: metav1.ObjectMeta{
×
1324
                        Name:      podName,
×
1325
                        Namespace: pvc.Namespace,
×
1326
                        Annotations: map[string]string{
×
1327
                                AnnCreatedBy: "yes",
×
1328
                        },
×
1329
                        Labels: map[string]string{
×
1330
                                common.CDILabelKey:        common.CDILabelValue,
×
1331
                                common.CDIComponentLabel:  common.ImporterPodName,
×
1332
                                common.PrometheusLabelKey: common.PrometheusLabelValue,
×
1333
                        },
×
1334
                        OwnerReferences: []metav1.OwnerReference{
×
1335
                                {
×
1336
                                        APIVersion:         "v1",
×
1337
                                        Kind:               "PersistentVolumeClaim",
×
1338
                                        Name:               pvc.Name,
×
1339
                                        UID:                pvc.GetUID(),
×
1340
                                        BlockOwnerDeletion: &blockOwnerDeletion,
×
1341
                                        Controller:         &isController,
×
1342
                                },
×
1343
                        },
×
1344
                },
×
1345
                Spec: corev1.PodSpec{
×
1346
                        Containers: []corev1.Container{
×
1347
                                {
×
1348
                                        Name:            common.ImporterPodName,
×
1349
                                        Image:           "test/myimage",
×
1350
                                        ImagePullPolicy: corev1.PullPolicy("Always"),
×
1351
                                        Args:            []string{"-v=5"},
×
1352
                                        Ports: []corev1.ContainerPort{
×
1353
                                                {
×
1354
                                                        Name:          "metrics",
×
1355
                                                        ContainerPort: 8443,
×
1356
                                                        Protocol:      corev1.ProtocolTCP,
×
1357
                                                },
×
1358
                                        },
×
1359
                                },
×
1360
                        },
×
1361
                        RestartPolicy: corev1.RestartPolicyOnFailure,
×
1362
                        Volumes:       volumes,
×
1363
                },
×
1364
        }
×
1365

×
1366
        ep, _ := GetEndpoint(pvc)
×
1367
        source := GetSource(pvc)
×
1368
        contentType := GetPVCContentType(pvc)
×
1369
        imageSize, _ := GetRequestedImageSize(pvc)
×
1370
        volumeMode := GetVolumeMode(pvc)
×
1371

×
1372
        env := []corev1.EnvVar{
×
1373
                {
×
1374
                        Name:  common.ImporterSource,
×
1375
                        Value: source,
×
1376
                },
×
1377
                {
×
1378
                        Name:  common.ImporterEndpoint,
×
1379
                        Value: ep,
×
1380
                },
×
1381
                {
×
1382
                        Name:  common.ImporterContentType,
×
1383
                        Value: string(contentType),
×
1384
                },
×
1385
                {
×
1386
                        Name:  common.ImporterImageSize,
×
1387
                        Value: imageSize,
×
1388
                },
×
1389
                {
×
1390
                        Name:  common.OwnerUID,
×
1391
                        Value: string(pvc.UID),
×
1392
                },
×
1393
                {
×
1394
                        Name:  common.InsecureTLSVar,
×
1395
                        Value: "false",
×
1396
                },
×
1397
        }
×
1398
        pod.Spec.Containers[0].Env = env
×
1399
        if volumeMode == corev1.PersistentVolumeBlock {
×
1400
                pod.Spec.Containers[0].VolumeDevices = AddVolumeDevices()
×
1401
        } else {
×
1402
                pod.Spec.Containers[0].VolumeMounts = AddImportVolumeMounts()
×
1403
        }
×
1404

1405
        if scratchPvc != nil {
×
1406
                pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
×
1407
                        Name:      ScratchVolName,
×
1408
                        MountPath: common.ScratchDataDir,
×
1409
                })
×
1410
        }
×
1411

1412
        return pod
×
1413
}
1414

1415
// CreateStorageClassWithProvisioner creates CR of storage class with provisioner
1416
func CreateStorageClassWithProvisioner(name string, annotations, labels map[string]string, provisioner string) *storagev1.StorageClass {
×
1417
        return &storagev1.StorageClass{
×
1418
                Provisioner: provisioner,
×
1419
                ObjectMeta: metav1.ObjectMeta{
×
1420
                        Name:        name,
×
1421
                        Annotations: annotations,
×
1422
                        Labels:      labels,
×
1423
                },
×
1424
        }
×
1425
}
×
1426

1427
// CreateClient creates a fake client
1428
func CreateClient(objs ...runtime.Object) client.Client {
1✔
1429
        s := scheme.Scheme
1✔
1430
        _ = cdiv1.AddToScheme(s)
1✔
1431
        _ = corev1.AddToScheme(s)
1✔
1432
        _ = storagev1.AddToScheme(s)
1✔
1433
        _ = ocpconfigv1.Install(s)
1✔
1434

1✔
1435
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1436
}
1✔
1437

1438
// ErrQuotaExceeded checked is the error is of exceeded quota
1439
func ErrQuotaExceeded(err error) bool {
×
1440
        return strings.Contains(err.Error(), "exceeded quota:")
×
1441
}
×
1442

1443
// GetContentType returns the content type. If invalid or not set, default to kubevirt
1444
func GetContentType(contentType cdiv1.DataVolumeContentType) cdiv1.DataVolumeContentType {
1✔
1445
        switch contentType {
1✔
1446
        case
1447
                cdiv1.DataVolumeKubeVirt,
1448
                cdiv1.DataVolumeArchive:
1✔
1449
        default:
×
1450
                // TODO - shouldn't archive be the default?
×
1451
                contentType = cdiv1.DataVolumeKubeVirt
×
1452
        }
1453
        return contentType
1✔
1454
}
1455

1456
// GetPVCContentType returns the content type of the source image. If invalid or not set, default to kubevirt
1457
func GetPVCContentType(pvc *corev1.PersistentVolumeClaim) cdiv1.DataVolumeContentType {
×
1458
        contentType, found := pvc.Annotations[AnnContentType]
×
1459
        if !found {
×
1460
                // TODO - shouldn't archive be the default?
×
1461
                return cdiv1.DataVolumeKubeVirt
×
1462
        }
×
1463

1464
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1465
}
1466

1467
// GetNamespace returns the given namespace if not empty, otherwise the default namespace
1468
func GetNamespace(namespace, defaultNamespace string) string {
×
1469
        if namespace == "" {
×
1470
                return defaultNamespace
×
1471
        }
×
1472
        return namespace
×
1473
}
1474

1475
// IsErrCacheNotStarted checked is the error is of cache not started
1476
func IsErrCacheNotStarted(err error) bool {
×
1477
        target := &runtimecache.ErrCacheNotStarted{}
×
1478
        return errors.As(err, &target)
×
1479
}
×
1480

1481
// NewImportDataVolume returns new import DataVolume CR
1482
func NewImportDataVolume(name string) *cdiv1.DataVolume {
×
1483
        return &cdiv1.DataVolume{
×
1484
                TypeMeta: metav1.TypeMeta{APIVersion: cdiv1.SchemeGroupVersion.String()},
×
1485
                ObjectMeta: metav1.ObjectMeta{
×
1486
                        Name:      name,
×
1487
                        Namespace: metav1.NamespaceDefault,
×
1488
                        UID:       types.UID(metav1.NamespaceDefault + "-" + name),
×
1489
                },
×
1490
                Spec: cdiv1.DataVolumeSpec{
×
1491
                        Source: &cdiv1.DataVolumeSource{
×
1492
                                HTTP: &cdiv1.DataVolumeSourceHTTP{
×
1493
                                        URL: "http://example.com/data",
×
1494
                                },
×
1495
                        },
×
1496
                        PVC: &corev1.PersistentVolumeClaimSpec{
×
1497
                                AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
×
1498
                        },
×
1499
                        PriorityClassName: "p0",
×
1500
                },
×
1501
        }
×
1502
}
×
1503

1504
// GetCloneSourceInfo returns the type, name and namespace of the cloning source
1505
func GetCloneSourceInfo(dv *cdiv1.DataVolume) (sourceType, sourceName, sourceNamespace string) {
×
1506
        // Cloning sources are mutually exclusive
×
1507
        if dv.Spec.Source.PVC != nil {
×
1508
                return "pvc", dv.Spec.Source.PVC.Name, dv.Spec.Source.PVC.Namespace
×
1509
        }
×
1510

1511
        if dv.Spec.Source.Snapshot != nil {
×
1512
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
×
1513
        }
×
1514

1515
        return "", "", ""
×
1516
}
1517

1518
// IsWaitForFirstConsumerEnabled tells us if we should respect "real" WFFC behavior or just let our worker pods randomly spawn
1519
func IsWaitForFirstConsumerEnabled(obj metav1.Object, gates featuregates.FeatureGates) (bool, error) {
×
1520
        // when PVC requests immediateBinding it cannot honor wffc logic
×
1521
        isImmediateBindingRequested := ImmediateBindingRequested(obj)
×
1522
        pvcHonorWaitForFirstConsumer := !isImmediateBindingRequested
×
1523
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
×
1524
        if err != nil {
×
1525
                return false, err
×
1526
        }
×
1527

1528
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1529
}
1530

1531
// AddImmediateBindingAnnotationIfWFFCDisabled adds the immediateBinding annotation if wffc feature gate is disabled
1532
func AddImmediateBindingAnnotationIfWFFCDisabled(obj metav1.Object, gates featuregates.FeatureGates) error {
×
1533
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
×
1534
        if err != nil {
×
1535
                return err
×
1536
        }
×
1537
        if !globalHonorWaitForFirstConsumer {
×
1538
                AddAnnotation(obj, AnnImmediateBinding, "")
×
1539
        }
×
1540
        return nil
×
1541
}
1542

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

×
1547
        if util.ResolveVolumeMode(pvcSpec.VolumeMode) == corev1.PersistentVolumeFilesystem {
×
1548
                fsOverhead, err := GetFilesystemOverheadForStorageClass(ctx, c, pvcSpec.StorageClassName)
×
1549
                if err != nil {
×
1550
                        return resource.Quantity{}, err
×
1551
                }
×
1552
                // Parse filesystem overhead (percentage) into a 64-bit float
1553
                fsOverheadFloat, _ := strconv.ParseFloat(string(fsOverhead), 64)
×
1554

×
1555
                // Merge the previous values into a 'resource.Quantity' struct
×
1556
                requiredSpace := util.GetRequiredSpace(fsOverheadFloat, imgSize)
×
1557
                returnSize = *resource.NewScaledQuantity(requiredSpace, 0)
×
1558
        } else {
×
1559
                // Inflation is not needed with 'Block' mode
×
1560
                returnSize = *resource.NewScaledQuantity(imgSize, 0)
×
1561
        }
×
1562

1563
        return returnSize, nil
×
1564
}
1565

1566
// IsBound returns if the pvc is bound
1567
func IsBound(pvc *corev1.PersistentVolumeClaim) bool {
×
1568
        return pvc != nil && pvc.Status.Phase == corev1.ClaimBound
×
1569
}
×
1570

1571
// IsUnbound returns if the pvc is not bound yet
1572
func IsUnbound(pvc *corev1.PersistentVolumeClaim) bool {
×
1573
        return !IsBound(pvc)
×
1574
}
×
1575

1576
// IsLost returns if the pvc is lost
1577
func IsLost(pvc *corev1.PersistentVolumeClaim) bool {
×
1578
        return pvc != nil && pvc.Status.Phase == corev1.ClaimLost
×
1579
}
×
1580

1581
// IsImageStream returns true if registry source is ImageStream
1582
func IsImageStream(pvc *corev1.PersistentVolumeClaim) bool {
×
1583
        return pvc.Annotations[AnnRegistryImageStream] == "true"
×
1584
}
×
1585

1586
// ShouldIgnorePod checks if a pod should be ignored.
1587
// If this is a completed pod that was used for one checkpoint of a multi-stage import, it
1588
// should be ignored by pod lookups as long as the retainAfterCompletion annotation is set.
1589
func ShouldIgnorePod(pod *corev1.Pod, pvc *corev1.PersistentVolumeClaim) bool {
×
1590
        retain := pvc.ObjectMeta.Annotations[AnnPodRetainAfterCompletion]
×
1591
        checkpoint := pvc.ObjectMeta.Annotations[AnnCurrentCheckpoint]
×
1592
        if checkpoint != "" && pod.Status.Phase == corev1.PodSucceeded {
×
1593
                return retain == "true"
×
1594
        }
×
1595
        return false
×
1596
}
1597

1598
// BuildHTTPClient generates an http client that accepts any certificate, since we are using
1599
// it to get prometheus data it doesn't matter if someone can intercept the data. Once we have
1600
// a mechanism to properly sign the server, we can update this method to get a proper client.
1601
func BuildHTTPClient(httpClient *http.Client) *http.Client {
×
1602
        if httpClient == nil {
×
1603
                defaultTransport := http.DefaultTransport.(*http.Transport)
×
1604
                // Create new Transport that ignores self-signed SSL
×
1605
                //nolint:gosec
×
1606
                tr := &http.Transport{
×
1607
                        Proxy:                 defaultTransport.Proxy,
×
1608
                        DialContext:           defaultTransport.DialContext,
×
1609
                        MaxIdleConns:          defaultTransport.MaxIdleConns,
×
1610
                        IdleConnTimeout:       defaultTransport.IdleConnTimeout,
×
1611
                        ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
×
1612
                        TLSHandshakeTimeout:   defaultTransport.TLSHandshakeTimeout,
×
1613
                        TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
×
1614
                }
×
1615
                httpClient = &http.Client{
×
1616
                        Transport: tr,
×
1617
                }
×
1618
        }
×
1619
        return httpClient
×
1620
}
1621

1622
// ErrConnectionRefused checks for connection refused errors
1623
func ErrConnectionRefused(err error) bool {
×
1624
        return strings.Contains(err.Error(), "connection refused")
×
1625
}
×
1626

1627
// GetPodMetricsPort returns, if exists, the metrics port from the passed pod
1628
func GetPodMetricsPort(pod *corev1.Pod) (int, error) {
1✔
1629
        for _, container := range pod.Spec.Containers {
2✔
1630
                for _, port := range container.Ports {
2✔
1631
                        if port.Name == "metrics" {
2✔
1632
                                return int(port.ContainerPort), nil
1✔
1633
                        }
1✔
1634
                }
1635
        }
1636
        return 0, errors.New("Metrics port not found in pod")
1✔
1637
}
1638

1639
// GetMetricsURL builds the metrics URL according to the specified pod
1640
func GetMetricsURL(pod *corev1.Pod) (string, error) {
1✔
1641
        if pod == nil {
1✔
1642
                return "", nil
×
1643
        }
×
1644
        port, err := GetPodMetricsPort(pod)
1✔
1645
        if err != nil || pod.Status.PodIP == "" {
2✔
1646
                return "", err
1✔
1647
        }
1✔
1648
        domain := net.JoinHostPort(pod.Status.PodIP, fmt.Sprint(port))
1✔
1649
        url := fmt.Sprintf("https://%s/metrics", domain)
1✔
1650
        return url, nil
1✔
1651
}
1652

1653
// GetProgressReportFromURL fetches the progress report from the passed URL according to an specific metric expression and ownerUID
1654
func GetProgressReportFromURL(ctx context.Context, url string, httpClient *http.Client, metricExp, ownerUID string) (string, error) {
×
1655
        regExp := regexp.MustCompile(fmt.Sprintf("(%s)\\{ownerUID\\=%q\\} (\\d{1,3}\\.?\\d*)", metricExp, ownerUID))
×
1656
        // pod could be gone, don't block an entire thread for 30 seconds
×
1657
        // just to get back an i/o timeout
×
1658
        ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
×
1659
        defer cancel()
×
1660
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
×
1661
        if err != nil {
×
1662
                return "", err
×
1663
        }
×
1664
        resp, err := httpClient.Do(req)
×
1665
        if err != nil {
×
1666
                if ErrConnectionRefused(err) {
×
1667
                        return "", nil
×
1668
                }
×
1669
                return "", err
×
1670
        }
1671
        defer resp.Body.Close()
×
1672
        body, err := io.ReadAll(resp.Body)
×
1673
        if err != nil {
×
1674
                return "", err
×
1675
        }
×
1676

1677
        // Parse the progress from the body
1678
        progressReport := ""
×
1679
        match := regExp.FindStringSubmatch(string(body))
×
1680
        if match != nil {
×
1681
                progressReport = match[len(match)-1]
×
1682
        }
×
1683
        return progressReport, nil
×
1684
}
1685

1686
// UpdateHTTPAnnotations updates the passed annotations for proper http import
1687
func UpdateHTTPAnnotations(annotations map[string]string, http *cdiv1.DataVolumeSourceHTTP) {
×
1688
        annotations[AnnEndpoint] = http.URL
×
1689
        annotations[AnnSource] = SourceHTTP
×
1690

×
1691
        if http.SecretRef != "" {
×
1692
                annotations[AnnSecret] = http.SecretRef
×
1693
        }
×
1694
        if http.CertConfigMap != "" {
×
1695
                annotations[AnnCertConfigMap] = http.CertConfigMap
×
1696
        }
×
1697
        for index, header := range http.ExtraHeaders {
×
1698
                annotations[fmt.Sprintf("%s.%d", AnnExtraHeaders, index)] = header
×
1699
        }
×
1700
        for index, header := range http.SecretExtraHeaders {
×
1701
                annotations[fmt.Sprintf("%s.%d", AnnSecretExtraHeaders, index)] = header
×
1702
        }
×
1703
}
1704

1705
// UpdateS3Annotations updates the passed annotations for proper S3 import
1706
func UpdateS3Annotations(annotations map[string]string, s3 *cdiv1.DataVolumeSourceS3) {
×
1707
        annotations[AnnEndpoint] = s3.URL
×
1708
        annotations[AnnSource] = SourceS3
×
1709
        if s3.SecretRef != "" {
×
1710
                annotations[AnnSecret] = s3.SecretRef
×
1711
        }
×
1712
        if s3.CertConfigMap != "" {
×
1713
                annotations[AnnCertConfigMap] = s3.CertConfigMap
×
1714
        }
×
1715
}
1716

1717
// UpdateGCSAnnotations updates the passed annotations for proper GCS import
1718
func UpdateGCSAnnotations(annotations map[string]string, gcs *cdiv1.DataVolumeSourceGCS) {
×
1719
        annotations[AnnEndpoint] = gcs.URL
×
1720
        annotations[AnnSource] = SourceGCS
×
1721
        if gcs.SecretRef != "" {
×
1722
                annotations[AnnSecret] = gcs.SecretRef
×
1723
        }
×
1724
}
1725

1726
// UpdateRegistryAnnotations updates the passed annotations for proper registry import
1727
func UpdateRegistryAnnotations(annotations map[string]string, registry *cdiv1.DataVolumeSourceRegistry) {
×
1728
        annotations[AnnSource] = SourceRegistry
×
1729
        pullMethod := registry.PullMethod
×
1730
        if pullMethod != nil && *pullMethod != "" {
×
1731
                annotations[AnnRegistryImportMethod] = string(*pullMethod)
×
1732
        }
×
1733
        url := registry.URL
×
1734
        if url != nil && *url != "" {
×
1735
                annotations[AnnEndpoint] = *url
×
1736
        } else {
×
1737
                imageStream := registry.ImageStream
×
1738
                if imageStream != nil && *imageStream != "" {
×
1739
                        annotations[AnnEndpoint] = *imageStream
×
1740
                        annotations[AnnRegistryImageStream] = "true"
×
1741
                }
×
1742
        }
1743
        secretRef := registry.SecretRef
×
1744
        if secretRef != nil && *secretRef != "" {
×
1745
                annotations[AnnSecret] = *secretRef
×
1746
        }
×
1747
        certConfigMap := registry.CertConfigMap
×
1748
        if certConfigMap != nil && *certConfigMap != "" {
×
1749
                annotations[AnnCertConfigMap] = *certConfigMap
×
1750
        }
×
1751

1752
        if registry.Platform != nil && registry.Platform.Architecture != "" {
×
1753
                annotations[AnnRegistryImageArchitecture] = registry.Platform.Architecture
×
1754
        }
×
1755
}
1756

1757
// UpdateVDDKAnnotations updates the passed annotations for proper VDDK import
1758
func UpdateVDDKAnnotations(annotations map[string]string, vddk *cdiv1.DataVolumeSourceVDDK) {
×
1759
        annotations[AnnEndpoint] = vddk.URL
×
1760
        annotations[AnnSource] = SourceVDDK
×
1761
        annotations[AnnSecret] = vddk.SecretRef
×
1762
        annotations[AnnBackingFile] = vddk.BackingFile
×
1763
        annotations[AnnUUID] = vddk.UUID
×
1764
        annotations[AnnThumbprint] = vddk.Thumbprint
×
1765
        if vddk.InitImageURL != "" {
×
1766
                annotations[AnnVddkInitImageURL] = vddk.InitImageURL
×
1767
        }
×
1768
        if vddk.ExtraArgs != "" {
×
1769
                annotations[AnnVddkExtraArgs] = vddk.ExtraArgs
×
1770
        }
×
1771
}
1772

1773
// UpdateImageIOAnnotations updates the passed annotations for proper imageIO import
1774
func UpdateImageIOAnnotations(annotations map[string]string, imageio *cdiv1.DataVolumeSourceImageIO) {
×
1775
        annotations[AnnEndpoint] = imageio.URL
×
1776
        annotations[AnnSource] = SourceImageio
×
1777
        annotations[AnnSecret] = imageio.SecretRef
×
1778
        annotations[AnnCertConfigMap] = imageio.CertConfigMap
×
1779
        annotations[AnnDiskID] = imageio.DiskID
×
1780
}
×
1781

1782
// IsPVBoundToPVC checks if a PV is bound to a specific PVC
1783
func IsPVBoundToPVC(pv *corev1.PersistentVolume, pvc *corev1.PersistentVolumeClaim) bool {
1✔
1784
        claimRef := pv.Spec.ClaimRef
1✔
1785
        return claimRef != nil && claimRef.Name == pvc.Name && claimRef.Namespace == pvc.Namespace && claimRef.UID == pvc.UID
1✔
1786
}
1✔
1787

1788
// Rebind binds the PV of source to target
1789
func Rebind(ctx context.Context, c client.Client, source, target *corev1.PersistentVolumeClaim) error {
1✔
1790
        pv := &corev1.PersistentVolume{
1✔
1791
                ObjectMeta: metav1.ObjectMeta{
1✔
1792
                        Name: source.Spec.VolumeName,
1✔
1793
                },
1✔
1794
        }
1✔
1795

1✔
1796
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
2✔
1797
                return err
1✔
1798
        }
1✔
1799

1800
        // Examine the claimref for the PV and see if it's still bound to PVC'
1801
        if pv.Spec.ClaimRef == nil {
1✔
1802
                return fmt.Errorf("PV %s claimRef is nil", pv.Name)
×
1803
        }
×
1804

1805
        if !IsPVBoundToPVC(pv, source) {
2✔
1806
                // Something is not right if the PV is neither bound to PVC' nor target PVC
1✔
1807
                if !IsPVBoundToPVC(pv, target) {
2✔
1808
                        klog.Errorf("PV bound to unexpected PVC: Could not rebind to target PVC '%s'", target.Name)
1✔
1809
                        return fmt.Errorf("PV %s bound to unexpected claim %s", pv.Name, pv.Spec.ClaimRef.Name)
1✔
1810
                }
1✔
1811
                // our work is done
1812
                return nil
1✔
1813
        }
1814

1815
        // Rebind PVC to target PVC
1816
        pv.Spec.ClaimRef = &corev1.ObjectReference{
1✔
1817
                Namespace:       target.Namespace,
1✔
1818
                Name:            target.Name,
1✔
1819
                UID:             target.UID,
1✔
1820
                ResourceVersion: target.ResourceVersion,
1✔
1821
        }
1✔
1822
        klog.V(3).Info("Rebinding PV to target PVC", "PVC", target.Name)
1✔
1823
        if err := c.Update(context.TODO(), pv); err != nil {
1✔
1824
                return err
×
1825
        }
×
1826

1827
        return nil
1✔
1828
}
1829

1830
// BulkDeleteResources deletes a bunch of resources
1831
func BulkDeleteResources(ctx context.Context, c client.Client, obj client.ObjectList, lo client.ListOption) error {
×
1832
        if err := c.List(ctx, obj, lo); err != nil {
×
1833
                if meta.IsNoMatchError(err) {
×
1834
                        return nil
×
1835
                }
×
1836
                return err
×
1837
        }
1838

1839
        sv := reflect.ValueOf(obj).Elem()
×
1840
        iv := sv.FieldByName("Items")
×
1841

×
1842
        for i := 0; i < iv.Len(); i++ {
×
1843
                obj := iv.Index(i).Addr().Interface().(client.Object)
×
1844
                if obj.GetDeletionTimestamp().IsZero() {
×
1845
                        klog.V(3).Infof("Deleting type %+v %+v", reflect.TypeOf(obj), obj)
×
1846
                        if err := c.Delete(ctx, obj); err != nil {
×
1847
                                return err
×
1848
                        }
×
1849
                }
1850
        }
1851

1852
        return nil
×
1853
}
1854

1855
// ValidateSnapshotCloneSize does proper size validation when doing a clone from snapshot operation
1856
func ValidateSnapshotCloneSize(snapshot *snapshotv1.VolumeSnapshot, pvcSpec *corev1.PersistentVolumeClaimSpec, targetSC *storagev1.StorageClass, log logr.Logger) (bool, error) {
×
1857
        restoreSize := snapshot.Status.RestoreSize
×
1858
        if restoreSize == nil {
×
1859
                return false, fmt.Errorf("snapshot has no RestoreSize")
×
1860
        }
×
1861
        targetRequest, hasTargetRequest := pvcSpec.Resources.Requests[corev1.ResourceStorage]
×
1862
        allowExpansion := targetSC.AllowVolumeExpansion != nil && *targetSC.AllowVolumeExpansion
×
1863
        if hasTargetRequest {
×
1864
                // otherwise will just use restoreSize
×
1865
                if restoreSize.Cmp(targetRequest) < 0 && !allowExpansion {
×
1866
                        log.V(3).Info("Can't expand restored PVC because SC does not allow expansion, need to fall back to host assisted")
×
1867
                        return false, nil
×
1868
                }
×
1869
        }
1870
        return true, nil
×
1871
}
1872

1873
// ValidateSnapshotCloneProvisioners validates the target PVC storage class against the snapshot class provisioner
1874
func ValidateSnapshotCloneProvisioners(vsc *snapshotv1.VolumeSnapshotContent, storageClass *storagev1.StorageClass) (bool, error) {
×
1875
        // Do snapshot and storage class validation
×
1876
        if storageClass == nil {
×
1877
                return false, fmt.Errorf("target storage class not found")
×
1878
        }
×
1879
        if storageClass.Provisioner != vsc.Spec.Driver {
×
1880
                return false, nil
×
1881
        }
×
1882
        // TODO: get sourceVolumeMode from volumesnapshotcontent and validate against target spec
1883
        // currently don't have CRDs in CI with sourceVolumeMode which is pretty new
1884
        // converting volume mode is possible but has security implications
1885
        return true, nil
×
1886
}
1887

1888
// GetSnapshotClassForSmartClone looks up the snapshot class based on the storage class
1889
func GetSnapshotClassForSmartClone(pvc *corev1.PersistentVolumeClaim, targetPvcStorageClassName, snapshotClassName *string, log logr.Logger, client client.Client, recorder record.EventRecorder) (string, error) {
×
1890
        logger := log.WithName("GetSnapshotClassForSmartClone").V(3)
×
1891
        // Check if relevant CRDs are available
×
1892
        if !isCsiCrdsDeployed(client, log) {
×
1893
                logger.Info("Missing CSI snapshotter CRDs, falling back to host assisted clone")
×
1894
                return "", nil
×
1895
        }
×
1896

1897
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(context.TODO(), client, targetPvcStorageClassName)
×
1898
        if err != nil {
×
1899
                return "", err
×
1900
        }
×
1901
        if targetStorageClass == nil {
×
1902
                logger.Info("Target PVC's Storage Class not found")
×
1903
                return "", nil
×
1904
        }
×
1905

1906
        vscName, err := GetVolumeSnapshotClass(context.TODO(), client, pvc, targetStorageClass.Provisioner, snapshotClassName, logger, recorder)
×
1907
        if err != nil {
×
1908
                return "", err
×
1909
        }
×
1910
        if vscName != nil {
×
1911
                if pvc != nil {
×
1912
                        logger.Info("smart-clone is applicable for datavolume", "datavolume",
×
1913
                                pvc.Name, "snapshot class", *vscName)
×
1914
                }
×
1915
                return *vscName, nil
×
1916
        }
1917

1918
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
1919
        return "", nil
×
1920
}
1921

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

×
1927
        logEvent := func(message, vscName string) {
×
1928
                logger.Info(message, "name", vscName)
×
1929
                if pvc != nil {
×
1930
                        msg := fmt.Sprintf("%s %s", message, vscName)
×
1931
                        recorder.Event(pvc, corev1.EventTypeNormal, VolumeSnapshotClassSelected, msg)
×
1932
                }
×
1933
        }
1934

1935
        if snapshotClassName != nil {
×
1936
                vsc := &snapshotv1.VolumeSnapshotClass{}
×
1937
                if err := c.Get(context.TODO(), types.NamespacedName{Name: *snapshotClassName}, vsc); err != nil {
×
1938
                        return nil, err
×
1939
                }
×
1940
                if vsc.Driver == driver {
×
1941
                        logEvent(MessageStorageProfileVolumeSnapshotClassSelected, vsc.Name)
×
1942
                        return snapshotClassName, nil
×
1943
                }
×
1944
                return nil, nil
×
1945
        }
1946

1947
        vscList := &snapshotv1.VolumeSnapshotClassList{}
×
1948
        if err := c.List(ctx, vscList); err != nil {
×
1949
                if meta.IsNoMatchError(err) {
×
1950
                        return nil, nil
×
1951
                }
×
1952
                return nil, err
×
1953
        }
1954

1955
        var candidates []string
×
1956
        for _, vsc := range vscList.Items {
×
1957
                if vsc.Driver == driver {
×
1958
                        if vsc.Annotations[AnnDefaultSnapshotClass] == "true" {
×
1959
                                logEvent(MessageDefaultVolumeSnapshotClassSelected, vsc.Name)
×
1960
                                vscName := vsc.Name
×
1961
                                return &vscName, nil
×
1962
                        }
×
1963
                        candidates = append(candidates, vsc.Name)
×
1964
                }
1965
        }
1966

1967
        if len(candidates) > 0 {
×
1968
                sort.Strings(candidates)
×
1969
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
×
1970
                return &candidates[0], nil
×
1971
        }
×
1972

1973
        return nil, nil
×
1974
}
1975

1976
// isCsiCrdsDeployed checks whether the CSI snapshotter CRD are deployed
1977
func isCsiCrdsDeployed(c client.Client, log logr.Logger) bool {
×
1978
        version := "v1"
×
1979
        vsClass := "volumesnapshotclasses." + snapshotv1.GroupName
×
1980
        vsContent := "volumesnapshotcontents." + snapshotv1.GroupName
×
1981
        vs := "volumesnapshots." + snapshotv1.GroupName
×
1982

×
1983
        return isCrdDeployed(c, vsClass, version, log) &&
×
1984
                isCrdDeployed(c, vsContent, version, log) &&
×
1985
                isCrdDeployed(c, vs, version, log)
×
1986
}
×
1987

1988
// isCrdDeployed checks whether a CRD is deployed
1989
func isCrdDeployed(c client.Client, name, version string, log logr.Logger) bool {
×
1990
        crd := &extv1.CustomResourceDefinition{}
×
1991
        err := c.Get(context.TODO(), types.NamespacedName{Name: name}, crd)
×
1992
        if err != nil {
×
1993
                if !k8serrors.IsNotFound(err) {
×
1994
                        log.Info("Error looking up CRD", "crd name", name, "version", version, "error", err)
×
1995
                }
×
1996
                return false
×
1997
        }
1998

1999
        for _, v := range crd.Spec.Versions {
×
2000
                if v.Name == version && v.Served {
×
2001
                        return true
×
2002
                }
×
2003
        }
2004

2005
        return false
×
2006
}
2007

2008
// IsSnapshotReady indicates if a volume snapshot is ready to be used
2009
func IsSnapshotReady(snapshot *snapshotv1.VolumeSnapshot) bool {
×
2010
        return snapshot.Status != nil && snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse
×
2011
}
×
2012

2013
// GetResource updates given obj with the data of the object with the same name and namespace
2014
func GetResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
×
2015
        obj.SetNamespace(namespace)
×
2016
        obj.SetName(name)
×
2017

×
2018
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
2019
        if err != nil {
×
2020
                if k8serrors.IsNotFound(err) {
×
2021
                        return false, nil
×
2022
                }
×
2023

2024
                return false, err
×
2025
        }
2026

2027
        return true, nil
×
2028
}
2029

2030
// PatchArgs are the args for Patch
2031
type PatchArgs struct {
2032
        Client client.Client
2033
        Log    logr.Logger
2034
        Obj    client.Object
2035
        OldObj client.Object
2036
}
2037

2038
// GetAnnotatedEventSource returns resource referenced by AnnEventSource annotations
2039
func GetAnnotatedEventSource(ctx context.Context, c client.Client, obj client.Object) (client.Object, error) {
×
2040
        esk, ok := obj.GetAnnotations()[AnnEventSourceKind]
×
2041
        if !ok {
×
2042
                return obj, nil
×
2043
        }
×
2044
        if esk != "PersistentVolumeClaim" {
×
2045
                return obj, nil
×
2046
        }
×
2047
        es, ok := obj.GetAnnotations()[AnnEventSource]
×
2048
        if !ok {
×
2049
                return obj, nil
×
2050
        }
×
2051
        namespace, name, err := cache.SplitMetaNamespaceKey(es)
×
2052
        if err != nil {
×
2053
                return nil, err
×
2054
        }
×
2055
        pvc := &corev1.PersistentVolumeClaim{
×
2056
                ObjectMeta: metav1.ObjectMeta{
×
2057
                        Namespace: namespace,
×
2058
                        Name:      name,
×
2059
                },
×
2060
        }
×
2061
        if err := c.Get(ctx, client.ObjectKeyFromObject(pvc), pvc); err != nil {
×
2062
                return nil, err
×
2063
        }
×
2064
        return pvc, nil
×
2065
}
2066

2067
// OwnedByDataVolume returns true if the object is owned by a DataVolume
2068
func OwnedByDataVolume(obj metav1.Object) bool {
×
2069
        owner := metav1.GetControllerOf(obj)
×
2070
        return owner != nil && owner.Kind == "DataVolume"
×
2071
}
×
2072

2073
// CopyAllowedAnnotations copies the allowed annotations from the source object
2074
// to the destination object
2075
func CopyAllowedAnnotations(srcObj, dstObj metav1.Object) {
×
2076
        for ann, def := range allowedAnnotations {
×
2077
                val, ok := srcObj.GetAnnotations()[ann]
×
2078
                if !ok && def != "" {
×
2079
                        val = def
×
2080
                }
×
2081
                if val != "" {
×
2082
                        klog.V(1).Info("Applying annotation", "Name", dstObj.GetName(), ann, val)
×
2083
                        AddAnnotation(dstObj, ann, val)
×
2084
                }
×
2085
        }
2086
}
2087

2088
// CopyAllowedLabels copies allowed labels matching the validLabelsMatch regexp from the
2089
// source map to the destination object allowing overwrites
2090
func CopyAllowedLabels(srcLabels map[string]string, dstObj metav1.Object, overwrite bool) {
1✔
2091
        for label, value := range srcLabels {
2✔
2092
                if _, found := dstObj.GetLabels()[label]; (!found || overwrite) && validLabelsMatch.MatchString(label) {
2✔
2093
                        AddLabel(dstObj, label, value)
1✔
2094
                }
1✔
2095
        }
2096
}
2097

2098
// ClaimMayExistBeforeDataVolume returns true if the PVC may exist before the DataVolume
2099
func ClaimMayExistBeforeDataVolume(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2100
        if ClaimIsPopulatedForDataVolume(pvc, dv) {
×
2101
                return true, nil
×
2102
        }
×
2103
        return AllowClaimAdoption(c, pvc, dv)
×
2104
}
2105

2106
// ClaimIsPopulatedForDataVolume returns true if the PVC is populated for the given DataVolume
2107
func ClaimIsPopulatedForDataVolume(pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) bool {
×
2108
        return pvc != nil && dv != nil && pvc.Annotations[AnnPopulatedFor] == dv.Name
×
2109
}
×
2110

2111
// AllowClaimAdoption returns true if the PVC may be adopted
2112
func AllowClaimAdoption(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2113
        if pvc == nil || dv == nil {
×
2114
                return false, nil
×
2115
        }
×
2116
        anno, ok := pvc.Annotations[AnnCreatedForDataVolume]
×
2117
        if ok && anno == string(dv.UID) {
×
2118
                return false, nil
×
2119
        }
×
2120
        anno, ok = dv.Annotations[AnnAllowClaimAdoption]
×
2121
        // if annotation exists, go with that regardless of featuregate
×
2122
        if ok {
×
2123
                val, _ := strconv.ParseBool(anno)
×
2124
                return val, nil
×
2125
        }
×
2126
        return featuregates.NewFeatureGates(c).ClaimAdoptionEnabled()
×
2127
}
2128

2129
// ResolveDataSourceChain resolves a DataSource reference.
2130
// Returns an error if DataSource reference is not found or
2131
// DataSource reference points to another DataSource
2132
func ResolveDataSourceChain(ctx context.Context, client client.Client, dataSource *cdiv1.DataSource) (*cdiv1.DataSource, error) {
×
2133
        if dataSource.Spec.Source.DataSource == nil {
×
2134
                return dataSource, nil
×
2135
        }
×
2136

2137
        ref := dataSource.Spec.Source.DataSource
×
2138
        refNs := GetNamespace(ref.Namespace, dataSource.Namespace)
×
2139
        if dataSource.Namespace != refNs {
×
2140
                return dataSource, ErrDataSourceCrossNamespace
×
2141
        }
×
2142
        if ref.Name == dataSource.Name && refNs == dataSource.Namespace {
×
2143
                return nil, ErrDataSourceSelfReference
×
2144
        }
×
2145

2146
        resolved := &cdiv1.DataSource{}
×
2147
        if err := client.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: refNs}, resolved); err != nil {
×
2148
                return nil, err
×
2149
        }
×
2150

2151
        if resolved.Spec.Source.DataSource != nil {
×
2152
                return nil, ErrDataSourceMaxDepthReached
×
2153
        }
×
2154

2155
        return resolved, nil
×
2156
}
2157

2158
func sortEvents(events *corev1.EventList, usingPopulator bool, pvcPrimeName string) {
1✔
2159
        // Sort event lists by containing primeName substring and most recent timestamp
1✔
2160
        sort.Slice(events.Items, func(i, j int) bool {
2✔
2161
                if usingPopulator {
2✔
2162
                        firstContainsPrime := strings.Contains(events.Items[i].Message, pvcPrimeName)
1✔
2163
                        secondContainsPrime := strings.Contains(events.Items[j].Message, pvcPrimeName)
1✔
2164

1✔
2165
                        if firstContainsPrime && !secondContainsPrime {
2✔
2166
                                return true
1✔
2167
                        }
1✔
2168
                        if !firstContainsPrime && secondContainsPrime {
2✔
2169
                                return false
1✔
2170
                        }
1✔
2171
                }
2172

2173
                // if the timestamps are the same, prioritze longer messages to make sure our sorting is deterministic
2174
                if events.Items[i].LastTimestamp.Time.Equal(events.Items[j].LastTimestamp.Time) {
1✔
2175
                        return len(events.Items[i].Message) > len(events.Items[j].Message)
×
2176
                }
×
2177

2178
                // if both contains primeName substring or neither, just sort on timestamp
2179
                return events.Items[i].LastTimestamp.Time.After(events.Items[j].LastTimestamp.Time)
1✔
2180
        })
2181
}
2182

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

×
2188
        anno := pvc.GetAnnotations()
×
2189
        if anno == nil {
×
2190
                return nil
×
2191
        }
×
2192

2193
        if IsBound(pvc) {
×
2194
                anno := pvc.GetAnnotations()
×
2195
                delete(anno, AnnBoundCondition)
×
2196
                delete(anno, AnnBoundConditionReason)
×
2197
                delete(anno, AnnBoundConditionMessage)
×
2198

×
2199
                if !reflect.DeepEqual(currentPvcCopy, pvc) {
×
2200
                        patch := client.MergeFrom(currentPvcCopy)
×
2201
                        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2202
                                return err
×
2203
                        }
×
2204
                }
2205

2206
                return nil
×
2207
        }
2208

2209
        if pvc.Status.Phase != corev1.ClaimPending {
×
2210
                return nil
×
2211
        }
×
2212

2213
        // set bound condition by getting the latest event
2214
        events := &corev1.EventList{}
×
2215

×
2216
        err := c.List(context.TODO(), events,
×
2217
                client.InNamespace(pvc.GetNamespace()),
×
2218
                client.MatchingFields{"involvedObject.name": pvc.GetName(),
×
2219
                        "involvedObject.uid": string(pvc.GetUID())},
×
2220
        )
×
2221

×
2222
        if err != nil {
×
2223
                // Log the error but don't fail the reconciliation
×
2224
                log.Error(err, "Unable to list events for PVC bound condition update", "pvc", pvc.Name)
×
2225
                return nil
×
2226
        }
×
2227

2228
        if len(events.Items) == 0 {
×
2229
                return nil
×
2230
        }
×
2231

2232
        pvcPrime, usingPopulator := anno[AnnPVCPrimeName]
×
2233

×
2234
        // Sort event lists by containing primeName substring and most recent timestamp
×
2235
        sortEvents(events, usingPopulator, pvcPrime)
×
2236

×
2237
        boundMessage := ""
×
2238
        // check if prime name annotation exists
×
2239
        if usingPopulator {
×
2240
                // if we are using populators get the latest event from prime pvc
×
2241
                pvcPrime = fmt.Sprintf("[%s] : ", pvcPrime)
×
2242

×
2243
                // if the first event does not contain a prime message, none will so return
×
2244
                primeIdx := strings.Index(events.Items[0].Message, pvcPrime)
×
2245
                if primeIdx == -1 {
×
2246
                        log.V(1).Info("No bound message found, skipping bound condition update", "pvc", pvc.Name)
×
2247
                        return nil
×
2248
                }
×
2249
                boundMessage = events.Items[0].Message[primeIdx+len(pvcPrime):]
×
2250
        } else {
×
2251
                // if not using populators just get the latest event
×
2252
                boundMessage = events.Items[0].Message
×
2253
        }
×
2254

2255
        // since we checked status of phase above, we know this is pending
2256
        anno[AnnBoundCondition] = "false"
×
2257
        anno[AnnBoundConditionReason] = "Pending"
×
2258
        anno[AnnBoundConditionMessage] = boundMessage
×
2259

×
2260
        patch := client.MergeFrom(currentPvcCopy)
×
2261
        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2262
                return err
×
2263
        }
×
2264

2265
        return nil
×
2266
}
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