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

kubevirt / containerized-data-importer / #5656

30 Oct 2025 02:23PM UTC coverage: 58.812% (-0.3%) from 59.076%
#5656

Pull #3938

travis-ci

Acedus
csv-generator: add -dump-network-policies option

This commit adds the -dump-network-policies optional flag to the
csv-generator tool in order to allow dumping CDI's required network
policies in case of a restrictive environment.

Signed-off-by: Adi Aloni <aaloni@redhat.com>
Pull Request #3938: Add network policies to CDI

31 of 193 new or added lines in 7 files covered. (16.06%)

426 existing lines in 5 files now uncovered.

17282 of 29385 relevant lines covered (58.81%)

0.65 hits per line

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

14.21
/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
        // AnnBackingFile provides a const for our PVC backing file annotation
185
        AnnBackingFile = AnnAPIGroup + "/storage.import.backingFile"
186
        // AnnThumbprint provides a const for our PVC backing thumbprint annotation
187
        AnnThumbprint = AnnAPIGroup + "/storage.import.vddk.thumbprint"
188
        // AnnExtraHeaders provides a const for our PVC extraHeaders annotation
189
        AnnExtraHeaders = AnnAPIGroup + "/storage.import.extraHeaders"
190
        // AnnSecretExtraHeaders provides a const for our PVC secretExtraHeaders annotation
191
        AnnSecretExtraHeaders = AnnAPIGroup + "/storage.import.secretExtraHeaders"
192
        // AnnRegistryImageArchitecture provides a const for our PVC registryImageArchitecture annotation
193
        AnnRegistryImageArchitecture = AnnAPIGroup + "/storage.import.registryImageArchitecture"
194

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

274
        cloneTokenLeeway = 10 * time.Second
275

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

382
        apiServerKeyOnce sync.Once
383
        apiServerKey     *rsa.PrivateKey
384

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

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

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

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

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

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

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

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

×
447
        tok, v := mtv.getTokenAndValidator(pvc)
×
448

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

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

463
        return validateTokenData(tokenData, vcs.Namespace, srcName, pvc.Namespace, pvc.Name, string(pvc.UID), tokenResourceName)
×
UNCOV
464
}
×
UNCOV
465

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

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

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

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

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

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

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

×
515
        if dv.Spec.Storage != nil {
×
516
                return dv.Spec.Storage.StorageClassName
517
        }
×
UNCOV
518

×
519
        return nil
×
520
}
UNCOV
521

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

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

×
540
        return storageClass, nil
541
}
UNCOV
542

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

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

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

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

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

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

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

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

1✔
604
        return &defaultClasses[0]
1✔
605
}
606

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

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

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

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

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

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

645
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
×
646

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

×
652
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
653
}
UNCOV
654

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

×
663
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
664
}
UNCOV
665

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

×
674
        return cdiconfig.Status.ImagePullSecrets, nil
×
675
}
UNCOV
676

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

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

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

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

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

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

776
        return pods, nil
777
}
UNCOV
778

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

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

×
790
        return &cr.Spec.Workloads, nil
×
791
}
UNCOV
792

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

×
UNCOV
800
        if len(crList.Items) == 0 {
×
801
                return nil, nil
802
        }
2✔
803

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

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

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

1✔
819
        return &activeResources[0], nil
1✔
820
}
821

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

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

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

×
844
        return cdiconfig.Status.Preallocation
×
845
}
UNCOV
846

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

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

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

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

×
870
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
871
}
UNCOV
872

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

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

×
886
        obj.SetFinalizers(finalizers)
887
}
UNCOV
888

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

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

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

×
910
        tokenResourceName := getTokenResourceNamePvc(source)
×
911
        srcName := getSourceNamePvc(source)
912

×
913
        return validateTokenData(tokenData, source.Namespace, srcName, target.Namespace, target.Name, string(target.UID), tokenResourceName)
×
UNCOV
914
}
×
UNCOV
915

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

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

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

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

×
938
        return validateTokenData(tokenData, sourceNamespace, sourceName, dv.Namespace, dv.Name, "", tokenResourceName)
×
939
}
UNCOV
940

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

×
948
        return ""
×
949
}
UNCOV
950

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

×
956
        return "persistentvolumeclaims"
×
957
}
UNCOV
958

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

×
966
        return sourcePvc.Name
967
}
UNCOV
968

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

×
981
        return nil
×
982
}
UNCOV
983

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

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

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

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

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

×
1036
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1037

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

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

×
1053
        return err
×
1054
}
UNCOV
1055

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

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

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

