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

kubevirt / containerized-data-importer / #5391

19 Jun 2025 11:38PM UTC coverage: 59.415% (+0.006%) from 59.409%
#5391

push

travis-ci

web-flow
VEP48: Support architecture specific image import for registry datasource (#3753)

* importer: introduce registry datasource architecture option

This commit introduces preliminary support for a new optional field under the
DV registry source.

The field, platform, has an additional subfield: architecture which
when specified serves as an image index filter to extract a
disk.img only from layers which match it.

If there's a mismatch between requested architecture and
available architectures in the image index the import will fail.

If the requested image is a manifest and not an index the architecture
of the manifest will be compared to the requested architecture and if
they mismatch the import will fail.

API naming has been chosen to mimic the OCI image index spec[1].

[1] https://github.com/opencontainers/image-spec/blob/main/image-index.md#oci-image-index-specification

Signed-off-by: Adi Aloni <aaloni@redhat.com>

* importer: pullMethod node architecture support

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

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

Signed-off-by: Adi Aloni <aaloni@redhat.com>

* docs, image-from-registry: add multi-platform support

Adds a section in the docs about using the platform field for
multi-platform image pull.

Signed-off-by: Adi Aloni <aaloni@redhat.com>

---------

Signed-off-by: Adi Aloni <aaloni@redhat.com>

42 of 61 new or added lines in 6 files covered. (68.85%)

4 existing lines in 1 file now uncovered.

16950 of 28528 relevant lines covered (59.42%)

0.66 hits per line

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

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

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

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

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

17
package common
18

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

275
        cloneTokenLeeway = 10 * time.Second
276

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

513
        return nil
×
514
}
515

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

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

534
        return storageClass, nil
×
535
}
536

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

770
        return pods, nil
×
771
}
772

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

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

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

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

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

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

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

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

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

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

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

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

838
        return cdiconfig.Status.Preallocation
×
839
}
840

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

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

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

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

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

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

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

880
        obj.SetFinalizers(finalizers)
×
881
}
882

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

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

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

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

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

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

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

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

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

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

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

942
        return ""
×
943
}
944

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

950
        return "persistentvolumeclaims"
×
951
}
952

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

960
        return sourcePvc.Name
×
961
}
962

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

975
        return nil
×
976
}
977

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

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

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

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

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

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

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

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

1047
        return err
×
1048
}
1049

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

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

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

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

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

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

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

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

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

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

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

1182
// CreatePvc creates PVC
1183
func CreatePvc(name, ns string, annotations, labels map[string]string) *corev1.PersistentVolumeClaim {
1✔
1184
        return CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1✔
1185
}
1✔
1186

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

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

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

1235
// CreateImporterTestPod creates importer test pod CR
1236
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
×
1237
        // importer pod name contains the pvc name
×
1238
        podName := fmt.Sprintf("%s-%s", common.ImporterPodName, pvc.Name)
×
1239

×
1240
        blockOwnerDeletion := true
×
1241
        isController := true
×
1242

×
1243
        volumes := []corev1.Volume{
×
1244
                {
×
1245
                        Name: dvname,
×
1246
                        VolumeSource: corev1.VolumeSource{
×
1247
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1248
                                        ClaimName: pvc.Name,
×
1249
                                        ReadOnly:  false,
×
1250
                                },
×
1251
                        },
×
1252
                },
×
1253
        }
×
1254

×
1255
        if scratchPvc != nil {
×
1256
                volumes = append(volumes, corev1.Volume{
×
1257
                        Name: ScratchVolName,
×
1258
                        VolumeSource: corev1.VolumeSource{
×
1259
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1260
                                        ClaimName: scratchPvc.Name,
×
1261
                                        ReadOnly:  false,
×
1262
                                },
×
1263
                        },
×
1264
                })
×
1265
        }
×
1266

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

×
1315
        ep, _ := GetEndpoint(pvc)
×
1316
        source := GetSource(pvc)
×
1317
        contentType := GetPVCContentType(pvc)
×
1318
        imageSize, _ := GetRequestedImageSize(pvc)
×
1319
        volumeMode := GetVolumeMode(pvc)
×
1320

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

1354
        if scratchPvc != nil {
×
1355
                pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
×
1356
                        Name:      ScratchVolName,
×
1357
                        MountPath: common.ScratchDataDir,
×
1358
                })
×
1359
        }
×
1360

1361
        return pod
×
1362
}
1363

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

1376
// CreateClient creates a fake client
1377
func CreateClient(objs ...runtime.Object) client.Client {
1✔
1378
        s := scheme.Scheme
1✔
1379
        _ = cdiv1.AddToScheme(s)
1✔
1380
        _ = corev1.AddToScheme(s)
1✔
1381
        _ = storagev1.AddToScheme(s)
1✔
1382
        _ = ocpconfigv1.Install(s)
1✔
1383

1✔
1384
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1385
}
1✔
1386

1387
// ErrQuotaExceeded checked is the error is of exceeded quota
1388
func ErrQuotaExceeded(err error) bool {
×
1389
        return strings.Contains(err.Error(), "exceeded quota:")
×
1390
}
×
1391

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

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

1413
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1414
}
1415

1416
// GetNamespace returns the given namespace if not empty, otherwise the default namespace
1417
func GetNamespace(namespace, defaultNamespace string) string {
×
1418
        if namespace == "" {
×
1419
                return defaultNamespace
×
1420
        }
×
1421
        return namespace
×
1422
}
1423

1424
// IsErrCacheNotStarted checked is the error is of cache not started
1425
func IsErrCacheNotStarted(err error) bool {
×
1426
        target := &runtimecache.ErrCacheNotStarted{}
×
1427
        return errors.As(err, &target)
×
1428
}
×
1429

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

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

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

1464
        return "", "", ""
×
1465
}
1466

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

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

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

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

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

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

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

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

1524
        return returnSize, nil
×
1525
}
1526

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

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

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

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

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

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

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

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

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

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

1638
        // Parse the progress from the body
1639
        progressReport := ""
×
1640
        match := regExp.FindStringSubmatch(string(body))
×
1641
        if match != nil {
×
1642
                progressReport = match[len(match)-1]
×
1643
        }
×
1644
        return progressReport, nil
×
1645
}
1646

1647
// UpdateHTTPAnnotations updates the passed annotations for proper http import
1648
func UpdateHTTPAnnotations(annotations map[string]string, http *cdiv1.DataVolumeSourceHTTP) {
×
1649
        annotations[AnnEndpoint] = http.URL
×
1650
        annotations[AnnSource] = SourceHTTP
×
1651

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

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

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

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

NEW
1713
        if registry.Platform != nil && registry.Platform.Architecture != "" {
×
NEW
1714
                annotations[AnnRegistryImageArchitecture] = registry.Platform.Architecture
×
NEW
1715
        }
×
1716
}
1717

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

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

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

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

1✔
1757
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
2✔
1758
                return err
1✔
1759
        }
1✔
1760

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

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

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

1788
        return nil
1✔
1789
}
1790

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

1800
        sv := reflect.ValueOf(obj).Elem()
×
1801
        iv := sv.FieldByName("Items")
×
1802

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

1813
        return nil
×
1814
}
1815

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

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

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

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

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

1879
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
1880
        return "", nil
×
1881
}
1882

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

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

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

1908
        vscList := &snapshotv1.VolumeSnapshotClassList{}
×
1909
        if err := c.List(ctx, vscList); err != nil {
×
1910
                if meta.IsNoMatchError(err) {
×
1911
                        return nil, nil
×
1912
                }
×
1913
                return nil, err
×
1914
        }
1915

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

1928
        if len(candidates) > 0 {
×
1929
                sort.Strings(candidates)
×
1930
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
×
1931
                return &candidates[0], nil
×
1932
        }
×
1933

1934
        return nil, nil
×
1935
}
1936

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

×
1944
        return isCrdDeployed(c, vsClass, version, log) &&
×
1945
                isCrdDeployed(c, vsContent, version, log) &&
×
1946
                isCrdDeployed(c, vs, version, log)
×
1947
}
×
1948

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

1960
        for _, v := range crd.Spec.Versions {
×
1961
                if v.Name == version && v.Served {
×
1962
                        return true
×
1963
                }
×
1964
        }
1965

1966
        return false
×
1967
}
1968

1969
// IsSnapshotReady indicates if a volume snapshot is ready to be used
1970
func IsSnapshotReady(snapshot *snapshotv1.VolumeSnapshot) bool {
×
1971
        return snapshot.Status != nil && snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse
×
1972
}
×
1973

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

×
1979
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
1980
        if err != nil {
×
1981
                if k8serrors.IsNotFound(err) {
×
1982
                        return false, nil
×
1983
                }
×
1984

1985
                return false, err
×
1986
        }
1987

1988
        return true, nil
×
1989
}
1990

1991
// PatchArgs are the args for Patch
1992
type PatchArgs struct {
1993
        Client client.Client
1994
        Log    logr.Logger
1995
        Obj    client.Object
1996
        OldObj client.Object
1997
}
1998

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

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

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

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

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

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

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

© 2025 Coveralls, Inc