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

kubevirt / containerized-data-importer / #5366

04 Jun 2025 02:52PM UTC coverage: 59.4%. First build
#5366

push

travis-ci

web-flow
Bump kvci to avoid loopback NFS (#3694)

* Bump kvci for "external" NFS setup

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* automation: define KUBEVIRT_NFS_DIR

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* address istio deployment changes

we can remove the non PSA supporting band aid,
and apparently to work with istio post PSA they rely
on pod level seccomp profile (as opposed to container level one)

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* archive upload tests: drop check for sparseness

for some reason, on xfs backe nfs, it takes like 3 minutes
for the size on disk to drop down.
I suspect it's the dynamic speculative EOF feature.
I don't think we're particularly interested in extracted
archive sparseness anyway.

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* static allocated pvc test: drop sparseness check

the sparseness check doesn't make sense here since the
test is using a preallocated volume

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

* fix cache mode ENOENT handling

in our context ENOENT is okay and just means
we're dealing with a filesystem

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

---------

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>

2 of 10 new or added lines in 2 files covered. (20.0%)

16919 of 28483 relevant lines covered (59.4%)

0.66 hits per line

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

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

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

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

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

17
package common
18

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

205
        // AnnUploadRequest marks that a PVC should be made available for upload
206
        AnnUploadRequest = AnnAPIGroup + "/storage.upload.target"
207

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

212
        // AnnPersistentVolumeList is an annotation storing a list of PV names
213
        AnnPersistentVolumeList = AnnAPIGroup + "/storage.persistentVolumeList"
214

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

221
        // AnnMinimumSupportedPVCSize annotation on a StorageProfile specifies its minimum supported PVC size
222
        AnnMinimumSupportedPVCSize = AnnAPIGroup + "/minimumSupportedPvcSize"
223

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

231
        // AnnSourceVolumeMode is the volume mode of the source PVC specified as an annotation on snapshots
232
        AnnSourceVolumeMode = AnnAPIGroup + "/storage.import.sourceVolumeMode"
233

234
        // AnnOpenShiftImageLookup is the annotation for OpenShift image stream lookup
235
        AnnOpenShiftImageLookup = "alpha.image.policy.openshift.io/resolve-names"
236

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

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

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

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

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

265
        // CloneSourceInUse is reason for event created when clone source pvc is in use
266
        CloneSourceInUse = "CloneSourceInUse"
267

268
        // CloneComplete message
269
        CloneComplete = "Clone Complete"
270

271
        cloneTokenLeeway = 10 * time.Second
272

273
        // Default value for preallocation option if not defined in DV or CDIConfig
274
        defaultPreallocation = false
275

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

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

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

313
        // ClaimLost reason const
314
        ClaimLost = "ClaimLost"
315
        // NotFound reason const
316
        NotFound = "NotFound"
317

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

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

331
        // LabelExcludeFromVeleroBackup provides a const to indicate whether an object should be excluded from velero backup
332
        LabelExcludeFromVeleroBackup = "velero.io/exclude-from-backup"
333

334
        // ProgressDone this means we are DONE
335
        ProgressDone = "100.0%"
336

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

342
        // AnnAllowClaimAdoption is the annotation that allows a claim to be adopted by a DataVolume
343
        AnnAllowClaimAdoption = AnnAPIGroup + "/allowClaimAdoption"
344

345
        // AnnCdiCustomizeComponentHash annotation is a hash of all customizations that live under spec.CustomizeComponents
346
        AnnCdiCustomizeComponentHash = AnnAPIGroup + "/customizer-identifier"
347

348
        // AnnCreatedForDataVolume stores the UID of the datavolume that the PVC was created for
349
        AnnCreatedForDataVolume = AnnAPIGroup + "/createdForDataVolume"
350
)
351

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

