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

kubevirt / containerized-data-importer / #4879

16 Aug 2024 03:54AM UTC coverage: 59.187% (-0.08%) from 59.27%
#4879

push

travis-ci

web-flow
Setup ginkgo cli build properly to avoid double dep (#3378)

* Setup ginkgo cli build properly to avoid double dep

Today we have the ginkgo CLI brought into the builder and
also to the project itself. This results in
```
 Ginkgo detected a version mismatch between the Ginkgo CLI and the version of Ginkgo imported by your packages:
  Ginkgo CLI Version:
    2.12.0
  Mismatched package versions found:
    2.17.1 used by tests
```
This commit provides the necessary build adaptations to get rid of the
builder ginkgo CLI dependency.

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

* update builder to latest

https://github.com/kubevirt/containerized-data-importer/pull/3379

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

---------

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

16609 of 28062 relevant lines covered (59.19%)

0.65 hits per line

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

14.22
/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
        // AnnDeleteAfterCompletion is PVC annotation for deleting DV after completion
99
        AnnDeleteAfterCompletion = AnnAPIGroup + "/storage.deleteAfterCompletion"
100
        // AnnPodRetainAfterCompletion is PVC annotation for retaining transfer pods after completion
101
        AnnPodRetainAfterCompletion = AnnAPIGroup + "/storage.pod.retainAfterCompletion"
102

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

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

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

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

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

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

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

146
        // AnnVddkVersion shows the last VDDK library version used by a DV's importer pod
147
        AnnVddkVersion = AnnAPIGroup + "/storage.pod.vddk.version"
148
        // AnnVddkHostConnection shows the last ESX host that serviced a DV's importer pod
149
        AnnVddkHostConnection = AnnAPIGroup + "/storage.pod.vddk.host"
150
        // AnnVddkInitImageURL saves a per-DV VDDK image URL on the PVC
151
        AnnVddkInitImageURL = AnnAPIGroup + "/storage.pod.vddk.initimageurl"
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
        // AnnDefaultStorageClass is the annotation indicating that a storage class is the default one
222
        AnnDefaultStorageClass = "storageclass.kubernetes.io/is-default-class"
223
        // AnnDefaultVirtStorageClass is the annotation indicating that a storage class is the default one for virtualization purposes
224
        AnnDefaultVirtStorageClass = "storageclass.kubevirt.io/is-default-virt-class"
225
        // AnnDefaultSnapshotClass is the annotation indicating that a snapshot class is the default one
226
        AnnDefaultSnapshotClass = "snapshot.storage.kubernetes.io/is-default-class"
227

228
        // AnnSourceVolumeMode is the volume mode of the source PVC specified as an annotation on snapshots
229
        AnnSourceVolumeMode = AnnAPIGroup + "/storage.import.sourceVolumeMode"
230

231
        // AnnOpenShiftImageLookup is the annotation for OpenShift image stream lookup
232
        AnnOpenShiftImageLookup = "alpha.image.policy.openshift.io/resolve-names"
233

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

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

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

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

259
        // AnnGarbageCollected is a PVC annotation indicating that the PVC was garbage collected
260
        AnnGarbageCollected = AnnAPIGroup + "/garbageCollected"
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
        // ProgressDone this means we are DONE
332
        ProgressDone = "100.0%"
333

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

339
        // AnnAllowClaimAdoption is the annotation that allows a claim to be adopted by a DataVolume
340
        AnnAllowClaimAdoption = AnnAPIGroup + "/allowClaimAdoption"
341

342
        // AnnCdiCustomizeComponentHash annotation is a hash of all customizations that live under spec.CustomizeComponents
343
        AnnCdiCustomizeComponentHash = AnnAPIGroup + "/customizer-identifier"
344

345
        // AnnCreatedForDataVolume stores the UID of the datavolume that the PVC was created for
346
        AnnCreatedForDataVolume = AnnAPIGroup + "/createdForDataVolume"
347
)
348

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

