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

kubevirt / containerized-data-importer / #5392

18 Jun 2025 09:22AM UTC coverage: 59.444% (+0.04%) from 59.409%
#5392

Pull #3793

travis-ci

jing2uo
Make error message in getDefaultVolumeAndAccessMode return more explicit.

Signed-off-by: jing guo <mail@guojing.io>
Pull Request #3793: Fix error message in datavolume-import-controller

1 of 5 new or added lines in 1 file covered. (20.0%)

585 existing lines in 8 files now uncovered.

16960 of 28531 relevant lines covered (59.44%)

0.66 hits per line

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

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

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

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

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

17
package common
18

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

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

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

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

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

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

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

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

98
        // 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
        // AnnMinimumSupportedPVCSize annotation on a StorageProfile specifies its minimum supported PVC size
222
        AnnMinimumSupportedPVCSize = AnnAPIGroup + "/minimumSupportedPvcSize"
223

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

231
        // AnnSourceVolumeMode is the volume mode of the source PVC specified as an annotation on snapshots
232
        AnnSourceVolumeMode = AnnAPIGroup + "/storage.import.sourceVolumeMode"
233

234
        // AnnOpenShiftImageLookup is the annotation for OpenShift image stream lookup
235
        AnnOpenShiftImageLookup = "alpha.image.policy.openshift.io/resolve-names"
236

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

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

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

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

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

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

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

271
        cloneTokenLeeway = 10 * time.Second
272

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

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

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

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

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

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

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

331
        // LabelExcludeFromVeleroBackup provides a const to indicate whether an object should be excluded from velero backup
332
        LabelExcludeFromVeleroBackup = "velero.io/exclude-from-backup"
333

334
        // ProgressDone this means we are DONE
335
        ProgressDone = "100.0%"
336

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

342
        // AnnAllowClaimAdoption is the annotation that allows a claim to be adopted by a DataVolume
343
        AnnAllowClaimAdoption = AnnAPIGroup + "/allowClaimAdoption"
344

345
        // AnnCdiCustomizeComponentHash annotation is a hash of all customizations that live under spec.CustomizeComponents
346
        AnnCdiCustomizeComponentHash = AnnAPIGroup + "/customizer-identifier"
347

348
        // AnnCreatedForDataVolume stores the UID of the datavolume that the PVC was created for
349
        AnnCreatedForDataVolume = AnnAPIGroup + "/createdForDataVolume"
350
)
351

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

362
var (
363
        // BlockMode is raw block device mode
364
        BlockMode = corev1.PersistentVolumeBlock
365
        // FilesystemMode is filesystem device mode
366
        FilesystemMode = corev1.PersistentVolumeFilesystem
367

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

376
        apiServerKeyOnce sync.Once
377
        apiServerKey     *rsa.PrivateKey
378

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

389
        validLabelsMatch = regexp.MustCompile(`^([\w.]+\.kubevirt.io|kubevirt.io)/[\w-]+$`)
390
)
391

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

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

×
UNCOV
419
// MultiTokenValidator is a token validator that can validate both short and long tokens
×
UNCOV
420
type MultiTokenValidator struct {
×
421
        ShortTokenValidator token.Validator
422
        LongTokenValidator  token.Validator
423
}
424

425
// ValidatePVC validates a PVC
426
func (mtv *MultiTokenValidator) ValidatePVC(source, target *corev1.PersistentVolumeClaim) error {
427
        tok, v := mtv.getTokenAndValidator(target)
428
        return ValidateCloneTokenPVC(tok, v, source, target)
429
}
UNCOV
430

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

×
437
        tok, v := mtv.getTokenAndValidator(pvc)
×
438

×
439
        tokenData, err := v.Validate(tok)
×
440
        if err != nil {
441
                return errors.Wrap(err, "error verifying token")
×
442
        }
×
UNCOV
443

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

×
453
        return validateTokenData(tokenData, vcs.Namespace, srcName, pvc.Namespace, pvc.Name, string(pvc.UID), tokenResourceName)
×
454
}
UNCOV
455

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

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

×
UNCOV
475
// NewCloneTokenValidator returns a new token validator
×
476
func NewCloneTokenValidator(issuer string, key *rsa.PublicKey) token.Validator {
×
477
        return token.NewValidator(issuer, key, cloneTokenLeeway)
×
478
}
479

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

