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

kubevirt / containerized-data-importer / #4812

26 Jul 2024 04:25AM UTC coverage: 59.174% (+0.1%) from 59.028%
#4812

push

travis-ci

web-flow
Remove datavolume clone source validation (#3331)

* Remove sourceref validation

The source can be changed and updated, we want the error to be
presented by an event and have the datavolume pending instead
of preventing the creation of the datavolume.
We are already handling the option of the source not existing yet
so lets also handle all the validation in the controller.

Signed-off-by: Shelly Kagan <skagan@redhat.com>

* Move pvc clone specific functions to pvc-clone-controller

Now that the validation is no longer done in the webhook
no need for that code to be in the controller common util
file. Moved the UT accordingly.

Signed-off-by: Shelly Kagan <skagan@redhat.com>

* Move function to new Describe node

Will add in future commits other tests to that section

Signed-off-by: Shelly Kagan <skagan@redhat.com>

* pvc-clone-controller: make better event and update dv if validation fails

In order to make it more clear that the validation failed
we should add the reason of validation failure to the event
Added an update watch to the clone controller so if something changes
in the source the clone will reconcile. Hence no need to return an
error, the reconcile will be triggered if someting will change in the
source.

Signed-off-by: Shelly Kagan <skagan@redhat.com>

* snap-clone: always validate snapshot source

This commit adds missing validation due to the remove validation
from the dv webhook, but also fixes an existing missing validation
in cases where the clone was created before the source. In such case
the webhook would not validate the source since it didnt exist yet,
and then if the clone happened with populators then we would not
validate the source size at all.
We should validate the snapshot source before continuing with the clone
whether it is populator/legacy clones.
Moved all the validation to one function.
Updated the event to include the reason for the validation failure and
updated dv status accordingly.

Signed-off-by: Shelly Kagan... (continued)

94 of 118 new or added lines in 4 files covered. (79.66%)

21 existing lines in 4 files now uncovered.

16464 of 27823 relevant lines covered (59.17%)

0.65 hits per line

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

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

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

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

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

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

1021
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1022

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

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

1038
        return err
×
1039
}
1040

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
1227
        blockOwnerDeletion := true
×
1228
        isController := true
×
1229

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

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

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

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

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

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

1348
        return pod
×
1349
}
1350

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

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

1✔
1371
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1372
}
1✔
1373

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

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

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

UNCOV
1400
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1401
}
1402

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

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

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

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

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

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

1461
        return "", "", ""
×
1462
}
1463

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

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

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

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

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

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

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

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

1521
        return returnSize, nil
×
1522
}
1523

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1765
        return nil
1✔
1766
}
1767

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

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

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

1790
        return nil
×
1791
}
1792

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

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

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

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

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

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

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

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

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

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

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

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

1918
        return nil, nil
×
1919
}
1920

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

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

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

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

1950
        return false
×
1951
}
1952

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

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

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

1969
                return false, err
×
1970
        }
1971

1972
        return true, nil
×
1973
}
1974

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

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

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

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

2033
// ClaimMayExistBeforeDataVolume returns true if the PVC may exist before the DataVolume
2034
func ClaimMayExistBeforeDataVolume(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2035
        if ClaimIsPopulatedForDataVolume(pvc, dv) {
×
2036
                return true, nil
×
2037
        }
×
2038
        return AllowClaimAdoption(c, pvc, dv)
×
2039
}
2040

2041
// ClaimIsPopulatedForDataVolume returns true if the PVC is populated for the given DataVolume
2042
func ClaimIsPopulatedForDataVolume(pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) bool {
×
2043
        return pvc != nil && dv != nil && pvc.Annotations[AnnPopulatedFor] == dv.Name
×
2044
}
×
2045

2046
// AllowClaimAdoption returns true if the PVC may be adopted
2047
func AllowClaimAdoption(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2048
        if pvc == nil || dv == nil {
×
2049
                return false, nil
×
2050
        }
×
2051
        anno, ok := pvc.Annotations[AnnCreatedForDataVolume]
×
2052
        if ok && anno == string(dv.UID) {
×
2053
                return false, nil
×
2054
        }
×
2055
        anno, ok = dv.Annotations[AnnAllowClaimAdoption]
×
2056
        // if annotation exists, go with that regardless of featuregate
×
2057
        if ok {
×
2058
                val, _ := strconv.ParseBool(anno)
×
2059
                return val, nil
×
2060
        }
×
2061
        return featuregates.NewFeatureGates(c).ClaimAdoptionEnabled()
×
2062
}
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