362
var (
363
        // BlockMode is raw block device mode
364
        BlockMode = corev1.PersistentVolumeBlock
365
        // FilesystemMode is filesystem device mode
366
        FilesystemMode = corev1.PersistentVolumeFilesystem
367

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

376
        apiServerKeyOnce sync.Once
377
        apiServerKey     *rsa.PrivateKey
378

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

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

392
// FakeValidator is a fake token validator
393
type FakeValidator struct {
394
        Match     string
395
        Operation token.Operation
396
        Name      string
397
        Namespace string
398
        Resource  metav1.GroupVersionResource
399
        Params    map[string]string
400
}
401

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

419
// MultiTokenValidator is a token validator that can validate both short and long tokens
420
type MultiTokenValidator struct {
421
        ShortTokenValidator token.Validator
422
        LongTokenValidator  token.Validator
423
}
424

425
// ValidatePVC validates a PVC
426
func (mtv *MultiTokenValidator) ValidatePVC(source, target *corev1.PersistentVolumeClaim) error {
×
427
        tok, v := mtv.getTokenAndValidator(target)
×
428
        return ValidateCloneTokenPVC(tok, v, source, target)
×
429
}
×
430

431
// ValidatePopulator valades a token for a populator
432
func (mtv *MultiTokenValidator) ValidatePopulator(vcs *cdiv1.VolumeCloneSource, pvc *corev1.PersistentVolumeClaim) error {
×
433
        if vcs.Namespace == pvc.Namespace {
×
434
                return nil
×
435
        }
×
436

437
        tok, v := mtv.getTokenAndValidator(pvc)
×
438

×
439
        tokenData, err := v.Validate(tok)
×
440
        if err != nil {
×
441
                return errors.Wrap(err, "error verifying token")
×
442
        }
×
443

444
        var tokenResourceName string
×
445
        switch vcs.Spec.Source.Kind {
×
446
        case "PersistentVolumeClaim":
×
447
                tokenResourceName = "persistentvolumeclaims"
×
448
        case "VolumeSnapshot":
×
449
                tokenResourceName = "volumesnapshots"
×
450
        }
451
        srcName := vcs.Spec.Source.Name
×
452

×
453
        return validateTokenData(tokenData, vcs.Namespace, srcName, pvc.Namespace, pvc.Name, string(pvc.UID), tokenResourceName)
×
454
}
455

456
func (mtv *MultiTokenValidator) getTokenAndValidator(pvc *corev1.PersistentVolumeClaim) (string, token.Validator) {
×
457
        v := mtv.LongTokenValidator
×
458
        tok, ok := pvc.Annotations[AnnExtendedCloneToken]
×
459
        if !ok {
×
460
                // if token doesn't exist, no prob for same namespace
×
461
                tok = pvc.Annotations[AnnCloneToken]
×
462
                v = mtv.ShortTokenValidator
×
463
        }
×
464
        return tok, v
×
465
}
466

467
// NewMultiTokenValidator returns a new multi token validator
468
func NewMultiTokenValidator(key *rsa.PublicKey) *MultiTokenValidator {
×
469
        return &MultiTokenValidator{
×
470
                ShortTokenValidator: NewCloneTokenValidator(common.CloneTokenIssuer, key),
×
471
                LongTokenValidator:  NewCloneTokenValidator(common.ExtendedCloneTokenIssuer, key),
×
472
        }
×
473
}
×
474

475
// NewCloneTokenValidator returns a new token validator
476
func NewCloneTokenValidator(issuer string, key *rsa.PublicKey) token.Validator {
×
477
        return token.NewValidator(issuer, key, cloneTokenLeeway)
×
478
}
×
479

480
// GetRequestedImageSize returns the PVC requested size
481
func GetRequestedImageSize(pvc *corev1.PersistentVolumeClaim) (string, error) {
1✔
482
        pvcSize, found := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
1✔
483
        if !found {
2✔
484
                return "", errors.Errorf("storage request is missing in pvc \"%s/%s\"", pvc.Namespace, pvc.Name)
1✔
485
        }
1✔
486
        return pvcSize.String(), nil
1✔
487
}
488

489
// GetVolumeMode returns the volumeMode from PVC handling default empty value
490
func GetVolumeMode(pvc *corev1.PersistentVolumeClaim) corev1.PersistentVolumeMode {
×
491
        return util.ResolveVolumeMode(pvc.Spec.VolumeMode)
×
492
}
×
493

494
// IsDataVolumeUsingDefaultStorageClass checks if the DataVolume is using the default StorageClass
495
func IsDataVolumeUsingDefaultStorageClass(dv *cdiv1.DataVolume) bool {
×
496
        return GetStorageClassFromDVSpec(dv) == nil
×
497
}
×
498

499
// GetStorageClassFromDVSpec returns the StorageClassName from DataVolume PVC or Storage spec
500
func GetStorageClassFromDVSpec(dv *cdiv1.DataVolume) *string {
×
501
        if dv.Spec.PVC != nil {
×
502
                return dv.Spec.PVC.StorageClassName
×
503
        }
×
504

505
        if dv.Spec.Storage != nil {
×
506
                return dv.Spec.Storage.StorageClassName
×
507
        }
×
508

509
        return nil
×
510
}
511

512
// getStorageClassByName looks up the storage class based on the name.
513
// If name is nil, it performs fallback to default according to the provided content type
514
// If no storage class is found, returns nil
515
func getStorageClassByName(ctx context.Context, client client.Client, name *string, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
1✔
516
        if name == nil {
2✔
517
                return getFallbackStorageClass(ctx, client, contentType)
1✔
518
        }
1✔
519

520
        // look up storage class by name
521
        storageClass := &storagev1.StorageClass{}
×
522
        if err := client.Get(ctx, types.NamespacedName{Name: *name}, storageClass); err != nil {
×
523
                if k8serrors.IsNotFound(err) {
×
524
                        return nil, nil
×
525
                }
×
526
                klog.V(3).Info("Unable to retrieve storage class", "storage class name", *name)
×
527
                return nil, errors.Errorf("unable to retrieve storage class %s", *name)
×
528
        }
529

530
        return storageClass, nil
×
531
}
532

533
// GetStorageClassByNameWithK8sFallback looks up the storage class based on the name
534
// If name is nil, it looks for the default k8s storage class storageclass.kubernetes.io/is-default-class
535
// If no storage class is found, returns nil
536
func GetStorageClassByNameWithK8sFallback(ctx context.Context, client client.Client, name *string) (*storagev1.StorageClass, error) {
1✔
537
        return getStorageClassByName(ctx, client, name, cdiv1.DataVolumeArchive)
1✔
538
}
1✔
539

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

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

558
        if GetContentType(contentType) == cdiv1.DataVolumeKubeVirt {
2✔
559
                if virtSc := GetPlatformDefaultStorageClass(storageClasses, AnnDefaultVirtStorageClass); virtSc != nil {
2✔
560
                        return virtSc, nil
1✔
561
                }
1✔
562
        }
563
        return GetPlatformDefaultStorageClass(storageClasses, AnnDefaultStorageClass), nil
1✔
564
}
565

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

