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

kubevirt / containerized-data-importer / #4794

14 Jul 2024 06:12PM UTC coverage: 58.983% (+0.01%) from 58.972%
#4794

push

travis-ci

web-flow
update to k8s 1.30 libs and controller-runtime 0.18.4 (#3336)

* make deps-update

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* ReourceRequirements -> VolumeResourceRequirements

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* fix calls to controller.Watch()

controller-runtime changed the API!

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* Fix errors with actual openshift/library-go lib

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* make all works now and everything compiles

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* fix "make update-codegen" because generate_groups.sh deprecated

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* run "make generate"

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

* fix transfer unittest because of change to controller-runtime

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

---------

Signed-off-by: Michael Henriksen <mhenriks@redhat.com>

6 of 238 new or added lines in 24 files covered. (2.52%)

10 existing lines in 4 files now uncovered.

16454 of 27896 relevant lines covered (58.98%)

0.65 hits per line

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

14.12
/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

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

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

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

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

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

432
        tok, v := mtv.getTokenAndValidator(pvc)
×
433

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

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

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

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

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

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

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

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

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

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

500
        if dv.Spec.Storage != nil {
×
501
                return dv.Spec.Storage.StorageClassName
×
502
        }
×
503

504
        return nil
×
505
}
506

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

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

525
        return storageClass, nil
×
526
}
527

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

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

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

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

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

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

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

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

589
        return &defaultClasses[0]
1✔
590
}
591

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

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

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

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

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

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

×
630
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
×
631

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

637
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
638
}
639

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

648
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
649
}
650

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

659
        return cdiconfig.Status.ImagePullSecrets, nil
×
660
}
661

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

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

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

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

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

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

761
        return pods, nil
×
762
}
763

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

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

775
        return &cr.Spec.Workloads, nil
×
776
}
777

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

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

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

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

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

804
        return &activeResources[0], nil
1✔
805
}
806

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

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

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

829
        return cdiconfig.Status.Preallocation
×
830
}
831

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

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

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

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

855
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
856
}
857

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

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

871
        obj.SetFinalizers(finalizers)
×
872
}
873

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

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

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

895
        tokenResourceName := getTokenResourceNamePvc(source)
×
896
        srcName := getSourceNamePvc(source)
×
897

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

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

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

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

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

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

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

933
        return ""
×
934
}
935

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

941
        return "persistentvolumeclaims"
×
942
}
943

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

951
        return sourcePvc.Name
×
952
}
953

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

966
        return nil
×
967
}
968

969
// validateContentTypes compares the content type of a clone DV against its source PVC's one
970
func validateContentTypes(sourcePVC *corev1.PersistentVolumeClaim, spec *cdiv1.DataVolumeSpec) (bool, cdiv1.DataVolumeContentType, cdiv1.DataVolumeContentType) {
1✔
971
        sourceContentType := GetPVCContentType(sourcePVC)
1✔
972
        targetContentType := spec.ContentType
1✔
973
        if targetContentType == "" {
2✔
974
                targetContentType = cdiv1.DataVolumeKubeVirt
1✔
975
        }
1✔
976
        return sourceContentType == targetContentType, sourceContentType, targetContentType
1✔
977
}
978

979
// ValidateClone compares a clone spec against its source PVC to validate its creation
980
func ValidateClone(sourcePVC *corev1.PersistentVolumeClaim, spec *cdiv1.DataVolumeSpec) error {
×
NEW
981
        var targetResources corev1.VolumeResourceRequirements
×
982

×
983
        valid, sourceContentType, targetContentType := validateContentTypes(sourcePVC, spec)
×
984
        if !valid {
×
985
                msg := fmt.Sprintf("Source contentType (%s) and target contentType (%s) do not match", sourceContentType, targetContentType)
×
986
                return errors.New(msg)
×
987
        }
×
988

989
        isSizelessClone := false
×
990
        explicitPvcRequest := spec.PVC != nil
×
991
        if explicitPvcRequest {
×
992
                targetResources = spec.PVC.Resources
×
993
        } else {
×
994
                targetResources = spec.Storage.Resources
×
995
                // The storage size in the target DV can be empty
×
996
                // when cloning using the 'Storage' API
×
997
                if _, ok := targetResources.Requests[corev1.ResourceStorage]; !ok {
×
998
                        isSizelessClone = true
×
999
                }
×
1000
        }
1001

1002
        // TODO: Spec.Storage API needs a better more complex check to validate clone size - to account for fsOverhead
1003
        // simple size comparison will not work here
1004
        if (!isSizelessClone && GetVolumeMode(sourcePVC) == corev1.PersistentVolumeBlock) || explicitPvcRequest {
×
1005
                if err := ValidateRequestedCloneSize(sourcePVC.Spec.Resources, targetResources); err != nil {
×
1006
                        return err
×
1007
                }
×
1008
        }
1009

1010
        return nil
×
1011
}
1012

1013
// ValidateSnapshotClone compares a snapshot clone spec against its source snapshot to validate its creation
1014
func ValidateSnapshotClone(sourceSnapshot *snapshotv1.VolumeSnapshot, spec *cdiv1.DataVolumeSpec) error {
×
NEW
1015
        var sourceResources, targetResources corev1.VolumeResourceRequirements
×
1016

×
1017
        if sourceSnapshot.Status == nil {
×
1018
                return fmt.Errorf("no status on source snapshot, not possible to proceed")
×
1019
        }
×
1020
        size := sourceSnapshot.Status.RestoreSize
×
1021
        restoreSizeAvailable := size != nil && size.Sign() > 0
×
1022
        if restoreSizeAvailable {
×
1023
                sourceResources.Requests = corev1.ResourceList{corev1.ResourceStorage: *size}
×
1024
        }
×
1025

1026
        isSizelessClone := false
×
1027
        explicitPvcRequest := spec.PVC != nil
×
1028
        if explicitPvcRequest {
×
1029
                targetResources = spec.PVC.Resources
×
1030
        } else {
×
1031
                targetResources = spec.Storage.Resources
×
1032
                if _, ok := targetResources.Requests["storage"]; !ok {
×
1033
                        isSizelessClone = true
×
1034
                }
×
1035
        }
1036

1037
        if !isSizelessClone && restoreSizeAvailable {
×
1038
                // Sizes available, make sure user picked something bigger than minimal
×
1039
                if err := ValidateRequestedCloneSize(sourceResources, targetResources); err != nil {
×
1040
                        return err
×
1041
                }
×
1042
        } else if isSizelessClone && !restoreSizeAvailable {
×
1043
                return fmt.Errorf("size not specified by user/provisioner, can't tell how much needed for restore")
×
1044
        }
×
1045

1046
        return nil
×
1047
}
1048

1049
// AddAnnotation adds an annotation to an object
1050
func AddAnnotation(obj metav1.Object, key, value string) {
1✔
1051
        if obj.GetAnnotations() == nil {
2✔
1052
                obj.SetAnnotations(make(map[string]string))
1✔
1053
        }
1✔
1054
        obj.GetAnnotations()[key] = value
1✔
1055
}
1056

1057
// AddLabel adds a label to an object
1058
func AddLabel(obj metav1.Object, key, value string) {
×
1059
        if obj.GetLabels() == nil {
×
1060
                obj.SetLabels(make(map[string]string))
×
1061
        }
×
1062
        obj.GetLabels()[key] = value
×
1063
}
1064

1065
// HandleFailedPod handles pod-creation errors and updates the pod's PVC without providing sensitive information
1066
func HandleFailedPod(err error, podName string, pvc *corev1.PersistentVolumeClaim, recorder record.EventRecorder, c client.Client) error {
×
1067
        if err == nil {
×
1068
                return nil
×
1069
        }
×
1070
        // Generic reason and msg to avoid providing sensitive information
1071
        reason := ErrStartingPod
×
1072
        msg := fmt.Sprintf(MessageErrStartingPod, podName)
×
1073

×
1074
        // Error handling to fine-tune the event with pertinent info
×
1075
        if ErrQuotaExceeded(err) {
×
1076
                reason = ErrExceededQuota
×
1077
        }
×
1078

1079
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1080

×
1081
        if isCloneSourcePod := CreateCloneSourcePodName(pvc) == podName; isCloneSourcePod {
×
1082
                AddAnnotation(pvc, AnnSourceRunningCondition, "false")
×
1083
                AddAnnotation(pvc, AnnSourceRunningConditionReason, reason)
×
1084
                AddAnnotation(pvc, AnnSourceRunningConditionMessage, msg)
×
1085
        } else {
×
1086
                AddAnnotation(pvc, AnnRunningCondition, "false")
×
1087
                AddAnnotation(pvc, AnnRunningConditionReason, reason)
×
1088
                AddAnnotation(pvc, AnnRunningConditionMessage, msg)
×
1089
        }
×
1090

1091
        AddAnnotation(pvc, AnnPodPhase, string(corev1.PodFailed))
×
1092
        if err := c.Update(context.TODO(), pvc); err != nil {
×
1093
                return err
×
1094
        }
×
1095

1096
        return err
×
1097
}
1098