359
var (
360
        // BlockMode is raw block device mode
361
        BlockMode = corev1.PersistentVolumeBlock
362
        // FilesystemMode is filesystem device mode
363
        FilesystemMode = corev1.PersistentVolumeFilesystem
364

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

373
        apiServerKeyOnce sync.Once
374
        apiServerKey     *rsa.PrivateKey
375

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

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

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

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

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

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

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

434
        tok, v := mtv.getTokenAndValidator(pvc)
×
435

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

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

×
450
        return validateTokenData(tokenData, vcs.Namespace, srcName, pvc.Namespace, pvc.Name, string(pvc.UID), tokenResourceName)
×
451
}
452

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

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

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

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

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

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

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

502
        if dv.Spec.Storage != nil {
×
503
                return dv.Spec.Storage.StorageClassName
×
504
        }
×
505

506
        return nil
×
507
}
508

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

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

527
        return storageClass, nil
×
528
}
529

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

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

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

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

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

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

573
        if len(defaultClasses) == 0 {
2✔
574
                return nil
1✔
575
        }
1✔
576

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

591
        return &defaultClasses[0]
1✔
592
}
593

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

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

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

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

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

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

×
632
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
×
633

×
634
        storageClassOverhead, found := perStorageConfig[targetStorageClass.GetName()]
×
635
        if found {
×
636
                return storageClassOverhead, nil
×
637
        }
×
638

639
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
640
}
641

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

650
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
651
}
652

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

661
        return cdiconfig.Status.ImagePullSecrets, nil
×
662
}
663

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

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

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

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

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

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

763
        return pods, nil
×
764
}
765

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

773
        if cr == nil {
×
774
                return nil, fmt.Errorf("no active CDI")
×
775
        }
×
776

777
        return &cr.Spec.Workloads, nil
×
778
}
779

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

787
        if len(crList.Items) == 0 {
2✔
788
                return nil, nil
1✔
789
        }
1✔
790

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

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

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

806
        return &activeResources[0], nil
1✔
807
}
808

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

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

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

831
        return cdiconfig.Status.Preallocation
×
832
}
833

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

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

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

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

857
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
858
}
859

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

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

873
        obj.SetFinalizers(finalizers)
×
874
}
875

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

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

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

897
        tokenResourceName := getTokenResourceNamePvc(source)
×
898
        srcName := getSourceNamePvc(source)
×
899

×
900
        return validateTokenData(tokenData, source.Namespace, srcName, target.Namespace, target.Name, string(target.UID), tokenResourceName)
×
901
}
902

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

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

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

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

925
        return validateTokenData(tokenData, sourceNamespace, sourceName, dv.Namespace, dv.Name, "", tokenResourceName)
×
926
}
927

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

935
        return ""
×
936
}
937

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

943
        return "persistentvolumeclaims"
×
944
}
945

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

953
        return sourcePvc.Name
×
954
}
955

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

968
        return nil
×
969
}
970

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

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

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

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

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

1023
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1024

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

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

1040
        return err
×
1041
}
1042

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

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

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

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

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

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

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

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

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

1154
        if hasVolumeMounts {
×
1155
                if podSpec.SecurityContext == nil {
×
1156
                        podSpec.SecurityContext = &corev1.PodSecurityContext{}
×
1157
                }
×
1158
                podSpec.SecurityContext.FSGroup = ptr.To[int64](common.QemuSubGid)
×
1159
        }
1160
}
1161

1162
// SetNodeNameIfPopulator sets NodeName in a pod spec when the PVC is being handled by a CDI volume populator
1163
func SetNodeNameIfPopulator(pvc *corev1.PersistentVolumeClaim, podSpec *corev1.PodSpec) {
×
1164
        _, isPopulator := pvc.Annotations[AnnPopulatorKind]
×
1165
        nodeName := pvc.Annotations[AnnSelectedNode]
×
1166
        if isPopulator && nodeName != "" {
×
1167
                podSpec.NodeName = nodeName
×
1168
        }
×
1169
}
1170

1171
// CreatePvc creates PVC
1172
func CreatePvc(name, ns string, annotations, labels map[string]string) *corev1.PersistentVolumeClaim {
1✔
1173
        return CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1✔
1174
}
1✔
1175

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