1✔
570
        for _, storageClass := range storageClasses.Items {
2✔
571
                if storageClass.Annotations[defaultAnnotationKey] == "true" {
2✔
572
                        defaultClasses = append(defaultClasses, storageClass)
1✔
573
                }
1✔
574
        }
575

576
        if len(defaultClasses) == 0 {
2✔
577
                return nil
1✔
578
        }
1✔
579

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

594
        return &defaultClasses[0]
1✔
595
}
596

597
// GetFilesystemOverheadForStorageClass determines the filesystem overhead defined in CDIConfig for the storageClass.
598
func GetFilesystemOverheadForStorageClass(ctx context.Context, client client.Client, storageClassName *string) (cdiv1.Percent, error) {
×
599
        if storageClassName != nil && *storageClassName == "" {
×
600
                klog.V(3).Info("No storage class name passed")
×
601
                return "0", nil
×
602
        }
×
603

604
        cdiConfig := &cdiv1.CDIConfig{}
×
605
        if err := client.Get(ctx, types.NamespacedName{Name: common.ConfigName}, cdiConfig); err != nil {
×
606
                if k8serrors.IsNotFound(err) {
×
607
                        klog.V(1).Info("CDIConfig does not exist, pod will not start until it does")
×
608
                        return "0", nil
×
609
                }
×
610
                return "0", err
×
611
        }
612

613
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(ctx, client, storageClassName)
×
614
        if err != nil || targetStorageClass == nil {
×
615
                klog.V(3).Info("Storage class", storageClassName, "not found, trying default storage class")
×
616
                targetStorageClass, err = GetStorageClassByNameWithK8sFallback(ctx, client, nil)
×
617
                if err != nil {
×
618
                        klog.V(3).Info("No default storage class found, continuing with global overhead")
×
619
                        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
620
                }
×
621
        }
622

623
        if cdiConfig.Status.FilesystemOverhead == nil {
×
624
                klog.Errorf("CDIConfig filesystemOverhead used before config controller ran reconcile. Hopefully this only happens during unit testing.")
×
625
                return "0", nil
×
626
        }
×
627

628
        if targetStorageClass == nil {
×
629
                klog.V(3).Info("Storage class", storageClassName, "not found, continuing with global overhead")
×
630
                return cdiConfig.Status.FilesystemOverhead.Global, nil
×
631
        }
×
632

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

×
635
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
×
636

×
637
        storageClassOverhead, found := perStorageConfig[targetStorageClass.GetName()]
×
638
        if found {
×
639
                return storageClassOverhead, nil
×
640
        }
×
641

642
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
643
}
644

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

653
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
654
}
655

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

664
        return cdiconfig.Status.ImagePullSecrets, nil
×
665
}
666

667
// GetPodFromPvc determines the pod associated with the pvc passed in.
668
func GetPodFromPvc(c client.Client, namespace string, pvc *corev1.PersistentVolumeClaim) (*corev1.Pod, error) {
×
669
        l, _ := labels.Parse(common.PrometheusLabelKey)
×
670
        pods := &corev1.PodList{}
×
671
        listOptions := client.ListOptions{
×
672
                LabelSelector: l,
×
673
        }
×
674
        if err := c.List(context.TODO(), pods, &listOptions); err != nil {
×
675
                return nil, err
×
676
        }
×
677

678
        pvcUID := pvc.GetUID()
×
679
        for _, pod := range pods.Items {
×
680
                if ShouldIgnorePod(&pod, pvc) {
×
681
                        continue
×
682
                }
683
                for _, or := range pod.OwnerReferences {
×
684
                        if or.UID == pvcUID {
×
685
                                return &pod, nil
×
686
                        }
×
687
                }
688

689
                // TODO: check this
690
                val, exists := pod.Labels[CloneUniqueID]
×
691
                if exists && val == string(pvcUID)+common.ClonerSourcePodNameSuffix {
×
692
                        return &pod, nil
×
693
                }
×
694
        }
695
        return nil, errors.Errorf("Unable to find pod owned by UID: %s, in namespace: %s", string(pvcUID), namespace)
×
696
}
697

698
// AddVolumeDevices returns VolumeDevice slice with one block device for pods using PV with block volume mode
699
func AddVolumeDevices() []corev1.VolumeDevice {
×
700
        volumeDevices := []corev1.VolumeDevice{
×
701
                {
×
702
                        Name:       DataVolName,
×
703
                        DevicePath: common.WriteBlockPath,
×
704
                },
×
705
        }
×
706
        return volumeDevices
×
707
}
×
708

709
// GetPodsUsingPVCs returns Pods currently using PVCs
710
func GetPodsUsingPVCs(ctx context.Context, c client.Client, namespace string, names sets.Set[string], allowReadOnly bool) ([]corev1.Pod, error) {
×
711
        pl := &corev1.PodList{}
×
712
        // hopefully using cached client here
×
713
        err := c.List(ctx, pl, &client.ListOptions{Namespace: namespace})
×
714
        if err != nil {
×
715
                return nil, err
×
716
        }
×
717

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

766
        return pods, nil
×
767
}
768

769
// GetWorkloadNodePlacement extracts the workload-specific nodeplacement values from the CDI CR
770
func GetWorkloadNodePlacement(ctx context.Context, c client.Client) (*sdkapi.NodePlacement, error) {
×
771
        cr, err := GetActiveCDI(ctx, c)
×
772
        if err != nil {
×
773
                return nil, err
×
774
        }
×
775

776
        if cr == nil {
×
777
                return nil, fmt.Errorf("no active CDI")
×
778
        }
×
779

780
        return &cr.Spec.Workloads, nil
×
781
}
782

783
// GetActiveCDI returns the active CDI CR
784
func GetActiveCDI(ctx context.Context, c client.Client) (*cdiv1.CDI, error) {
1✔
785
        crList := &cdiv1.CDIList{}
1✔
786
        if err := c.List(ctx, crList, &client.ListOptions{}); err != nil {
1✔
787
                return nil, err
×
788
        }
×
789

790
        if len(crList.Items) == 0 {
2✔
791
                return nil, nil
1✔
792
        }
1✔
793

794
        if len(crList.Items) == 1 {
2✔
795
                return &crList.Items[0], nil
1✔
796
        }
1✔
797

798
        var activeResources []cdiv1.CDI
1✔
799
        for _, cr := range crList.Items {
2✔
800
                if cr.Status.Phase != sdkapi.PhaseError {
2✔
801
                        activeResources = append(activeResources, cr)
1✔
802
                }
1✔
803
        }
804

805
        if len(activeResources) != 1 {
2✔
806
                return nil, fmt.Errorf("invalid number of active CDI resources: %d", len(activeResources))
1✔
807
        }
1✔
808

809
        return &activeResources[0], nil
1✔
810
}
811

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

821
// GetPreallocation returns the preallocation setting for the specified object (DV or VolumeImportSource), falling back to StorageClass and global setting (in this order)
822
func GetPreallocation(ctx context.Context, client client.Client, preallocation *bool) bool {
×
823
        // First, the DV's preallocation
×
824
        if preallocation != nil {
×
825
                return *preallocation
×
826
        }
×
827

828
        cdiconfig := &cdiv1.CDIConfig{}
×
829
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
×
830
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
831
                return defaultPreallocation
×
832
        }
×
833

834
        return cdiconfig.Status.Preallocation
×
835
}
836

837
// ImmediateBindingRequested returns if an object has the ImmediateBinding annotation
838
func ImmediateBindingRequested(obj metav1.Object) bool {
×
839
        _, isImmediateBindingRequested := obj.GetAnnotations()[AnnImmediateBinding]
×
840
        return isImmediateBindingRequested
×
841
}
×
842

843
// GetPriorityClass gets PVC priority class
844
func GetPriorityClass(pvc *corev1.PersistentVolumeClaim) string {
×
845
        anno := pvc.GetAnnotations()
×
846
        return anno[AnnPriorityClassName]
×
847
}
×
848

849
// ShouldDeletePod returns whether the PVC workload pod should be deleted
850
func ShouldDeletePod(pvc *corev1.PersistentVolumeClaim) bool {
×
851
        return pvc.GetAnnotations()[AnnPodRetainAfterCompletion] != "true" || pvc.GetAnnotations()[AnnRequiresScratch] == "true" || pvc.GetAnnotations()[AnnRequiresDirectIO] == "true" || pvc.DeletionTimestamp != nil
×
852
}
×
853