1099
// GetSource returns the source string which determines the type of source. If no source or invalid source found, default to http
1100
func GetSource(pvc *corev1.PersistentVolumeClaim) string {
×
1101
        source, found := pvc.Annotations[AnnSource]
×
1102
        if !found {
×
1103
                source = ""
×
1104
        }
×
1105
        switch source {
×
1106
        case
1107
                SourceHTTP,
1108
                SourceS3,
1109
                SourceGCS,
1110
                SourceGlance,
1111
                SourceNone,
1112
                SourceRegistry,
1113
                SourceImageio,
1114
                SourceVDDK:
×
1115
        default:
×
1116
                source = SourceHTTP
×
1117
        }
1118
        return source
×
1119
}
1120

1121
// GetEndpoint returns the endpoint string which contains the full path URI of the target object to be copied.
1122
func GetEndpoint(pvc *corev1.PersistentVolumeClaim) (string, error) {
×
1123
        ep, found := pvc.Annotations[AnnEndpoint]
×
1124
        if !found || ep == "" {
×
1125
                verb := "empty"
×
1126
                if !found {
×
1127
                        verb = "missing"
×
1128
                }
×
1129
                return ep, errors.Errorf("annotation %q in pvc \"%s/%s\" is %s", AnnEndpoint, pvc.Namespace, pvc.Name, verb)
×
1130
        }
1131
        return ep, nil
×
1132
}
1133

1134
// AddImportVolumeMounts is being called for pods using PV with filesystem volume mode
1135
func AddImportVolumeMounts() []corev1.VolumeMount {
×
1136
        volumeMounts := []corev1.VolumeMount{
×
1137
                {
×
1138
                        Name:      DataVolName,
×
1139
                        MountPath: common.ImporterDataDir,
×
1140
                },
×
1141
        }
×
1142
        return volumeMounts
×
1143
}
×
1144

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

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

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

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

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

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

1210
        if hasVolumeMounts {
×
1211
                if podSpec.SecurityContext == nil {
×
1212
                        podSpec.SecurityContext = &corev1.PodSecurityContext{}
×
1213
                }
×
1214
                podSpec.SecurityContext.FSGroup = ptr.To[int64](common.QemuSubGid)
×
1215
        }
1216
}
1217

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

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

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

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

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

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

×
1285
        blockOwnerDeletion := true
×
1286
        isController := true
×
1287

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

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

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

×
1360
        ep, _ := GetEndpoint(pvc)
×
1361
        source := GetSource(pvc)
×
1362
        contentType := GetPVCContentType(pvc)
×
1363
        imageSize, _ := GetRequestedImageSize(pvc)
×
1364
        volumeMode := GetVolumeMode(pvc)
×
1365

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

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

1406
        return pod
×
1407
}
1408

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

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

1✔
1429
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1430
}
1✔
1431

1432
// ErrQuotaExceeded checked is the error is of exceeded quota
1433
func ErrQuotaExceeded(err error) bool {
×
1434
        return strings.Contains(err.Error(), "exceeded quota:")
×
1435
}
×
1436

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

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

1458
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
1✔
1459
}
1460

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

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

1475
// GetDataVolumeTTLSeconds gets the current DataVolume TTL in seconds if GC is enabled, or < 0 if GC is disabled
1476
// Garbage collection is disabled by default
1477
func GetDataVolumeTTLSeconds(config *cdiv1.CDIConfig) int32 {
×
1478
        const defaultDataVolumeTTLSeconds = -1
×
1479
        if config.Spec.DataVolumeTTLSeconds != nil {
×
1480
                return *config.Spec.DataVolumeTTLSeconds
×
1481
        }
×
1482
        return defaultDataVolumeTTLSeconds
×
1483
}
1484

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

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

1515
        if dv.Spec.Source.Snapshot != nil {
×
1516
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
×
1517
        }
×
1518

1519
        return "", "", ""
×
1520
}
1521

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

1532
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1533
}
1534

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

1547
// GetRequiredSpace calculates space required taking file system overhead into account
1548
func GetRequiredSpace(filesystemOverhead float64, requestedSpace int64) int64 {
×
1549
        // the `image` has to be aligned correctly, so the space requested has to be aligned to
×
1550
        // next value that is a multiple of a block size
×
1551
        alignedSize := util.RoundUp(requestedSpace, util.DefaultAlignBlockSize)
×
1552

×
1553
        // count overhead as a percentage of the whole/new size, including aligned image
×
1554
        // and the space required by filesystem metadata
×
1555
        spaceWithOverhead := int64(math.Ceil(float64(alignedSize) / (1 - filesystemOverhead)))
×
1556
        return spaceWithOverhead
×
1557
}
×
1558

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

×
1563
        if util.ResolveVolumeMode(pvcSpec.VolumeMode) == corev1.PersistentVolumeFilesystem {
×
1564
                fsOverhead, err := GetFilesystemOverheadForStorageClass(ctx, c, pvcSpec.StorageClassName)
×
1565
                if err != nil {
×
1566
                        return resource.Quantity{}, err
×
1567
                }
×
1568
                // Parse filesystem overhead (percentage) into a 64-bit float
1569
                fsOverheadFloat, _ := strconv.ParseFloat(string(fsOverhead), 64)
×
1570

×
1571
                // Merge the previous values into a 'resource.Quantity' struct
×
1572
                requiredSpace := GetRequiredSpace(fsOverheadFloat, imgSize)
×
1573
                returnSize = *resource.NewScaledQuantity(requiredSpace, 0)
×
1574
        } else {
×
1575
                // Inflation is not needed with 'Block' mode
×
1576
                returnSize = *resource.NewScaledQuantity(imgSize, 0)
×
1577
        }
×
1578

1579
        return returnSize, nil
×
1580
}
1581

1582
// IsBound returns if the pvc is bound
1583
func IsBound(pvc *corev1.PersistentVolumeClaim) bool {
×
1584
        return pvc.Spec.VolumeName != ""
×
1585
}
×
1586

1587
// IsUnbound returns if the pvc is not bound yet
1588
func IsUnbound(pvc *corev1.PersistentVolumeClaim) bool {
×
1589
        return !IsBound(pvc)
×
1590
}
×
1591

1592
// IsImageStream returns true if registry source is ImageStream
1593
func IsImageStream(pvc *corev1.PersistentVolumeClaim) bool {
×
1594
        return pvc.Annotations[AnnRegistryImageStream] == "true"
×
1595
}
×
1596

1597
// ShouldIgnorePod checks if a pod should be ignored.
1598
// If this is a completed pod that was used for one checkpoint of a multi-stage import, it
1599
// should be ignored by pod lookups as long as the retainAfterCompletion annotation is set.
1600
func ShouldIgnorePod(pod *corev1.Pod, pvc *corev1.PersistentVolumeClaim) bool {
×
1601
        retain := pvc.ObjectMeta.Annotations[AnnPodRetainAfterCompletion]
×
1602
        checkpoint := pvc.ObjectMeta.Annotations[AnnCurrentCheckpoint]
×
1603
        if checkpoint != "" && pod.Status.Phase == corev1.PodSucceeded {
×
1604
                return retain == "true"
×
1605
        }
×
1606
        return false
×
1607
}
1608

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

1633
// ErrConnectionRefused checks for connection refused errors
1634
func ErrConnectionRefused(err error) bool {
×
1635
        return strings.Contains(err.Error(), "connection refused")
×
1636
}
×
1637

1638
// GetPodMetricsPort returns, if exists, the metrics port from the passed pod
1639
func GetPodMetricsPort(pod *corev1.Pod) (int, error) {
1✔
1640
        for _, container := range pod.Spec.Containers {
2✔
1641
                for _, port := range container.Ports {
2✔
1642
                        if port.Name == "metrics" {
2✔
1643
                                return int(port.ContainerPort), nil
1✔
1644
                        }
1✔
1645
                }
1646
        }
1647
        return 0, errors.New("Metrics port not found in pod")
1✔
1648
}
1649