1✔
489
// GetVolumeMode returns the volumeMode from PVC handling default empty value
1✔
490
func GetVolumeMode(pvc *corev1.PersistentVolumeClaim) corev1.PersistentVolumeMode {
1✔
491
        return util.ResolveVolumeMode(pvc.Spec.VolumeMode)
492
}
493

UNCOV
494
// IsDataVolumeUsingDefaultStorageClass checks if the DataVolume is using the default StorageClass
×
495
func IsDataVolumeUsingDefaultStorageClass(dv *cdiv1.DataVolume) bool {
×
496
        return GetStorageClassFromDVSpec(dv) == nil
×
497
}
498

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

×
505
        if dv.Spec.Storage != nil {
×
506
                return dv.Spec.Storage.StorageClassName
×
507
        }
×
508

509
        return nil
×
UNCOV
510
}
×
UNCOV
511

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

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

×
530
        return storageClass, nil
×
UNCOV
531
}
×
532

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

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

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

1✔
UNCOV
558
        if GetContentType(contentType) == cdiv1.DataVolumeKubeVirt {
×
UNCOV
559
                if virtSc := GetPlatformDefaultStorageClass(storageClasses, AnnDefaultVirtStorageClass); virtSc != nil {
×
UNCOV
560
                        return virtSc, nil
×
561
                }
562
        }
2✔
563
        return GetPlatformDefaultStorageClass(storageClasses, AnnDefaultStorageClass), nil
2✔
564
}
1✔
565

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

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

2✔
576
        if len(defaultClasses) == 0 {
1✔
577
                return nil
1✔
578
        }
579

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

594
        return &defaultClasses[0]
2✔
595
}
1✔
596

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

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

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

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

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

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

×
635
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
×
636

637
        storageClassOverhead, found := perStorageConfig[targetStorageClass.GetName()]
×
638
        if found {
×
639
                return storageClassOverhead, nil
×
640
        }
×
UNCOV
641

×
642
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
UNCOV
643
}
×
UNCOV
644

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

×
653
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
UNCOV
654
}
×
UNCOV
655

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

×
664
        return cdiconfig.Status.ImagePullSecrets, nil
×
UNCOV
665
}
×
UNCOV
666

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

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

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

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

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

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

766
        return pods, nil
767
}
768

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

×
776
        if cr == nil {
×
777
                return nil, fmt.Errorf("no active CDI")
×
778
        }
×
779

780
        return &cr.Spec.Workloads, nil
×
UNCOV
781
}
×
UNCOV
782

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

1✔
790
        if len(crList.Items) == 0 {
1✔
UNCOV
791
                return nil, nil
×
UNCOV
792
        }
×
793

794
        if len(crList.Items) == 1 {
2✔
795
                return &crList.Items[0], nil
1✔
796
        }
1✔
797

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

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

809
        return &activeResources[0], nil
2✔
810
}
1✔
811

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

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

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

×
834
        return cdiconfig.Status.Preallocation
×
UNCOV
835
}
×
UNCOV
836

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

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

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

UNCOV
854
// AddFinalizer adds a finalizer to a resource
×
855
func AddFinalizer(obj metav1.Object, name string) {
×
856
        if HasFinalizer(obj, name) {
×
857
                return
858
        }
UNCOV
859

×
860
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
UNCOV
861
}
×
UNCOV
862

×
863
// RemoveFinalizer removes a finalizer from a resource
864
func RemoveFinalizer(obj metav1.Object, name string) {
×
865
        if !HasFinalizer(obj, name) {
866
                return
867
        }
UNCOV
868

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

×
876
        obj.SetFinalizers(finalizers)
×
UNCOV
877
}
×
878

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

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

×
895
        tokenData, err := v.Validate(t)
×
896
        if err != nil {
×
897
                return errors.Wrap(err, "error verifying token")
×
898
        }
UNCOV
899

×
900
        tokenResourceName := getTokenResourceNamePvc(source)
×
901
        srcName := getSourceNamePvc(source)
×
902

×
903
        return validateTokenData(tokenData, source.Namespace, srcName, target.Namespace, target.Name, string(target.UID), tokenResourceName)
UNCOV
904
}
×
UNCOV
905

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

×
913
        tok, ok := dv.Annotations[AnnCloneToken]
×
914
        if !ok {
×
915
                return errors.New("clone token missing")
×
916
        }
UNCOV
917

×
918
        tokenData, err := validator.Validate(tok)
×
919
        if err != nil {
×
920
                return errors.Wrap(err, "error verifying token")
×
921
        }
UNCOV
922

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

×
928
        return validateTokenData(tokenData, sourceNamespace, sourceName, dv.Namespace, dv.Name, "", tokenResourceName)
×
UNCOV
929
}
×
UNCOV
930

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

×
938
        return ""
×
UNCOV
939
}
×
UNCOV
940

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

×
946
        return "persistentvolumeclaims"
×
UNCOV
947
}
×
UNCOV
948

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

×
956
        return sourcePvc.Name
×
UNCOV
957
}
×
958

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

×
971
        return nil
×
UNCOV
972
}
×
UNCOV
973

×
974
// IsSnapshotValidForClone returns an error if the passed snapshot is not valid for cloning
975
func IsSnapshotValidForClone(sourceSnapshot *snapshotv1.VolumeSnapshot) error {
×
976
        if sourceSnapshot.Status == nil {
977
                return fmt.Errorf("no status on source snapshot yet")
978
        }
979
        if !IsSnapshotReady(sourceSnapshot) {
×
980
                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)
×
981
        }
×
982
        if sourceSnapshot.Status.Error != nil {
×
983
                errMessage := "no details"
×
984
                if msg := sourceSnapshot.Status.Error.Message; msg != nil {
×
985
                        errMessage = *msg
×
986
                }
×
987
                return fmt.Errorf("snapshot in error state with msg: %s", errMessage)
×
UNCOV
988
        }
×
989
        if sourceSnapshot.Spec.VolumeSnapshotClassName == nil ||
×
990
                *sourceSnapshot.Spec.VolumeSnapshotClassName == "" {
×
991
                return fmt.Errorf("snapshot %s/%s does not have volume snap class populated, can't clone", sourceSnapshot.Name, sourceSnapshot.Namespace)
×
992
        }
993
        return nil
×
UNCOV
994
}
×
UNCOV
995

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

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

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

×
1021
        // Error handling to fine-tune the event with pertinent info
1022
        if ErrQuotaExceeded(err) {
×
1023
                reason = ErrExceededQuota
×
1024
        }
×
UNCOV
1025

×
1026
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1027

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

×
1038
        AddAnnotation(pvc, AnnPodPhase, string(corev1.PodFailed))
×
1039
        if err := c.Update(context.TODO(), pvc); err != nil {
×
1040
                return err
×
1041
        }
UNCOV
1042

×
1043
        return err
×
UNCOV
1044
}
×
UNCOV
1045

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

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

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

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

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

×
UNCOV
1107
// CreateCloneSourcePodName creates clone source pod name
×
1108
func CreateCloneSourcePodName(targetPvc *corev1.PersistentVolumeClaim) string {
×
1109
        return string(targetPvc.GetUID()) + common.ClonerSourcePodNameSuffix
1110
}
1111

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

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

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

×
1157
        if podSpec.SecurityContext == nil {
×
1158
                podSpec.SecurityContext = &corev1.PodSecurityContext{}
1159
        }
1160
        // Some tools like istio inject containers and thus rely on a pod level seccomp profile being specified
1161
        podSpec.SecurityContext.SeccompProfile = &corev1.SeccompProfile{
×
1162
                Type: corev1.SeccompProfileTypeRuntimeDefault,
×
1163
        }
×
1164
        if hasVolumeMounts {
1165
                podSpec.SecurityContext.FSGroup = ptr.To[int64](common.QemuSubGid)
×
1166
        }
×
UNCOV
1167
}
×
UNCOV
1168

×
UNCOV
1169
// SetNodeNameIfPopulator sets NodeName in a pod spec when the PVC is being handled by a CDI volume populator
×
1170
func SetNodeNameIfPopulator(pvc *corev1.PersistentVolumeClaim, podSpec *corev1.PodSpec) {
×
1171
        _, isPopulator := pvc.Annotations[AnnPopulatorKind]
1172
        nodeName := pvc.Annotations[AnnSelectedNode]
1173
        if isPopulator && nodeName != "" {
1174
                podSpec.NodeName = nodeName
×
1175
        }
×
UNCOV
1176
}
×
UNCOV
1177

×
UNCOV
1178
// CreatePvc creates PVC
×
UNCOV
1179
func CreatePvc(name, ns string, annotations, labels map[string]string) *corev1.PersistentVolumeClaim {
×
1180
        return CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1181
}
1182

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

1✔
1213
// GetAPIServerKey returns API server RSA key
1✔
1214
func GetAPIServerKey() *rsa.PrivateKey {
1✔
1215
        apiServerKeyOnce.Do(func() {
1216
                apiServerKey, _ = rsa.GenerateKey(rand.Reader, 2048)
1217
        })
1218
        return apiServerKey
×
UNCOV
1219
}
×
UNCOV
1220

×
UNCOV
1221
// CreateStorageClass creates storage class CR
×
UNCOV
1222
func CreateStorageClass(name string, annotations map[string]string) *storagev1.StorageClass {
×
1223
        return &storagev1.StorageClass{
1224
                ObjectMeta: metav1.ObjectMeta{
1225
                        Name:        name,
1226
                        Annotations: annotations,
1✔
1227
                },
1✔
1228
        }
1✔
1229
}
1✔
1230

1✔
1231
// CreateImporterTestPod creates importer test pod CR
1✔
1232
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
1✔
1233
        // importer pod name contains the pvc name
1✔
1234
        podName := fmt.Sprintf("%s-%s", common.ImporterPodName, pvc.Name)
1235

1236
        blockOwnerDeletion := true
×
1237
        isController := true
×
1238

×
1239
        volumes := []corev1.Volume{
×
1240
                {
×
1241
                        Name: dvname,
×
1242
                        VolumeSource: corev1.VolumeSource{
×
1243
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1244
                                        ClaimName: pvc.Name,
×
1245
                                        ReadOnly:  false,
×
1246
                                },
×
1247
                        },
×
1248
                },
×
1249
        }
×
1250

×
1251
        if scratchPvc != nil {
×
1252
                volumes = append(volumes, corev1.Volume{
×
1253
                        Name: ScratchVolName,
×
1254
                        VolumeSource: corev1.VolumeSource{
×
1255
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1256
                                        ClaimName: scratchPvc.Name,
×
1257
                                        ReadOnly:  false,
×
1258
                                },
×
1259
                        },
×
1260
                })
×
1261
        }
×
UNCOV
1262

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

×
1311
        ep, _ := GetEndpoint(pvc)
×
1312
        source := GetSource(pvc)
×
1313
        contentType := GetPVCContentType(pvc)
×
1314
        imageSize, _ := GetRequestedImageSize(pvc)
×
1315
        volumeMode := GetVolumeMode(pvc)
×
1316

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

×
1350
        if scratchPvc != nil {
×
1351
                pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
×
1352
                        Name:      ScratchVolName,
×
1353
                        MountPath: common.ScratchDataDir,
1354
                })
×
1355
        }
×
UNCOV
1356

×
1357
        return pod
×
UNCOV
1358
}
×
UNCOV
1359

×
1360
// CreateStorageClassWithProvisioner creates CR of storage class with provisioner
1361
func CreateStorageClassWithProvisioner(name string, annotations, labels map[string]string, provisioner string) *storagev1.StorageClass {
×
1362
        return &storagev1.StorageClass{
1363
                Provisioner: provisioner,
1364
                ObjectMeta: metav1.ObjectMeta{
1365
                        Name:        name,
×
1366
                        Annotations: annotations,
×
1367
                        Labels:      labels,
×
1368
                },
×
1369
        }
×
1370
}
×
UNCOV
1371

×
UNCOV
1372
// CreateClient creates a fake client
×
UNCOV
1373
func CreateClient(objs ...runtime.Object) client.Client {
×
UNCOV
1374
        s := scheme.Scheme
×
1375
        _ = cdiv1.AddToScheme(s)
1376
        _ = corev1.AddToScheme(s)
1377
        _ = storagev1.AddToScheme(s)
1✔
1378
        _ = ocpconfigv1.Install(s)
1✔
1379

1✔
1380
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1381
}
1✔
1382

1✔
1383
// ErrQuotaExceeded checked is the error is of exceeded quota
1✔
1384
func ErrQuotaExceeded(err error) bool {
1✔
1385
        return strings.Contains(err.Error(), "exceeded quota:")
1✔
1386
}
1387

UNCOV
1388
// GetContentType returns the content type. If invalid or not set, default to kubevirt
×
UNCOV
1389
func GetContentType(contentType cdiv1.DataVolumeContentType) cdiv1.DataVolumeContentType {
×
UNCOV
1390
        switch contentType {
×
1391
        case
1392
                cdiv1.DataVolumeKubeVirt,
1393
                cdiv1.DataVolumeArchive:
1✔
1394
        default:
1✔
1395
                // TODO - shouldn't archive be the default?
1396
                contentType = cdiv1.DataVolumeKubeVirt
1397
        }
1✔
UNCOV
1398
        return contentType
×
UNCOV
1399
}
×
UNCOV
1400

×
1401
// GetPVCContentType returns the content type of the source image. If invalid or not set, default to kubevirt
1402
func GetPVCContentType(pvc *corev1.PersistentVolumeClaim) cdiv1.DataVolumeContentType {
1✔
1403
        contentType, found := pvc.Annotations[AnnContentType]
1404
        if !found {
1405
                // TODO - shouldn't archive be the default?
1406
                return cdiv1.DataVolumeKubeVirt
×
1407
        }
×
UNCOV
1408

×
1409
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
UNCOV
1410
}
×
UNCOV
1411

×
1412
// GetNamespace returns the given namespace if not empty, otherwise the default namespace
1413
func GetNamespace(namespace, defaultNamespace string) string {
×
1414
        if namespace == "" {
1415
                return defaultNamespace
1416
        }
1417
        return namespace
×
UNCOV
1418
}
×
UNCOV
1419

×
UNCOV
1420
// IsErrCacheNotStarted checked is the error is of cache not started
×
1421
func IsErrCacheNotStarted(err error) bool {
×
1422
        target := &runtimecache.ErrCacheNotStarted{}
1423
        return errors.As(err, &target)
1424
}
UNCOV
1425

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

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

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

1460
        return "", "", ""
×
UNCOV
1461
}
×
UNCOV
1462

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

×
1473
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
UNCOV
1474
}
×
UNCOV
1475

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

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

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

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

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

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

×
1520
        return returnSize, nil
×
UNCOV
1521
}
×
UNCOV
1522

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

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

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

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

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

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

×
UNCOV
1579
// ErrConnectionRefused checks for connection refused errors
×
1580
func ErrConnectionRefused(err error) bool {
×
1581
        return strings.Contains(err.Error(), "connection refused")
1582
}
1583

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

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

1✔
1610
// GetProgressReportFromURL fetches the progress report from the passed URL according to an specific metric expression and ownerUID
1✔
1611
func GetProgressReportFromURL(ctx context.Context, url string, httpClient *http.Client, metricExp, ownerUID string) (string, error) {
1✔
1612
        regExp := regexp.MustCompile(fmt.Sprintf("(%s)\\{ownerUID\\=%q\\} (\\d{1,3}\\.?\\d*)", metricExp, ownerUID))
1613
        // pod could be gone, don't block an entire thread for 30 seconds
1614
        // just to get back an i/o timeout
1615
        ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
×
1616
        defer cancel()
×
1617
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
×
1618
        if err != nil {
×
1619
                return "", err
×
1620
        }
×
1621
        resp, err := httpClient.Do(req)
×
1622
        if err != nil {
×
1623
                if ErrConnectionRefused(err) {
×
1624
                        return "", nil
×
1625
                }
×
1626
                return "", err
×
UNCOV
1627
        }
×
1628
        defer resp.Body.Close()
×
1629
        body, err := io.ReadAll(resp.Body)
×
1630
        if err != nil {
×
1631
                return "", err
1632
        }
×
UNCOV
1633

×
UNCOV
1634
        // Parse the progress from the body
×
1635
        progressReport := ""
×
1636
        match := regExp.FindStringSubmatch(string(body))
×
1637
        if match != nil {
1638
                progressReport = match[len(match)-1]
1639
        }
×
1640
        return progressReport, nil
×
UNCOV
1641
}
×
UNCOV
1642

×
UNCOV
1643
// UpdateHTTPAnnotations updates the passed annotations for proper http import
×
1644
func UpdateHTTPAnnotations(annotations map[string]string, http *cdiv1.DataVolumeSourceHTTP) {
×
1645
        annotations[AnnEndpoint] = http.URL
1646
        annotations[AnnSource] = SourceHTTP
1647

1648
        if http.SecretRef != "" {
×
1649
                annotations[AnnSecret] = http.SecretRef
×
1650
        }
×
1651
        if http.CertConfigMap != "" {
×
1652
                annotations[AnnCertConfigMap] = http.CertConfigMap
×
1653
        }
×
1654
        for index, header := range http.ExtraHeaders {
×
1655
                annotations[fmt.Sprintf("%s.%d", AnnExtraHeaders, index)] = header
×
1656
        }
×
1657
        for index, header := range http.SecretExtraHeaders {
×
1658
                annotations[fmt.Sprintf("%s.%d", AnnSecretExtraHeaders, index)] = header
×
1659
        }
×
UNCOV
1660
}
×
UNCOV
1661

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

×
UNCOV
1674
// UpdateGCSAnnotations updates the passed annotations for proper GCS import
×
1675
func UpdateGCSAnnotations(annotations map[string]string, gcs *cdiv1.DataVolumeSourceGCS) {
×
1676
        annotations[AnnEndpoint] = gcs.URL
1677
        annotations[AnnSource] = SourceGCS
1678
        if gcs.SecretRef != "" {
1679
                annotations[AnnSecret] = gcs.SecretRef
×
1680
        }
×
UNCOV
1681
}
×
UNCOV
1682

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

×
UNCOV
1710
// UpdateVDDKAnnotations updates the passed annotations for proper VDDK import
×
1711
func UpdateVDDKAnnotations(annotations map[string]string, vddk *cdiv1.DataVolumeSourceVDDK) {
×
1712
        annotations[AnnEndpoint] = vddk.URL
1713
        annotations[AnnSource] = SourceVDDK
×
1714
        annotations[AnnSecret] = vddk.SecretRef
×
1715
        annotations[AnnBackingFile] = vddk.BackingFile
×
1716
        annotations[AnnUUID] = vddk.UUID
1717
        annotations[AnnThumbprint] = vddk.Thumbprint
1718
        if vddk.InitImageURL != "" {
1719
                annotations[AnnVddkInitImageURL] = vddk.InitImageURL
×
1720
        }
×
1721
        if vddk.ExtraArgs != "" {
×
1722
                annotations[AnnVddkExtraArgs] = vddk.ExtraArgs
×
1723
        }
×
UNCOV
1724
}
×
UNCOV
1725

×
UNCOV
1726
// UpdateImageIOAnnotations updates the passed annotations for proper imageIO import
×
1727
func UpdateImageIOAnnotations(annotations map[string]string, imageio *cdiv1.DataVolumeSourceImageIO) {
×
1728
        annotations[AnnEndpoint] = imageio.URL
×
1729
        annotations[AnnSource] = SourceImageio
×
1730
        annotations[AnnSecret] = imageio.SecretRef
×
1731
        annotations[AnnCertConfigMap] = imageio.CertConfigMap
×
1732
        annotations[AnnDiskID] = imageio.DiskID
1733
}
1734

UNCOV
1735
// IsPVBoundToPVC checks if a PV is bound to a specific PVC
×
UNCOV
1736
func IsPVBoundToPVC(pv *corev1.PersistentVolume, pvc *corev1.PersistentVolumeClaim) bool {
×
UNCOV
1737
        claimRef := pv.Spec.ClaimRef
×
UNCOV
1738
        return claimRef != nil && claimRef.Name == pvc.Name && claimRef.Namespace == pvc.Namespace && claimRef.UID == pvc.UID
×
UNCOV
1739
}
×
UNCOV
1740

×
UNCOV
1741
// Rebind binds the PV of source to target
×
1742
func Rebind(ctx context.Context, c client.Client, source, target *corev1.PersistentVolumeClaim) error {
1743
        pv := &corev1.PersistentVolume{
1744
                ObjectMeta: metav1.ObjectMeta{
1✔
1745
                        Name: source.Spec.VolumeName,
1✔
1746
                },
1✔
1747
        }
1✔
1748

1749
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
1750
                return err
1✔
1751
        }
1✔
1752

1✔
1753
        // Examine the claimref for the PV and see if it's still bound to PVC'
1✔
1754
        if pv.Spec.ClaimRef == nil {
1✔
1755
                return fmt.Errorf("PV %s claimRef is nil", pv.Name)
1✔
1756
        }
1✔
1757

2✔
1758
        if !IsPVBoundToPVC(pv, source) {
1✔
1759
                // Something is not right if the PV is neither bound to PVC' nor target PVC
1✔
1760
                if !IsPVBoundToPVC(pv, target) {
1761
                        klog.Errorf("PV bound to unexpected PVC: Could not rebind to target PVC '%s'", target.Name)
1762
                        return fmt.Errorf("PV %s bound to unexpected claim %s", pv.Name, pv.Spec.ClaimRef.Name)
1✔
UNCOV
1763
                }
×
UNCOV
1764
                // our work is done
×
1765
                return nil
1766
        }
2✔
1767

1✔
1768
        // Rebind PVC to target PVC
2✔
1769
        pv.Spec.ClaimRef = &corev1.ObjectReference{
1✔
1770
                Namespace:       target.Namespace,
1✔
1771
                Name:            target.Name,
1✔
1772
                UID:             target.UID,
1773
                ResourceVersion: target.ResourceVersion,
1✔
1774
        }
1775
        klog.V(3).Info("Rebinding PV to target PVC", "PVC", target.Name)
1776
        if err := c.Update(context.TODO(), pv); err != nil {
1777
                return err
1✔
1778
        }
1✔
1779

1✔
1780
        return nil
1✔
1781
}
1✔
1782

1✔
1783
// BulkDeleteResources deletes a bunch of resources
1✔
1784
func BulkDeleteResources(ctx context.Context, c client.Client, obj client.ObjectList, lo client.ListOption) error {
1✔
1785
        if err := c.List(ctx, obj, lo); err != nil {
×
1786
                if meta.IsNoMatchError(err) {
×
1787
                        return nil
1788
                }
1✔
1789
                return err
1790
        }
1791

1792
        sv := reflect.ValueOf(obj).Elem()
×
1793
        iv := sv.FieldByName("Items")
×
1794

×
1795
        for i := 0; i < iv.Len(); i++ {
×
1796
                obj := iv.Index(i).Addr().Interface().(client.Object)
×
1797
                if obj.GetDeletionTimestamp().IsZero() {
×
1798
                        klog.V(3).Infof("Deleting type %+v %+v", reflect.TypeOf(obj), obj)
1799
                        if err := c.Delete(ctx, obj); err != nil {
1800
                                return err
×
1801
                        }
×
UNCOV
1802
                }
×
UNCOV
1803
        }
×
UNCOV
1804

×
1805
        return nil
×
UNCOV
1806
}
×
UNCOV
1807

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

×
UNCOV
1826
// ValidateSnapshotCloneProvisioners validates the target PVC storage class against the snapshot class provisioner
×
1827
func ValidateSnapshotCloneProvisioners(vsc *snapshotv1.VolumeSnapshotContent, storageClass *storagev1.StorageClass) (bool, error) {
×
1828
        // Do snapshot and storage class validation
×
1829
        if storageClass == nil {
×
1830
                return false, fmt.Errorf("target storage class not found")
1831
        }
×
1832
        if storageClass.Provisioner != vsc.Spec.Driver {
1833
                return false, nil
1834
        }
UNCOV
1835
        // TODO: get sourceVolumeMode from volumesnapshotcontent and validate against target spec
×
UNCOV
1836
        // currently don't have CRDs in CI with sourceVolumeMode which is pretty new
×
UNCOV
1837
        // converting volume mode is possible but has security implications
×
1838
        return true, nil
×
UNCOV
1839
}
×
UNCOV
1840

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

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

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

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

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

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

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

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

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

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

1926
        return nil, nil
1927
}
UNCOV
1928

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

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

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

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

×
1958
        return false
1959
}
UNCOV
1960

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

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

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

×
1977
                return false, err
×
UNCOV
1978
        }
×
UNCOV
1979

×
1980
        return true, nil
×
UNCOV
1981
}
×
UNCOV
1982

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

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

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

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

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

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

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

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