854
// AddFinalizer adds a finalizer to a resource
855
func AddFinalizer(obj metav1.Object, name string) {
×
856
        if HasFinalizer(obj, name) {
×
857
                return
×
858
        }
×
859

860
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
861
}
862

863
// RemoveFinalizer removes a finalizer from a resource
864
func RemoveFinalizer(obj metav1.Object, name string) {
×
865
        if !HasFinalizer(obj, name) {
×
866
                return
×
867
        }
×
868

869
        var finalizers []string
×
870
        for _, f := range obj.GetFinalizers() {
×
871
                if f != name {
×
872
                        finalizers = append(finalizers, f)
×
873
                }
×
874
        }
875

876
        obj.SetFinalizers(finalizers)
×
877
}
878

879
// HasFinalizer returns true if a resource has a specific finalizer
880
func HasFinalizer(object metav1.Object, value string) bool {
×
881
        for _, f := range object.GetFinalizers() {
×
882
                if f == value {
×
883
                        return true
×
884
                }
×
885
        }
886
        return false
×
887
}
888

889
// ValidateCloneTokenPVC validates clone token for source and target PVCs
890
func ValidateCloneTokenPVC(t string, v token.Validator, source, target *corev1.PersistentVolumeClaim) error {
×
891
        if source.Namespace == target.Namespace {
×
892
                return nil
×
893
        }
×
894

895
        tokenData, err := v.Validate(t)
×
896
        if err != nil {
×
897
                return errors.Wrap(err, "error verifying token")
×
898
        }
×
899

900
        tokenResourceName := getTokenResourceNamePvc(source)
×
901
        srcName := getSourceNamePvc(source)
×
902

×
903
        return validateTokenData(tokenData, source.Namespace, srcName, target.Namespace, target.Name, string(target.UID), tokenResourceName)
×
904
}
905

906
// ValidateCloneTokenDV validates clone token for DV
907
func ValidateCloneTokenDV(validator token.Validator, dv *cdiv1.DataVolume) error {
×
908
        _, sourceName, sourceNamespace := GetCloneSourceInfo(dv)
×
909
        if sourceNamespace == "" || sourceNamespace == dv.Namespace {
×
910
                return nil
×
911
        }
×
912

913
        tok, ok := dv.Annotations[AnnCloneToken]
×
914
        if !ok {
×
915
                return errors.New("clone token missing")
×
916
        }
×
917

918
        tokenData, err := validator.Validate(tok)
×
919
        if err != nil {
×
920
                return errors.Wrap(err, "error verifying token")
×
921
        }
×
922

923
        tokenResourceName := getTokenResourceNameDataVolume(dv.Spec.Source)
×
924
        if tokenResourceName == "" {
×
925
                return errors.New("token resource name empty, can't verify properly")
×
926
        }
×
927

928
        return validateTokenData(tokenData, sourceNamespace, sourceName, dv.Namespace, dv.Name, "", tokenResourceName)
×
929
}
930

931
func getTokenResourceNameDataVolume(source *cdiv1.DataVolumeSource) string {
×
932
        if source.PVC != nil {
×
933
                return "persistentvolumeclaims"
×
934
        } else if source.Snapshot != nil {
×
935
                return "volumesnapshots"
×
936
        }
×
937

938
        return ""
×
939
}
940

941
func getTokenResourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
942
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
×
943
                return "volumesnapshots"
×
944
        }
×
945

946
        return "persistentvolumeclaims"
×
947
}
948

949
func getSourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
950
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
×
951
                if sourcePvc.Spec.DataSourceRef != nil {
×
952
                        return sourcePvc.Spec.DataSourceRef.Name
×
953
                }
×
954
        }
955

956
        return sourcePvc.Name
×
957
}
958

959
func validateTokenData(tokenData *token.Payload, srcNamespace, srcName, targetNamespace, targetName, targetUID, tokenResourceName string) error {
×
960
        uid := tokenData.Params["uid"]
×
961
        if tokenData.Operation != token.OperationClone ||
×
962
                tokenData.Name != srcName ||
×
963
                tokenData.Namespace != srcNamespace ||
×
964
                tokenData.Resource.Resource != tokenResourceName ||
×
965
                tokenData.Params["targetNamespace"] != targetNamespace ||
×
966
                tokenData.Params["targetName"] != targetName ||
×
967
                (uid != "" && uid != targetUID) {
×
968
                return errors.New("invalid token")
×
969
        }
×
970

971
        return nil
×
972
}
973

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

996
// AddAnnotation adds an annotation to an object
997
func AddAnnotation(obj metav1.Object, key, value string) {
1✔
998
        if obj.GetAnnotations() == nil {
2✔
999
                obj.SetAnnotations(make(map[string]string))
1✔
1000
        }
1✔
1001
        obj.GetAnnotations()[key] = value
1✔
1002
}
1003