×
UNCOV
1102
// ValidateRequestedCloneSize validates the clone size requirements on block
×
1103
func ValidateRequestedCloneSize(sourceResources, targetResources corev1.VolumeResourceRequirements) error {
1104
        sourceRequest, hasSource := sourceResources.Requests[corev1.ResourceStorage]
1105
        targetRequest, hasTarget := targetResources.Requests[corev1.ResourceStorage]
×
1106
        if !hasSource || !hasTarget {
×
1107
                return errors.New("source/target missing storage resource requests")
×
1108
        }
×
UNCOV
1109

×
UNCOV
1110
        // Verify that the target PVC size is equal or larger than the source.
×
1111
        if sourceRequest.Value() > targetRequest.Value() {
1112
                return errors.Errorf("target resources requests storage size is smaller than the source %d < %d", targetRequest.Value(), sourceRequest.Value())
1113
        }
×
1114
        return nil
×
UNCOV
1115
}
×
UNCOV
1116

×
1117
// CreateCloneSourcePodName creates clone source pod name
1118
func CreateCloneSourcePodName(targetPvc *corev1.PersistentVolumeClaim) string {
1119
        return string(targetPvc.GetUID()) + common.ClonerSourcePodNameSuffix
1120
}
×
UNCOV
1121

×
UNCOV
1122
// IsPVCComplete returns true if a PVC is in 'Succeeded' phase, false if not
×
1123
func IsPVCComplete(pvc *corev1.PersistentVolumeClaim) bool {
1124
        if pvc != nil {
1125
                phase, exists := pvc.ObjectMeta.Annotations[AnnPodPhase]
×
1126
                return exists && (phase == string(corev1.PodSucceeded))
×
1127
        }
×
1128
        return false
×
UNCOV
1129
}
×
UNCOV
1130

×
1131
// IsMultiStageImportInProgress returns true when a PVC is being part of an ongoing multi-stage import
1132
func IsMultiStageImportInProgress(pvc *corev1.PersistentVolumeClaim) bool {
1133
        if pvc != nil {
1134
                multiStageImport := metav1.HasAnnotation(pvc.ObjectMeta, AnnCurrentCheckpoint)
×
1135
                multiStageAlreadyDone := metav1.HasAnnotation(pvc.ObjectMeta, AnnMultiStageImportDone)
×
1136
                return multiStageImport && !multiStageAlreadyDone
×
1137
        }
×
1138
        return false
×
UNCOV
1139
}
×
UNCOV
1140

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

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

×
1179
// SetNodeNameIfPopulator sets NodeName in a pod spec when the PVC is being handled by a CDI volume populator
1180
func SetNodeNameIfPopulator(pvc *corev1.PersistentVolumeClaim, podSpec *corev1.PodSpec) {
1181
        _, isPopulator := pvc.Annotations[AnnPopulatorKind]
1182
        nodeName := pvc.Annotations[AnnSelectedNode]
×
1183
        if isPopulator && nodeName != "" {
×
1184
                podSpec.NodeName = nodeName
×
1185
        }
×
UNCOV
1186
}
×
UNCOV
1187

×
1188
// CreatePvc creates PVC
1189
func CreatePvc(name, ns string, annotations, labels map[string]string) *corev1.PersistentVolumeClaim {
1190
        return CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1191
}
1✔
1192

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

1✔
1223
// GetAPIServerKey returns API server RSA key
1224
func GetAPIServerKey() *rsa.PrivateKey {
1225
        apiServerKeyOnce.Do(func() {
1226
                apiServerKey, _ = rsa.GenerateKey(rand.Reader, 2048)
×
1227
        })
×
1228
        return apiServerKey
×
UNCOV
1229
}
×
UNCOV
1230

×
1231
// CreateStorageClass creates storage class CR
1232
func CreateStorageClass(name string, annotations map[string]string) *storagev1.StorageClass {
1233
        return &storagev1.StorageClass{
1234
                ObjectMeta: metav1.ObjectMeta{
1✔
1235
                        Name:        name,
1✔
1236
                        Annotations: annotations,
1✔
1237
                },
1✔
1238
        }
1✔
1239
}
1✔
1240

1✔
1241
// CreateImporterTestPod creates importer test pod CR
1✔
1242
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
1243
        // importer pod name contains the pvc name
1244
        podName := fmt.Sprintf("%s-%s", common.ImporterPodName, pvc.Name)
×
1245

×
1246
        blockOwnerDeletion := true
×
1247
        isController := true
×
1248