1206
// GetAPIServerKey returns API server RSA key
1207
func GetAPIServerKey() *rsa.PrivateKey {
×
1208
        apiServerKeyOnce.Do(func() {
×
1209
                apiServerKey, _ = rsa.GenerateKey(rand.Reader, 2048)
×
1210
        })
×
1211
        return apiServerKey
×
1212
}
1213

1214
// CreateStorageClass creates storage class CR
1215
func CreateStorageClass(name string, annotations map[string]string) *storagev1.StorageClass {
1✔
1216
        return &storagev1.StorageClass{
1✔
1217
                ObjectMeta: metav1.ObjectMeta{
1✔
1218
                        Name:        name,
1✔
1219
                        Annotations: annotations,
1✔
1220
                },
1✔
1221
        }
1✔
1222
}
1✔
1223

1224
// CreateImporterTestPod creates importer test pod CR
1225
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
×
1226
        // importer pod name contains the pvc name
×
1227
        podName := fmt.Sprintf("%s-%s", common.ImporterPodName, pvc.Name)
×
1228

×
1229
        blockOwnerDeletion := true
×
1230
        isController := true
×
1231

×
1232
        volumes := []corev1.Volume{
×
1233
                {
×
1234
                        Name: dvname,
×
1235
                        VolumeSource: corev1.VolumeSource{
×
1236
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1237
                                        ClaimName: pvc.Name,
×
1238
                                        ReadOnly:  false,
×
1239
                                },
×
1240
                        },
×
1241
                },
×
1242
        }
×
1243

×
1244
        if scratchPvc != nil {
×
1245
                volumes = append(volumes, corev1.Volume{
×
1246
                        Name: ScratchVolName,
×
1247
                        VolumeSource: corev1.VolumeSource{
×
1248
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1249
                                        ClaimName: scratchPvc.Name,
×
1250
                                        ReadOnly:  false,
×
1251
                                },
×
1252
                        },
×
1253
                })
×
1254
        }
×
1255

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

×
1304
        ep, _ := GetEndpoint(pvc)
×
1305
        source := GetSource(pvc)
×
1306
        contentType := GetPVCContentType(pvc)
×
1307
        imageSize, _ := GetRequestedImageSize(pvc)
×
1308
        volumeMode := GetVolumeMode(pvc)
×
1309

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

1343
        if scratchPvc != nil {
×
1344
                pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
×
1345
                        Name:      ScratchVolName,
×
1346
                        MountPath: common.ScratchDataDir,
×
1347
                })
×
1348
        }
×
1349

1350
        return pod
×
1351
}
1352

1353
// CreateStorageClassWithProvisioner creates CR of storage class with provisioner
1354
func CreateStorageClassWithProvisioner(name string, annotations, labels map[string]string, provisioner string) *storagev1.StorageClass {
×
1355
        return &storagev1.StorageClass{
×
1356
                Provisioner: provisioner,
×
1357
                ObjectMeta: metav1.ObjectMeta{
×
1358
                        Name:        name,
×
1359
                        Annotations: annotations,
×
1360
                        Labels:      labels,
×
1361
                },
×
1362
        }
×
1363
}
×
1364

1365
// CreateClient creates a fake client
1366
func CreateClient(objs ...runtime.Object) client.Client {
1✔
1367
        s := scheme.Scheme
1✔
1368
        _ = cdiv1.AddToScheme(s)
1✔
1369
        _ = corev1.AddToScheme(s)
1✔
1370
        _ = storagev1.AddToScheme(s)
1✔
1371
        _ = ocpconfigv1.Install(s)
1✔
1372

1✔
1373
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1374
}
1✔
1375

1376
// ErrQuotaExceeded checked is the error is of exceeded quota
1377
func ErrQuotaExceeded(err error) bool {
×
1378
        return strings.Contains(err.Error(), "exceeded quota:")
×
1379
}
×
1380