1004
// AddLabel adds a label to an object
1005
func AddLabel(obj metav1.Object, key, value string) {
1✔
1006
        if obj.GetLabels() == nil {
2✔
1007
                obj.SetLabels(make(map[string]string))
1✔
1008
        }
1✔
1009
        obj.GetLabels()[key] = value
1✔
1010
}
1011

1012
// HandleFailedPod handles pod-creation errors and updates the pod's PVC without providing sensitive information
1013
func HandleFailedPod(err error, podName string, pvc *corev1.PersistentVolumeClaim, recorder record.EventRecorder, c client.Client) error {
×
1014
        if err == nil {
×
1015
                return nil
×
1016
        }
×
1017
        // Generic reason and msg to avoid providing sensitive information
1018
        reason := ErrStartingPod
×
1019
        msg := fmt.Sprintf(MessageErrStartingPod, podName)
×
1020

×
1021
        // Error handling to fine-tune the event with pertinent info
×
1022
        if ErrQuotaExceeded(err) {
×
1023
                reason = ErrExceededQuota
×
1024
        }
×
1025

1026
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1027

×
1028
        if isCloneSourcePod := CreateCloneSourcePodName(pvc) == podName; isCloneSourcePod {
×
1029
                AddAnnotation(pvc, AnnSourceRunningCondition, "false")
×
1030
                AddAnnotation(pvc, AnnSourceRunningConditionReason, reason)
×
1031
                AddAnnotation(pvc, AnnSourceRunningConditionMessage, msg)
×
1032
        } else {
×
1033
                AddAnnotation(pvc, AnnRunningCondition, "false")
×
1034
                AddAnnotation(pvc, AnnRunningConditionReason, reason)
×
1035
                AddAnnotation(pvc, AnnRunningConditionMessage, msg)
×
1036
        }
×
1037

1038
        AddAnnotation(pvc, AnnPodPhase, string(corev1.PodFailed))
×
1039
        if err := c.Update(context.TODO(), pvc); err != nil {
×
1040
                return err
×
1041
        }
×
1042

1043
        return err
×
1044
}
1045

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

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

1081
// AddImportVolumeMounts is being called for pods using PV with filesystem volume mode
1082
func AddImportVolumeMounts() []corev1.VolumeMount {
×
1083
        volumeMounts := []corev1.VolumeMount{
×
1084
                {
×
1085
                        Name:      DataVolName,
×
1086
                        MountPath: common.ImporterDataDir,
×
1087
                },
×
1088
        }
×
1089
        return volumeMounts
×
1090
}
×
1091

1092
// ValidateRequestedCloneSize validates the clone size requirements on block
1093
func ValidateRequestedCloneSize(sourceResources, targetResources corev1.VolumeResourceRequirements) error {
×
1094
        sourceRequest, hasSource := sourceResources.Requests[corev1.ResourceStorage]
×
1095
        targetRequest, hasTarget := targetResources.Requests[corev1.ResourceStorage]
×
1096
        if !hasSource || !hasTarget {
×
1097
                return errors.New("source/target missing storage resource requests")
×
1098
        }
×
1099

1100
        // Verify that the target PVC size is equal or larger than the source.
1101
        if sourceRequest.Value() > targetRequest.Value() {
×
1102
                return errors.Errorf("target resources requests storage size is smaller than the source %d < %d", targetRequest.Value(), sourceRequest.Value())
×
1103
        }
×
1104
        return nil
×
1105
}
1106

1107
// CreateCloneSourcePodName creates clone source pod name
1108
func CreateCloneSourcePodName(targetPvc *corev1.PersistentVolumeClaim) string {
×
1109
        return string(targetPvc.GetUID()) + common.ClonerSourcePodNameSuffix
×
1110
}
×
1111

1112
// IsPVCComplete returns true if a PVC is in 'Succeeded' phase, false if not
1113
func IsPVCComplete(pvc *corev1.PersistentVolumeClaim) bool {
×
1114
        if pvc != nil {
×
1115
                phase, exists := pvc.ObjectMeta.Annotations[AnnPodPhase]
×
1116
                return exists && (phase == string(corev1.PodSucceeded))
×
1117
        }
×
1118
        return false
×
1119
}
1120

1121
// IsMultiStageImportInProgress returns true when a PVC is being part of an ongoing multi-stage import
1122
func IsMultiStageImportInProgress(pvc *corev1.PersistentVolumeClaim) bool {
×
1123
        if pvc != nil {
×
1124
                multiStageImport := metav1.HasAnnotation(pvc.ObjectMeta, AnnCurrentCheckpoint)
×
1125
                multiStageAlreadyDone := metav1.HasAnnotation(pvc.ObjectMeta, AnnMultiStageImportDone)
×
1126
                return multiStageImport && !multiStageAlreadyDone
×
1127
        }
×
1128
        return false
×
1129
}
1130

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

