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

kubevirt / containerized-data-importer / #5100

22 Jan 2025 06:02PM UTC coverage: 59.371% (-0.03%) from 59.405%
#5100

push

travis-ci

web-flow
switch to bash array to construct ginkgo cmd (#3600)

Signed-off-by: Thomas-David Griedel griedel911@gmail.com

Signed-off-by: Thomas-David Griedel griedel911@gmail.com

16748 of 28209 relevant lines covered (59.37%)

0.66 hits per line

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

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

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

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

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

17
package common
18

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

144
        // AnnVddkVersion shows the last VDDK library version used by a DV's importer pod
145
        AnnVddkVersion = AnnAPIGroup + "/storage.pod.vddk.version"
146
        // AnnVddkHostConnection shows the last ESX host that serviced a DV's importer pod
147
        AnnVddkHostConnection = AnnAPIGroup + "/storage.pod.vddk.host"
148
        // AnnVddkInitImageURL saves a per-DV VDDK image URL on the PVC
149
        AnnVddkInitImageURL = AnnAPIGroup + "/storage.pod.vddk.initimageurl"
150
        // AnnVddkExtraArgs references a ConfigMap that holds arguments to pass directly to the VDDK library
151
        AnnVddkExtraArgs = AnnAPIGroup + "/storage.pod.vddk.extraargs"
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
        // CloneUniqueID is used as a special label to be used when we search for the pod
260
        CloneUniqueID = AnnAPIGroup + "/storage.clone.cloneUniqeId"
261

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

265
        // CloneComplete message
266
        CloneComplete = "Clone Complete"
267

268
        cloneTokenLeeway = 10 * time.Second
269

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

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

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

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

310
        // ClaimLost reason const
311
        ClaimLost = "ClaimLost"
312
        // NotFound reason const
313
        NotFound = "NotFound"
314

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

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

328
        // LabelExcludeFromVeleroBackup provides a const to indicate whether an object should be excluded from velero backup
329
        LabelExcludeFromVeleroBackup = "velero.io/exclude-from-backup"
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
// NewImportDataVolume returns new import DataVolume CR
1420
func NewImportDataVolume(name string) *cdiv1.DataVolume {
×
1421
        return &cdiv1.DataVolume{
×
1422
                TypeMeta: metav1.TypeMeta{APIVersion: cdiv1.SchemeGroupVersion.String()},
×
1423
                ObjectMeta: metav1.ObjectMeta{
×
1424
                        Name:      name,
×
1425
                        Namespace: metav1.NamespaceDefault,
×
1426
                        UID:       types.UID(metav1.NamespaceDefault + "-" + name),
×
1427
                },
×
1428
                Spec: cdiv1.DataVolumeSpec{
×
1429
                        Source: &cdiv1.DataVolumeSource{
×
1430
                                HTTP: &cdiv1.DataVolumeSourceHTTP{
×
1431
                                        URL: "http://example.com/data",
×
1432
                                },
×
1433
                        },
×
1434
                        PVC: &corev1.PersistentVolumeClaimSpec{
×
1435
                                AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
×
1436
                        },
×
1437
                        PriorityClassName: "p0",
×
1438
                },
×
1439
        }
×
1440
}
×
1441

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

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

1453
        return "", "", ""
×
1454
}
1455

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

1466
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1467
}
1468

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

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

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

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

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

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

1513
        return returnSize, nil
×
1514
}
1515

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1762
        return nil
1✔
1763
}
1764

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

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

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

1787
        return nil
×
1788
}
1789

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

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

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

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

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

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

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

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

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

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

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

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

1915
        return nil, nil
×
1916
}
1917

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

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

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

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

1947
        return false
×
1948
}
1949

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

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

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

1966
                return false, err
×
1967
        }
1968

1969
        return true, nil
×
1970
}
1971

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

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

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

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

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

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

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

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