×
1249
        volumes := []corev1.Volume{
×
1250
                {
×
1251
                        Name: dvname,
×
1252
                        VolumeSource: corev1.VolumeSource{
×
1253
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1254
                                        ClaimName: pvc.Name,
×
1255
                                        ReadOnly:  false,
×
1256
                                },
×
1257
                        },
×
1258
                },
×
1259
        }
×
1260

×
1261
        if scratchPvc != nil {
×
1262
                volumes = append(volumes, corev1.Volume{
×
1263
                        Name: ScratchVolName,
×
1264
                        VolumeSource: corev1.VolumeSource{
×
1265
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1266
                                        ClaimName: scratchPvc.Name,
×
1267
                                        ReadOnly:  false,
×
1268
                                },
×
1269
                        },
×
1270
                })
×
1271
        }
×
UNCOV
1272

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

×
1321
        ep, _ := GetEndpoint(pvc)
×
1322
        source := GetSource(pvc)
×
1323
        contentType := GetPVCContentType(pvc)
×
1324
        imageSize, _ := GetRequestedImageSize(pvc)
×
1325
        volumeMode := GetVolumeMode(pvc)
×
1326

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

×
1360
        if scratchPvc != nil {
×
1361
                pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
1362
                        Name:      ScratchVolName,
×
1363
                        MountPath: common.ScratchDataDir,
×
1364
                })
×
1365
        }
×
UNCOV
1366

×
1367
        return pod
×
1368
}
UNCOV
1369

×
1370
// CreateStorageClassWithProvisioner creates CR of storage class with provisioner
1371
func CreateStorageClassWithProvisioner(name string, annotations, labels map[string]string, provisioner string) *storagev1.StorageClass {
1372
        return &storagev1.StorageClass{
1373
                Provisioner: provisioner,
×
1374
                ObjectMeta: metav1.ObjectMeta{
×
1375
                        Name:        name,
×
1376
                        Annotations: annotations,
×
1377
                        Labels:      labels,
×
1378
                },
×
1379
        }
×
1380
}
×
UNCOV
1381

×
UNCOV
1382
// CreateClient creates a fake client
×
1383
func CreateClient(objs ...runtime.Object) client.Client {
1384
        s := scheme.Scheme
1385
        _ = cdiv1.AddToScheme(s)
1✔
1386
        _ = corev1.AddToScheme(s)
1✔
1387
        _ = storagev1.AddToScheme(s)
1✔
1388
        _ = ocpconfigv1.Install(s)
1✔
1389

1✔
1390
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1391
}
1✔
1392

1✔
1393
// ErrQuotaExceeded checked is the error is of exceeded quota
1✔
1394
func ErrQuotaExceeded(err error) bool {
1395
        return strings.Contains(err.Error(), "exceeded quota:")
1396
}
×
UNCOV
1397

×
UNCOV
1398
// GetContentType returns the content type. If invalid or not set, default to kubevirt
×
1399
func GetContentType(contentType cdiv1.DataVolumeContentType) cdiv1.DataVolumeContentType {
1400
        switch contentType {
1401
        case
1✔
1402
                cdiv1.DataVolumeKubeVirt,
1✔
1403
                cdiv1.DataVolumeArchive:
1404
        default:
1405
                // TODO - shouldn't archive be the default?
1✔
1406
                contentType = cdiv1.DataVolumeKubeVirt
×
UNCOV
1407
        }
×
UNCOV
1408
        return contentType
×
1409
}
1410

1✔
1411
// GetPVCContentType returns the content type of the source image. If invalid or not set, default to kubevirt
1412
func GetPVCContentType(pvc *corev1.PersistentVolumeClaim) cdiv1.DataVolumeContentType {
1413
        contentType, found := pvc.Annotations[AnnContentType]
1414
        if !found {
×
1415
                // TODO - shouldn't archive be the default?
×
1416
                return cdiv1.DataVolumeKubeVirt
×
1417
        }
×
UNCOV
1418

×
1419
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
×
1420
}
UNCOV
1421

×
1422
// GetNamespace returns the given namespace if not empty, otherwise the default namespace
1423
func GetNamespace(namespace, defaultNamespace string) string {
1424
        if namespace == "" {
1425
                return defaultNamespace
×
1426
        }
×
1427
        return namespace
×
UNCOV
1428
}
×
UNCOV
1429

×
1430
// IsErrCacheNotStarted checked is the error is of cache not started
1431
func IsErrCacheNotStarted(err error) bool {
1432
        target := &runtimecache.ErrCacheNotStarted{}
1433
        return errors.As(err, &target)
×
1434
}
×
UNCOV
1435

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

