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

kubevirt / containerized-data-importer / #5048

09 Dec 2024 05:21PM UTC coverage: 59.379% (+0.1%) from 59.276%
#5048

push

travis-ci

web-flow
Make deps update over apiserver change (#3560)

Not sure why the CI lane deps-verify didn't catch this.

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

16689 of 28106 relevant lines covered (59.38%)

0.66 hits per line

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

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

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

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

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

17
package common
18

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

219
        // AnnDefaultStorageClass is the annotation indicating that a storage class is the default one
220
        AnnDefaultStorageClass = "storageclass.kubernetes.io/is-default-class"
221
        // AnnDefaultVirtStorageClass is the annotation indicating that a storage class is the default one for virtualization purposes
222
        AnnDefaultVirtStorageClass = "storageclass.kubevirt.io/is-default-virt-class"
223
        // AnnDefaultSnapshotClass is the annotation indicating that a snapshot class is the default one
224
        AnnDefaultSnapshotClass = "snapshot.storage.kubernetes.io/is-default-class"
225

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

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

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

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

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

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

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

260
        // CloneSourceInUse is reason for event created when clone source pvc is in use
261
        CloneSourceInUse = "CloneSourceInUse"
262

263
        // CloneComplete message
264
        CloneComplete = "Clone Complete"
265

266
        cloneTokenLeeway = 10 * time.Second
267

268
        // Default value for preallocation option if not defined in DV or CDIConfig
269
        defaultPreallocation = false
270

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

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

299
        // VolumeSnapshotClassSelected reports that a VolumeSnapshotClass was selected
300
        VolumeSnapshotClassSelected = "VolumeSnapshotClassSelected"
301
        // MessageStorageProfileVolumeSnapshotClassSelected reports that a VolumeSnapshotClass was selected according to StorageProfile
302
        MessageStorageProfileVolumeSnapshotClassSelected = "VolumeSnapshotClass selected according to StorageProfile"
303
        // MessageDefaultVolumeSnapshotClassSelected reports that the default VolumeSnapshotClass was selected
304
        MessageDefaultVolumeSnapshotClassSelected = "Default VolumeSnapshotClass selected"
305
        // MessageFirstVolumeSnapshotClassSelected reports that the first VolumeSnapshotClass was selected
306
        MessageFirstVolumeSnapshotClassSelected = "First VolumeSnapshotClass selected"
307

308
        // ClaimLost reason const
309
        ClaimLost = "ClaimLost"
310
        // NotFound reason const
311
        NotFound = "NotFound"
312

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

322
        // LabelDynamicCredentialSupport specifies if the OS supports updating credentials at runtime.
323
        //nolint:gosec // These are not credentials
324
        LabelDynamicCredentialSupport = "kubevirt.io/dynamic-credentials-support"
325

326
        // LabelExcludeFromVeleroBackup provides a const to indicate whether an object should be excluded from velero backup
327
        LabelExcludeFromVeleroBackup = "velero.io/exclude-from-backup"
328

329
        // ProgressDone this means we are DONE
330
        ProgressDone = "100.0%"
331

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

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

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

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

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

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

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

371
        apiServerKeyOnce sync.Once
372
        apiServerKey     *rsa.PrivateKey
373

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

384
        validLabelsMatch = regexp.MustCompile(`^([\w.]+\.kubevirt.io|kubevirt.io)/[\w-]+$`)
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
970
func IsSnapshotValidForClone(sourceSnapshot *snapshotv1.VolumeSnapshot) error {
×
971
        if sourceSnapshot.Status == nil {
×
972
                return fmt.Errorf("no status on source snapshot yet")
×
973
        }
×
974
        if !IsSnapshotReady(sourceSnapshot) {
×
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)
×
976
        }
×
977
        if sourceSnapshot.Status.Error != nil {
×
978
                errMessage := "no details"
×
979
                if msg := sourceSnapshot.Status.Error.Message; msg != nil {
×
980
                        errMessage = *msg
×
981
                }
×
982
                return fmt.Errorf("snapshot in error state with msg: %s", errMessage)
×
983
        }
984
        if sourceSnapshot.Spec.VolumeSnapshotClassName == nil ||
×
985
                *sourceSnapshot.Spec.VolumeSnapshotClassName == "" {
×
986
                return fmt.Errorf("snapshot %s/%s does not have volume snap class populated, can't clone", sourceSnapshot.Name, sourceSnapshot.Namespace)
×
987
        }
×
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) {
1✔
1001
        if obj.GetLabels() == nil {
2✔
1002
                obj.SetLabels(make(map[string]string))
1✔
1003
        }
1✔
1004
        obj.GetLabels()[key] = value
1✔
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✔
1385
        default:
×
1386
                // TODO - shouldn't archive be the default?
×
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
1393
func GetPVCContentType(pvc *corev1.PersistentVolumeClaim) cdiv1.DataVolumeContentType {
×
1394
        contentType, found := pvc.Annotations[AnnContentType]
×
1395
        if !found {
×
1396
                // TODO - shouldn't archive be the default?
×
1397
                return cdiv1.DataVolumeKubeVirt
×
1398
        }
×
1399

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
// NewImportDataVolume returns new import DataVolume CR
1418
func NewImportDataVolume(name string) *cdiv1.DataVolume {
×
1419
        return &cdiv1.DataVolume{
×
1420
                TypeMeta: metav1.TypeMeta{APIVersion: cdiv1.SchemeGroupVersion.String()},
×
1421
                ObjectMeta: metav1.ObjectMeta{
×
1422
                        Name:      name,
×
1423
                        Namespace: metav1.NamespaceDefault,
×
1424
                        UID:       types.UID(metav1.NamespaceDefault + "-" + name),
×
1425
                },
×
1426
                Spec: cdiv1.DataVolumeSpec{
×
1427
                        Source: &cdiv1.DataVolumeSource{
×
1428
                                HTTP: &cdiv1.DataVolumeSourceHTTP{
×
1429
                                        URL: "http://example.com/data",
×
1430
                                },
×
1431
                        },
×
1432
                        PVC: &corev1.PersistentVolumeClaimSpec{
×
1433
                                AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
×
1434
                        },
×
1435
                        PriorityClassName: "p0",
×
1436
                },
×
1437
        }
×
1438
}
×
1439

1440
// GetCloneSourceInfo returns the type, name and namespace of the cloning source
1441
func GetCloneSourceInfo(dv *cdiv1.DataVolume) (sourceType, sourceName, sourceNamespace string) {
×
1442
        // Cloning sources are mutually exclusive
×
1443
        if dv.Spec.Source.PVC != nil {
×
1444
                return "pvc", dv.Spec.Source.PVC.Name, dv.Spec.Source.PVC.Namespace
×
1445
        }
×
1446

1447
        if dv.Spec.Source.Snapshot != nil {
×
1448
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
×
1449
        }
×
1450

1451
        return "", "", ""
×
1452
}
1453

1454
// IsWaitForFirstConsumerEnabled tells us if we should respect "real" WFFC behavior or just let our worker pods randomly spawn
1455
func IsWaitForFirstConsumerEnabled(obj metav1.Object, gates featuregates.FeatureGates) (bool, error) {
×
1456
        // when PVC requests immediateBinding it cannot honor wffc logic
×
1457
        isImmediateBindingRequested := ImmediateBindingRequested(obj)
×
1458
        pvcHonorWaitForFirstConsumer := !isImmediateBindingRequested
×
1459
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
×
1460
        if err != nil {
×
1461
                return false, err
×
1462
        }
×
1463

1464
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1465
}
1466

1467
// AddImmediateBindingAnnotationIfWFFCDisabled adds the immediateBinding annotation if wffc feature gate is disabled
1468
func AddImmediateBindingAnnotationIfWFFCDisabled(obj metav1.Object, gates featuregates.FeatureGates) error {
×
1469
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
×
1470
        if err != nil {
×
1471
                return err
×
1472
        }
×
1473
        if !globalHonorWaitForFirstConsumer {
×
1474
                AddAnnotation(obj, AnnImmediateBinding, "")
×
1475
        }
×
1476
        return nil
×
1477
}
1478

1479
// GetRequiredSpace calculates space required taking file system overhead into account
1480
func GetRequiredSpace(filesystemOverhead float64, requestedSpace int64) int64 {
×
1481
        // the `image` has to be aligned correctly, so the space requested has to be aligned to
×
1482
        // next value that is a multiple of a block size
×
1483
        alignedSize := util.RoundUp(requestedSpace, util.DefaultAlignBlockSize)
×
1484

×
1485
        // count overhead as a percentage of the whole/new size, including aligned image
×
1486
        // and the space required by filesystem metadata
×
1487
        spaceWithOverhead := int64(math.Ceil(float64(alignedSize) / (1 - filesystemOverhead)))
×
1488
        return spaceWithOverhead
×
1489
}
×
1490

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

×
1495
        if util.ResolveVolumeMode(pvcSpec.VolumeMode) == corev1.PersistentVolumeFilesystem {
×
1496
                fsOverhead, err := GetFilesystemOverheadForStorageClass(ctx, c, pvcSpec.StorageClassName)
×
1497
                if err != nil {
×
1498
                        return resource.Quantity{}, err
×
1499
                }
×
1500
                // Parse filesystem overhead (percentage) into a 64-bit float
1501
                fsOverheadFloat, _ := strconv.ParseFloat(string(fsOverhead), 64)
×
1502

×
1503
                // Merge the previous values into a 'resource.Quantity' struct
×
1504
                requiredSpace := GetRequiredSpace(fsOverheadFloat, imgSize)
×
1505
                returnSize = *resource.NewScaledQuantity(requiredSpace, 0)
×
1506
        } else {
×
1507
                // Inflation is not needed with 'Block' mode
×
1508
                returnSize = *resource.NewScaledQuantity(imgSize, 0)
×
1509
        }
×
1510

1511
        return returnSize, nil
×
1512
}
1513

1514
// IsBound returns if the pvc is bound
1515
func IsBound(pvc *corev1.PersistentVolumeClaim) bool {
×
1516
        return pvc != nil && pvc.Status.Phase == corev1.ClaimBound
×
1517
}
×
1518

1519
// IsUnbound returns if the pvc is not bound yet
1520
func IsUnbound(pvc *corev1.PersistentVolumeClaim) bool {
×
1521
        return !IsBound(pvc)
×
1522
}
×
1523

1524
// IsLost returns if the pvc is lost
1525
func IsLost(pvc *corev1.PersistentVolumeClaim) bool {
×
1526
        return pvc != nil && pvc.Status.Phase == corev1.ClaimLost
×
1527
}
×
1528

1529
// IsImageStream returns true if registry source is ImageStream
1530
func IsImageStream(pvc *corev1.PersistentVolumeClaim) bool {
×
1531
        return pvc.Annotations[AnnRegistryImageStream] == "true"
×
1532
}
×
1533

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

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

1570
// ErrConnectionRefused checks for connection refused errors
1571
func ErrConnectionRefused(err error) bool {
×
1572
        return strings.Contains(err.Error(), "connection refused")
×
1573
}
×
1574

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

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

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

1617
        // Parse the progress from the body
1618
        progressReport := ""
×
1619
        match := regExp.FindStringSubmatch(string(body))
×
1620
        if match != nil {
×
1621
                progressReport = match[len(match)-1]
×
1622
        }
×
1623
        return progressReport, nil
×
1624
}
1625

1626
// UpdateHTTPAnnotations updates the passed annotations for proper http import
1627
func UpdateHTTPAnnotations(annotations map[string]string, http *cdiv1.DataVolumeSourceHTTP) {
×
1628
        annotations[AnnEndpoint] = http.URL
×
1629
        annotations[AnnSource] = SourceHTTP
×
1630

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

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

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

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

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

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

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

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

1✔
1729
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
2✔
1730
                return err
1✔
1731
        }
1✔
1732

1733
        // Examine the claimref for the PV and see if it's still bound to PVC'
1734
        if pv.Spec.ClaimRef == nil {
1✔
1735
                return fmt.Errorf("PV %s claimRef is nil", pv.Name)
×
1736
        }
×
1737

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

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

1760
        return nil
1✔
1761
}
1762

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

1772
        sv := reflect.ValueOf(obj).Elem()
×
1773
        iv := sv.FieldByName("Items")
×
1774

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

1785
        return nil
×
1786
}
1787

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

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

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

1837
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(context.TODO(), client, targetPvcStorageClassName)
×
1838
        if err != nil {
×
1839
                return "", err
×
1840
        }
×
1841
        if targetStorageClass == nil {
×
1842
                logger.Info("Target PVC's Storage Class not found")
×
1843
                return "", nil
×
1844
        }
×
1845

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

1858
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
1859
        return "", nil
×
1860
}
1861

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

×
1867
        logEvent := func(message, vscName string) {
×
1868
                logger.Info(message, "name", vscName)
×
1869
                if pvc != nil {
×
1870
                        msg := fmt.Sprintf("%s %s", message, vscName)
×
1871
                        recorder.Event(pvc, corev1.EventTypeNormal, VolumeSnapshotClassSelected, msg)
×
1872
                }
×
1873
        }
1874

1875
        if snapshotClassName != nil {
×
1876
                vsc := &snapshotv1.VolumeSnapshotClass{}
×
1877
                if err := c.Get(context.TODO(), types.NamespacedName{Name: *snapshotClassName}, vsc); err != nil {
×
1878
                        return nil, err
×
1879
                }
×
1880
                if vsc.Driver == driver {
×
1881
                        logEvent(MessageStorageProfileVolumeSnapshotClassSelected, vsc.Name)
×
1882
                        return snapshotClassName, nil
×
1883
                }
×
1884
                return nil, nil
×
1885
        }
1886

1887
        vscList := &snapshotv1.VolumeSnapshotClassList{}
×
1888
        if err := c.List(ctx, vscList); err != nil {
×
1889
                if meta.IsNoMatchError(err) {
×
1890
                        return nil, nil
×
1891
                }
×
1892
                return nil, err
×
1893
        }
1894

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

1907
        if len(candidates) > 0 {
×
1908
                sort.Strings(candidates)
×
1909
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
×
1910
                return &candidates[0], nil
×
1911
        }
×
1912

1913
        return nil, nil
×
1914
}
1915

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

×
1923
        return isCrdDeployed(c, vsClass, version, log) &&
×
1924
                isCrdDeployed(c, vsContent, version, log) &&
×
1925
                isCrdDeployed(c, vs, version, log)
×
1926
}
×
1927

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

1939
        for _, v := range crd.Spec.Versions {
×
1940
                if v.Name == version && v.Served {
×
1941
                        return true
×
1942
                }
×
1943
        }
1944

1945
        return false
×
1946
}
1947

1948
// IsSnapshotReady indicates if a volume snapshot is ready to be used
1949
func IsSnapshotReady(snapshot *snapshotv1.VolumeSnapshot) bool {
×
1950
        return snapshot.Status != nil && snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse
×
1951
}
×
1952

1953
// GetResource updates given obj with the data of the object with the same name and namespace
1954
func GetResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
×
1955
        obj.SetNamespace(namespace)
×
1956
        obj.SetName(name)
×
1957

×
1958
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
1959
        if err != nil {
×
1960
                if k8serrors.IsNotFound(err) {
×
1961
                        return false, nil
×
1962
                }
×
1963

1964
                return false, err
×
1965
        }
1966

1967
        return true, nil
×
1968
}
1969

1970
// PatchArgs are the args for Patch
1971
type PatchArgs struct {
1972
        Client client.Client
1973
        Log    logr.Logger
1974
        Obj    client.Object
1975
        OldObj client.Object
1976
}
1977

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

2007
// OwnedByDataVolume returns true if the object is owned by a DataVolume
2008
func OwnedByDataVolume(obj metav1.Object) bool {
×
2009
        owner := metav1.GetControllerOf(obj)
×
2010
        return owner != nil && owner.Kind == "DataVolume"
×
2011
}
×
2012

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

2028
// CopyAllowedLabels copies allowed labels matching the validLabelsMatch regexp from the
2029
// source map to the destination object allowing overwrites
2030
func CopyAllowedLabels(srcLabels map[string]string, dstObj metav1.Object, overwrite bool) {
1✔
2031
        for label, value := range srcLabels {
2✔
2032
                if _, found := dstObj.GetLabels()[label]; (!found || overwrite) && validLabelsMatch.MatchString(label) {
2✔
2033
                        AddLabel(dstObj, label, value)
1✔
2034
                }
1✔
2035
        }
2036
}
2037

2038
// ClaimMayExistBeforeDataVolume returns true if the PVC may exist before the DataVolume
2039
func ClaimMayExistBeforeDataVolume(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2040
        if ClaimIsPopulatedForDataVolume(pvc, dv) {
×
2041
                return true, nil
×
2042
        }
×
2043
        return AllowClaimAdoption(c, pvc, dv)
×
2044
}
2045

2046
// ClaimIsPopulatedForDataVolume returns true if the PVC is populated for the given DataVolume
2047
func ClaimIsPopulatedForDataVolume(pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) bool {
×
2048
        return pvc != nil && dv != nil && pvc.Annotations[AnnPopulatedFor] == dv.Name
×
2049
}
×
2050

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