1381
// GetContentType returns the content type. If invalid or not set, default to kubevirt
1382
func GetContentType(contentType cdiv1.DataVolumeContentType) cdiv1.DataVolumeContentType {
1✔
1383
        switch contentType {
1✔
1384
        case
1385
                cdiv1.DataVolumeKubeVirt,
1386
                cdiv1.DataVolumeArchive:
1✔
1387
        default:
×
1388
                // TODO - shouldn't archive be the default?
×
1389
                contentType = cdiv1.DataVolumeKubeVirt
×
1390
        }
1391
        return contentType
1✔
1392
}
1393

1394
// GetPVCContentType returns the content type of the source image. If invalid or not set, default to kubevirt
1395
func GetPVCContentType(pvc *corev1.PersistentVolumeClaim) cdiv1.DataVolumeContentType {
×
1396
        contentType, found := pvc.Annotations[AnnContentType]
×
1397
        if !found {
×
1398
                // TODO - shouldn't archive be the default?
×
1399
                return cdiv1.DataVolumeKubeVirt
×
1400
        }
×
1401

1402
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1403
}
1404

1405
// GetNamespace returns the given namespace if not empty, otherwise the default namespace
1406
func GetNamespace(namespace, defaultNamespace string) string {
×
1407
        if namespace == "" {
×
1408
                return defaultNamespace
×
1409
        }
×
1410
        return namespace
×
1411
}
1412

1413
// IsErrCacheNotStarted checked is the error is of cache not started
1414
func IsErrCacheNotStarted(err error) bool {
×
1415
        target := &runtimecache.ErrCacheNotStarted{}
×
1416
        return errors.As(err, &target)
×
1417
}
×
1418

1419
// GetDataVolumeTTLSeconds gets the current DataVolume TTL in seconds if GC is enabled, or < 0 if GC is disabled
1420
// Garbage collection is disabled by default
1421
func GetDataVolumeTTLSeconds(config *cdiv1.CDIConfig) int32 {
×
1422
        const defaultDataVolumeTTLSeconds = -1
×
1423
        if config.Spec.DataVolumeTTLSeconds != nil {
×
1424
                return *config.Spec.DataVolumeTTLSeconds
×
1425
        }
×
1426
        return defaultDataVolumeTTLSeconds
×
1427
}
1428

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

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

1459
        if dv.Spec.Source.Snapshot != nil {
×
1460
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
×
1461
        }
×
1462

1463
        return "", "", ""
×
1464
}
1465

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

1476
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1477
}
1478

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

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

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

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

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

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

1523
        return returnSize, nil
×
1524
}
1525

1526
// IsBound returns if the pvc is bound
1527
func IsBound(pvc *corev1.PersistentVolumeClaim) bool {
×
1528
        return pvc.Spec.VolumeName != ""
×
1529
}
×
1530

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

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

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

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

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

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

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

1608
// GetProgressReportFromURL fetches the progress report from the passed URL according to an specific metric expression and ownerUID
1609
func GetProgressReportFromURL(url string, httpClient *http.Client, metricExp, ownerUID string) (string, error) {
×
1610
        regExp := regexp.MustCompile(fmt.Sprintf("(%s)\\{ownerUID\\=%q\\} (\\d{1,3}\\.?\\d*)", metricExp, ownerUID))
×
1611
        resp, err := httpClient.Get(url)
×
1612
        if err != nil {
×
1613
                if ErrConnectionRefused(err) {
×
1614
                        return "", nil
×
1615
                }
×
1616
                return "", err
×
1617
        }
1618
        defer resp.Body.Close()
×
1619
        body, err := io.ReadAll(resp.Body)
×
1620
        if err != nil {
×
1621
                return "", err
×
1622
        }
×
1623

1624
        // Parse the progress from the body
1625
        progressReport := ""
×
1626
        match := regExp.FindStringSubmatch(string(body))
×
1627
        if match != nil {
×
1628
                progressReport = match[len(match)-1]
×
1629
        }
×
1630
        return progressReport, nil
×
1631
}
1632

1633
// UpdateHTTPAnnotations updates the passed annotations for proper http import
1634
func UpdateHTTPAnnotations(annotations map[string]string, http *cdiv1.DataVolumeSourceHTTP) {
×
1635
        annotations[AnnEndpoint] = http.URL
×
1636
        annotations[AnnSource] = SourceHTTP
×
1637

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

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

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

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

1700
// UpdateVDDKAnnotations updates the passed annotations for proper VDDK import
1701
func UpdateVDDKAnnotations(annotations map[string]string, vddk *cdiv1.DataVolumeSourceVDDK) {
×
1702
        annotations[AnnEndpoint] = vddk.URL
×
1703
        annotations[AnnSource] = SourceVDDK
×
1704
        annotations[AnnSecret] = vddk.SecretRef
×
1705
        annotations[AnnBackingFile] = vddk.BackingFile
×
1706
        annotations[AnnUUID] = vddk.UUID
×
1707
        annotations[AnnThumbprint] = vddk.Thumbprint
×
1708
        if vddk.InitImageURL != "" {
×
1709
                annotations[AnnVddkInitImageURL] = vddk.InitImageURL
×
1710
        }
×
1711
}
1712

1713
// UpdateImageIOAnnotations updates the passed annotations for proper imageIO import
1714
func UpdateImageIOAnnotations(annotations map[string]string, imageio *cdiv1.DataVolumeSourceImageIO) {
×
1715
        annotations[AnnEndpoint] = imageio.URL
×
1716
        annotations[AnnSource] = SourceImageio
×
1717
        annotations[AnnSecret] = imageio.SecretRef
×
1718
        annotations[AnnCertConfigMap] = imageio.CertConfigMap
×
1719
        annotations[AnnDiskID] = imageio.DiskID
×
1720
}
×
1721

1722
// IsPVBoundToPVC checks if a PV is bound to a specific PVC
1723
func IsPVBoundToPVC(pv *corev1.PersistentVolume, pvc *corev1.PersistentVolumeClaim) bool {
1✔
1724
        claimRef := pv.Spec.ClaimRef
1✔
1725
        return claimRef != nil && claimRef.Name == pvc.Name && claimRef.Namespace == pvc.Namespace && claimRef.UID == pvc.UID
1✔
1726
}
1✔
1727

1728
// Rebind binds the PV of source to target
1729
func Rebind(ctx context.Context, c client.Client, source, target *corev1.PersistentVolumeClaim) error {
1✔
1730
        pv := &corev1.PersistentVolume{
1✔
1731
                ObjectMeta: metav1.ObjectMeta{
1✔
1732
                        Name: source.Spec.VolumeName,
1✔
1733
                },
1✔
1734
        }
1✔
1735

1✔
1736
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
2✔
1737
                return err
1✔
1738
        }
1✔
1739

1740
        // Examine the claimref for the PV and see if it's still bound to PVC'
1741
        if pv.Spec.ClaimRef == nil {
1✔
1742
                return fmt.Errorf("PV %s claimRef is nil", pv.Name)
×
1743
        }
×
1744

1745
        if !IsPVBoundToPVC(pv, source) {
2✔
1746
                // Something is not right if the PV is neither bound to PVC' nor target PVC
1✔
1747
                if !IsPVBoundToPVC(pv, target) {
2✔
1748
                        klog.Errorf("PV bound to unexpected PVC: Could not rebind to target PVC '%s'", target.Name)
1✔
1749
                        return fmt.Errorf("PV %s bound to unexpected claim %s", pv.Name, pv.Spec.ClaimRef.Name)
1✔
1750
                }
1✔
1751
                // our work is done
1752
                return nil
1✔
1753
        }
1754

1755
        // Rebind PVC to target PVC
1756
        pv.Spec.ClaimRef = &corev1.ObjectReference{
1✔
1757
                Namespace:       target.Namespace,
1✔
1758
                Name:            target.Name,
1✔
1759
                UID:             target.UID,
1✔
1760
                ResourceVersion: target.ResourceVersion,
1✔
1761
        }
1✔
1762
        klog.V(3).Info("Rebinding PV to target PVC", "PVC", target.Name)
1✔
1763
        if err := c.Update(context.TODO(), pv); err != nil {
1✔
1764
                return err
×
1765
        }
×
1766

1767
        return nil
1✔
1768
}
1769

1770
// BulkDeleteResources deletes a bunch of resources
1771
func BulkDeleteResources(ctx context.Context, c client.Client, obj client.ObjectList, lo client.ListOption) error {
×
1772
        if err := c.List(ctx, obj, lo); err != nil {
×
1773
                if meta.IsNoMatchError(err) {
×
1774
                        return nil
×
1775
                }
×
1776
                return err
×
1777
        }
1778

1779
        sv := reflect.ValueOf(obj).Elem()
×
1780
        iv := sv.FieldByName("Items")
×
1781

×
1782
        for i := 0; i < iv.Len(); i++ {
×
1783
                obj := iv.Index(i).Addr().Interface().(client.Object)
×
1784
                if obj.GetDeletionTimestamp().IsZero() {
×
1785
                        klog.V(3).Infof("Deleting type %+v %+v", reflect.TypeOf(obj), obj)
×
1786
                        if err := c.Delete(ctx, obj); err != nil {
×
1787
                                return err
×
1788
                        }
×
1789
                }
1790
        }
1791

1792
        return nil
×
1793
}
1794

1795
// ValidateSnapshotCloneSize does proper size validation when doing a clone from snapshot operation
1796
func ValidateSnapshotCloneSize(snapshot *snapshotv1.VolumeSnapshot, pvcSpec *corev1.PersistentVolumeClaimSpec, targetSC *storagev1.StorageClass, log logr.Logger) (bool, error) {
×
1797
        restoreSize := snapshot.Status.RestoreSize
×
1798
        if restoreSize == nil {
×
1799
                return false, fmt.Errorf("snapshot has no RestoreSize")
×
1800
        }
×
1801
        targetRequest, hasTargetRequest := pvcSpec.Resources.Requests[corev1.ResourceStorage]
×
1802
        allowExpansion := targetSC.AllowVolumeExpansion != nil && *targetSC.AllowVolumeExpansion
×
1803
        if hasTargetRequest {
×
1804
                // otherwise will just use restoreSize
×
1805
                if restoreSize.Cmp(targetRequest) < 0 && !allowExpansion {
×
1806
                        log.V(3).Info("Can't expand restored PVC because SC does not allow expansion, need to fall back to host assisted")
×
1807
                        return false, nil
×
1808
                }
×
1809
        }
1810
        return true, nil
×
1811
}
1812

1813
// ValidateSnapshotCloneProvisioners validates the target PVC storage class against the snapshot class provisioner
1814
func ValidateSnapshotCloneProvisioners(ctx context.Context, c client.Client, snapshot *snapshotv1.VolumeSnapshot, storageClass *storagev1.StorageClass) (bool, error) {
×
1815
        // Do snapshot and storage class validation
×
1816
        if storageClass == nil {
×
1817
                return false, fmt.Errorf("target storage class not found")
×
1818
        }
×
1819
        if snapshot.Status == nil || snapshot.Status.BoundVolumeSnapshotContentName == nil {
×
1820
                return false, fmt.Errorf("volumeSnapshotContent name not found")
×
1821
        }
×
1822
        volumeSnapshotContent := &snapshotv1.VolumeSnapshotContent{}
×
1823
        if err := c.Get(ctx, types.NamespacedName{Name: *snapshot.Status.BoundVolumeSnapshotContentName}, volumeSnapshotContent); err != nil {
×
1824
                return false, err
×
1825
        }
×
1826
        if storageClass.Provisioner != volumeSnapshotContent.Spec.Driver {
×
1827
                return false, nil
×
1828
        }
×
1829
        // TODO: get sourceVolumeMode from volumesnapshotcontent and validate against target spec
1830
        // currently don't have CRDs in CI with sourceVolumeMode which is pretty new
1831
        // converting volume mode is possible but has security implications
1832
        return true, nil
×
1833
}
1834

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

1844
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(context.TODO(), client, targetPvcStorageClassName)
×
1845
        if err != nil {
×
1846
                return "", err
×
1847
        }
×
1848
        if targetStorageClass == nil {
×
1849
                logger.Info("Target PVC's Storage Class not found")
×
1850
                return "", nil
×
1851
        }
×
1852

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

1865
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
1866
        return "", nil
×
1867
}
1868

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

×
1874
        logEvent := func(message, vscName string) {
×
1875
                logger.Info(message, "name", vscName)
×
1876
                if pvc != nil {
×
1877
                        msg := fmt.Sprintf("%s %s", message, vscName)
×
1878
                        recorder.Event(pvc, corev1.EventTypeNormal, VolumeSnapshotClassSelected, msg)
×
1879
                }
×
1880
        }
1881

1882
        if snapshotClassName != nil {
×
1883
                vsc := &snapshotv1.VolumeSnapshotClass{}
×
1884
                if err := c.Get(context.TODO(), types.NamespacedName{Name: *snapshotClassName}, vsc); err != nil {
×
1885
                        return nil, err
×
1886
                }
×
1887
                if vsc.Driver == driver {
×
1888
                        logEvent(MessageStorageProfileVolumeSnapshotClassSelected, vsc.Name)
×
1889
                        return snapshotClassName, nil
×
1890
                }
×
1891
                return nil, nil
×
1892
        }
1893

1894
        vscList := &snapshotv1.VolumeSnapshotClassList{}
×
1895
        if err := c.List(ctx, vscList); err != nil {
×
1896
                if meta.IsNoMatchError(err) {
×
1897
                        return nil, nil
×
1898
                }
×
1899
                return nil, err
×
1900
        }
1901

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

1914
        if len(candidates) > 0 {
×
1915
                sort.Strings(candidates)
×
1916
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
×
1917
                return &candidates[0], nil
×
1918
        }
×
1919

1920
        return nil, nil
×
1921
}
1922

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

×
1930
        return isCrdDeployed(c, vsClass, version, log) &&
×
1931
                isCrdDeployed(c, vsContent, version, log) &&
×
1932
                isCrdDeployed(c, vs, version, log)
×
1933
}
×
1934

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

1946
        for _, v := range crd.Spec.Versions {
×
1947
                if v.Name == version && v.Served {
×
1948
                        return true
×
1949
                }
×
1950
        }
1951

1952
        return false
×
1953
}
1954

1955
// IsSnapshotReady indicates if a volume snapshot is ready to be used
1956
func IsSnapshotReady(snapshot *snapshotv1.VolumeSnapshot) bool {
×
1957
        return snapshot.Status != nil && snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse
×
1958
}
×
1959

1960
// GetResource updates given obj with the data of the object with the same name and namespace
1961
func GetResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
×
1962
        obj.SetNamespace(namespace)
×
1963
        obj.SetName(name)
×
1964

×
1965
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
1966
        if err != nil {
×
1967
                if k8serrors.IsNotFound(err) {
×
1968
                        return false, nil
×
1969
                }
×
1970

1971
                return false, err
×
1972
        }
1973

1974
        return true, nil
×
1975
}
1976

1977
// PatchArgs are the args for Patch
1978
type PatchArgs struct {
1979
        Client client.Client
1980
        Log    logr.Logger
1981
        Obj    client.Object
1982
        OldObj client.Object
1983
}
1984

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

2014
// OwnedByDataVolume returns true if the object is owned by a DataVolume
2015
func OwnedByDataVolume(obj metav1.Object) bool {
×
2016
        owner := metav1.GetControllerOf(obj)
×
2017
        return owner != nil && owner.Kind == "DataVolume"
×
2018
}
×
2019

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

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

2045
// ClaimMayExistBeforeDataVolume returns true if the PVC may exist before the DataVolume
2046
func ClaimMayExistBeforeDataVolume(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2047
        if ClaimIsPopulatedForDataVolume(pvc, dv) {
×
2048
                return true, nil
×
2049
        }
×
2050
        return AllowClaimAdoption(c, pvc, dv)
×
2051
}
2052

2053
// ClaimIsPopulatedForDataVolume returns true if the PVC is populated for the given DataVolume
2054
func ClaimIsPopulatedForDataVolume(pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) bool {
×
2055
        return pvc != nil && dv != nil && pvc.Annotations[AnnPopulatedFor] == dv.Name
×
2056
}
×
2057

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

© 2025 Coveralls, Inc