1650
// GetMetricsURL builds the metrics URL according to the specified pod
1651
func GetMetricsURL(pod *corev1.Pod) (string, error) {
1✔
1652
        if pod == nil {
1✔
1653
                return "", nil
×
1654
        }
×
1655
        port, err := GetPodMetricsPort(pod)
1✔
1656
        if err != nil || pod.Status.PodIP == "" {
2✔
1657
                return "", err
1✔
1658
        }
1✔
1659
        domain := net.JoinHostPort(pod.Status.PodIP, fmt.Sprint(port))
1✔
1660
        url := fmt.Sprintf("https://%s/metrics", domain)
1✔
1661
        return url, nil
1✔
1662
}
1663

1664
// GetProgressReportFromURL fetches the progress report from the passed URL according to an specific metric expression and ownerUID
1665
func GetProgressReportFromURL(url string, httpClient *http.Client, metricExp, ownerUID string) (string, error) {
×
1666
        regExp := regexp.MustCompile(fmt.Sprintf("(%s)\\{ownerUID\\=%q\\} (\\d{1,3}\\.?\\d*)", metricExp, ownerUID))
×
1667
        resp, err := httpClient.Get(url)
×
1668
        if err != nil {
×
1669
                if ErrConnectionRefused(err) {
×
1670
                        return "", nil
×
1671
                }
×
1672
                return "", err
×
1673
        }
1674
        defer resp.Body.Close()
×
1675
        body, err := io.ReadAll(resp.Body)
×
1676
        if err != nil {
×
1677
                return "", err
×
1678
        }
×
1679

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1823
        return nil
1✔
1824
}
1825

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

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

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

1848
        return nil
×
1849
}
1850

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

1869
// ValidateSnapshotCloneProvisioners validates the target PVC storage class against the snapshot class provisioner
1870
func ValidateSnapshotCloneProvisioners(ctx context.Context, c client.Client, snapshot *snapshotv1.VolumeSnapshot, storageClass *storagev1.StorageClass) (bool, error) {
×
1871
        // Do snapshot and storage class validation
×
1872
        if storageClass == nil {
×
1873
                return false, fmt.Errorf("target storage class not found")
×
1874
        }
×
1875
        if snapshot.Status == nil || snapshot.Status.BoundVolumeSnapshotContentName == nil {
×
1876
                return false, fmt.Errorf("volumeSnapshotContent name not found")
×
1877
        }
×
1878
        volumeSnapshotContent := &snapshotv1.VolumeSnapshotContent{}
×
1879
        if err := c.Get(ctx, types.NamespacedName{Name: *snapshot.Status.BoundVolumeSnapshotContentName}, volumeSnapshotContent); err != nil {
×
1880
                return false, err
×
1881
        }
×
1882
        if storageClass.Provisioner != volumeSnapshotContent.Spec.Driver {
×
1883
                return false, nil
×
1884
        }
×
1885
        // TODO: get sourceVolumeMode from volumesnapshotcontent and validate against target spec
1886
        // currently don't have CRDs in CI with sourceVolumeMode which is pretty new
1887
        // converting volume mode is possible but has security implications
1888
        return true, nil
×
1889
}
1890

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

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

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

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

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

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

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

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

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

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

1976
        return nil, nil
×
1977
}
1978

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

×
1986
        return isCrdDeployed(c, vsClass, version, log) &&
×
1987
                isCrdDeployed(c, vsContent, version, log) &&
×
1988
                isCrdDeployed(c, vs, version, log)
×
1989
}
×
1990

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

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

2008
        return false
×
2009
}
2010

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

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

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

2027
                return false, err
×
2028
        }
2029

2030
        return true, nil
×
2031
}
2032

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

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

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

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

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

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

2104
// AllowClaimAdoption returns true if the PVC may be adopted
2105
func AllowClaimAdoption(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2106
        if pvc == nil || dv == nil {
×
2107
                return false, nil
×
2108
        }
×
2109
        anno, ok := pvc.Annotations[AnnCreatedForDataVolume]
×
2110
        if ok && anno == string(dv.UID) {
×
2111
                return false, nil
×
2112
        }
×
2113
        anno, ok = dv.Annotations[AnnAllowClaimAdoption]
×
2114
        // if annotation exists, go with that regardless of featuregate
×
2115
        if ok {
×
2116
                val, _ := strconv.ParseBool(anno)
×
2117
                return val, nil
×
2118
        }
×
2119
        return featuregates.NewFeatureGates(c).ClaimAdoptionEnabled()
×
2120
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc