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

kubevirt / containerized-data-importer / #5676

17 Nov 2025 10:06PM UTC coverage: 58.665% (-0.08%) from 58.748%
#5676

push

travis-ci

web-flow
Copy Events from tmp PVCs during Clone (#3933)

* move CopyEvents to common package so it can be called from other controllers

Signed-off-by: dsanatar <dsanatar@redhat.com>

* copy events from tmp clone pvcs to their target pvc

Signed-off-by: dsanatar <dsanatar@redhat.com>

---------

Signed-off-by: dsanatar <dsanatar@redhat.com>

19 of 64 new or added lines in 6 files covered. (29.69%)

7 existing lines in 1 file now uncovered.

17389 of 29641 relevant lines covered (58.67%)

0.65 hits per line

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

13.5
/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
        "net"
27
        "net/http"
28
        "reflect"
29
        "regexp"
30
        "sort"
31
        "strconv"
32
        "strings"
33
        "sync"
34
        "time"
35

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

276
        cloneTokenLeeway = 10 * time.Second
277

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

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

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

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

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

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

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

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

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

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

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

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

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

356
        // AnnPVCPrimeName annotation is the name of the PVC' that is used to populate the PV which is then rebound to the target PVC
357
        AnnPVCPrimeName = AnnAPIGroup + "/storage.populator.pvcPrime"
358
)
359

360
// Size-detection pod error codes
361
const (
362
        NoErr int = iota
363
        ErrBadArguments
364
        ErrInvalidFile
365
        ErrInvalidPath
366
        ErrBadTermFile
367
        ErrUnknown
368
)
369

370
var (
371
        // BlockMode is raw block device mode
372
        BlockMode = corev1.PersistentVolumeBlock
373
        // FilesystemMode is filesystem device mode
374
        FilesystemMode = corev1.PersistentVolumeFilesystem
375

376
        // DefaultInstanceTypeLabels is a list of currently supported default instance type labels
377
        DefaultInstanceTypeLabels = []string{
378
                LabelDefaultInstancetype,
379
                LabelDefaultInstancetypeKind,
380
                LabelDefaultPreference,
381
                LabelDefaultPreferenceKind,
382
        }
383

384
        apiServerKeyOnce sync.Once
385
        apiServerKey     *rsa.PrivateKey
386

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

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

399
        ErrDataSourceMaxDepthReached = errors.New("DataSource reference chain exceeds maximum depth of 1")
400
        ErrDataSourceSelfReference   = errors.New("DataSource cannot self-reference")
401
        ErrDataSourceCrossNamespace  = errors.New("DataSource cannot reference a DataSource in another namespace")
402
)
403

404
// FakeValidator is a fake token validator
405
type FakeValidator struct {
406
        Match     string
407
        Operation token.Operation
408
        Name      string
409
        Namespace string
410
        Resource  metav1.GroupVersionResource
411
        Params    map[string]string
412
}
413

414
// Validate is a fake token validation
415
func (v *FakeValidator) Validate(value string) (*token.Payload, error) {
×
416
        if value != v.Match {
×
417
                return nil, fmt.Errorf("token does not match expected")
×
418
        }
×
419
        resource := metav1.GroupVersionResource{
×
420
                Resource: "persistentvolumeclaims",
×
421
        }
×
422
        return &token.Payload{
×
423
                Name:      v.Name,
×
424
                Namespace: v.Namespace,
×
425
                Operation: token.OperationClone,
×
426
                Resource:  resource,
×
427
                Params:    v.Params,
×
428
        }, nil
×
429
}
430

431
// MultiTokenValidator is a token validator that can validate both short and long tokens
432
type MultiTokenValidator struct {
433
        ShortTokenValidator token.Validator
434
        LongTokenValidator  token.Validator
435
}
436

437
// ValidatePVC validates a PVC
438
func (mtv *MultiTokenValidator) ValidatePVC(source, target *corev1.PersistentVolumeClaim) error {
×
439
        tok, v := mtv.getTokenAndValidator(target)
×
440
        return ValidateCloneTokenPVC(tok, v, source, target)
×
441
}
×
442

443
// ValidatePopulator valades a token for a populator
444
func (mtv *MultiTokenValidator) ValidatePopulator(vcs *cdiv1.VolumeCloneSource, pvc *corev1.PersistentVolumeClaim) error {
×
445
        if vcs.Namespace == pvc.Namespace {
×
446
                return nil
×
447
        }
×
448

449
        tok, v := mtv.getTokenAndValidator(pvc)
×
450

×
451
        tokenData, err := v.Validate(tok)
×
452
        if err != nil {
×
453
                return errors.Wrap(err, "error verifying token")
×
454
        }
×
455

456
        var tokenResourceName string
×
457
        switch vcs.Spec.Source.Kind {
×
458
        case "PersistentVolumeClaim":
×
459
                tokenResourceName = "persistentvolumeclaims"
×
460
        case "VolumeSnapshot":
×
461
                tokenResourceName = "volumesnapshots"
×
462
        }
463
        srcName := vcs.Spec.Source.Name
×
464

×
465
        return validateTokenData(tokenData, vcs.Namespace, srcName, pvc.Namespace, pvc.Name, string(pvc.UID), tokenResourceName)
×
466
}
467

468
func (mtv *MultiTokenValidator) getTokenAndValidator(pvc *corev1.PersistentVolumeClaim) (string, token.Validator) {
×
469
        v := mtv.LongTokenValidator
×
470
        tok, ok := pvc.Annotations[AnnExtendedCloneToken]
×
471
        if !ok {
×
472
                // if token doesn't exist, no prob for same namespace
×
473
                tok = pvc.Annotations[AnnCloneToken]
×
474
                v = mtv.ShortTokenValidator
×
475
        }
×
476
        return tok, v
×
477
}
478

479
// NewMultiTokenValidator returns a new multi token validator
480
func NewMultiTokenValidator(key *rsa.PublicKey) *MultiTokenValidator {
×
481
        return &MultiTokenValidator{
×
482
                ShortTokenValidator: NewCloneTokenValidator(common.CloneTokenIssuer, key),
×
483
                LongTokenValidator:  NewCloneTokenValidator(common.ExtendedCloneTokenIssuer, key),
×
484
        }
×
485
}
×
486

487
// NewCloneTokenValidator returns a new token validator
488
func NewCloneTokenValidator(issuer string, key *rsa.PublicKey) token.Validator {
×
489
        return token.NewValidator(issuer, key, cloneTokenLeeway)
×
490
}
×
491

492
// GetRequestedImageSize returns the PVC requested size
493
func GetRequestedImageSize(pvc *corev1.PersistentVolumeClaim) (string, error) {
1✔
494
        pvcSize, found := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
1✔
495
        if !found {
2✔
496
                return "", errors.Errorf("storage request is missing in pvc \"%s/%s\"", pvc.Namespace, pvc.Name)
1✔
497
        }
1✔
498
        return pvcSize.String(), nil
1✔
499
}
500

501
// GetVolumeMode returns the volumeMode from PVC handling default empty value
502
func GetVolumeMode(pvc *corev1.PersistentVolumeClaim) corev1.PersistentVolumeMode {
×
503
        return util.ResolveVolumeMode(pvc.Spec.VolumeMode)
×
504
}
×
505

506
// IsDataVolumeUsingDefaultStorageClass checks if the DataVolume is using the default StorageClass
507
func IsDataVolumeUsingDefaultStorageClass(dv *cdiv1.DataVolume) bool {
×
508
        return GetStorageClassFromDVSpec(dv) == nil
×
509
}
×
510

511
// GetStorageClassFromDVSpec returns the StorageClassName from DataVolume PVC or Storage spec
512
func GetStorageClassFromDVSpec(dv *cdiv1.DataVolume) *string {
×
513
        if dv.Spec.PVC != nil {
×
514
                return dv.Spec.PVC.StorageClassName
×
515
        }
×
516

517
        if dv.Spec.Storage != nil {
×
518
                return dv.Spec.Storage.StorageClassName
×
519
        }
×
520

521
        return nil
×
522
}
523

524
// getStorageClassByName looks up the storage class based on the name.
525
// If name is nil, it performs fallback to default according to the provided content type
526
// If no storage class is found, returns nil
527
func getStorageClassByName(ctx context.Context, client client.Client, name *string, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
1✔
528
        if name == nil {
2✔
529
                return getFallbackStorageClass(ctx, client, contentType)
1✔
530
        }
1✔
531

532
        // look up storage class by name
533
        storageClass := &storagev1.StorageClass{}
×
534
        if err := client.Get(ctx, types.NamespacedName{Name: *name}, storageClass); err != nil {
×
535
                if k8serrors.IsNotFound(err) {
×
536
                        return nil, nil
×
537
                }
×
538
                klog.V(3).Info("Unable to retrieve storage class", "storage class name", *name)
×
539
                return nil, errors.Errorf("unable to retrieve storage class %s", *name)
×
540
        }
541

542
        return storageClass, nil
×
543
}
544

545
// GetStorageClassByNameWithK8sFallback looks up the storage class based on the name
546
// If name is nil, it looks for the default k8s storage class storageclass.kubernetes.io/is-default-class
547
// If no storage class is found, returns nil
548
func GetStorageClassByNameWithK8sFallback(ctx context.Context, client client.Client, name *string) (*storagev1.StorageClass, error) {
1✔
549
        return getStorageClassByName(ctx, client, name, cdiv1.DataVolumeArchive)
1✔
550
}
1✔
551

552
// GetStorageClassByNameWithVirtFallback looks up the storage class based on the name
553
// If name is nil, it looks for the following, in this order:
554
// default kubevirt storage class (if the caller is interested) storageclass.kubevirt.io/is-default-class
555
// default k8s storage class storageclass.kubernetes.io/is-default-class
556
// If no storage class is found, returns nil
557
func GetStorageClassByNameWithVirtFallback(ctx context.Context, client client.Client, name *string, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
1✔
558
        return getStorageClassByName(ctx, client, name, contentType)
1✔
559
}
1✔
560

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

570
        if GetContentType(contentType) == cdiv1.DataVolumeKubeVirt {
2✔
571
                if virtSc := GetPlatformDefaultStorageClass(storageClasses, AnnDefaultVirtStorageClass); virtSc != nil {
2✔
572
                        return virtSc, nil
1✔
573
                }
1✔
574
        }
575
        return GetPlatformDefaultStorageClass(storageClasses, AnnDefaultStorageClass), nil
1✔
576
}
577

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

1✔
582
        for _, storageClass := range storageClasses.Items {
2✔
583
                if storageClass.Annotations[defaultAnnotationKey] == "true" {
2✔
584
                        defaultClasses = append(defaultClasses, storageClass)
1✔
585
                }
1✔
586
        }
587

588
        if len(defaultClasses) == 0 {
2✔
589
                return nil
1✔
590
        }
