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

kubevirt / containerized-data-importer / #5338

19 May 2025 11:30AM UTC coverage: 59.353% (+0.01%) from 59.34%
#5338

Pull #3753

travis-ci

Acedus
importer: pullMethod node architecture support

This commit adds support for specifying architecture for the registry
data source with pullMethod node. When configured, the importer Pod will
gain another NodeSelector for nodes that have the matching architecture
label.

In the event that the importer Pod is unschedulable due to the
pullMethod node's node selector, the condition will be propagated to the
DV's running condition until it becomes schedulable.

Signed-off-by: Adi Aloni <aaloni@redhat.com>
Pull Request #3753: DNM: Support architecture specific image import for registry datasource

44 of 62 new or added lines in 6 files covered. (70.97%)

1 existing line in 1 file now uncovered.

16908 of 28487 relevant lines covered (59.35%)

0.66 hits per line

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

14.17
/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
        // AnnPodSchedulable is a PVC annotation that tells if the Pod is schedulable or not
90
        AnnPodSchedulable = AnnAPIGroup + "/storage.pod.schedulable"
91
        // AnnPopulatedFor is a PVC annotation telling the datavolume controller that the PVC is already populated
92
        AnnPopulatedFor = AnnAPIGroup + "/storage.populatedFor"
93
        // AnnPrePopulated is a PVC annotation telling the datavolume controller that the PVC is already populated
94
        AnnPrePopulated = AnnAPIGroup + "/storage.prePopulated"
95
        // AnnPriorityClassName is PVC annotation to indicate the priority class name for importer, cloner and uploader pod
96
        AnnPriorityClassName = AnnAPIGroup + "/storage.pod.priorityclassname"
97
        // AnnExternalPopulation annotation marks a PVC as "externally populated", allowing the import-controller to skip it
98
        AnnExternalPopulation = AnnAPIGroup + "/externalPopulation"
99

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

209
        // AnnUploadRequest marks that a PVC should be made available for upload
210
        AnnUploadRequest = AnnAPIGroup + "/storage.upload.target"
211

212
        // AnnCheckStaticVolume checks if a statically allocated PV exists before creating the target PVC.
213
        // If so, PVC is still created but population is skipped
214
        AnnCheckStaticVolume = AnnAPIGroup + "/storage.checkStaticVolume"
215

216
        // AnnPersistentVolumeList is an annotation storing a list of PV names
217
        AnnPersistentVolumeList = AnnAPIGroup + "/storage.persistentVolumeList"
218

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

225
        // AnnMinimumSupportedPVCSize annotation on a StorageProfile specifies its minimum supported PVC size
226
        AnnMinimumSupportedPVCSize = AnnAPIGroup + "/minimumSupportedPvcSize"
227

228
        // AnnDefaultStorageClass is the annotation indicating that a storage class is the default one
229
        AnnDefaultStorageClass = "storageclass.kubernetes.io/is-default-class"
230
        // AnnDefaultVirtStorageClass is the annotation indicating that a storage class is the default one for virtualization purposes
231
        AnnDefaultVirtStorageClass = "storageclass.kubevirt.io/is-default-virt-class"
232
        // AnnDefaultSnapshotClass is the annotation indicating that a snapshot class is the default one
233
        AnnDefaultSnapshotClass = "snapshot.storage.kubernetes.io/is-default-class"
234

235
        // AnnSourceVolumeMode is the volume mode of the source PVC specified as an annotation on snapshots
236
        AnnSourceVolumeMode = AnnAPIGroup + "/storage.import.sourceVolumeMode"
237

238
        // AnnOpenShiftImageLookup is the annotation for OpenShift image stream lookup
239
        AnnOpenShiftImageLookup = "alpha.image.policy.openshift.io/resolve-names"
240

241
        // AnnCloneRequest sets our expected annotation for a CloneRequest
242
        AnnCloneRequest = "k8s.io/CloneRequest"
243
        // AnnCloneOf is used to indicate that cloning was complete
244
        AnnCloneOf = "k8s.io/CloneOf"
245

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

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

262
        // AnnSelectedNode annotation is added to a PVC that has been triggered by scheduler to
263
        // be dynamically provisioned. Its value is the name of the selected node.
264
        AnnSelectedNode = "volume.kubernetes.io/selected-node"
265

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

269
        // CloneSourceInUse is reason for event created when clone source pvc is in use
270
        CloneSourceInUse = "CloneSourceInUse"
271

272
        // CloneComplete message
273
        CloneComplete = "Clone Complete"
274

275
        cloneTokenLeeway = 10 * time.Second
276

277
        // Default value for preallocation option if not defined in DV or CDIConfig
278
        defaultPreallocation = false
279

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

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

308
        // VolumeSnapshotClassSelected reports that a VolumeSnapshotClass was selected
309
        VolumeSnapshotClassSelected = "VolumeSnapshotClassSelected"
310
        // MessageStorageProfileVolumeSnapshotClassSelected reports that a VolumeSnapshotClass was selected according to StorageProfile
311
        MessageStorageProfileVolumeSnapshotClassSelected = "VolumeSnapshotClass selected according to StorageProfile"
312
        // MessageDefaultVolumeSnapshotClassSelected reports that the default VolumeSnapshotClass was selected
313
        MessageDefaultVolumeSnapshotClassSelected = "Default VolumeSnapshotClass selected"
314
        // MessageFirstVolumeSnapshotClassSelected reports that the first VolumeSnapshotClass was selected
315
        MessageFirstVolumeSnapshotClassSelected = "First VolumeSnapshotClass selected"
316

317
        // ClaimLost reason const
318
        ClaimLost = "ClaimLost"
319
        // NotFound reason const
320
        NotFound = "NotFound"
321

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

331
        // LabelDynamicCredentialSupport specifies if the OS supports updating credentials at runtime.
332
        //nolint:gosec // These are not credentials
333
        LabelDynamicCredentialSupport = "kubevirt.io/dynamic-credentials-support"
334

335
        // LabelExcludeFromVeleroBackup provides a const to indicate whether an object should be excluded from velero backup
336
        LabelExcludeFromVeleroBackup = "velero.io/exclude-from-backup"
337

338
        // ProgressDone this means we are DONE
339
        ProgressDone = "100.0%"
340

341
        // AnnEventSourceKind is the source kind that should be related to events
342
        AnnEventSourceKind = AnnAPIGroup + "/events.source.kind"
343
        // AnnEventSource is the source that should be related to events (namespace/name)
344
        AnnEventSource = AnnAPIGroup + "/events.source"
345

346
        // AnnAllowClaimAdoption is the annotation that allows a claim to be adopted by a DataVolume
347
        AnnAllowClaimAdoption = AnnAPIGroup + "/allowClaimAdoption"
348

349
        // AnnCdiCustomizeComponentHash annotation is a hash of all customizations that live under spec.CustomizeComponents
350
        AnnCdiCustomizeComponentHash = AnnAPIGroup + "/customizer-identifier"
351

352
        // AnnCreatedForDataVolume stores the UID of the datavolume that the PVC was created for
353
        AnnCreatedForDataVolume = AnnAPIGroup + "/createdForDataVolume"
354
)
355

356
// Size-detection pod error codes
357
const (
358
        NoErr int = iota
359
        ErrBadArguments
360
        ErrInvalidFile
361
        ErrInvalidPath
362
        ErrBadTermFile
363
        ErrUnknown
364
)
365

366
var (
367
        // BlockMode is raw block device mode
368
        BlockMode = corev1.PersistentVolumeBlock
369
        // FilesystemMode is filesystem device mode
370
        FilesystemMode = corev1.PersistentVolumeFilesystem
371

372
        // DefaultInstanceTypeLabels is a list of currently supported default instance type labels
373
        DefaultInstanceTypeLabels = []string{
374
                LabelDefaultInstancetype,
375
                LabelDefaultInstancetypeKind,
376
                LabelDefaultPreference,
377
                LabelDefaultPreferenceKind,
378
        }
379

380
        apiServerKeyOnce sync.Once
381
        apiServerKey     *rsa.PrivateKey
382

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

393
        validLabelsMatch = regexp.MustCompile(`^([\w.]+\.kubevirt.io|kubevirt.io)/[\w-]+$`)
394
)
395

396
// FakeValidator is a fake token validator
397
type FakeValidator struct {
398
        Match     string
399
        Operation token.Operation
400
        Name      string
401
        Namespace string
402
        Resource  metav1.GroupVersionResource
403
        Params    map[string]string
404
}
405

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

423
// MultiTokenValidator is a token validator that can validate both short and long tokens
424
type MultiTokenValidator struct {
425
        ShortTokenValidator token.Validator
426
        LongTokenValidator  token.Validator
427
}
428

429
// ValidatePVC validates a PVC
430
func (mtv *MultiTokenValidator) ValidatePVC(source, target *corev1.PersistentVolumeClaim) error {
×
431
        tok, v := mtv.getTokenAndValidator(target)
×
432
        return ValidateCloneTokenPVC(tok, v, source, target)
×
433
}
×
434

435
// ValidatePopulator valades a token for a populator
436
func (mtv *MultiTokenValidator) ValidatePopulator(vcs *cdiv1.VolumeCloneSource, pvc *corev1.PersistentVolumeClaim) error {
×
437
        if vcs.Namespace == pvc.Namespace {
×
438
                return nil
×
439
        }
×
440

441
        tok, v := mtv.getTokenAndValidator(pvc)
×
442

×
443
        tokenData, err := v.Validate(tok)
×
444
        if err != nil {
×
445
                return errors.Wrap(err, "error verifying token")
×
446
        }
×
447

448
        var tokenResourceName string
×
449
        switch vcs.Spec.Source.Kind {
×
450
        case "PersistentVolumeClaim":
×
451
                tokenResourceName = "persistentvolumeclaims"
×
452
        case "VolumeSnapshot":
×
453
                tokenResourceName = "volumesnapshots"
×
454
        }
455
        srcName := vcs.Spec.Source.Name
×
456

×
457
        return validateTokenData(tokenData, vcs.Namespace, srcName, pvc.Namespace, pvc.Name, string(pvc.UID), tokenResourceName)
×
458
}
459

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

471
// NewMultiTokenValidator returns a new multi token validator
472
func NewMultiTokenValidator(key *rsa.PublicKey) *MultiTokenValidator {
×
473
        return &MultiTokenValidator{
×
474
                ShortTokenValidator: NewCloneTokenValidator(common.CloneTokenIssuer, key),
×
475
                LongTokenValidator:  NewCloneTokenValidator(common.ExtendedCloneTokenIssuer, key),
×
476
        }
×
477
}
×
478

479
// NewCloneTokenValidator returns a new token validator
480
func NewCloneTokenValidator(issuer string, key *rsa.PublicKey) token.Validator {
×
481
        return token.NewValidator(issuer, key, cloneTokenLeeway)
×
482
}
×
483

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

493
// GetVolumeMode returns the volumeMode from PVC handling default empty value
494
func GetVolumeMode(pvc *corev1.PersistentVolumeClaim) corev1.PersistentVolumeMode {
×
495
        return util.ResolveVolumeMode(pvc.Spec.VolumeMode)
×
496
}
×
497

498
// IsDataVolumeUsingDefaultStorageClass checks if the DataVolume is using the default StorageClass
499
func IsDataVolumeUsingDefaultStorageClass(dv *cdiv1.DataVolume) bool {
×
500
        return GetStorageClassFromDVSpec(dv) == nil
×
501
}
×
502

503
// GetStorageClassFromDVSpec returns the StorageClassName from DataVolume PVC or Storage spec
504
func GetStorageClassFromDVSpec(dv *cdiv1.DataVolume) *string {
×
505
        if dv.Spec.PVC != nil {
×
506
                return dv.Spec.PVC.StorageClassName
×
507
        }
×
508

509
        if dv.Spec.Storage != nil {
×
510
                return dv.Spec.Storage.StorageClassName
×
511
        }
×
512

513
        return nil
×
514
}
515

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

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

534
        return storageClass, nil
×
535
}
536

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

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

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

562
        if GetContentType(contentType) == cdiv1.DataVolumeKubeVirt {
2✔
563
                if virtSc := GetPlatformDefaultStorageClass(storageClasses, AnnDefaultVirtStorageClass); virtSc != nil {
2✔
564
                        return virtSc, nil
1✔
565
                }
1✔
566
        }
567
        return GetPlatformDefaultStorageClass(storageClasses, AnnDefaultStorageClass), nil
1✔
568
}
569

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

1✔
574
        for _, storageClass := range storageClasses.Items {
2✔
575
                if storageClass.Annotations[defaultAnnotationKey] == "true" {
2✔
576
                        defaultClasses = append(defaultClasses, storageClass)
1✔
577
                }
1✔
578
        }
579

580
        if len(defaultClasses) == 0 {
2✔
581
                return nil
1✔
582
        }
1✔
583

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

598
        return &defaultClasses[0]
1✔
599
}
600

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

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

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

627
        if cdiConfig.Status.FilesystemOverhead == nil {
×
628
                klog.Errorf("CDIConfig filesystemOverhead used before config controller ran reconcile. Hopefully this only happens during unit testing.")
×
629
                return "0", nil
×
630
        }
×
631

632
        if targetStorageClass == nil {
×
633
                klog.V(3).Info("Storage class", storageClassName, "not found, continuing with global overhead")
×
634
                return cdiConfig.Status.FilesystemOverhead.Global, nil
×
635
        }
×
636

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

×
639
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
×
640

×
641
        storageClassOverhead, found := perStorageConfig[targetStorageClass.GetName()]
×
642
        if found {
×
643
                return storageClassOverhead, nil
×
644
        }
×
645

646
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
647
}
648

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

657
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
658
}
659

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

668
        return cdiconfig.Status.ImagePullSecrets, nil
×
669
}
670

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

682
        pvcUID := pvc.GetUID()
×
683
        for _, pod := range pods.Items {
×
684
                if ShouldIgnorePod(&pod, pvc) {
×
685
                        continue
×
686
                }
687
                for _, or := range pod.OwnerReferences {
×
688
                        if or.UID == pvcUID {
×
689
                                return &pod, nil
×
690
                        }
×
691
                }
692

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

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

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

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

770
        return pods, nil
×
771
}
772

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

780
        if cr == nil {
×
781
                return nil, fmt.Errorf("no active CDI")
×
782
        }
×
783

784
        return &cr.Spec.Workloads, nil
×
785
}
786

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

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

798
        if len(crList.Items) == 1 {
2✔
799
                return &crList.Items[0], nil
1✔
800
        }
1✔
801

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

809
        if len(activeResources) != 1 {
2✔
810
                return nil, fmt.Errorf("invalid number of active CDI resources: %d", len(activeResources))
1✔
811
        }
1✔
812

813
        return &activeResources[0], nil
1✔
814
}
815

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

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

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

838
        return cdiconfig.Status.Preallocation
×
839
}
840

841
// ImmediateBindingRequested returns if an object has the ImmediateBinding annotation
842
func ImmediateBindingRequested(obj metav1.Object) bool {
×
843
        _, isImmediateBindingRequested := obj.GetAnnotations()[AnnImmediateBinding]
×
844
        return isImmediateBindingRequested
×
845
}
×
846

847
// GetPriorityClass gets PVC priority class
848
func GetPriorityClass(pvc *corev1.PersistentVolumeClaim) string {
×
849
        anno := pvc.GetAnnotations()
×
850
        return anno[AnnPriorityClassName]
×
851
}
×
852

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

858
// AddFinalizer adds a finalizer to a resource
859
func AddFinalizer(obj metav1.Object, name string) {
×
860
        if HasFinalizer(obj, name) {
×
861
                return
×
862
        }
×
863

864
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
865
}
866

867
// RemoveFinalizer removes a finalizer from a resource
868
func RemoveFinalizer(obj metav1.Object, name string) {
×
869
        if !HasFinalizer(obj, name) {
×
870
                return
×
871
        }
×
872

873
        var finalizers []string
×
874
        for _, f := range obj.GetFinalizers() {
×
875
                if f != name {
×
876
                        finalizers = append(finalizers, f)
×
877
                }
×
878
        }
879

880
        obj.SetFinalizers(finalizers)
×
881
}
882

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

893
// ValidateCloneTokenPVC validates clone token for source and target PVCs
894
func ValidateCloneTokenPVC(t string, v token.Validator, source, target *corev1.PersistentVolumeClaim) error {
×
895
        if source.Namespace == target.Namespace {
×
896
                return nil
×
897
        }
×
898

899
        tokenData, err := v.Validate(t)
×
900
        if err != nil {
×
901
                return errors.Wrap(err, "error verifying token")
×
902
        }
×
903

904
        tokenResourceName := getTokenResourceNamePvc(source)
×
905
        srcName := getSourceNamePvc(source)
×
906

×
907
        return validateTokenData(tokenData, source.Namespace, srcName, target.Namespace, target.Name, string(target.UID), tokenResourceName)
×
908
}
909

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

917
        tok, ok := dv.Annotations[AnnCloneToken]
×
918
        if !ok {
×
919
                return errors.New("clone token missing")
×
920
        }
×
921

922
        tokenData, err := validator.Validate(tok)
×
923
        if err != nil {
×
924
                return errors.Wrap(err, "error verifying token")
×
925
        }
×
926

927
        tokenResourceName := getTokenResourceNameDataVolume(dv.Spec.Source)
×
928
        if tokenResourceName == "" {
×
929
                return errors.New("token resource name empty, can't verify properly")
×
930
        }
×
931

932
        return validateTokenData(tokenData, sourceNamespace, sourceName, dv.Namespace, dv.Name, "", tokenResourceName)
×
933
}
934

935
func getTokenResourceNameDataVolume(source *cdiv1.DataVolumeSource) string {
×
936
        if source.PVC != nil {
×
937
                return "persistentvolumeclaims"
×
938
        } else if source.Snapshot != nil {
×
939
                return "volumesnapshots"
×
940
        }
×
941

942
        return ""
×
943
}
944

945
func getTokenResourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
946
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
×
947
                return "volumesnapshots"
×
948
        }
×
949

950
        return "persistentvolumeclaims"
×
951
}
952

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

960
        return sourcePvc.Name
×
961
}
962

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

975
        return nil
×
976
}
977

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

1000
// AddAnnotation adds an annotation to an object
1001
func AddAnnotation(obj metav1.Object, key, value string) {
1✔
1002
        if obj.GetAnnotations() == nil {
2✔
1003
                obj.SetAnnotations(make(map[string]string))
1✔
1004
        }
1✔
1005
        obj.GetAnnotations()[key] = value
1✔
1006
}
1007

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

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

×
1025
        // Error handling to fine-tune the event with pertinent info
×
1026
        if ErrQuotaExceeded(err) {
×
1027
                reason = ErrExceededQuota
×
1028
        }
×
1029

1030
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1031

×
1032
        if isCloneSourcePod := CreateCloneSourcePodName(pvc) == podName; isCloneSourcePod {
×
1033
                AddAnnotation(pvc, AnnSourceRunningCondition, "false")
×
1034
                AddAnnotation(pvc, AnnSourceRunningConditionReason, reason)
×
1035
                AddAnnotation(pvc, AnnSourceRunningConditionMessage, msg)
×
1036
        } else {
×
1037
                AddAnnotation(pvc, AnnRunningCondition, "false")
×
1038
                AddAnnotation(pvc, AnnRunningConditionReason, reason)
×
1039
                AddAnnotation(pvc, AnnRunningConditionMessage, msg)
×
1040
        }
×
1041

1042
        AddAnnotation(pvc, AnnPodPhase, string(corev1.PodFailed))
×
1043
        if err := c.Update(context.TODO(), pvc); err != nil {
×
1044
                return err
×
1045
        }
×
1046

1047
        return err
×
1048
}
1049

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

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

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

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

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

1111
// CreateCloneSourcePodName creates clone source pod name
1112
func CreateCloneSourcePodName(targetPvc *corev1.PersistentVolumeClaim) string {
×
1113
        return string(targetPvc.GetUID()) + common.ClonerSourcePodNameSuffix
×
1114
}
×
1115

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

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

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

1161
        if hasVolumeMounts {
×
1162
                if podSpec.SecurityContext == nil {
×
1163
                        podSpec.SecurityContext = &corev1.PodSecurityContext{}
×
1164
                }
×
1165
                podSpec.SecurityContext.FSGroup = ptr.To[int64](common.QemuSubGid)
×
1166
        }
1167
}
1168

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
        }
×
1176
}
1177

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

1183
// CreatePvcInStorageClass creates PVC with storgae class
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{
1✔
1187
                        Name:        name,
1✔
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 {
2✔
1208
                pvc.Spec.VolumeName = "pv-" + string(pvc.UID)
1✔
1209
        }
1✔
1210
        return pvc
1✔
1211
}
1212

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

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

1231
// CreateImporterTestPod creates importer test pod CR
1232
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
×
1233
        // importer pod name contains the pvc name
×
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
        }
×
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
        }
×
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
        }
×
1356

1357
        return pod
×
1358
}
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
}
×
1371

1372
// CreateClient creates a fake client
1373
func CreateClient(objs ...runtime.Object) client.Client {
1✔
1374
        s := scheme.Scheme
1✔
1375
        _ = cdiv1.AddToScheme(s)
1✔
1376
        _ = corev1.AddToScheme(s)
1✔
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

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

1388
// GetContentType returns the content type. If invalid or not set, default to kubevirt
1389
func GetContentType(contentType cdiv1.DataVolumeContentType) cdiv1.DataVolumeContentType {
1✔
1390
        switch contentType {
1✔
1391
        case
1392
                cdiv1.DataVolumeKubeVirt,
1393
                cdiv1.DataVolumeArchive:
1✔
1394
        default:
×
1395
                // TODO - shouldn't archive be the default?
×
1396
                contentType = cdiv1.DataVolumeKubeVirt
×
1397
        }
1398
        return contentType
1✔
1399
}
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 {
×
1403
        contentType, found := pvc.Annotations[AnnContentType]
×
1404
        if !found {
×
1405
                // TODO - shouldn't archive be the default?
×
1406
                return cdiv1.DataVolumeKubeVirt
×
1407
        }
×
1408

1409
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1410
}
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
×
1418
}
1419

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
}
×
1425

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
}
×
1448

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
        }
×
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 "", "", ""
×
1461
}
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
        }
×
1472

1473
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1474
}
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
×
1486
}
1487

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
}
×
1499

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
                }
×
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
        }
×
1519

1520
        return returnSize, nil
×
1521
}
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

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

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

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

1543
// ShouldIgnorePod checks if a pod should be ignored.
1544
// If this is a completed pod that was used for one checkpoint of a multi-stage import, it
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
×
1553
}
1554

1555
// BuildHTTPClient generates an http client that accepts any certificate, since we are using
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
×
1577
}
1578

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

1584
// GetPodMetricsPort returns, if exists, the metrics port from the passed pod
1585
func GetPodMetricsPort(pod *corev1.Pod) (int, error) {
1✔
1586
        for _, container := range pod.Spec.Containers {
2✔
1587
                for _, port := range container.Ports {
2✔
1588
                        if port.Name == "metrics" {
2✔
1589
                                return int(port.ContainerPort), nil
1✔
1590
                        }
1✔
1591
                }
1592
        }
1593
        return 0, errors.New("Metrics port not found in pod")
1✔
1594
}
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 {
1✔
1599
                return "", nil
×
1600
        }
×
1601
        port, err := GetPodMetricsPort(pod)
1✔
1602
        if err != nil || pod.Status.PodIP == "" {
2✔
1603
                return "", err
1✔
1604
        }
1✔
1605
        domain := net.JoinHostPort(pod.Status.PodIP, fmt.Sprint(port))
1✔
1606
        url := fmt.Sprintf("https://%s/metrics", domain)
1✔
1607
        return url, nil
1✔
1608
}
1609

1610
// GetProgressReportFromURL fetches the progress report from the passed URL according to an specific metric expression and ownerUID
1611
func GetProgressReportFromURL(ctx context.Context, url string, httpClient *http.Client, metricExp, ownerUID string) (string, error) {
×
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
×
1627
        }
1628
        defer resp.Body.Close()
×
1629
        body, err := io.ReadAll(resp.Body)
×
1630
        if err != nil {
×
1631
                return "", err
×
1632
        }
×
1633

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
×
1641
}
1642

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
        }
×
1660
}
1661

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
        }
×
1672
}
1673

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
        }
×
1681
}
1682

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
                }
×
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
        }
×
1708

NEW
1709
        if registry.Platform != nil && registry.Platform.Architecture != "" {
×
NEW
1710
                annotations[AnnRegistryImageArchitecture] = registry.Platform.Architecture
×
NEW
1711
        }
×
1712
}
1713

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

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

1739
// IsPVBoundToPVC checks if a PV is bound to a specific PVC
1740
func IsPVBoundToPVC(pv *corev1.PersistentVolume, pvc *corev1.PersistentVolumeClaim) bool {
1✔
1741
        claimRef := pv.Spec.ClaimRef
1✔
1742
        return claimRef != nil && claimRef.Name == pvc.Name && claimRef.Namespace == pvc.Namespace && claimRef.UID == pvc.UID
1✔
1743
}
1✔
1744

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

1✔
1753
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
2✔
1754
                return err
1✔
1755
        }
1✔
1756

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

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

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

1784
        return nil
1✔
1785
}
1786

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

1796
        sv := reflect.ValueOf(obj).Elem()
×
1797
        iv := sv.FieldByName("Items")
×
1798

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

1809
        return nil
×
1810
}
1811

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

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

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

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

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

1875
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
1876
        return "", nil
×
1877
}
1878

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

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

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

1904
        vscList := &snapshotv1.VolumeSnapshotClassList{}
×
1905
        if err := c.List(ctx, vscList); err != nil {
×
1906
                if meta.IsNoMatchError(err) {
×
1907
                        return nil, nil
×
1908
                }
×
1909
                return nil, err
×
1910
        }
1911

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

1924
        if len(candidates) > 0 {
×
1925
                sort.Strings(candidates)
×
1926
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
×
1927
                return &candidates[0], nil
×
1928
        }
×
1929

1930
        return nil, nil
×
1931
}
1932

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

×
1940
        return isCrdDeployed(c, vsClass, version, log) &&
×
1941
                isCrdDeployed(c, vsContent, version, log) &&
×
1942
                isCrdDeployed(c, vs, version, log)
×
1943
}
×
1944

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

1956
        for _, v := range crd.Spec.Versions {
×
1957
                if v.Name == version && v.Served {
×
1958
                        return true
×
1959
                }
×
1960
        }
1961

1962
        return false
×
1963
}
1964

1965
// IsSnapshotReady indicates if a volume snapshot is ready to be used
1966
func IsSnapshotReady(snapshot *snapshotv1.VolumeSnapshot) bool {
×
1967
        return snapshot.Status != nil && snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse
×
1968
}
×
1969

1970
// GetResource updates given obj with the data of the object with the same name and namespace
1971
func GetResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
×
1972
        obj.SetNamespace(namespace)
×
1973
        obj.SetName(name)
×
1974

×
1975
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
1976
        if err != nil {
×
1977
                if k8serrors.IsNotFound(err) {
×
1978
                        return false, nil
×
1979
                }
×
1980

1981
                return false, err
×
1982
        }
1983

1984
        return true, nil
×
1985
}
1986

1987
// PatchArgs are the args for Patch
1988
type PatchArgs struct {
1989
        Client client.Client
1990
        Log    logr.Logger
1991
        Obj    client.Object
1992
        OldObj client.Object
1993
}
1994

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

2024
// OwnedByDataVolume returns true if the object is owned by a DataVolume
2025
func OwnedByDataVolume(obj metav1.Object) bool {
×
2026
        owner := metav1.GetControllerOf(obj)
×
2027
        return owner != nil && owner.Kind == "DataVolume"
×
2028
}
×
2029

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

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

2055
// ClaimMayExistBeforeDataVolume returns true if the PVC may exist before the DataVolume
2056
func ClaimMayExistBeforeDataVolume(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2057
        if ClaimIsPopulatedForDataVolume(pvc, dv) {
×
2058
                return true, nil
×
2059
        }
×
2060
        return AllowClaimAdoption(c, pvc, dv)
×
2061
}
2062

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

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