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

kubevirt / containerized-data-importer / #5517

05 Aug 2025 09:50AM UTC coverage: 59.26% (-0.03%) from 59.294%
#5517

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

16 of 53 new or added lines in 4 files covered. (30.19%)

348 existing lines in 4 files now uncovered.

17174 of 28981 relevant lines covered (59.26%)

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 {
×
NEW
1108
                return nil, err
×
NEW
1109
        }
×
NEW
1110
        if sc == nil {
×
NEW
1111
                return &storageResources, nil
×
NEW
1112
        }
×
1113

NEW
1114
        storageProfile := &cdiv1.StorageProfile{}
×
NEW
1115
        if err := client.Get(ctx, types.NamespacedName{Name: sc.Name}, storageProfile); err != nil {
×
NEW
1116
                if k8serrors.IsNotFound(err) {
×
NEW
1117
                        return &storageResources, nil
×
NEW
1118
                }
×
NEW
1119
                return nil, err
×
1120
        }
1121

NEW
1122
        requestedSize, hasSize := storageResources.Requests[corev1.ResourceStorage]
×
NEW
1123
        if !hasSize {
×
NEW
1124
                return &storageResources, nil
×
NEW
1125
        }
×
1126

NEW
1127
        if val, exists := storageProfile.Annotations[AnnMinimumSupportedPVCSize]; exists {
×
NEW
1128
                if minSize, err := resource.ParseQuantity(val); err == nil {
×
NEW
1129
                        if requestedSize.Cmp(minSize) == -1 {
×
NEW
1130
                                return &corev1.VolumeResourceRequirements{
×
NEW
1131
                                        Requests: corev1.ResourceList{
×
NEW
1132
                                                corev1.ResourceStorage: requestedSize,
×
NEW
1133
                                        },
×
NEW
1134
                                }, nil
×
NEW
1135
                        }
×
NEW
1136
                } else {
×
NEW
1137
                        log.V(1).Info("Invalid minimum PVC size in annotation", "value", val, "error", err)
×
NEW
1138
                }
×
1139
        }
1140

NEW
1141
        return &storageResources, nil
×
1142
}
1143

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

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

1159
// CreateCloneSourcePodName creates clone source pod name
UNCOV
1160
func CreateCloneSourcePodName(targetPvc *corev1.PersistentVolumeClaim) string {
×
UNCOV
1161
        return string(targetPvc.GetUID()) + common.ClonerSourcePodNameSuffix
×
UNCOV
1162
}
×
1163

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

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

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

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

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

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

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

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

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

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

×
1288
        blockOwnerDeletion := true
×
1289
        isController := true
×
1290

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

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

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

×
1363
        ep, _ := GetEndpoint(pvc)
×
1364
        source := GetSource(pvc)
×
1365
        contentType := GetPVCContentType(pvc)
×
1366
        imageSize, _ := GetRequestedImageSize(pvc)
×
1367
        volumeMode := GetVolumeMode(pvc)
×
1368

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

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

1409
        return pod
×
1410
}
1411

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

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

1✔
1432
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1433
}
1✔
1434

1435
// ErrQuotaExceeded checked is the error is of exceeded quota
UNCOV
1436
func ErrQuotaExceeded(err error) bool {
×
UNCOV
1437
        return strings.Contains(err.Error(), "exceeded quota:")
×
UNCOV
1438
}
×
1439

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

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

1461
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1462
}
1463

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

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

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

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

1508
        if dv.Spec.Source.Snapshot != nil {
×
1509
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
×
UNCOV
1510
        }
×
1511

1512
        return "", "", ""
×
1513
}
1514

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

1525
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1526
}
1527

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

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

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

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

1560
        return returnSize, nil
×
1561
}
1562

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

1568
// IsUnbound returns if the pvc is not bound yet
1569
func IsUnbound(pvc *corev1.PersistentVolumeClaim) bool {
×
UNCOV
1570
        return !IsBound(pvc)
×
UNCOV
1571
}
×
1572

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

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

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

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

1619
// ErrConnectionRefused checks for connection refused errors
UNCOV
1620
func ErrConnectionRefused(err error) bool {
×
UNCOV
1621
        return strings.Contains(err.Error(), "connection refused")
×
UNCOV
1622
}
×
1623

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

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

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

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

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

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

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

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

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

1749
        if registry.Platform != nil && registry.Platform.Architecture != "" {
×
1750
                annotations[AnnRegistryImageArchitecture] = registry.Platform.Architecture
×
UNCOV
1751
        }
×
1752
}
1753

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

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

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

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

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

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

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

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

1824
        return nil
1✔
1825
}
1826

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

1836
        sv := reflect.ValueOf(obj).Elem()
×
UNCOV
1837
        iv := sv.FieldByName("Items")
×
UNCOV
1838

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

UNCOV
1849
        return nil
×
1850
}
1851

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

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

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

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

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

1915
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
UNCOV
1916
        return "", nil
×
1917
}
1918

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

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

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

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

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

UNCOV
1964
        if len(candidates) > 0 {
×
UNCOV
1965
                sort.Strings(candidates)
×
UNCOV
1966
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
×
1967
                return &candidates[0], nil
×
1968
        }
×
1969

1970
        return nil, nil
×
1971
}
1972

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

×
1980
        return isCrdDeployed(c, vsClass, version, log) &&
×
1981
                isCrdDeployed(c, vsContent, version, log) &&
×
1982
                isCrdDeployed(c, vs, version, log)
×
1983
}
×
1984

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

1996
        for _, v := range crd.Spec.Versions {
×
UNCOV
1997
                if v.Name == version && v.Served {
×
UNCOV
1998
                        return true
×
1999
                }
×
2000
        }
2001

2002
        return false
×
2003
}
2004

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

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

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

2021
                return false, err
×
2022
        }
2023

2024
        return true, nil
×
2025
}
2026

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

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

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

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

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

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

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

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

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

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

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

2148
        if resolved.Spec.Source.DataSource != nil {
×
2149
                return nil, ErrDataSourceMaxDepthReached
×
UNCOV
2150
        }
×
2151

2152
        return resolved, nil
×
2153
}
2154

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

1✔
2162
                        if firstContainsPrime && !secondContainsPrime {
2✔
2163
                                return true
1✔
2164
                        }
1✔
2165
                        if !firstContainsPrime && secondContainsPrime {
2✔
2166
                                return false
1✔
2167
                        }
1✔
2168
                }
2169

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

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

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

×
2185
        anno := pvc.GetAnnotations()
×
2186
        if anno == nil {
×
2187
                return nil
×
2188
        }
×
2189

2190
        if IsBound(pvc) {
×
2191
                anno := pvc.GetAnnotations()
×
UNCOV
2192
                delete(anno, AnnBoundCondition)
×
2193
                delete(anno, AnnBoundConditionReason)
×
2194
                delete(anno, AnnBoundConditionMessage)
×
2195

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

2203
                return nil
×
2204
        }
2205

2206
        if pvc.Status.Phase != corev1.ClaimPending {
×
UNCOV
2207
                return nil
×
UNCOV
2208
        }
×
2209

2210
        // set bound condition by getting the latest event
2211
        events := &corev1.EventList{}
×
UNCOV
2212

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

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

2225
        if len(events.Items) == 0 {
×
2226
                return nil
×
UNCOV
2227
        }
×
2228

2229
        pvcPrime, usingPopulator := anno[AnnPVCPrimeName]
×
2230

×
UNCOV
2231
        // Sort event lists by containing primeName substring and most recent timestamp
×
2232
        sortEvents(events, usingPopulator, pvcPrime)
×
2233

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

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

2252
        // since we checked status of phase above, we know this is pending
2253
        anno[AnnBoundCondition] = "false"
×
UNCOV
2254
        anno[AnnBoundConditionReason] = "Pending"
×
UNCOV
2255
        anno[AnnBoundConditionMessage] = boundMessage
×
2256

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

2262
        return nil
×
2263
}
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