1✔
591

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

606
        return &defaultClasses[0]
1✔
607
}
608

609
// GetFilesystemOverheadForStorageClass determines the filesystem overhead defined in CDIConfig for the storageClass.
610
func GetFilesystemOverheadForStorageClass(ctx context.Context, client client.Client, storageClassName *string) (cdiv1.Percent, error) {
×
611
        if storageClassName != nil && *storageClassName == "" {
×
612
                klog.V(3).Info("No storage class name passed")
×
613
                return "0", nil
×
614
        }
×
615

616
        cdiConfig := &cdiv1.CDIConfig{}
×
617
        if err := client.Get(ctx, types.NamespacedName{Name: common.ConfigName}, cdiConfig); err != nil {
×
618
                if k8serrors.IsNotFound(err) {
×
619
                        klog.V(1).Info("CDIConfig does not exist, pod will not start until it does")
×
620
                        return "0", nil
×
621
                }
×
622
                return "0", err
×
623
        }
624

625
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(ctx, client, storageClassName)
×
626
        if err != nil || targetStorageClass == nil {
×
627
                klog.V(3).Info("Storage class", storageClassName, "not found, trying default storage class")
×
628
                targetStorageClass, err = GetStorageClassByNameWithK8sFallback(ctx, client, nil)
×
629
                if err != nil {
×
630
                        klog.V(3).Info("No default storage class found, continuing with global overhead")
×
631
                        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
632
                }
×
633
        }
634

635
        if cdiConfig.Status.FilesystemOverhead == nil {
×
636
                klog.Errorf("CDIConfig filesystemOverhead used before config controller ran reconcile. Hopefully this only happens during unit testing.")
×
637
                return "0", nil
×
638
        }
×
639

640
        if targetStorageClass == nil {
×
641
                klog.V(3).Info("Storage class", storageClassName, "not found, continuing with global overhead")
×
642
                return cdiConfig.Status.FilesystemOverhead.Global, nil
×
643
        }
×
644

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

×
647
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
×
648

×
649
        storageClassOverhead, found := perStorageConfig[targetStorageClass.GetName()]
×
650
        if found {
×
651
                return storageClassOverhead, nil
×
652
        }
×
653

654
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
655
}
656

657
// GetDefaultPodResourceRequirements gets default pod resource requirements from cdi config status
658
func GetDefaultPodResourceRequirements(client client.Client) (*corev1.ResourceRequirements, error) {
×
659
        cdiconfig := &cdiv1.CDIConfig{}
×
660
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
×
661
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
662
                return nil, err
×
663
        }
×
664

665
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
666
}
667

668
// GetImagePullSecrets gets the imagePullSecrets needed to pull images from the cdi config
669
func GetImagePullSecrets(client client.Client) ([]corev1.LocalObjectReference, error) {
×
670
        cdiconfig := &cdiv1.CDIConfig{}
×
671
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
×
672
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
673
                return nil, err
×
674
        }
×
675

676
        return cdiconfig.Status.ImagePullSecrets, nil
×
677
}
678

679
// GetPodFromPvc determines the pod associated with the pvc passed in.
680
func GetPodFromPvc(c client.Client, namespace string, pvc *corev1.PersistentVolumeClaim) (*corev1.Pod, error) {
×
681
        l, _ := labels.Parse(common.PrometheusLabelKey)
×
682
        pods := &corev1.PodList{}
×
683
        listOptions := client.ListOptions{
×
684
                LabelSelector: l,
×
685
        }
×
686
        if err := c.List(context.TODO(), pods, &listOptions); err != nil {
×
687
                return nil, err
×
688
        }
×
689

690
        pvcUID := pvc.GetUID()
×
691
        for _, pod := range pods.Items {
×
692
                if ShouldIgnorePod(&pod, pvc) {
×
693
                        continue
×
694
                }
695
                for _, or := range pod.OwnerReferences {
×
696
                        if or.UID == pvcUID {
×
697
                                return &pod, nil
×
698
                        }
×
699
                }
700

701
                // TODO: check this
702
                val, exists := pod.Labels[CloneUniqueID]
×
703
                if exists && val == string(pvcUID)+common.ClonerSourcePodNameSuffix {
×
704
                        return &pod, nil
×
705
                }
×
706
        }
707
        return nil, errors.Errorf("Unable to find pod owned by UID: %s, in namespace: %s", string(pvcUID), namespace)
×
708
}
709

710
// AddVolumeDevices returns VolumeDevice slice with one block device for pods using PV with block volume mode
711
func AddVolumeDevices() []corev1.VolumeDevice {
×
712
        volumeDevices := []corev1.VolumeDevice{
×
713
                {
×
714
                        Name:       DataVolName,
×
715
                        DevicePath: common.WriteBlockPath,
×
716
                },
×
717
        }
×
718
        return volumeDevices
×
719
}
×
720

721
// GetPodsUsingPVCs returns Pods currently using PVCs
722
func GetPodsUsingPVCs(ctx context.Context, c client.Client, namespace string, names sets.Set[string], allowReadOnly bool) ([]corev1.Pod, error) {
×
723
        pl := &corev1.PodList{}
×
724
        // hopefully using cached client here
×
725
        err := c.List(ctx, pl, &client.ListOptions{Namespace: namespace})
×
726
        if err != nil {
×
727
                return nil, err
×
728
        }
×
729

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

778
        return pods, nil
×
779
}
780

781
// GetWorkloadNodePlacement extracts the workload-specific nodeplacement values from the CDI CR
782
func GetWorkloadNodePlacement(ctx context.Context, c client.Client) (*sdkapi.NodePlacement, error) {
×
783
        cr, err := GetActiveCDI(ctx, c)
×
784
        if err != nil {
×
785
                return nil, err
×
786
        }
×
787

788
        if cr == nil {
×
789
                return nil, fmt.Errorf("no active CDI")
×
790
        }
×
791

792
        return &cr.Spec.Workloads, nil
×
793
}
794

795
// GetActiveCDI returns the active CDI CR
796
func GetActiveCDI(ctx context.Context, c client.Client) (*cdiv1.CDI, error) {
1✔
797
        crList := &cdiv1.CDIList{}
1✔
798
        if err := c.List(ctx, crList, &client.ListOptions{}); err != nil {
1✔
799
                return nil, err
×
800
        }
×
801

802
        if len(crList.Items) == 0 {
2✔
803
                return nil, nil
1✔
804
        }
1✔
805

806
        if len(crList.Items) == 1 {
2✔
807
                return &crList.Items[0], nil
1✔
808
        }
1✔
809

810
        var activeResources []cdiv1.CDI
1✔
811
        for _, cr := range crList.Items {
2✔
812
                if cr.Status.Phase != sdkapi.PhaseError {
2✔
813
                        activeResources = append(activeResources, cr)
1✔
814
                }
1✔
815
        }
816

817
        if len(activeResources) != 1 {
2✔
818
                return nil, fmt.Errorf("invalid number of active CDI resources: %d", len(activeResources))
1✔
819
        }
1✔
820

821
        return &activeResources[0], nil
1✔
822
}
823

824
// IsPopulated returns if the passed in PVC has been populated according to the rules outlined in pkg/apis/core/<version>/utils.go
825
func IsPopulated(pvc *corev1.PersistentVolumeClaim, c client.Client) (bool, error) {
×
826
        return cdiv1utils.IsPopulated(pvc, func(name, namespace string) (*cdiv1.DataVolume, error) {
×
827
                dv := &cdiv1.DataVolume{}
×
828
                err := c.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, dv)
×
829
                return dv, err
×
830
        })
×
831
}
832

833
// GetPreallocation returns the preallocation setting for the specified object (DV or VolumeImportSource), falling back to StorageClass and global setting (in this order)
834
func GetPreallocation(ctx context.Context, client client.Client, preallocation *bool) bool {
×
835
        // First, the DV's preallocation
×
836
        if preallocation != nil {
×
837
                return *preallocation
×
838
        }
×
839

840
        cdiconfig := &cdiv1.CDIConfig{}
×
841
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
×
842
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
843
                return defaultPreallocation
×
844
        }
×
845

846
        return cdiconfig.Status.Preallocation
×
847
}
848

849
// ImmediateBindingRequested returns if an object has the ImmediateBinding annotation
850
func ImmediateBindingRequested(obj metav1.Object) bool {
×
851
        _, isImmediateBindingRequested := obj.GetAnnotations()[AnnImmediateBinding]
×
852
        return isImmediateBindingRequested
×
853
}
×
854

855
// GetPriorityClass gets PVC priority class
856
func GetPriorityClass(pvc *corev1.PersistentVolumeClaim) string {
×
857
        anno := pvc.GetAnnotations()
×
858
        return anno[AnnPriorityClassName]
×
859
}
×
860

861
// ShouldDeletePod returns whether the PVC workload pod should be deleted
862
func ShouldDeletePod(pvc *corev1.PersistentVolumeClaim) bool {
×
863
        return pvc.GetAnnotations()[AnnPodRetainAfterCompletion] != "true" || pvc.GetAnnotations()[AnnRequiresScratch] == "true" || pvc.GetAnnotations()[AnnRequiresDirectIO] == "true" || pvc.DeletionTimestamp != nil
×
864
}
×
865

866
// AddFinalizer adds a finalizer to a resource
867
func AddFinalizer(obj metav1.Object, name string) {
×
868
        if HasFinalizer(obj, name) {
×
869
                return
×
870
        }
×
871

872
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
873
}
874

875
// RemoveFinalizer removes a finalizer from a resource
876
func RemoveFinalizer(obj metav1.Object, name string) {
×
877
        if !HasFinalizer(obj, name) {
×
878
                return
×
879
        }
×
880

881
        var finalizers []string
×
882
        for _, f := range obj.GetFinalizers() {
×
883
                if f != name {
×
884
                        finalizers = append(finalizers, f)
×
885
                }
×
886
        }
887

888
        obj.SetFinalizers(finalizers)
×
889
}
890

891
// HasFinalizer returns true if a resource has a specific finalizer
892
func HasFinalizer(object metav1.Object, value string) bool {
×
893
        for _, f := range object.GetFinalizers() {
×
894
                if f == value {
×
895
                        return true
×
896
                }
×
897
        }
898
        return false
×
899
}
900

901
// ValidateCloneTokenPVC validates clone token for source and target PVCs
902
func ValidateCloneTokenPVC(t string, v token.Validator, source, target *corev1.PersistentVolumeClaim) error {
×
903
        if source.Namespace == target.Namespace {
×
904
                return nil
×
905
        }
×
906

907
        tokenData, err := v.Validate(t)
×
908
        if err != nil {
×
909
                return errors.Wrap(err, "error verifying token")
×
910
        }
×
911

912
        tokenResourceName := getTokenResourceNamePvc(source)
×
913
        srcName := getSourceNamePvc(source)
×
914

×
915
        return validateTokenData(tokenData, source.Namespace, srcName, target.Namespace, target.Name, string(target.UID), tokenResourceName)
×
916
}
917

