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

kubevirt / containerized-data-importer / #5335

18 May 2025 12:30PM UTC coverage: 59.356% (+0.02%) from 59.34%
#5335

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.

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

40 of 54 new or added lines in 5 files covered. (74.07%)

1 existing line in 1 file now uncovered.

16904 of 28479 relevant lines covered (59.36%)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

270
        // CloneComplete message
271
        CloneComplete = "Clone Complete"
272

273
        cloneTokenLeeway = 10 * time.Second
274

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

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

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

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

315
        // ClaimLost reason const
316
        ClaimLost = "ClaimLost"
317
        // NotFound reason const
318
        NotFound = "NotFound"
319

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

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

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

336
        // ProgressDone this means we are DONE
337
        ProgressDone = "100.0%"
338

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

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

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

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

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

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

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

378
        apiServerKeyOnce sync.Once
379
        apiServerKey     *rsa.PrivateKey
380

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

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

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

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

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

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

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

439
        tok, v := mtv.getTokenAndValidator(pvc)
×
440

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

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

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

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

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

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

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

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

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

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

507
        if dv.Spec.Storage != nil {
×
508
                return dv.Spec.Storage.StorageClassName
×
509
        }
×
510

511
        return nil
×
512
}
513

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

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

532
        return storageClass, nil
×
533
}
534

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

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

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

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

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

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

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

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

596
        return &defaultClasses[0]
1✔
597
}
598

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

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

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

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

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

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

×
637
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
×
638

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

644
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
645
}
646

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

655
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
656
}
657

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

666
        return cdiconfig.Status.ImagePullSecrets, nil
×
667
}
668

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

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

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

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

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

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

768
        return pods, nil
×
769
}
770

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

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

782
        return &cr.Spec.Workloads, nil
×
783
}
784

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

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

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

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

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

811
        return &activeResources[0], nil
1✔
812
}
813

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

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

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

836
        return cdiconfig.Status.Preallocation
×
837
}
838

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

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

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

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

862
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
863
}
864

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

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

878
        obj.SetFinalizers(finalizers)
×
879
}
880

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

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

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

902
        tokenResourceName := getTokenResourceNamePvc(source)
×
903
        srcName := getSourceNamePvc(source)
×
904

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

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

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

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

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

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

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

940
        return ""
×
941
}
942

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

948
        return "persistentvolumeclaims"
×
949
}
950

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

958
        return sourcePvc.Name
×
959
}
960

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

973
        return nil
×
974
}
975

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

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

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

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

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

1028
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1029

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

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

1045
        return err
×
1046
}
1047

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
1234
        blockOwnerDeletion := true
×
1235
        isController := true
×
1236

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

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

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

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

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

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

1355
        return pod
×
1356
}
1357

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

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

1✔
1378
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1379
}
1✔
1380

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

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

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

1407
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1408
}
1409

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

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

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

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

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

1458
        return "", "", ""
×
1459
}
1460

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

1471
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1472
}
1473

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

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

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

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

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

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

1518
        return returnSize, nil
×
1519
}
1520

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1782
        return nil
1✔
1783
}
1784

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

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

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

1807
        return nil
×
1808
}
1809

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

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

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

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

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

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

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

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

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

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

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

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

1928
        return nil, nil
×
1929
}
1930

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

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

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

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

1960
        return false
×
1961
}
1962

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

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

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

1979
                return false, err
×
1980
        }
1981

1982
        return true, nil
×
1983
}
1984

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

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

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

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

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

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

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

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