NEW
1157
        if podSpec.SecurityContext == nil {
×
NEW
1158
                podSpec.SecurityContext = &corev1.PodSecurityContext{}
×
NEW
1159
        }
×
1160
        // Some tools like istio inject containers and thus rely on a pod level seccomp profile being specified
NEW
1161
        podSpec.SecurityContext.SeccompProfile = &corev1.SeccompProfile{
×
NEW
1162
                Type: corev1.SeccompProfileTypeRuntimeDefault,
×
NEW
1163
        }
×
1164
        if hasVolumeMounts {
×
1165
                podSpec.SecurityContext.FSGroup = ptr.To[int64](common.QemuSubGid)
×
1166
        }
×
1167
}
1168

1169
// SetNodeNameIfPopulator sets NodeName in a pod spec when the PVC is being handled by a CDI volume populator
1170
func SetNodeNameIfPopulator(pvc *corev1.PersistentVolumeClaim, podSpec *corev1.PodSpec) {
×
1171
        _, isPopulator := pvc.Annotations[AnnPopulatorKind]
×
1172
        nodeName := pvc.Annotations[AnnSelectedNode]
×
1173
        if isPopulator && nodeName != "" {
×
1174
                podSpec.NodeName = nodeName
×
1175
        }
×
1176
}
1177

1178
// CreatePvc creates PVC
1179
func CreatePvc(name, ns string, annotations, labels map[string]string) *corev1.PersistentVolumeClaim {
1✔
1180
        return CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1✔
1181
}
1✔
1182

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

1213
// GetAPIServerKey returns API server RSA key
1214
func GetAPIServerKey() *rsa.PrivateKey {
×
1215
        apiServerKeyOnce.Do(func() {
×
1216
                apiServerKey, _ = rsa.GenerateKey(rand.Reader, 2048)
×
1217
        })
×
1218
        return apiServerKey
×
1219
}
1220

1221
// CreateStorageClass creates storage class CR
1222
func CreateStorageClass(name string, annotations map[string]string) *storagev1.StorageClass {
1✔
1223
        return &storagev1.StorageClass{
1✔
1224
                ObjectMeta: metav1.ObjectMeta{
1✔
1225
                        Name:        name,
1✔
1226
                        Annotations: annotations,
1✔
1227
                },
1✔
1228
        }
1✔
1229
}
1✔
1230

1231
// CreateImporterTestPod creates importer test pod CR
1232
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
×
1233
        // importer pod name contains the pvc name
×
1234
        podName := fmt.Sprintf("%s-%s", common.ImporterPodName, pvc.Name)
×
1235

×
1236
        blockOwnerDeletion := true
×
1237
        isController := true
×
1238

×
1239
        volumes := []corev1.Volume{
×
1240
                {
×
1241
                        Name: dvname,
×
1242
                        VolumeSource: corev1.VolumeSource{
×
1243
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1244
                                        ClaimName: pvc.Name,
×
1245
                                        ReadOnly:  false,
×
1246
                                },
×
1247
                        },
×
1248
                },
×
1249
        }
×
1250

×
1251
        if scratchPvc != nil {
×
1252
                volumes = append(volumes, corev1.Volume{
×
1253
                        Name: ScratchVolName,
×
1254
                        VolumeSource: corev1.VolumeSource{
×
1255
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1256
                                        ClaimName: scratchPvc.Name,
×
1257
                                        ReadOnly:  false,
×
1258
                                },
×
1259
                        },
×
1260
                })
×
1261
        }
×
1262

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

×
1311
        ep, _ := GetEndpoint(pvc)
×
1312
        source := GetSource(pvc)
×
1313
        contentType := GetPVCContentType(pvc)
×
1314
        imageSize, _ := GetRequestedImageSize(pvc)
×
1315
        volumeMode := GetVolumeMode(pvc)
×
1316

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

1350
        if scratchPvc != nil {
×
1351
                pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
×
1352
                        Name:      ScratchVolName,
×
1353
                        MountPath: common.ScratchDataDir,
×
1354
                })
×
1355
        }
×
1356

1357
        return pod
×
1358
}
1359

1360
// CreateStorageClassWithProvisioner creates CR of storage class with provisioner
1361
func CreateStorageClassWithProvisioner(name string, annotations, labels map[string]string, provisioner string) *storagev1.StorageClass {
×
1362
        return &storagev1.StorageClass{
×
1363
                Provisioner: provisioner,
×
1364
                ObjectMeta: metav1.ObjectMeta{
×
1365
                        Name:        name,
×
1366
                        Annotations: annotations,
×
1367
                        Labels:      labels,
×
1368
                },
×
1369
        }
×
1370
}
×
1371