918
// ValidateCloneTokenDV validates clone token for DV
919
func ValidateCloneTokenDV(validator token.Validator, dv *cdiv1.DataVolume) error {
×
920
        _, sourceName, sourceNamespace := GetCloneSourceInfo(dv)
×
921
        if sourceNamespace == "" || sourceNamespace == dv.Namespace {
×
922
                return nil
×
923
        }
×
924

925
        tok, ok := dv.Annotations[AnnCloneToken]
×
926
        if !ok {
×
927
                return errors.New("clone token missing")
×
928
        }
×
929

930
        tokenData, err := validator.Validate(tok)
×
931
        if err != nil {
×
932
                return errors.Wrap(err, "error verifying token")
×
933
        }
×
934

935
        tokenResourceName := getTokenResourceNameDataVolume(dv.Spec.Source)
×
936
        if tokenResourceName == "" {
×
937
                return errors.New("token resource name empty, can't verify properly")
×
938
        }
×
939

940
        return validateTokenData(tokenData, sourceNamespace, sourceName, dv.Namespace, dv.Name, "", tokenResourceName)
×
941
}
942

943
func getTokenResourceNameDataVolume(source *cdiv1.DataVolumeSource) string {
×
944
        if source.PVC != nil {
×
945
                return "persistentvolumeclaims"
×
946
        } else if source.Snapshot != nil {
×
947
                return "volumesnapshots"
×
948
        }
×
949

950
        return ""
×
951
}
952

953
func getTokenResourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
954
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
×
955
                return "volumesnapshots"
×
956
        }
×
957

958
        return "persistentvolumeclaims"
×
959
}
960

961
func getSourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
962
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
×
963
                if sourcePvc.Spec.DataSourceRef != nil {
×
964
                        return sourcePvc.Spec.DataSourceRef.Name
×
965
                }
×
966
        }
967

968
        return sourcePvc.Name
×
969
}
970

971
func validateTokenData(tokenData *token.Payload, srcNamespace, srcName, targetNamespace, targetName, targetUID, tokenResourceName string) error {
×
972
        uid := tokenData.Params["uid"]
×
973
        if tokenData.Operation != token.OperationClone ||
×
974
                tokenData.Name != srcName ||
×
975
                tokenData.Namespace != srcNamespace ||
×
976
                tokenData.Resource.Resource != tokenResourceName ||
×
977
                tokenData.Params["targetNamespace"] != targetNamespace ||
×
978
                tokenData.Params["targetName"] != targetName ||
×
979
                (uid != "" && uid != targetUID) {
×
980
                return errors.New("invalid token")
×
981
        }
×
982

983
        return nil
×
984
}
985

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

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

1016
// AddLabel adds a label to an object
1017
func AddLabel(obj metav1.Object, key, value string) {
1✔
1018
        if obj.GetLabels() == nil {
2✔
1019
                obj.SetLabels(make(map[string]string))
1✔
1020
        }
1✔
1021
        obj.GetLabels()[key] = value
1✔
1022
}
1023

1024
// HandleFailedPod handles pod-creation errors and updates the pod's PVC without providing sensitive information
1025
func HandleFailedPod(err error, podName string, pvc *corev1.PersistentVolumeClaim, recorder record.EventRecorder, c client.Client) error {
×
1026
        if err == nil {
×
1027
                return nil
×
1028
        }
×
1029
        // Generic reason and msg to avoid providing sensitive information
1030
        reason := ErrStartingPod
×
1031
        msg := fmt.Sprintf(MessageErrStartingPod, podName)
×
1032

×
1033
        // Error handling to fine-tune the event with pertinent info
×
1034
        if ErrQuotaExceeded(err) {
×
1035
                reason = ErrExceededQuota
×
1036
        }
×
1037

1038
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1039

×
1040
        if isCloneSourcePod := CreateCloneSourcePodName(pvc) == podName; isCloneSourcePod {
×
1041
                AddAnnotation(pvc, AnnSourceRunningCondition, "false")
×
1042
                AddAnnotation(pvc, AnnSourceRunningConditionReason, reason)
×
1043
                AddAnnotation(pvc, AnnSourceRunningConditionMessage, msg)
×
1044
        } else {
×
1045
                AddAnnotation(pvc, AnnRunningCondition, "false")
×
1046
                AddAnnotation(pvc, AnnRunningConditionReason, reason)
×
1047
                AddAnnotation(pvc, AnnRunningConditionMessage, msg)
×
1048
        }
×
1049

1050
        AddAnnotation(pvc, AnnPodPhase, string(corev1.PodFailed))
×
1051
        if err := c.Update(context.TODO(), pvc); err != nil {
×
1052
                return err
×
1053
        }
×
1054

1055
        return err
×
1056
}
1057

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

1080
// GetEndpoint returns the endpoint string which contains the full path URI of the target object to be copied.
1081
func GetEndpoint(pvc *corev1.PersistentVolumeClaim) (string, error) {
×
1082
        ep, found := pvc.Annotations[AnnEndpoint]
×
1083
        if !found || ep == "" {
×
1084
                verb := "empty"
×
1085
                if !found {
×
1086
                        verb = "missing"
×
1087
                }
×
1088
                return ep, errors.Errorf("annotation %q in pvc \"%s/%s\" is %s", AnnEndpoint, pvc.Namespace, pvc.Name, verb)
×
1089
        }
1090
        return ep, nil
×
1091
}
1092

1093
// AddImportVolumeMounts is being called for pods using PV with filesystem volume mode
1094
func AddImportVolumeMounts() []corev1.VolumeMount {
×
1095
        volumeMounts := []corev1.VolumeMount{
×
1096
                {
×
1097
                        Name:      DataVolName,
×
1098
                        MountPath: common.ImporterDataDir,
×
1099
                },
×
1100
        }
×
1101
        return volumeMounts
×
1102
}
×
1103

1104
// GetEffectiveStorageResources returns the maximum of the passed storageResources and the storageProfile minimumSupportedPVCSize.
1105
// If the passed storageResources has no size, it is returned as-is.
1106
func GetEffectiveStorageResources(ctx context.Context, client client.Client, storageResources corev1.VolumeResourceRequirements,
1107
        storageClassName *string, contentType cdiv1.DataVolumeContentType, log logr.Logger) (corev1.VolumeResourceRequirements, error) {
×
1108
        sc, err := GetStorageClassByNameWithVirtFallback(ctx, client, storageClassName, contentType)
×
1109
        if err != nil || sc == nil {
×
1110
                return storageResources, err
×
1111
        }
×
1112

1113
        requestedSize, hasSize := storageResources.Requests[corev1.ResourceStorage]
×
1114
        if !hasSize {
×
1115
                return storageResources, nil
×
1116
        }
×
1117

1118
        if requestedSize, err = GetEffectiveVolumeSize(ctx, client, requestedSize, sc.Name, &log); err != nil {
×
1119
                return storageResources, err
×
1120
        }
×
1121

1122
        return corev1.VolumeResourceRequirements{
×
1123
                Requests: corev1.ResourceList{
×
1124
                        corev1.ResourceStorage: requestedSize,
×
1125
                },
×
1126
        }, nil
×
1127
}
1128

1129
// GetEffectiveVolumeSize returns the maximum of the passed requestedSize and the storageProfile minimumSupportedPVCSize.
1130
func GetEffectiveVolumeSize(ctx context.Context, client client.Client, requestedSize resource.Quantity, storageClassName string, log *logr.Logger) (resource.Quantity, error) {
×
1131
        storageProfile := &cdiv1.StorageProfile{}
×
1132
        if err := client.Get(ctx, types.NamespacedName{Name: storageClassName}, storageProfile); err != nil {
×
1133
                return requestedSize, IgnoreNotFound(err)
×
1134
        }
×
1135

1136
        if val, exists := storageProfile.Annotations[AnnMinimumSupportedPVCSize]; exists {
×
1137
                if minSize, err := resource.ParseQuantity(val); err == nil {
×
1138
                        if requestedSize.Cmp(minSize) == -1 {
×
1139
                                return minSize, nil
×
1140
                        }
×
1141
                } else if log != nil {
×
1142
                        log.V(1).Info("Invalid minimum PVC size in annotation", "value", val, "error", err)
×
1143
                }
×
1144
        }
1145

1146
        return requestedSize, nil
×
1147
}
1148

1149
// ValidateRequestedCloneSize validates the clone size requirements on block
1150
func ValidateRequestedCloneSize(sourceResources, targetResources corev1.VolumeResourceRequirements) error {
×
1151
        sourceRequest, hasSource := sourceResources.Requests[corev1.ResourceStorage]
×
1152
        targetRequest, hasTarget := targetResources.Requests[corev1.ResourceStorage]
×
1153
        if !hasSource || !hasTarget {
×
1154
                return errors.New("source/target missing storage resource requests")
×
1155
        }
×
1156

1157
        // Verify that the target PVC size is equal or larger than the source.
1158
        if sourceRequest.Value() > targetRequest.Value() {
×
1159
                return errors.Errorf("target resources requests storage size is smaller than the source %d < %d", targetRequest.Value(), sourceRequest.Value())
×
1160
        }
×
1161
        return nil
×
1162
}
1163

1164
// CreateCloneSourcePodName creates clone source pod name
1165
func CreateCloneSourcePodName(targetPvc *corev1.PersistentVolumeClaim) string {
×
1166
        return string(targetPvc.GetUID()) + common.ClonerSourcePodNameSuffix
×
1167
}
×
1168

1169
// IsPVCComplete returns true if a PVC is in 'Succeeded' phase, false if not
1170
func IsPVCComplete(pvc *corev1.PersistentVolumeClaim) bool {
×
1171
        if pvc != nil {
×
1172
                phase, exists := pvc.ObjectMeta.Annotations[AnnPodPhase]
×
1173
                return exists && (phase == string(corev1.PodSucceeded))
×
1174
        }
×
1175
        return false
×
1176
}
1177

1178
// IsMultiStageImportInProgress returns true when a PVC is being part of an ongoing multi-stage import
1179
func IsMultiStageImportInProgress(pvc *corev1.PersistentVolumeClaim) bool {
×
1180
        if pvc != nil {
×
1181
                multiStageImport := metav1.HasAnnotation(pvc.ObjectMeta, AnnCurrentCheckpoint)
×
1182
                multiStageAlreadyDone := metav1.HasAnnotation(pvc.ObjectMeta, AnnMultiStageImportDone)
×
1183
                return multiStageImport && !multiStageAlreadyDone
×
1184
        }
×
1185
        return false
×
1186
}
1187

