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

kubevirt / containerized-data-importer / #4907

23 Aug 2024 07:08PM UTC coverage: 59.122% (-0.05%) from 59.176%
#4907

push

travis-ci

web-flow
Run bazelisk run //robots/cmd/uploader:uploader -- -workspace /home/prow/go/src/github.com/kubevirt/project-infra/../containerized-data-importer/WORKSPACE -dry-run=false (#3407)

Signed-off-by: kubevirt-bot <kubevirtbot@redhat.com>

16599 of 28076 relevant lines covered (59.12%)

0.65 hits per line

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

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

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

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

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

17
package common
18

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

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

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

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

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

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

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

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

98
        // AnnDeleteAfterCompletion is PVC annotation for deleting DV after completion
99
        AnnDeleteAfterCompletion = AnnAPIGroup + "/storage.deleteAfterCompletion"
100
        // AnnPodRetainAfterCompletion is PVC annotation for retaining transfer pods after completion
101
        AnnPodRetainAfterCompletion = AnnAPIGroup + "/storage.pod.retainAfterCompletion"
102

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

259
        // AnnGarbageCollected is a PVC annotation indicating that the PVC was garbage collected
260
        AnnGarbageCollected = AnnAPIGroup + "/garbageCollected"
261

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

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

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

271
        cloneTokenLeeway = 10 * time.Second
272

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

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

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

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

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

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

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

331
        // ProgressDone this means we are DONE
332
        ProgressDone = "100.0%"
333

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

506
        return nil
×
507
}
508

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

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

527
        return storageClass, nil
×
528
}
529

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

763
        return pods, nil
×
764
}
765

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

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

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

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

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

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

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

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

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

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

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

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

831
        return cdiconfig.Status.Preallocation
×
832
}
833

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

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

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

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

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

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

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

873
        obj.SetFinalizers(finalizers)
×
874
}
875

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

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

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

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

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

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

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

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

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

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

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

935
        return ""
×
936
}
937

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

943
        return "persistentvolumeclaims"
×
944
}
945

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

953
        return sourcePvc.Name
×
954
}
955

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

968
        return nil
×
969
}
970

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

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

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

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

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

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

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

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

1040
        return err
×
1041
}
1042

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1350
        return pod
×
1351
}
1352

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1523
        return returnSize, nil
×
1524
}
1525

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

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

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

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

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

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

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

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

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

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

1629
        // Parse the progress from the body
1630
        progressReport := ""
×
1631
        match := regExp.FindStringSubmatch(string(body))
×
1632
        if match != nil {
×
1633
                progressReport = match[len(match)-1]
×
1634
        }
×
1635
        return progressReport, nil
×
1636
}
1637

1638
// UpdateHTTPAnnotations updates the passed annotations for proper http import
1639
func UpdateHTTPAnnotations(annotations map[string]string, http *cdiv1.DataVolumeSourceHTTP) {
×
1640
        annotations[AnnEndpoint] = http.URL
×
1641
        annotations[AnnSource] = SourceHTTP
×
1642

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

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

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

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

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

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

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

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

1✔
1741
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
2✔
1742
                return err
1✔
1743
        }
1✔
1744

1745
        // Examine the claimref for the PV and see if it's still bound to PVC'
1746
        if pv.Spec.ClaimRef == nil {
1✔
1747
                return fmt.Errorf("PV %s claimRef is nil", pv.Name)
×
1748
        }
×
1749

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

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

1772
        return nil
1✔
1773
}
1774

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

1784
        sv := reflect.ValueOf(obj).Elem()
×
1785
        iv := sv.FieldByName("Items")
×
1786

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

1797
        return nil
×
1798
}
1799

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

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

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

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

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

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

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

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

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

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

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

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

1925
        return nil, nil
×
1926
}
1927

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

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

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

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

1957
        return false
×
1958
}
1959

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

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

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

1976
                return false, err
×
1977
        }
1978

1979
        return true, nil
×
1980
}
1981

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

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

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

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

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

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

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

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