1372
// CreateClient creates a fake client
1373
func CreateClient(objs ...runtime.Object) client.Client {
1✔
1374
        s := scheme.Scheme
1✔
1375
        _ = cdiv1.AddToScheme(s)
1✔
1376
        _ = corev1.AddToScheme(s)
1✔
1377
        _ = storagev1.AddToScheme(s)
1✔
1378
        _ = ocpconfigv1.Install(s)
1✔
1379

1✔
1380
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1381
}
1✔
1382

1383
// ErrQuotaExceeded checked is the error is of exceeded quota
1384
func ErrQuotaExceeded(err error) bool {
×
1385
        return strings.Contains(err.Error(), "exceeded quota:")
×
1386
}
×
1387

1388
// GetContentType returns the content type. If invalid or not set, default to kubevirt
1389
func GetContentType(contentType cdiv1.DataVolumeContentType) cdiv1.DataVolumeContentType {
1✔
1390
        switch contentType {
1✔
1391
        case
1392
                cdiv1.DataVolumeKubeVirt,
1393
                cdiv1.DataVolumeArchive:
1✔
1394
        default:
×
1395
                // TODO - shouldn't archive be the default?
×
1396
                contentType = cdiv1.DataVolumeKubeVirt
×
1397
        }
1398
        return contentType
1✔
1399
}
1400

1401
// GetPVCContentType returns the content type of the source image. If invalid or not set, default to kubevirt
1402
func GetPVCContentType(pvc *corev1.PersistentVolumeClaim) cdiv1.DataVolumeContentType {
×
1403
        contentType, found := pvc.Annotations[AnnContentType]
×
1404
        if !found {
×
1405
                // TODO - shouldn't archive be the default?
×
1406
                return cdiv1.DataVolumeKubeVirt
×
1407
        }
×
1408

1409
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1410
}
1411

1412
// GetNamespace returns the given namespace if not empty, otherwise the default namespace
1413
func GetNamespace(namespace, defaultNamespace string) string {
×
1414
        if namespace == "" {
×
1415
                return defaultNamespace
×
1416
        }
×
1417
        return namespace
×
1418
}
1419

1420
// IsErrCacheNotStarted checked is the error is of cache not started
1421
func IsErrCacheNotStarted(err error) bool {
×
1422
        target := &runtimecache.ErrCacheNotStarted{}
×
1423
        return errors.As(err, &target)
×
1424
}
×
1425

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

1449
// GetCloneSourceInfo returns the type, name and namespace of the cloning source
1450
func GetCloneSourceInfo(dv *cdiv1.DataVolume) (sourceType, sourceName, sourceNamespace string) {
×
1451
        // Cloning sources are mutually exclusive
×
1452
        if dv.Spec.Source.PVC != nil {
×
1453
                return "pvc", dv.Spec.Source.PVC.Name, dv.Spec.Source.PVC.Namespace
×
1454
        }
×
1455

1456
        if dv.Spec.Source.Snapshot != nil {
×
1457
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
×
1458
        }
×
1459

1460
        return "", "", ""
×
1461
}
1462

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

1473
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1474
}
1475

1476
// AddImmediateBindingAnnotationIfWFFCDisabled adds the immediateBinding annotation if wffc feature gate is disabled
1477
func AddImmediateBindingAnnotationIfWFFCDisabled(obj metav1.Object, gates featuregates.FeatureGates) error {
×
1478
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
×
1479
        if err != nil {
×
1480
                return err
×
1481
        }
×
1482
        if !globalHonorWaitForFirstConsumer {
×
1483
                AddAnnotation(obj, AnnImmediateBinding, "")
×
1484
        }
×
1485
        return nil
×
1486
}
1487

1488
// GetRequiredSpace calculates space required taking file system overhead into account
1489
func GetRequiredSpace(filesystemOverhead float64, requestedSpace int64) int64 {
×
1490
        // the `image` has to be aligned correctly, so the space requested has to be aligned to
×
1491
        // next value that is a multiple of a block size
×
1492
        alignedSize := util.RoundUp(requestedSpace, util.DefaultAlignBlockSize)
×
1493

×
1494
        // count overhead as a percentage of the whole/new size, including aligned image
×
1495
        // and the space required by filesystem metadata
×
1496
        spaceWithOverhead := int64(math.Ceil(float64(alignedSize) / (1 - filesystemOverhead)))
×
1497
        return spaceWithOverhead
×
1498
}
×
1499

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

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

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

1520
        return returnSize, nil
×
1521
}
1522

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1780
        return nil
1✔
1781
}
1782

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

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

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

1805
        return nil
×
1806
}
1807

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

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

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

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

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

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

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

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

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

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

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

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

1926
        return nil, nil
×
1927
}
1928

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

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

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

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

1958
        return false
×
1959
}
1960

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

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

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

1977
                return false, err
×
1978
        }
1979

1980
        return true, nil
×
1981
}
1982

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

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

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

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

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

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

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

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