1188
// SetRestrictedSecurityContext sets the pod security params to be compatible with restricted PSA
1189
func SetRestrictedSecurityContext(podSpec *corev1.PodSpec) {
×
1190
        hasVolumeMounts := false
×
1191
        for _, containers := range [][]corev1.Container{podSpec.InitContainers, podSpec.Containers} {
×
1192
                for i := range containers {
×
1193
                        container := &containers[i]
×
1194
                        if container.SecurityContext == nil {
×
1195
                                container.SecurityContext = &corev1.SecurityContext{}
×
1196
                        }
×
1197
                        container.SecurityContext.Capabilities = &corev1.Capabilities{
×
1198
                                Drop: []corev1.Capability{
×
1199
                                        "ALL",
×
1200
                                },
×
1201
                        }
×
1202
                        container.SecurityContext.SeccompProfile = &corev1.SeccompProfile{
×
1203
                                Type: corev1.SeccompProfileTypeRuntimeDefault,
×
1204
                        }
×
1205
                        container.SecurityContext.AllowPrivilegeEscalation = ptr.To[bool](false)
×
1206
                        container.SecurityContext.RunAsNonRoot = ptr.To[bool](true)
×
1207
                        container.SecurityContext.RunAsUser = ptr.To[int64](common.QemuSubGid)
×
1208
                        if len(container.VolumeMounts) > 0 {
×
1209
                                hasVolumeMounts = true
×
1210
                        }
×
1211
                }
1212
        }
1213

1214
        if podSpec.SecurityContext == nil {
×
1215
                podSpec.SecurityContext = &corev1.PodSecurityContext{}
×
1216
        }
×
1217
        // Some tools like istio inject containers and thus rely on a pod level seccomp profile being specified
1218
        podSpec.SecurityContext.SeccompProfile = &corev1.SeccompProfile{
×
1219
                Type: corev1.SeccompProfileTypeRuntimeDefault,
×
1220
        }
×
1221
        if hasVolumeMounts {
×
1222
                podSpec.SecurityContext.FSGroup = ptr.To[int64](common.QemuSubGid)
×
1223
        }
×
1224
}
1225

1226
// SetNodeNameIfPopulator sets NodeName in a pod spec when the PVC is being handled by a CDI volume populator
1227
func SetNodeNameIfPopulator(pvc *corev1.PersistentVolumeClaim, podSpec *corev1.PodSpec) {
×
1228
        _, isPopulator := pvc.Annotations[AnnPopulatorKind]
×
1229
        nodeName := pvc.Annotations[AnnSelectedNode]
×
1230
        if isPopulator && nodeName != "" {
×
1231
                podSpec.NodeName = nodeName
×
1232
        }
×
1233
}
1234

1235
// CreatePvc creates PVC
1236
func CreatePvc(name, ns string, annotations, labels map[string]string) *corev1.PersistentVolumeClaim {
1✔
1237
        return CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1✔
1238
}
1✔
1239

1240
// CreatePvcInStorageClass creates PVC with storgae class
1241
func CreatePvcInStorageClass(name, ns string, storageClassName *string, annotations, labels map[string]string, phase corev1.PersistentVolumeClaimPhase) *corev1.PersistentVolumeClaim {
1✔
1242
        pvc := &corev1.PersistentVolumeClaim{
1✔
1243
                ObjectMeta: metav1.ObjectMeta{
1✔
1244
                        Name:        name,
1✔
1245
                        Namespace:   ns,
1✔
1246
                        Annotations: annotations,
1✔
1247
                        Labels:      labels,
1✔
1248
                        UID:         types.UID(ns + "-" + name),
1✔
1249
                },
1✔
1250
                Spec: corev1.PersistentVolumeClaimSpec{
1✔
1251
                        AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadOnlyMany, corev1.ReadWriteOnce},
1✔
1252
                        Resources: corev1.VolumeResourceRequirements{
1✔
1253
                                Requests: corev1.ResourceList{
1✔
1254
                                        corev1.ResourceStorage: resource.MustParse("1G"),
1✔
1255
                                },
1✔
1256
                        },
1✔
1257
                        StorageClassName: storageClassName,
1✔
1258
                },
1✔
1259
                Status: corev1.PersistentVolumeClaimStatus{
1✔
1260
                        Phase: phase,
1✔
1261
                },
1✔
1262
        }
1✔
1263
        pvc.Status.Capacity = pvc.Spec.Resources.Requests.DeepCopy()
1✔
1264
        if pvc.Status.Phase == corev1.ClaimBound {
2✔
1265
                pvc.Spec.VolumeName = "pv-" + string(pvc.UID)
1✔
1266
        }
1✔
1267
        return pvc
1✔
1268
}
1269

1270
// GetAPIServerKey returns API server RSA key
1271
func GetAPIServerKey() *rsa.PrivateKey {
×
1272
        apiServerKeyOnce.Do(func() {
×
1273
                apiServerKey, _ = rsa.GenerateKey(rand.Reader, 2048)
×
1274
        })
×
1275
        return apiServerKey
×
1276
}
1277

1278
// CreateStorageClass creates storage class CR
1279
func CreateStorageClass(name string, annotations map[string]string) *storagev1.StorageClass {
1✔
1280
        return &storagev1.StorageClass{
1✔
1281
                ObjectMeta: metav1.ObjectMeta{
1✔
1282
                        Name:        name,
1✔
1283
                        Annotations: annotations,
1✔
1284
                },
1✔
1285
        }
1✔
1286
}
1✔
1287

1288
// CreateImporterTestPod creates importer test pod CR
1289
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
×
1290
        // importer pod name contains the pvc name
×
1291
        podName := fmt.Sprintf("%s-%s", common.ImporterPodName, pvc.Name)
×
1292

×
1293
        blockOwnerDeletion := true
×
1294
        isController := true
×
1295

×
1296
        volumes := []corev1.Volume{
×
1297
                {
×
1298
                        Name: dvname,
×
1299
                        VolumeSource: corev1.VolumeSource{
×
1300
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1301
                                        ClaimName: pvc.Name,
×
1302
                                        ReadOnly:  false,
×
1303
                                },
×
1304
                        },
×
1305
                },
×
1306
        }
×
1307

×
1308
        if scratchPvc != nil {
×
1309
                volumes = append(volumes, corev1.Volume{
×
1310
                        Name: ScratchVolName,
×
1311
                        VolumeSource: corev1.VolumeSource{
×
1312
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1313
                                        ClaimName: scratchPvc.Name,
×
1314
                                        ReadOnly:  false,
×
1315
                                },
×
1316
                        },
×
1317
                })
×
1318
        }
×
1319

1320
        pod := &corev1.Pod{
×
1321
                TypeMeta: metav1.TypeMeta{
×
1322
                        Kind:       "Pod",
×
1323
                        APIVersion: "v1",
×
1324
                },
×
1325
                ObjectMeta: metav1.ObjectMeta{
×
1326
                        Name:      podName,
×
1327
                        Namespace: pvc.Namespace,
×
1328
                        Annotations: map[string]string{
×
1329
                                AnnCreatedBy: "yes",
×
1330
                        },
×
1331
                        Labels: map[string]string{
×
1332
                                common.CDILabelKey:        common.CDILabelValue,
×
1333
                                common.CDIComponentLabel:  common.ImporterPodName,
×
1334
                                common.PrometheusLabelKey: common.PrometheusLabelValue,
×
1335
                        },
×
1336
                        OwnerReferences: []metav1.OwnerReference{
×
1337
                                {
×
1338
                                        APIVersion:         "v1",
×
1339
                                        Kind:               "PersistentVolumeClaim",
×
1340
                                        Name:               pvc.Name,
×
1341
                                        UID:                pvc.GetUID(),
×
1342
                                        BlockOwnerDeletion: &blockOwnerDeletion,
×
1343
                                        Controller:         &isController,
×
1344
                                },
×
1345
                        },
×
1346
                },
×
1347
                Spec: corev1.PodSpec{
×
1348
                        Containers: []corev1.Container{
×
1349
                                {
×
1350
                                        Name:            common.ImporterPodName,
×
1351
                                        Image:           "test/myimage",
×
1352
                                        ImagePullPolicy: corev1.PullPolicy("Always"),
×
1353
                                        Args:            []string{"-v=5"},
×
1354
                                        Ports: []corev1.ContainerPort{
×
1355
                                                {
×
1356
                                                        Name:          "metrics",
×
1357
                                                        ContainerPort: 8443,
×
1358
                                                        Protocol:      corev1.ProtocolTCP,
×
1359
                                                },
×
1360
                                        },
×
1361
                                },
×
1362
                        },
×
1363
                        RestartPolicy: corev1.RestartPolicyOnFailure,
×
1364
                        Volumes:       volumes,
×
1365
                },
×
1366
        }
×
1367

×
1368
        ep, _ := GetEndpoint(pvc)
×
1369
        source := GetSource(pvc)
×
1370
        contentType := GetPVCContentType(pvc)
×
1371
        imageSize, _ := GetRequestedImageSize(pvc)
×
1372
        volumeMode := GetVolumeMode(pvc)
×
1373

×
1374
        env := []corev1.EnvVar{
×
1375
                {
×
1376
                        Name:  common.ImporterSource,
×
1377
                        Value: source,
×
1378
                },
×
1379
                {
×
1380
                        Name:  common.ImporterEndpoint,
×
1381
                        Value: ep,
×
1382
                },
×
1383
                {
×
1384
                        Name:  common.ImporterContentType,
×
1385
                        Value: string(contentType),
×
1386
                },
×
1387
                {
×
1388
                        Name:  common.ImporterImageSize,
×
1389
                        Value: imageSize,
×
1390
                },
×
1391
                {
×
1392
                        Name:  common.OwnerUID,
×
1393
                        Value: string(pvc.UID),
×
1394
                },
×
1395
                {
×
1396
                        Name:  common.InsecureTLSVar,
×
1397
                        Value: "false",
×
1398
                },
×
1399
        }
×
1400
        pod.Spec.Containers[0].Env = env
×
1401
        if volumeMode == corev1.PersistentVolumeBlock {
×
1402
                pod.Spec.Containers[0].VolumeDevices = AddVolumeDevices()
×
1403
        } else {
×
1404
                pod.Spec.Containers[0].VolumeMounts = AddImportVolumeMounts()
×
1405
        }
×
1406

1407
        if scratchPvc != nil {
×
1408
                pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
×
1409
                        Name:      ScratchVolName,
×
1410
                        MountPath: common.ScratchDataDir,
×
1411
                })
×
1412
        }
×
1413

1414
        return pod
×
1415
}
1416

1417
// CreateStorageClassWithProvisioner creates CR of storage class with provisioner
1418
func CreateStorageClassWithProvisioner(name string, annotations, labels map[string]string, provisioner string) *storagev1.StorageClass {
×
1419
        return &storagev1.StorageClass{
×
1420
                Provisioner: provisioner,
×
1421
                ObjectMeta: metav1.ObjectMeta{
×
1422
                        Name:        name,
×
1423
                        Annotations: annotations,
×
1424
                        Labels:      labels,
×
1425
                },
×
1426
        }
×
1427
}
×
1428

1429
// CreateClient creates a fake client
1430
func CreateClient(objs ...runtime.Object) client.Client {
1✔
1431
        s := scheme.Scheme
1✔
1432
        _ = cdiv1.AddToScheme(s)
1✔
1433
        _ = corev1.AddToScheme(s)
1✔
1434
        _ = storagev1.AddToScheme(s)
1✔
1435
        _ = ocpconfigv1.Install(s)
1✔
1436

1✔
1437
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1438
}
1✔
1439

1440
// ErrQuotaExceeded checked is the error is of exceeded quota
1441
func ErrQuotaExceeded(err error) bool {
×
1442
        return strings.Contains(err.Error(), "exceeded quota:")
×
1443
}
×
1444

1445
// GetContentType returns the content type. If invalid or not set, default to kubevirt
1446
func GetContentType(contentType cdiv1.DataVolumeContentType) cdiv1.DataVolumeContentType {
1✔
1447
        switch contentType {
1✔
1448
        case
1449
                cdiv1.DataVolumeKubeVirt,
1450
                cdiv1.DataVolumeArchive:
1✔
1451
        default:
×
1452
                // TODO - shouldn't archive be the default?
×
1453
                contentType = cdiv1.DataVolumeKubeVirt
×
1454
        }
1455
        return contentType
1✔
1456
}
1457

1458
// GetPVCContentType returns the content type of the source image. If invalid or not set, default to kubevirt
1459
func GetPVCContentType(pvc *corev1.PersistentVolumeClaim) cdiv1.DataVolumeContentType {
×
1460
        contentType, found := pvc.Annotations[AnnContentType]
×
1461
        if !found {
×
1462
                // TODO - shouldn't archive be the default?
×
1463
                return cdiv1.DataVolumeKubeVirt
×
1464
        }
×
1465

1466
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1467
}
1468

1469
// GetNamespace returns the given namespace if not empty, otherwise the default namespace
1470
func GetNamespace(namespace, defaultNamespace string) string {
×
1471
        if namespace == "" {
×
1472
                return defaultNamespace
×
1473
        }
×
1474
        return namespace
×
1475
}
1476

1477
// IsErrCacheNotStarted checked is the error is of cache not started
1478
func IsErrCacheNotStarted(err error) bool {
×
1479
        target := &runtimecache.ErrCacheNotStarted{}
×
1480
        return errors.As(err, &target)
×
1481
}
×
1482

1483
// NewImportDataVolume returns new import DataVolume CR
1484
func NewImportDataVolume(name string) *cdiv1.DataVolume {
×
1485
        return &cdiv1.DataVolume{
×
1486
                TypeMeta: metav1.TypeMeta{APIVersion: cdiv1.SchemeGroupVersion.String()},
×
1487
                ObjectMeta: metav1.ObjectMeta{
×
1488
                        Name:      name,
×
1489
                        Namespace: metav1.NamespaceDefault,
×
1490
                        UID:       types.UID(metav1.NamespaceDefault + "-" + name),
×
1491
                },
×
1492
                Spec: cdiv1.DataVolumeSpec{
×
1493
                        Source: &cdiv1.DataVolumeSource{
×
1494
                                HTTP: &cdiv1.DataVolumeSourceHTTP{
×
1495
                                        URL: "http://example.com/data",
×
1496
                                },
×
1497
                        },
×
1498
                        PVC: &corev1.PersistentVolumeClaimSpec{
×
1499
                                AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
×
1500
                        },
×
1501
                        PriorityClassName: "p0",
×
1502
                },
×
1503
        }
×
1504
}
×
1505

1506
// GetCloneSourceInfo returns the type, name and namespace of the cloning source
1507
func GetCloneSourceInfo(dv *cdiv1.DataVolume) (sourceType, sourceName, sourceNamespace string) {
×
1508
        // Cloning sources are mutually exclusive
×
1509
        if dv.Spec.Source.PVC != nil {
×
1510
                return "pvc", dv.Spec.Source.PVC.Name, dv.Spec.Source.PVC.Namespace
×
1511
        }
×
1512

1513
        if dv.Spec.Source.Snapshot != nil {
×
1514
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
×
1515
        }
×
1516

1517
        return "", "", ""
×
1518
}
1519

1520
// IsWaitForFirstConsumerEnabled tells us if we should respect "real" WFFC behavior or just let our worker pods randomly spawn
1521
func IsWaitForFirstConsumerEnabled(obj metav1.Object, gates featuregates.FeatureGates) (bool, error) {
×
1522
        // when PVC requests immediateBinding it cannot honor wffc logic
×
1523
        isImmediateBindingRequested := ImmediateBindingRequested(obj)
×
1524
        pvcHonorWaitForFirstConsumer := !isImmediateBindingRequested
×
1525
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
×
1526
        if err != nil {
×
1527
                return false, err
×
1528
        }
×
1529

1530
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1531
}
1532

1533
// AddImmediateBindingAnnotationIfWFFCDisabled adds the immediateBinding annotation if wffc feature gate is disabled
1534
func AddImmediateBindingAnnotationIfWFFCDisabled(obj metav1.Object, gates featuregates.FeatureGates) error {
×
1535
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
×
1536
        if err != nil {
×
1537
                return err
×
1538
        }
×
1539
        if !globalHonorWaitForFirstConsumer {
×
1540
                AddAnnotation(obj, AnnImmediateBinding, "")
×
1541
        }
×
1542
        return nil
×
1543
}
1544

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

×
1549
        if util.ResolveVolumeMode(pvcSpec.VolumeMode) == corev1.PersistentVolumeFilesystem {
×
1550
                fsOverhead, err := GetFilesystemOverheadForStorageClass(ctx, c, pvcSpec.StorageClassName)
×
1551
                if err != nil {
×
1552
                        return resource.Quantity{}, err
×
1553
                }
×
1554
                // Parse filesystem overhead (percentage) into a 64-bit float
1555
                fsOverheadFloat, _ := strconv.ParseFloat(string(fsOverhead), 64)
×
1556

×
1557
                // Merge the previous values into a 'resource.Quantity' struct
×
1558
                requiredSpace := util.GetRequiredSpace(fsOverheadFloat, imgSize)
×
1559
                returnSize = *resource.NewScaledQuantity(requiredSpace, 0)
×
1560
        } else {
×
1561
                // Inflation is not needed with 'Block' mode
×
1562
                returnSize = *resource.NewScaledQuantity(imgSize, 0)
×
1563
        }
×
1564

1565
        return returnSize, nil
×
1566
}
1567

1568
// IsBound returns if the pvc is bound
1569
func IsBound(pvc *corev1.PersistentVolumeClaim) bool {
×
1570
        return pvc != nil && pvc.Status.Phase == corev1.ClaimBound
×
1571
}
×
1572

1573
// IsUnbound returns if the pvc is not bound yet
1574
func IsUnbound(pvc *corev1.PersistentVolumeClaim) bool {
×
1575
        return !IsBound(pvc)
×
1576
}
×
1577

1578
// IsLost returns if the pvc is lost
1579
func IsLost(pvc *corev1.PersistentVolumeClaim) bool {
×
1580
        return pvc != nil && pvc.Status.Phase == corev1.ClaimLost
×
1581
}
×
1582

1583
// IsImageStream returns true if registry source is ImageStream
1584
func IsImageStream(pvc *corev1.PersistentVolumeClaim) bool {
×
1585
        return pvc.Annotations[AnnRegistryImageStream] == "true"
×
1586
}
×
1587

1588
// ShouldIgnorePod checks if a pod should be ignored.
1589
// If this is a completed pod that was used for one checkpoint of a multi-stage import, it
1590
// should be ignored by pod lookups as long as the retainAfterCompletion annotation is set.
1591
func ShouldIgnorePod(pod *corev1.Pod, pvc *corev1.PersistentVolumeClaim) bool {
×
1592
        retain := pvc.ObjectMeta.Annotations[AnnPodRetainAfterCompletion]
×
1593
        checkpoint := pvc.ObjectMeta.Annotations[AnnCurrentCheckpoint]
×
1594
        if checkpoint != "" && pod.Status.Phase == corev1.PodSucceeded {
×
1595
                return retain == "true"
×
1596
        }
×
1597
        return false
×
1598
}
1599

1600
// BuildHTTPClient generates an http client that accepts any certificate, since we are using
1601
// it to get prometheus data it doesn't matter if someone can intercept the data. Once we have
1602
// a mechanism to properly sign the server, we can update this method to get a proper client.
1603
func BuildHTTPClient(httpClient *http.Client) *http.Client {
×
1604
        if httpClient == nil {
×
1605
                defaultTransport := http.DefaultTransport.(*http.Transport)
×
1606
                // Create new Transport that ignores self-signed SSL
×
1607
                //nolint:gosec
×
1608
                tr := &http.Transport{
×
1609
                        Proxy:                 defaultTransport.Proxy,
×
1610
                        DialContext:           defaultTransport.DialContext,
×
1611
                        MaxIdleConns:          defaultTransport.MaxIdleConns,
×
1612
                        IdleConnTimeout:       defaultTransport.IdleConnTimeout,
×
1613
                        ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
×
1614
                        TLSHandshakeTimeout:   defaultTransport.TLSHandshakeTimeout,
×
1615
                        TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
×
1616
                }
×
1617
                httpClient = &http.Client{
×
1618
                        Transport: tr,
×
1619
                }
×
1620
        }
×
1621
        return httpClient
×
1622
}
1623

1624
// ErrConnectionRefused checks for connection refused errors
1625
func ErrConnectionRefused(err error) bool {
×
1626
        return strings.Contains(err.Error(), "connection refused")
×
1627
}
×
1628

1629
// GetPodMetricsPort returns, if exists, the metrics port from the passed pod
1630
func GetPodMetricsPort(pod *corev1.Pod) (int, error) {
1✔
1631
        for _, container := range pod.Spec.Containers {
2✔
1632
                for _, port := range container.Ports {
2✔
1633
                        if port.Name == "metrics" {
2✔
1634
                                return int(port.ContainerPort), nil
1✔
1635
                        }
1✔
1636
                }
1637
        }
1638
        return 0, errors.New("Metrics port not found in pod")
1✔
1639
}
1640

1641
// GetMetricsURL builds the metrics URL according to the specified pod
1642
func GetMetricsURL(pod *corev1.Pod) (string, error) {
1✔
1643
        if pod == nil {
1✔
1644
                return "", nil
×
1645
        }
×
1646
        port, err := GetPodMetricsPort(pod)
1✔
1647
        if err != nil || pod.Status.PodIP == "" {
2✔
1648
                return "", err
1✔
1649
        }
1✔
1650
        domain := net.JoinHostPort(pod.Status.PodIP, fmt.Sprint(port))
1✔
1651
        url := fmt.Sprintf("https://%s/metrics", domain)
1✔
1652
        return url, nil
1✔
1653
}
1654

1655
// GetProgressReportFromURL fetches the progress report from the passed URL according to an specific metric expression and ownerUID
1656
func GetProgressReportFromURL(ctx context.Context, url string, httpClient *http.Client, metricExp, ownerUID string) (string, error) {
×
1657
        regExp := regexp.MustCompile(fmt.Sprintf("(%s)\\{ownerUID\\=%q\\} (\\d{1,3}\\.?\\d*)", metricExp, ownerUID))
×
1658
        // pod could be gone, don't block an entire thread for 30 seconds
×
1659
        // just to get back an i/o timeout
×
1660
        ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
×
1661
        defer cancel()
×
1662
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
×
1663
        if err != nil {
×
1664
                return "", err
×
1665
        }
×
1666
        resp, err := httpClient.Do(req)
×
1667
        if err != nil {
×
1668
                if ErrConnectionRefused(err) {
×
1669
                        return "", nil
×
1670
                }
×
1671
                return "", err
×
1672
        }
1673
        defer resp.Body.Close()
×
1674
        body, err := io.ReadAll(resp.Body)
×
1675
        if err != nil {
×
1676
                return "", err
×
1677
        }
×
1678

1679
        // Parse the progress from the body
1680
        progressReport := ""
×
1681
        match := regExp.FindStringSubmatch(string(body))
×
1682
        if match != nil {
×
1683
                progressReport = match[len(match)-1]
×
1684
        }
×
1685
        return progressReport, nil
×
1686
}
1687

1688
// UpdateHTTPAnnotations updates the passed annotations for proper http import
1689
func UpdateHTTPAnnotations(annotations map[string]string, http *cdiv1.DataVolumeSourceHTTP) {
×
1690
        annotations[AnnEndpoint] = http.URL
×
1691
        annotations[AnnSource] = SourceHTTP
×
1692

×
1693
        if http.SecretRef != "" {
×
1694
                annotations[AnnSecret] = http.SecretRef
×
1695
        }
×
1696
        if http.CertConfigMap != "" {
×
1697
                annotations[AnnCertConfigMap] = http.CertConfigMap
×
1698
        }
×
1699
        for index, header := range http.ExtraHeaders {
×
1700
                annotations[fmt.Sprintf("%s.%d", AnnExtraHeaders, index)] = header
×
1701
        }
×
1702
        for index, header := range http.SecretExtraHeaders {
×
1703
                annotations[fmt.Sprintf("%s.%d", AnnSecretExtraHeaders, index)] = header
×
1704
        }
×
1705
}
1706

1707
// UpdateS3Annotations updates the passed annotations for proper S3 import
1708
func UpdateS3Annotations(annotations map[string]string, s3 *cdiv1.DataVolumeSourceS3) {
×
1709
        annotations[AnnEndpoint] = s3.URL
×
1710
        annotations[AnnSource] = SourceS3
×
1711
        if s3.SecretRef != "" {
×
1712
                annotations[AnnSecret] = s3.SecretRef
×
1713
        }
×
1714
        if s3.CertConfigMap != "" {
×
1715
                annotations[AnnCertConfigMap] = s3.CertConfigMap
×
1716
        }
×
1717
}
1718

1719
// UpdateGCSAnnotations updates the passed annotations for proper GCS import
1720
func UpdateGCSAnnotations(annotations map[string]string, gcs *cdiv1.DataVolumeSourceGCS) {
×
1721
        annotations[AnnEndpoint] = gcs.URL
×
1722
        annotations[AnnSource] = SourceGCS
×
1723
        if gcs.SecretRef != "" {
×
1724
                annotations[AnnSecret] = gcs.SecretRef
×
1725
        }
×
1726
}
1727

1728
// UpdateRegistryAnnotations updates the passed annotations for proper registry import
1729
func UpdateRegistryAnnotations(annotations map[string]string, registry *cdiv1.DataVolumeSourceRegistry) {
×
1730
        annotations[AnnSource] = SourceRegistry
×
1731
        pullMethod := registry.PullMethod
×
1732
        if pullMethod != nil && *pullMethod != "" {
×
1733
                annotations[AnnRegistryImportMethod] = string(*pullMethod)
×
1734
        }
×
1735
        url := registry.URL
×
1736
        if url != nil && *url != "" {
×
1737
                annotations[AnnEndpoint] = *url
×
1738
        } else {
×
1739
                imageStream := registry.ImageStream
×
1740
                if imageStream != nil && *imageStream != "" {
×
1741
                        annotations[AnnEndpoint] = *imageStream
×
1742
                        annotations[AnnRegistryImageStream] = "true"
×
1743
                }
×
1744
        }
1745
        secretRef := registry.SecretRef
×
1746
        if secretRef != nil && *secretRef != "" {
×
1747
                annotations[AnnSecret] = *secretRef
×
1748
        }
×
1749
        certConfigMap := registry.CertConfigMap
×
1750
        if certConfigMap != nil && *certConfigMap != "" {
×
1751
                annotations[AnnCertConfigMap] = *certConfigMap
×
1752
        }
×
1753

1754
        if registry.Platform != nil && registry.Platform.Architecture != "" {
×
1755
                annotations[AnnRegistryImageArchitecture] = registry.Platform.Architecture
×
1756
        }
×
1757
}
1758

1759
// UpdateVDDKAnnotations updates the passed annotations for proper VDDK import
1760
func UpdateVDDKAnnotations(annotations map[string]string, vddk *cdiv1.DataVolumeSourceVDDK) {
×
1761
        annotations[AnnEndpoint] = vddk.URL
×
1762
        annotations[AnnSource] = SourceVDDK
×
1763
        annotations[AnnSecret] = vddk.SecretRef
×
1764
        annotations[AnnBackingFile] = vddk.BackingFile
×
1765
        annotations[AnnUUID] = vddk.UUID
×
1766
        annotations[AnnThumbprint] = vddk.Thumbprint
×
1767
        if vddk.InitImageURL != "" {
×
1768
                annotations[AnnVddkInitImageURL] = vddk.InitImageURL
×
1769
        }
×
1770
        if vddk.ExtraArgs != "" {
×
1771
                annotations[AnnVddkExtraArgs] = vddk.ExtraArgs
×
1772
        }
×
1773
}
1774

1775
// UpdateImageIOAnnotations updates the passed annotations for proper imageIO import
1776
func UpdateImageIOAnnotations(annotations map[string]string, imageio *cdiv1.DataVolumeSourceImageIO) {
×
1777
        annotations[AnnEndpoint] = imageio.URL
×
1778
        annotations[AnnSource] = SourceImageio
×
1779
        annotations[AnnSecret] = imageio.SecretRef
×
1780
        annotations[AnnCertConfigMap] = imageio.CertConfigMap
×
1781
        annotations[AnnDiskID] = imageio.DiskID
×
1782
        if imageio.InsecureSkipVerify != nil && *imageio.InsecureSkipVerify {
×
1783
                annotations[AnnInsecureSkipVerify] = "true"
×
1784
        }
×
1785
}
1786

1787
// IsPVBoundToPVC checks if a PV is bound to a specific PVC
1788
func IsPVBoundToPVC(pv *corev1.PersistentVolume, pvc *corev1.PersistentVolumeClaim) bool {
1✔
1789
        claimRef := pv.Spec.ClaimRef
1✔
1790
        return claimRef != nil && claimRef.Name == pvc.Name && claimRef.Namespace == pvc.Namespace && claimRef.UID == pvc.UID
1✔
1791
}
1✔
1792

1793
// Rebind binds the PV of source to target
1794
func Rebind(ctx context.Context, c client.Client, source, target *corev1.PersistentVolumeClaim) error {
1✔
1795
        pv := &corev1.PersistentVolume{
1✔
1796
                ObjectMeta: metav1.ObjectMeta{
1✔
1797
                        Name: source.Spec.VolumeName,
1✔
1798
                },
1✔
1799
        }
1✔
1800

1✔
1801
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
2✔
1802
                return err
1✔
1803
        }
1✔
1804

1805
        // Examine the claimref for the PV and see if it's still bound to PVC'
1806
        if pv.Spec.ClaimRef == nil {
1✔
1807
                return fmt.Errorf("PV %s claimRef is nil", pv.Name)
×
1808
        }
×
1809

1810
        if !IsPVBoundToPVC(pv, source) {
2✔
1811
                // Something is not right if the PV is neither bound to PVC' nor target PVC
1✔
1812
                if !IsPVBoundToPVC(pv, target) {
2✔
1813
                        klog.Errorf("PV bound to unexpected PVC: Could not rebind to target PVC '%s'", target.Name)
1✔
1814
                        return fmt.Errorf("PV %s bound to unexpected claim %s", pv.Name, pv.Spec.ClaimRef.Name)
1✔
1815
                }
1✔
1816
                // our work is done
1817
                return nil
1✔
1818
        }
1819

1820
        // Rebind PVC to target PVC
1821
        pv.Spec.ClaimRef = &corev1.ObjectReference{
1✔
1822
                Namespace:       target.Namespace,
1✔
1823
                Name:            target.Name,
1✔
1824
                UID:             target.UID,
1✔
1825
                ResourceVersion: target.ResourceVersion,
1✔
1826
        }
1✔
1827
        klog.V(3).Info("Rebinding PV to target PVC", "PVC", target.Name)
1✔
1828
        if err := c.Update(context.TODO(), pv); err != nil {
1✔
1829
                return err
×
1830
        }
×
1831

1832
        return nil
1✔
1833
}
1834

1835
// BulkDeleteResources deletes a bunch of resources
1836
func BulkDeleteResources(ctx context.Context, c client.Client, obj client.ObjectList, lo client.ListOption) error {
×
1837
        if err := c.List(ctx, obj, lo); err != nil {
×
1838
                if meta.IsNoMatchError(err) {
×
1839
                        return nil
×
1840
                }
×
1841
                return err
×
1842
        }
1843

1844
        sv := reflect.ValueOf(obj).Elem()
×
1845
        iv := sv.FieldByName("Items")
×
1846

×
1847
        for i := 0; i < iv.Len(); i++ {
×
1848
                obj := iv.Index(i).Addr().Interface().(client.Object)
×
1849
                if obj.GetDeletionTimestamp().IsZero() {
×
1850
                        klog.V(3).Infof("Deleting type %+v %+v", reflect.TypeOf(obj), obj)
×
1851
                        if err := c.Delete(ctx, obj); err != nil {
×
1852
                                return err
×
1853
                        }
×
1854
                }
1855
        }
1856

1857
        return nil
×
1858
}
1859

1860
// ValidateSnapshotCloneSize does proper size validation when doing a clone from snapshot operation
1861
func ValidateSnapshotCloneSize(snapshot *snapshotv1.VolumeSnapshot, pvcSpec *corev1.PersistentVolumeClaimSpec, targetSC *storagev1.StorageClass, log logr.Logger) (bool, error) {
×
1862
        restoreSize := snapshot.Status.RestoreSize
×
1863
        if restoreSize == nil {
×
1864
                return false, fmt.Errorf("snapshot has no RestoreSize")
×
1865
        }
×
1866
        targetRequest, hasTargetRequest := pvcSpec.Resources.Requests[corev1.ResourceStorage]
×
1867
        allowExpansion := targetSC.AllowVolumeExpansion != nil && *targetSC.AllowVolumeExpansion
×
1868
        if hasTargetRequest {
×
1869
                // otherwise will just use restoreSize
×
1870
                if restoreSize.Cmp(targetRequest) < 0 && !allowExpansion {
×
1871
                        log.V(3).Info("Can't expand restored PVC because SC does not allow expansion, need to fall back to host assisted")
×
1872
                        return false, nil
×
1873
                }
×
1874
        }
1875
        return true, nil
×
1876
}
1877

1878
// ValidateSnapshotCloneProvisioners validates the target PVC storage class against the snapshot class provisioner
1879
func ValidateSnapshotCloneProvisioners(vsc *snapshotv1.VolumeSnapshotContent, storageClass *storagev1.StorageClass) (bool, error) {
×
1880
        // Do snapshot and storage class validation
×
1881
        if storageClass == nil {
×
1882
                return false, fmt.Errorf("target storage class not found")
×
1883
        }
×
1884
        if storageClass.Provisioner != vsc.Spec.Driver {
×
1885
                return false, nil
×
1886
        }
×
1887
        // TODO: get sourceVolumeMode from volumesnapshotcontent and validate against target spec
1888
        // currently don't have CRDs in CI with sourceVolumeMode which is pretty new
1889
        // converting volume mode is possible but has security implications
1890
        return true, nil
×
1891
}
1892

1893
// GetSnapshotClassForSmartClone looks up the snapshot class based on the storage class
1894
func GetSnapshotClassForSmartClone(pvc *corev1.PersistentVolumeClaim, targetPvcStorageClassName, snapshotClassName *string, log logr.Logger, client client.Client, recorder record.EventRecorder) (string, error) {
×
1895
        logger := log.WithName("GetSnapshotClassForSmartClone").V(3)
×
1896
        // Check if relevant CRDs are available
×
1897
        if !isCsiCrdsDeployed(client, log) {
×
1898
                logger.Info("Missing CSI snapshotter CRDs, falling back to host assisted clone")
×
1899
                return "", nil
×
1900
        }
×
1901

1902
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(context.TODO(), client, targetPvcStorageClassName)
×
1903
        if err != nil {
×
1904
                return "", err
×
1905
        }
×
1906
        if targetStorageClass == nil {
×
1907
                logger.Info("Target PVC's Storage Class not found")
×
1908
                return "", nil
×
1909
        }
×
1910

1911
        vscName, err := GetVolumeSnapshotClass(context.TODO(), client, pvc, targetStorageClass.Provisioner, snapshotClassName, logger, recorder)
×
1912
        if err != nil {
×
1913
                return "", err
×
1914
        }
×
1915
        if vscName != nil {
×
1916
                if pvc != nil {
×
1917
                        logger.Info("smart-clone is applicable for datavolume", "datavolume",
×
1918
                                pvc.Name, "snapshot class", *vscName)
×
1919
                }
×
1920
                return *vscName, nil
×
1921
        }
1922

1923
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
1924
        return "", nil
×
1925
}
1926

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

×
1932
        logEvent := func(message, vscName string) {
×
1933
                logger.Info(message, "name", vscName)
×
1934
                if pvc != nil {
×
1935
                        msg := fmt.Sprintf("%s %s", message, vscName)
×
1936
                        recorder.Event(pvc, corev1.EventTypeNormal, VolumeSnapshotClassSelected, msg)
×
1937
                }
×
1938
        }
1939

1940
        if snapshotClassName != nil {
×
1941
                vsc := &snapshotv1.VolumeSnapshotClass{}
×
1942
                if err := c.Get(context.TODO(), types.NamespacedName{Name: *snapshotClassName}, vsc); err != nil {
×
1943
                        return nil, err
×
1944
                }
×
1945
                if vsc.Driver == driver {
×
1946
                        logEvent(MessageStorageProfileVolumeSnapshotClassSelected, vsc.Name)
×
1947
                        return snapshotClassName, nil
×
1948
                }
×
1949
                return nil, nil
×
1950
        }
1951

1952
        vscList := &snapshotv1.VolumeSnapshotClassList{}
×
1953
        if err := c.List(ctx, vscList); err != nil {
×
1954
                if meta.IsNoMatchError(err) {
×
1955
                        return nil, nil
×
1956
                }
×
1957
                return nil, err
×
1958
        }
1959

1960
        var candidates []string
×
1961
        for _, vsc := range vscList.Items {
×
1962
                if vsc.Driver == driver {
×
1963
                        if vsc.Annotations[AnnDefaultSnapshotClass] == "true" {
×
1964
                                logEvent(MessageDefaultVolumeSnapshotClassSelected, vsc.Name)
×
1965
                                vscName := vsc.Name
×
1966
                                return &vscName, nil
×
1967
                        }
×
1968
                        candidates = append(candidates, vsc.Name)
×
1969
                }
1970
        }
1971

1972
        if len(candidates) > 0 {
×
1973
                sort.Strings(candidates)
×
1974
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
×
1975
                return &candidates[0], nil
×
1976
        }
×
1977

1978
        return nil, nil
×
1979
}
1980

1981
// isCsiCrdsDeployed checks whether the CSI snapshotter CRD are deployed
1982
func isCsiCrdsDeployed(c client.Client, log logr.Logger) bool {
×
1983
        version := "v1"
×
1984
        vsClass := "volumesnapshotclasses." + snapshotv1.GroupName
×
1985
        vsContent := "volumesnapshotcontents." + snapshotv1.GroupName
×
1986
        vs := "volumesnapshots." + snapshotv1.GroupName
×
1987

×
1988
        return isCrdDeployed(c, vsClass, version, log) &&
×
1989
                isCrdDeployed(c, vsContent, version, log) &&
×
1990
                isCrdDeployed(c, vs, version, log)
×
1991
}
×
1992

1993
// isCrdDeployed checks whether a CRD is deployed
1994
func isCrdDeployed(c client.Client, name, version string, log logr.Logger) bool {
×
1995
        crd := &extv1.CustomResourceDefinition{}
×
1996
        err := c.Get(context.TODO(), types.NamespacedName{Name: name}, crd)
×
1997
        if err != nil {
×
1998
                if !k8serrors.IsNotFound(err) {
×
1999
                        log.Info("Error looking up CRD", "crd name", name, "version", version, "error", err)
×
2000
                }
×
2001
                return false
×
2002
        }
2003

2004
        for _, v := range crd.Spec.Versions {
×
2005
                if v.Name == version && v.Served {
×
2006
                        return true
×
2007
                }
×
2008
        }
2009

2010
        return false
×
2011
}
2012

2013
// IsSnapshotReady indicates if a volume snapshot is ready to be used
2014
func IsSnapshotReady(snapshot *snapshotv1.VolumeSnapshot) bool {
×
2015
        return snapshot.Status != nil && snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse
×
2016
}
×
2017

2018
// GetResource updates given obj with the data of the object with the same name and namespace
2019
func GetResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
×
2020
        obj.SetNamespace(namespace)
×
2021
        obj.SetName(name)
×
2022

×
2023
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
2024
        if err != nil {
×
2025
                if k8serrors.IsNotFound(err) {
×
2026
                        return false, nil
×
2027
                }
×
2028

2029
                return false, err
×
2030
        }
2031

2032
        return true, nil
×
2033
}
2034

2035
// PatchArgs are the args for Patch
2036
type PatchArgs struct {
2037
        Client client.Client
2038
        Log    logr.Logger
2039
        Obj    client.Object
2040
        OldObj client.Object
2041
}
2042

2043
// GetAnnotatedEventSource returns resource referenced by AnnEventSource annotations
2044
func GetAnnotatedEventSource(ctx context.Context, c client.Client, obj client.Object) (client.Object, error) {
×
2045
        esk, ok := obj.GetAnnotations()[AnnEventSourceKind]
×
2046
        if !ok {
×
2047
                return obj, nil
×
2048
        }
×
2049
        if esk != "PersistentVolumeClaim" {
×
2050
                return obj, nil
×
2051
        }
×
2052
        es, ok := obj.GetAnnotations()[AnnEventSource]
×
2053
        if !ok {
×
2054
                return obj, nil
×
2055
        }
×
2056
        namespace, name, err := cache.SplitMetaNamespaceKey(es)
×
2057
        if err != nil {
×
2058
                return nil, err
×
2059
        }
×
2060
        pvc := &corev1.PersistentVolumeClaim{
×
2061
                ObjectMeta: metav1.ObjectMeta{
×
2062
                        Namespace: namespace,
×
2063
                        Name:      name,
×
2064
                },
×
2065
        }
×
2066
        if err := c.Get(ctx, client.ObjectKeyFromObject(pvc), pvc); err != nil {
×
2067
                return nil, err
×
2068
        }
×
2069
        return pvc, nil
×
2070
}
2071

2072
// OwnedByDataVolume returns true if the object is owned by a DataVolume
2073
func OwnedByDataVolume(obj metav1.Object) bool {
×
2074
        owner := metav1.GetControllerOf(obj)
×
2075
        return owner != nil && owner.Kind == "DataVolume"
×
2076
}
×
2077

2078
// CopyAllowedAnnotations copies the allowed annotations from the source object
2079
// to the destination object
2080
func CopyAllowedAnnotations(srcObj, dstObj metav1.Object) {
×
2081
        for ann, def := range allowedAnnotations {
×
2082
                val, ok := srcObj.GetAnnotations()[ann]
×
2083
                if !ok && def != "" {
×
2084
                        val = def
×
2085
                }
×
2086
                if val != "" {
×
2087
                        klog.V(1).Info("Applying annotation", "Name", dstObj.GetName(), ann, val)
×
2088
                        AddAnnotation(dstObj, ann, val)
×
2089
                }
×
2090
        }
2091
}
2092

2093
// CopyAllowedLabels copies allowed labels matching the validLabelsMatch regexp from the
2094
// source map to the destination object allowing overwrites
2095
func CopyAllowedLabels(srcLabels map[string]string, dstObj metav1.Object, overwrite bool) {
1✔
2096
        for label, value := range srcLabels {
2✔
2097
                if _, found := dstObj.GetLabels()[label]; (!found || overwrite) && validLabelsMatch.MatchString(label) {
2✔
2098
                        AddLabel(dstObj, label, value)
1✔
2099
                }
1✔
2100
        }
2101
}
2102

2103
// ClaimMayExistBeforeDataVolume returns true if the PVC may exist before the DataVolume
2104
func ClaimMayExistBeforeDataVolume(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2105
        if ClaimIsPopulatedForDataVolume(pvc, dv) {
×
2106
                return true, nil
×
2107
        }
×
2108
        return AllowClaimAdoption(c, pvc, dv)
×
2109
}
2110

2111
// ClaimIsPopulatedForDataVolume returns true if the PVC is populated for the given DataVolume
2112
func ClaimIsPopulatedForDataVolume(pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) bool {
×
2113
        return pvc != nil && dv != nil && pvc.Annotations[AnnPopulatedFor] == dv.Name
×
2114
}
×
2115

2116
// AllowClaimAdoption returns true if the PVC may be adopted
2117
func AllowClaimAdoption(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2118
        if pvc == nil || dv == nil {
×
2119
                return false, nil
×
2120
        }
×
2121
        anno, ok := pvc.Annotations[AnnCreatedForDataVolume]
×
2122
        if ok && anno == string(dv.UID) {
×
2123
                return false, nil
×
2124
        }
×
2125
        anno, ok = dv.Annotations[AnnAllowClaimAdoption]
×
2126
        // if annotation exists, go with that regardless of featuregate
×
2127
        if ok {
×
2128
                val, _ := strconv.ParseBool(anno)
×
2129
                return val, nil
×
2130
        }
×
2131
        return featuregates.NewFeatureGates(c).ClaimAdoptionEnabled()
×
2132
}
2133

2134
// ResolveDataSourceChain resolves a DataSource reference.
2135
// Returns an error if DataSource reference is not found or
2136
// DataSource reference points to another DataSource
2137
func ResolveDataSourceChain(ctx context.Context, client client.Client, dataSource *cdiv1.DataSource) (*cdiv1.DataSource, error) {
×
2138
        if dataSource.Spec.Source.DataSource == nil {
×
2139
                return dataSource, nil
×
2140
        }
×
2141

2142
        ref := dataSource.Spec.Source.DataSource
×
2143
        refNs := GetNamespace(ref.Namespace, dataSource.Namespace)
×
2144
        if dataSource.Namespace != refNs {
×
2145
                return dataSource, ErrDataSourceCrossNamespace
×
2146
        }
×
2147
        if ref.Name == dataSource.Name && refNs == dataSource.Namespace {
×
2148
                return nil, ErrDataSourceSelfReference
×
2149
        }
×
2150

2151
        resolved := &cdiv1.DataSource{}
×
2152
        if err := client.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: refNs}, resolved); err != nil {
×
2153
                return nil, err
×
2154
        }
×
2155

2156
        if resolved.Spec.Source.DataSource != nil {
×
2157
                return nil, ErrDataSourceMaxDepthReached
×
2158
        }
×
2159

2160
        return resolved, nil
×
2161
}
2162

2163
func sortEvents(events *corev1.EventList, usingPopulator bool, pvcPrimeName string) {
1✔
2164
        // Sort event lists by containing primeName substring and most recent timestamp
1✔
2165
        sort.Slice(events.Items, func(i, j int) bool {
2✔
2166
                if usingPopulator {
2✔
2167
                        firstContainsPrime := strings.Contains(events.Items[i].Message, pvcPrimeName)
1✔
2168
                        secondContainsPrime := strings.Contains(events.Items[j].Message, pvcPrimeName)
1✔
2169

1✔
2170
                        if firstContainsPrime && !secondContainsPrime {
2✔
2171
                                return true
1✔
2172
                        }
1✔
2173
                        if !firstContainsPrime && secondContainsPrime {
2✔
2174
                                return false
1✔
2175
                        }
1✔
2176
                }
2177

2178
                // if the timestamps are the same, prioritze longer messages to make sure our sorting is deterministic
2179
                if events.Items[i].LastTimestamp.Time.Equal(events.Items[j].LastTimestamp.Time) {
1✔
2180
                        return len(events.Items[i].Message) > len(events.Items[j].Message)
×
2181
                }
×
2182

2183
                // if both contains primeName substring or neither, just sort on timestamp
2184
                return events.Items[i].LastTimestamp.Time.After(events.Items[j].LastTimestamp.Time)
1✔
2185
        })
2186
}
2187

2188
// UpdatePVCBoundContionFromEvents updates the bound condition annotations on the PVC based on recent events
2189
// This function can be used by both controller and populator packages to update PVC bound condition information
2190
func UpdatePVCBoundContionFromEvents(pvc *corev1.PersistentVolumeClaim, c client.Client, log logr.Logger) error {
×
2191
        currentPvcCopy := pvc.DeepCopy()
×
2192

×
2193
        anno := pvc.GetAnnotations()
×
2194
        if anno == nil {
×
2195
                return nil
×
2196
        }
×
2197

2198
        if IsBound(pvc) {
×
2199
                anno := pvc.GetAnnotations()
×
2200
                delete(anno, AnnBoundCondition)
×
2201
                delete(anno, AnnBoundConditionReason)
×
2202
                delete(anno, AnnBoundConditionMessage)
×
2203

×
2204
                if !reflect.DeepEqual(currentPvcCopy, pvc) {
×
2205
                        patch := client.MergeFrom(currentPvcCopy)
×
2206
                        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2207
                                return err
×
2208
                        }
×
2209
                }
2210

2211
                return nil
×
2212
        }
2213

2214
        if pvc.Status.Phase != corev1.ClaimPending {
×
2215
                return nil
×
2216
        }
×
2217

2218
        // set bound condition by getting the latest event
2219
        events := &corev1.EventList{}
×
2220

×
2221
        err := c.List(context.TODO(), events,
×
2222
                client.InNamespace(pvc.GetNamespace()),
×
2223
                client.MatchingFields{"involvedObject.name": pvc.GetName(),
×
2224
                        "involvedObject.uid": string(pvc.GetUID())},
×
2225
        )
×
2226

×
2227
        if err != nil {
×
2228
                // Log the error but don't fail the reconciliation
×
2229
                log.Error(err, "Unable to list events for PVC bound condition update", "pvc", pvc.Name)
×
2230
                return nil
×
2231
        }
×
2232

2233
        if len(events.Items) == 0 {
×
2234
                return nil
×
2235
        }
×
2236

2237
        pvcPrime, usingPopulator := anno[AnnPVCPrimeName]
×
2238

×
2239
        // Sort event lists by containing primeName substring and most recent timestamp
×
2240
        sortEvents(events, usingPopulator, pvcPrime)
×
2241

×
2242
        boundMessage := ""
×
2243
        // check if prime name annotation exists
×
2244
        if usingPopulator {
×
2245
                // if we are using populators get the latest event from prime pvc
×
2246
                pvcPrime = fmt.Sprintf("[%s] : ", pvcPrime)
×
2247

×
2248
                // if the first event does not contain a prime message, none will so return
×
2249
                primeIdx := strings.Index(events.Items[0].Message, pvcPrime)
×
2250
                if primeIdx == -1 {
×
2251
                        log.V(1).Info("No bound message found, skipping bound condition update", "pvc", pvc.Name)
×
2252
                        return nil
×
2253
                }
×
2254
                boundMessage = events.Items[0].Message[primeIdx+len(pvcPrime):]
×
2255
        } else {
×
2256
                // if not using populators just get the latest event
×
2257
                boundMessage = events.Items[0].Message
×
2258
        }
×
2259

2260
        // since we checked status of phase above, we know this is pending
2261
        anno[AnnBoundCondition] = "false"
×
2262
        anno[AnnBoundConditionReason] = "Pending"
×
2263
        anno[AnnBoundConditionMessage] = boundMessage
×
2264

×
2265
        patch := client.MergeFrom(currentPvcCopy)
×
2266
        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2267
                return err
×
2268
        }
×
2269

2270
        return nil
×
2271
}
2272

2273
// CopyEvents gets srcPvc events and re-emits them on the target PVC with the src name prefix
NEW
2274
func CopyEvents(srcPVC, targetPVC client.Object, c client.Client, recorder record.EventRecorder) {
×
NEW
2275
        srcPrefixMsg := fmt.Sprintf("[%s] : ", srcPVC.GetName())
×
NEW
2276

×
NEW
2277
        newEvents := &corev1.EventList{}
×
NEW
2278
        err := c.List(context.TODO(), newEvents,
×
NEW
2279
                client.InNamespace(srcPVC.GetNamespace()),
×
NEW
2280
                client.MatchingFields{"involvedObject.name": srcPVC.GetName(),
×
NEW
2281
                        "involvedObject.uid": string(srcPVC.GetUID())},
×
NEW
2282
        )
×
NEW
2283

×
NEW
2284
        if err != nil {
×
NEW
2285
                klog.Error(err, "Could not retrieve srcPVC list of Events")
×
NEW
2286
        }
×
2287

NEW
2288
        currEvents := &corev1.EventList{}
×
NEW
2289
        err = c.List(context.TODO(), currEvents,
×
NEW
2290
                client.InNamespace(targetPVC.GetNamespace()),
×
NEW
2291
                client.MatchingFields{"involvedObject.name": targetPVC.GetName(),
×
NEW
2292
                        "involvedObject.uid": string(targetPVC.GetUID())},
×
NEW
2293
        )
×
NEW
2294

×
NEW
2295
        if err != nil {
×
NEW
2296
                klog.Error(err, "Could not retrieve targetPVC list of Events")
×
NEW
2297
        }
×
2298

2299
        // use this to hash each message for quick lookup, value is unused
NEW
2300
        eventMap := map[string]struct{}{}
×
NEW
2301

×
NEW
2302
        for _, event := range currEvents.Items {
×
NEW
2303
                eventMap[event.Message] = struct{}{}
×
NEW
2304
        }
×
2305

NEW
2306
        for _, newEvent := range newEvents.Items {
×
NEW
2307
                msg := newEvent.Message
×
NEW
2308

×
NEW
2309
                // check if target PVC already has this equivalent event
×
NEW
2310
                if _, exists := eventMap[msg]; exists {
×
NEW
2311
                        continue
×
2312
                }
2313

NEW
2314
                formattedMsg := srcPrefixMsg + msg
×
NEW
2315
                // check if we already emitted this event with the src prefix
×
NEW
2316
                if _, exists := eventMap[formattedMsg]; exists {
×
NEW
2317
                        continue
×
2318
                }
NEW
2319
                recorder.Event(targetPVC, newEvent.Type, newEvent.Reason, formattedMsg)
×
2320
        }
2321
}
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