×
UNCOV
1459
// GetCloneSourceInfo returns the type, name and namespace of the cloning source
×
1460
func GetCloneSourceInfo(dv *cdiv1.DataVolume) (sourceType, sourceName, sourceNamespace string) {
1461
        // Cloning sources are mutually exclusive
1462
        if dv.Spec.Source.PVC != nil {
×
1463
                return "pvc", dv.Spec.Source.PVC.Name, dv.Spec.Source.PVC.Namespace
×
1464
        }
×
UNCOV
1465

×
1466
        if dv.Spec.Source.Snapshot != nil {
×
1467
                return "snapshot", dv.Spec.Source.Snapshot.Name, dv.Spec.Source.Snapshot.Namespace
1468
        }
×
UNCOV
1469

×
1470
        return "", "", ""
×
1471
}
UNCOV
1472

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

×
1483
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1484
}
UNCOV
1485

×
1486
// AddImmediateBindingAnnotationIfWFFCDisabled adds the immediateBinding annotation if wffc feature gate is disabled
1487
func AddImmediateBindingAnnotationIfWFFCDisabled(obj metav1.Object, gates featuregates.FeatureGates) error {
1488
        globalHonorWaitForFirstConsumer, err := gates.HonorWaitForFirstConsumerEnabled()
1489
        if err != nil {
×
1490
                return err
×
1491
        }
×
1492
        if !globalHonorWaitForFirstConsumer {
×
1493
                AddAnnotation(obj, AnnImmediateBinding, "")
×
1494
        }
×
1495
        return nil
×
UNCOV
1496
}
×
UNCOV
1497

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

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

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

×
1518
        return returnSize, nil
×
1519
}
UNCOV
1520

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
1782
        return nil
1✔
1783
}
1✔
UNCOV
1784

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

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

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

×
1807
        return nil
×
UNCOV
1808
}
×
1809

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

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

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

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

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

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

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

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

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

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

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

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

×
1928
        return nil, nil
×
UNCOV
1929
}
×
UNCOV
1930

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

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

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

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

×
1960
        return false
×
UNCOV
1961
}
×
UNCOV
1962

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

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

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

×
1979
                return false, err
×
UNCOV
1980
        }
×
UNCOV
1981

×
1982
        return true, nil
×
1983
}
UNCOV
1984

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

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

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

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

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

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

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

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

×
UNCOV
2084
// ResolveDataSourceChain resolves a DataSource reference.
×
UNCOV
2085
// Returns an error if DataSource reference is not found or
×
UNCOV
2086
// DataSource reference points to another DataSource
×
2087
func ResolveDataSourceChain(ctx context.Context, client client.Client, dataSource *cdiv1.DataSource) (*cdiv1.DataSource, error) {
2088
        if dataSource.Spec.Source.DataSource == nil {
2089
                return dataSource, nil
2090
        }
2091

2092
        ref := dataSource.Spec.Source.DataSource
×
2093
        refNs := GetNamespace(ref.Namespace, dataSource.Namespace)
×
2094
        if dataSource.Namespace != refNs {
×
2095
                return dataSource, ErrDataSourceCrossNamespace
×
2096
        }
2097
        if ref.Name == dataSource.Name && refNs == dataSource.Namespace {
×
2098
                return nil, ErrDataSourceSelfReference
×
2099
        }
×
UNCOV
2100

×
2101
        resolved := &cdiv1.DataSource{}
×
2102
        if err := client.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: refNs}, resolved); err != nil {
×
2103
                return nil, err
×
2104
        }
×
2105

2106
        if resolved.Spec.Source.DataSource != nil {
×
2107
                return nil, ErrDataSourceMaxDepthReached
×
2108
        }
×
UNCOV
2109

×
2110
        return resolved, nil
UNCOV
2111
}
×
UNCOV
2112

×
UNCOV
2113
func sortEvents(events *corev1.EventList, usingPopulator bool, pvcPrimeName string) {
×
2114
        // Sort event lists by containing primeName substring and most recent timestamp
UNCOV
2115
        sort.Slice(events.Items, func(i, j int) bool {
×
2116
                if usingPopulator {
2117
                        firstContainsPrime := strings.Contains(events.Items[i].Message, pvcPrimeName)
2118
                        secondContainsPrime := strings.Contains(events.Items[j].Message, pvcPrimeName)
1✔
2119

1✔
2120
                        if firstContainsPrime && !secondContainsPrime {
2✔
2121
                                return true
2✔
2122
                        }
1✔
2123
                        if !firstContainsPrime && secondContainsPrime {
1✔
2124
                                return false
1✔
2125
                        }
2✔
2126
                }
1✔
2127

1✔
2128
                // if the timestamps are the same, prioritze longer messages to make sure our sorting is deterministic
2✔
2129
                if events.Items[i].LastTimestamp.Time.Equal(events.Items[j].LastTimestamp.Time) {
1✔
2130
                        return len(events.Items[i].Message) > len(events.Items[j].Message)
1✔
2131
                }
2132

2133
                // if both contains primeName substring or neither, just sort on timestamp
2134
                return events.Items[i].LastTimestamp.Time.After(events.Items[j].LastTimestamp.Time)
1✔
UNCOV
2135
        })
×
UNCOV
2136
}
×
2137

2138
// UpdatePVCBoundContionFromEvents updates the bound condition annotations on the PVC based on recent events
2139
// This function can be used by both controller and populator packages to update PVC bound condition information
1✔
2140
func UpdatePVCBoundContionFromEvents(pvc *corev1.PersistentVolumeClaim, c client.Client, log logr.Logger) error {
2141
        currentPvcCopy := pvc.DeepCopy()
2142

2143
        anno := pvc.GetAnnotations()
2144
        if anno == nil {
2145
                return nil
×
2146
        }
×
UNCOV
2147

×
2148
        if IsBound(pvc) {
×
2149
                anno := pvc.GetAnnotations()
×
2150
                delete(anno, AnnBoundCondition)
×
2151
                delete(anno, AnnBoundConditionReason)
×
2152
                delete(anno, AnnBoundConditionMessage)
2153

×
2154
                if !reflect.DeepEqual(currentPvcCopy, pvc) {
×
2155
                        patch := client.MergeFrom(currentPvcCopy)
×
2156
                        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2157
                                return err
×
2158
                        }
×
UNCOV
2159
                }
×
UNCOV
2160

×
2161
                return nil
×
UNCOV
2162
        }
×
UNCOV
2163

×
2164
        if pvc.Status.Phase != corev1.ClaimPending {
2165
                return nil
2166
        }
×
2167

2168
        // set bound condition by getting the latest event
2169
        events := &corev1.EventList{}
×
2170

×
2171
        err := c.List(context.TODO(), events,
×
2172
                client.InNamespace(pvc.GetNamespace()),
2173
                client.MatchingFields{"involvedObject.name": pvc.GetName(),
2174
                        "involvedObject.uid": string(pvc.GetUID())},
×
2175
        )
×
2176

×
2177
        if err != nil {
×
2178
                // Log the error but don't fail the reconciliation
×
2179
                log.Error(err, "Unable to list events for PVC bound condition update", "pvc", pvc.Name)
×
2180
                return nil
×
2181
        }
×
UNCOV
2182

×
2183
        if len(events.Items) == 0 {
×
2184
                return nil
×
2185
        }
×
UNCOV
2186

×
2187
        pvcPrime, usingPopulator := anno[AnnPVCPrimeName]
2188

×
2189
        // Sort event lists by containing primeName substring and most recent timestamp
×
2190
        sortEvents(events, usingPopulator, pvcPrime)
×
2191

2192
        boundMessage := ""
×
2193
        // check if prime name annotation exists
×
2194
        if usingPopulator {
×
2195
                // if we are using populators get the latest event from prime pvc
×
2196
                pvcPrime = fmt.Sprintf("[%s] : ", pvcPrime)
×
2197

×
2198
                // if the first event does not contain a prime message, none will so return
×
2199
                primeIdx := strings.Index(events.Items[0].Message, pvcPrime)
×
2200
                if primeIdx == -1 {
×
2201
                        log.V(1).Info("No bound message found, skipping bound condition update", "pvc", pvc.Name)
×
2202
                        return nil
×
2203
                }
×
2204
                boundMessage = events.Items[0].Message[primeIdx+len(pvcPrime):]
×
2205
        } else {
×
2206
                // if not using populators just get the latest event
×
2207
                boundMessage = events.Items[0].Message
×
2208
        }
×
UNCOV
2209

×
UNCOV
2210
        // since we checked status of phase above, we know this is pending
×
2211
        anno[AnnBoundCondition] = "false"
×
2212
        anno[AnnBoundConditionReason] = "Pending"
×
2213
        anno[AnnBoundConditionMessage] = boundMessage
×
2214

2215
        patch := client.MergeFrom(currentPvcCopy)
2216
        if err := c.Patch(context.TODO(), pvc, patch); err != nil {
×
2217
                return err
×
2218
        }
×
UNCOV
2219

×
2220
        return nil
×
UNCOV
2221
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc