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

kubevirt / containerized-data-importer / #4693

30 May 2024 02:29PM UTC coverage: 58.427% (-0.05%) from 58.481%
#4693

push

travis-ci

web-flow
Enable Gosec linter (#3283)

* Move gosec into golangci-lint

Remove gosec target and scripts and use the golangci-lint linter
This ensures we stay up-to-date (so long as golangci-lint is up to date
too).

Signed-off-by: Edu Gómez Escandell <egomez@redhat.com>

* G601: Disable for-loop variable aliassing warning (not relevant fro Go>=1.22)

Signed-off-by: Edu Gómez Escandell <egomez@redhat.com>

* G101: Ignore warning about plain-text credentials

They are false positives

Signed-off-by: Edu Gómez Escandell <egomez@redhat.com>

* G102: Don't listen to all interfaces

Signed-off-by: Edu Gómez Escandell <egomez@redhat.com>

* G107: Ignore potentially tainted GET requests

They are all in test code

Signed-off-by: Edu Gómez Escandell <egomez@redhat.com>

* G109: Avoid integer overflows after parsing strings

Signed-off-by: Edu Gómez Escandell <egomez@redhat.com>

* G110: Potential DoS vulnerability via decompression bomb

Signed-off-by: Edu Gómez Escandell <egomez@redhat.com>

* G112: Potential Slowloris Attack because ReadHeaderTimeout is not configured

Signed-off-by: Edu Gómez Escandell <egomez@redhat.com>

* G114: Use of net/http serve function that has no support for setting timeouts

Signed-off-by: Edu Gómez Escandell <egomez@redhat.com>

* G204: Subprocess launched with a potential tainted input or cmd arguments

Signed-off-by: Edu Gómez Escandell <egomez@redhat.com>

* G305: File traversal when extracting zip/tar archive

Signed-off-by: Edu Gómez Escandell <egomez@redhat.com>

* G306: Expect WriteFile permissions to be 0600 or less

Signed-off-by: Edu Gómez Escandell <egomez@redhat.com>

* Bugfix: Misuse of file descriptor flags in file permission bits

os.WriteFile always uses O_WRONLY|O_CREATE|O_TRUNC, the third argument
is for the file's permission bits. This code is misleading, it will
truncate the file and not append to it. For that you'd need
os.Openfile(path, os.O_APPEND, 0600)

I also simplified the unnecessary []byte conversion.

Signed-... (continued)

20 of 55 new or added lines in 12 files covered. (36.36%)

9 existing lines in 2 files now uncovered.

16012 of 27405 relevant lines covered (58.43%)

0.65 hits per line

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

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

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

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

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

17
package common
18

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

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

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

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

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

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

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

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

98
        // AnnDeleteAfterCompletion is PVC annotation for deleting DV after completion
99
        AnnDeleteAfterCompletion = AnnAPIGroup + "/storage.deleteAfterCompletion"
100
        // AnnPodRetainAfterCompletion is PVC annotation for retaining transfer pods after completion
101
        AnnPodRetainAfterCompletion = AnnAPIGroup + "/storage.pod.retainAfterCompletion"
102

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

221
        // AnnDefaultStorageClass is the annotation indicating that a storage class is the default one
222
        AnnDefaultStorageClass = "storageclass.kubernetes.io/is-default-class"
223
        // AnnDefaultVirtStorageClass is the annotation indicating that a storage class is the default one for virtualization purposes
224
        AnnDefaultVirtStorageClass = "storageclass.kubevirt.io/is-default-virt-class"
225
        // AnnDefaultSnapshotClass is the annotation indicating that a snapshot class is the default one
226
        AnnDefaultSnapshotClass = "snapshot.storage.kubernetes.io/is-default-class"
227

228
        // AnnSourceVolumeMode is the volume mode of the source PVC specified as an annotation on snapshots
229
        AnnSourceVolumeMode = AnnAPIGroup + "/storage.import.sourceVolumeMode"
230

231
        // AnnOpenShiftImageLookup is the annotation for OpenShift image stream lookup
232
        AnnOpenShiftImageLookup = "alpha.image.policy.openshift.io/resolve-names"
233

234
        // AnnCloneRequest sets our expected annotation for a CloneRequest
235
        AnnCloneRequest = "k8s.io/CloneRequest"
236
        // AnnCloneOf is used to indicate that cloning was complete
237
        AnnCloneOf = "k8s.io/CloneOf"
238

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

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

255
        // AnnSelectedNode annotation is added to a PVC that has been triggered by scheduler to
256
        // be dynamically provisioned. Its value is the name of the selected node.
257
        AnnSelectedNode = "volume.kubernetes.io/selected-node"
258

259
        // AnnGarbageCollected is a PVC annotation indicating that the PVC was garbage collected
260
        AnnGarbageCollected = AnnAPIGroup + "/garbageCollected"
261

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

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

268
        // CloneComplete message
269
        CloneComplete = "Clone Complete"
270

271
        cloneTokenLeeway = 10 * time.Second
272

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

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

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

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

313
        // ClaimLost reason const
314
        ClaimLost = "ClaimLost"
315
        // NotFound reason const
316
        NotFound = "NotFound"
317

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

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

331
        // ProgressDone this means we are DONE
332
        ProgressDone = "100.0%"
333

334
        // AnnEventSourceKind is the source kind that should be related to events
335
        AnnEventSourceKind = AnnAPIGroup + "/events.source.kind"
336
        // AnnEventSource is the source that should be related to events (namespace/name)
337
        AnnEventSource = AnnAPIGroup + "/events.source"
338

339
        // AnnAllowClaimAdoption is the annotation that allows a claim to be adopted by a DataVolume
340
        AnnAllowClaimAdoption = AnnAPIGroup + "/allowClaimAdoption"
341

342
        // AnnCdiCustomizeComponentHash annotation is a hash of all customizations that live under spec.CustomizeComponents
343
        AnnCdiCustomizeComponentHash = AnnAPIGroup + "/customizer-identifier"
344

345
        // AnnCreatedForDataVolume stores the UID of the datavolume that the PVC was created for
346
        AnnCreatedForDataVolume = AnnAPIGroup + "/createdForDataVolume"
347
)
348

349
// Size-detection pod error codes
350
const (
351
        NoErr int = iota
352
        ErrBadArguments
353
        ErrInvalidFile
354
        ErrInvalidPath
355
        ErrBadTermFile
356
        ErrUnknown
357
)
358

359
var (
360
        // BlockMode is raw block device mode
361
        BlockMode = corev1.PersistentVolumeBlock
362
        // FilesystemMode is filesystem device mode
363
        FilesystemMode = corev1.PersistentVolumeFilesystem
364

365
        // DefaultInstanceTypeLabels is a list of currently supported default instance type labels
366
        DefaultInstanceTypeLabels = []string{
367
                LabelDefaultInstancetype,
368
                LabelDefaultInstancetypeKind,
369
                LabelDefaultPreference,
370
                LabelDefaultPreferenceKind,
371
        }
372

373
        apiServerKeyOnce sync.Once
374
        apiServerKey     *rsa.PrivateKey
375

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

387
// FakeValidator is a fake token validator
388
type FakeValidator struct {
389
        Match     string
390
        Operation token.Operation
391
        Name      string
392
        Namespace string
393
        Resource  metav1.GroupVersionResource
394
        Params    map[string]string
395
}
396

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

414
// MultiTokenValidator is a token validator that can validate both short and long tokens
415
type MultiTokenValidator struct {
416
        ShortTokenValidator token.Validator
417
        LongTokenValidator  token.Validator
418
}
419

420
// ValidatePVC validates a PVC
421
func (mtv *MultiTokenValidator) ValidatePVC(source, target *corev1.PersistentVolumeClaim) error {
×
422
        tok, v := mtv.getTokenAndValidator(target)
×
423
        return ValidateCloneTokenPVC(tok, v, source, target)
×
424
}
×
425

426
// ValidatePopulator valades a token for a populator
427
func (mtv *MultiTokenValidator) ValidatePopulator(vcs *cdiv1.VolumeCloneSource, pvc *corev1.PersistentVolumeClaim) error {
×
428
        if vcs.Namespace == pvc.Namespace {
×
429
                return nil
×
430
        }
×
431

432
        tok, v := mtv.getTokenAndValidator(pvc)
×
433

×
434
        tokenData, err := v.Validate(tok)
×
435
        if err != nil {
×
436
                return errors.Wrap(err, "error verifying token")
×
437
        }
×
438

439
        var tokenResourceName string
×
440
        switch vcs.Spec.Source.Kind {
×
441
        case "PersistentVolumeClaim":
×
442
                tokenResourceName = "persistentvolumeclaims"
×
443
        case "VolumeSnapshot":
×
444
                tokenResourceName = "volumesnapshots"
×
445
        }
446
        srcName := vcs.Spec.Source.Name
×
447

×
448
        return validateTokenData(tokenData, vcs.Namespace, srcName, pvc.Namespace, pvc.Name, string(pvc.UID), tokenResourceName)
×
449
}
450

451
func (mtv *MultiTokenValidator) getTokenAndValidator(pvc *corev1.PersistentVolumeClaim) (string, token.Validator) {
×
452
        v := mtv.LongTokenValidator
×
453
        tok, ok := pvc.Annotations[AnnExtendedCloneToken]
×
454
        if !ok {
×
455
                // if token doesn't exist, no prob for same namespace
×
456
                tok = pvc.Annotations[AnnCloneToken]
×
457
                v = mtv.ShortTokenValidator
×
458
        }
×
459
        return tok, v
×
460
}
461

462
// NewMultiTokenValidator returns a new multi token validator
463
func NewMultiTokenValidator(key *rsa.PublicKey) *MultiTokenValidator {
×
464
        return &MultiTokenValidator{
×
465
                ShortTokenValidator: NewCloneTokenValidator(common.CloneTokenIssuer, key),
×
466
                LongTokenValidator:  NewCloneTokenValidator(common.ExtendedCloneTokenIssuer, key),
×
467
        }
×
468
}
×
469

470
// NewCloneTokenValidator returns a new token validator
471
func NewCloneTokenValidator(issuer string, key *rsa.PublicKey) token.Validator {
×
472
        return token.NewValidator(issuer, key, cloneTokenLeeway)
×
473
}
×
474

475
// GetRequestedImageSize returns the PVC requested size
476
func GetRequestedImageSize(pvc *corev1.PersistentVolumeClaim) (string, error) {
1✔
477
        pvcSize, found := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
1✔
478
        if !found {
2✔
479
                return "", errors.Errorf("storage request is missing in pvc \"%s/%s\"", pvc.Namespace, pvc.Name)
1✔
480
        }
1✔
481
        return pvcSize.String(), nil
1✔
482
}
483

484
// GetVolumeMode returns the volumeMode from PVC handling default empty value
485
func GetVolumeMode(pvc *corev1.PersistentVolumeClaim) corev1.PersistentVolumeMode {
×
486
        return util.ResolveVolumeMode(pvc.Spec.VolumeMode)
×
487
}
×
488

489
// IsDataVolumeUsingDefaultStorageClass checks if the DataVolume is using the default StorageClass
490
func IsDataVolumeUsingDefaultStorageClass(dv *cdiv1.DataVolume) bool {
×
491
        return GetStorageClassFromDVSpec(dv) == nil
×
492
}
×
493

494
// GetStorageClassFromDVSpec returns the StorageClassName from DataVolume PVC or Storage spec
495
func GetStorageClassFromDVSpec(dv *cdiv1.DataVolume) *string {
×
496
        if dv.Spec.PVC != nil {
×
497
                return dv.Spec.PVC.StorageClassName
×
498
        }
×
499

500
        if dv.Spec.Storage != nil {
×
501
                return dv.Spec.Storage.StorageClassName
×
502
        }
×
503

504
        return nil
×
505
}
506

507
// getStorageClassByName looks up the storage class based on the name.
508
// If name is nil, it performs fallback to default according to the provided content type
509
// If no storage class is found, returns nil
510
func getStorageClassByName(ctx context.Context, client client.Client, name *string, contentType cdiv1.DataVolumeContentType) (*storagev1.StorageClass, error) {
1✔
511
        if name == nil {
2✔
512
                return getFallbackStorageClass(ctx, client, contentType)
1✔
513
        }
1✔
514

515
        // look up storage class by name
516
        storageClass := &storagev1.StorageClass{}
×
517
        if err := client.Get(ctx, types.NamespacedName{Name: *name}, storageClass); err != nil {
×
518
                if k8serrors.IsNotFound(err) {
×
519
                        return nil, nil
×
520
                }
×
521
                klog.V(3).Info("Unable to retrieve storage class", "storage class name", *name)
×
522
                return nil, errors.Errorf("unable to retrieve storage class %s", *name)
×
523
        }
524

525
        return storageClass, nil
×
526
}
527

528
// GetStorageClassByNameWithK8sFallback looks up the storage class based on the name
529
// If name is nil, it looks for the default k8s storage class storageclass.kubernetes.io/is-default-class
530
// If no storage class is found, returns nil
531
func GetStorageClassByNameWithK8sFallback(ctx context.Context, client client.Client, name *string) (*storagev1.StorageClass, error) {
1✔
532
        return getStorageClassByName(ctx, client, name, cdiv1.DataVolumeArchive)
1✔
533
}
1✔
534

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

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

553
        if GetContentType(contentType) == cdiv1.DataVolumeKubeVirt {
2✔
554
                if virtSc := GetPlatformDefaultStorageClass(storageClasses, AnnDefaultVirtStorageClass); virtSc != nil {
2✔
555
                        return virtSc, nil
1✔
556
                }
1✔
557
        }
558
        return GetPlatformDefaultStorageClass(storageClasses, AnnDefaultStorageClass), nil
1✔
559
}
560

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

1✔
565
        for _, storageClass := range storageClasses.Items {
2✔
566
                if storageClass.Annotations[defaultAnnotationKey] == "true" {
2✔
567
                        defaultClasses = append(defaultClasses, storageClass)
1✔
568
                }
1✔
569
        }
570

571
        if len(defaultClasses) == 0 {
2✔
572
                return nil
1✔
573
        }
1✔
574

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

589
        return &defaultClasses[0]
1✔
590
}
591

592
// GetFilesystemOverheadForStorageClass determines the filesystem overhead defined in CDIConfig for the storageClass.
593
func GetFilesystemOverheadForStorageClass(ctx context.Context, client client.Client, storageClassName *string) (cdiv1.Percent, error) {
×
594
        if storageClassName != nil && *storageClassName == "" {
×
595
                klog.V(3).Info("No storage class name passed")
×
596
                return "0", nil
×
597
        }
×
598

599
        cdiConfig := &cdiv1.CDIConfig{}
×
600
        if err := client.Get(ctx, types.NamespacedName{Name: common.ConfigName}, cdiConfig); err != nil {
×
601
                if k8serrors.IsNotFound(err) {
×
602
                        klog.V(1).Info("CDIConfig does not exist, pod will not start until it does")
×
603
                        return "0", nil
×
604
                }
×
605
                return "0", err
×
606
        }
607

608
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(ctx, client, storageClassName)
×
609
        if err != nil || targetStorageClass == nil {
×
610
                klog.V(3).Info("Storage class", storageClassName, "not found, trying default storage class")
×
611
                targetStorageClass, err = GetStorageClassByNameWithK8sFallback(ctx, client, nil)
×
612
                if err != nil {
×
613
                        klog.V(3).Info("No default storage class found, continuing with global overhead")
×
614
                        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
615
                }
×
616
        }
617

618
        if cdiConfig.Status.FilesystemOverhead == nil {
×
619
                klog.Errorf("CDIConfig filesystemOverhead used before config controller ran reconcile. Hopefully this only happens during unit testing.")
×
620
                return "0", nil
×
621
        }
×
622

623
        if targetStorageClass == nil {
×
624
                klog.V(3).Info("Storage class", storageClassName, "not found, continuing with global overhead")
×
625
                return cdiConfig.Status.FilesystemOverhead.Global, nil
×
626
        }
×
627

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

×
630
        perStorageConfig := cdiConfig.Status.FilesystemOverhead.StorageClass
×
631

×
632
        storageClassOverhead, found := perStorageConfig[targetStorageClass.GetName()]
×
633
        if found {
×
634
                return storageClassOverhead, nil
×
635
        }
×
636

637
        return cdiConfig.Status.FilesystemOverhead.Global, nil
×
638
}
639

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

648
        return cdiconfig.Status.DefaultPodResourceRequirements, nil
×
649
}
650

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

659
        return cdiconfig.Status.ImagePullSecrets, nil
×
660
}
661

662
// GetPodFromPvc determines the pod associated with the pvc passed in.
663
func GetPodFromPvc(c client.Client, namespace string, pvc *corev1.PersistentVolumeClaim) (*corev1.Pod, error) {
×
664
        l, _ := labels.Parse(common.PrometheusLabelKey)
×
665
        pods := &corev1.PodList{}
×
666
        listOptions := client.ListOptions{
×
667
                LabelSelector: l,
×
668
        }
×
669
        if err := c.List(context.TODO(), pods, &listOptions); err != nil {
×
670
                return nil, err
×
671
        }
×
672

673
        pvcUID := pvc.GetUID()
×
674
        for _, pod := range pods.Items {
×
675
                if ShouldIgnorePod(&pod, pvc) {
×
676
                        continue
×
677
                }
678
                for _, or := range pod.OwnerReferences {
×
679
                        if or.UID == pvcUID {
×
680
                                return &pod, nil
×
681
                        }
×
682
                }
683

684
                // TODO: check this
685
                val, exists := pod.Labels[CloneUniqueID]
×
686
                if exists && val == string(pvcUID)+common.ClonerSourcePodNameSuffix {
×
687
                        return &pod, nil
×
688
                }
×
689
        }
690
        return nil, errors.Errorf("Unable to find pod owned by UID: %s, in namespace: %s", string(pvcUID), namespace)
×
691
}
692

693
// AddVolumeDevices returns VolumeDevice slice with one block device for pods using PV with block volume mode
694
func AddVolumeDevices() []corev1.VolumeDevice {
×
695
        volumeDevices := []corev1.VolumeDevice{
×
696
                {
×
697
                        Name:       DataVolName,
×
698
                        DevicePath: common.WriteBlockPath,
×
699
                },
×
700
        }
×
701
        return volumeDevices
×
702
}
×
703

704
// GetPodsUsingPVCs returns Pods currently using PVCs
705
func GetPodsUsingPVCs(ctx context.Context, c client.Client, namespace string, names sets.Set[string], allowReadOnly bool) ([]corev1.Pod, error) {
×
706
        pl := &corev1.PodList{}
×
707
        // hopefully using cached client here
×
708
        err := c.List(ctx, pl, &client.ListOptions{Namespace: namespace})
×
709
        if err != nil {
×
710
                return nil, err
×
711
        }
×
712

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

761
        return pods, nil
×
762
}
763

764
// GetWorkloadNodePlacement extracts the workload-specific nodeplacement values from the CDI CR
765
func GetWorkloadNodePlacement(ctx context.Context, c client.Client) (*sdkapi.NodePlacement, error) {
×
766
        cr, err := GetActiveCDI(ctx, c)
×
767
        if err != nil {
×
768
                return nil, err
×
769
        }
×
770

771
        if cr == nil {
×
772
                return nil, fmt.Errorf("no active CDI")
×
773
        }
×
774

775
        return &cr.Spec.Workloads, nil
×
776
}
777

778
// GetActiveCDI returns the active CDI CR
779
func GetActiveCDI(ctx context.Context, c client.Client) (*cdiv1.CDI, error) {
1✔
780
        crList := &cdiv1.CDIList{}
1✔
781
        if err := c.List(ctx, crList, &client.ListOptions{}); err != nil {
1✔
782
                return nil, err
×
783
        }
×
784

785
        if len(crList.Items) == 0 {
2✔
786
                return nil, nil
1✔
787
        }
1✔
788

789
        if len(crList.Items) == 1 {
2✔
790
                return &crList.Items[0], nil
1✔
791
        }
1✔
792

793
        var activeResources []cdiv1.CDI
1✔
794
        for _, cr := range crList.Items {
2✔
795
                if cr.Status.Phase != sdkapi.PhaseError {
2✔
796
                        activeResources = append(activeResources, cr)
1✔
797
                }
1✔
798
        }
799

800
        if len(activeResources) != 1 {
2✔
801
                return nil, fmt.Errorf("invalid number of active CDI resources: %d", len(activeResources))
1✔
802
        }
1✔
803

804
        return &activeResources[0], nil
1✔
805
}
806

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

816
// GetPreallocation returns the preallocation setting for the specified object (DV or VolumeImportSource), falling back to StorageClass and global setting (in this order)
817
func GetPreallocation(ctx context.Context, client client.Client, preallocation *bool) bool {
×
818
        // First, the DV's preallocation
×
819
        if preallocation != nil {
×
820
                return *preallocation
×
821
        }
×
822

823
        cdiconfig := &cdiv1.CDIConfig{}
×
824
        if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, cdiconfig); err != nil {
×
825
                klog.Errorf("Unable to find CDI configuration, %v\n", err)
×
826
                return defaultPreallocation
×
827
        }
×
828

829
        return cdiconfig.Status.Preallocation
×
830
}
831

832
// ImmediateBindingRequested returns if an object has the ImmediateBinding annotation
833
func ImmediateBindingRequested(obj metav1.Object) bool {
×
834
        _, isImmediateBindingRequested := obj.GetAnnotations()[AnnImmediateBinding]
×
835
        return isImmediateBindingRequested
×
836
}
×
837

838
// GetPriorityClass gets PVC priority class
839
func GetPriorityClass(pvc *corev1.PersistentVolumeClaim) string {
×
840
        anno := pvc.GetAnnotations()
×
841
        return anno[AnnPriorityClassName]
×
842
}
×
843

844
// ShouldDeletePod returns whether the PVC workload pod should be deleted
845
func ShouldDeletePod(pvc *corev1.PersistentVolumeClaim) bool {
×
846
        return pvc.GetAnnotations()[AnnPodRetainAfterCompletion] != "true" || pvc.GetAnnotations()[AnnRequiresScratch] == "true" || pvc.GetAnnotations()[AnnRequiresDirectIO] == "true" || pvc.DeletionTimestamp != nil
×
847
}
×
848

849
// AddFinalizer adds a finalizer to a resource
850
func AddFinalizer(obj metav1.Object, name string) {
×
851
        if HasFinalizer(obj, name) {
×
852
                return
×
853
        }
×
854

855
        obj.SetFinalizers(append(obj.GetFinalizers(), name))
×
856
}
857

858
// RemoveFinalizer removes a finalizer from a resource
859
func RemoveFinalizer(obj metav1.Object, name string) {
×
860
        if !HasFinalizer(obj, name) {
×
861
                return
×
862
        }
×
863

864
        var finalizers []string
×
865
        for _, f := range obj.GetFinalizers() {
×
866
                if f != name {
×
867
                        finalizers = append(finalizers, f)
×
868
                }
×
869
        }
870

871
        obj.SetFinalizers(finalizers)
×
872
}
873

874
// HasFinalizer returns true if a resource has a specific finalizer
875
func HasFinalizer(object metav1.Object, value string) bool {
×
876
        for _, f := range object.GetFinalizers() {
×
877
                if f == value {
×
878
                        return true
×
879
                }
×
880
        }
881
        return false
×
882
}
883

884
// ValidateCloneTokenPVC validates clone token for source and target PVCs
885
func ValidateCloneTokenPVC(t string, v token.Validator, source, target *corev1.PersistentVolumeClaim) error {
×
886
        if source.Namespace == target.Namespace {
×
887
                return nil
×
888
        }
×
889

890
        tokenData, err := v.Validate(t)
×
891
        if err != nil {
×
892
                return errors.Wrap(err, "error verifying token")
×
893
        }
×
894

895
        tokenResourceName := getTokenResourceNamePvc(source)
×
896
        srcName := getSourceNamePvc(source)
×
897

×
898
        return validateTokenData(tokenData, source.Namespace, srcName, target.Namespace, target.Name, string(target.UID), tokenResourceName)
×
899
}
900

901
// ValidateCloneTokenDV validates clone token for DV
902
func ValidateCloneTokenDV(validator token.Validator, dv *cdiv1.DataVolume) error {
×
903
        _, sourceName, sourceNamespace := GetCloneSourceInfo(dv)
×
904
        if sourceNamespace == "" || sourceNamespace == dv.Namespace {
×
905
                return nil
×
906
        }
×
907

908
        tok, ok := dv.Annotations[AnnCloneToken]
×
909
        if !ok {
×
910
                return errors.New("clone token missing")
×
911
        }
×
912

913
        tokenData, err := validator.Validate(tok)
×
914
        if err != nil {
×
915
                return errors.Wrap(err, "error verifying token")
×
916
        }
×
917

918
        tokenResourceName := getTokenResourceNameDataVolume(dv.Spec.Source)
×
919
        if tokenResourceName == "" {
×
920
                return errors.New("token resource name empty, can't verify properly")
×
921
        }
×
922

923
        return validateTokenData(tokenData, sourceNamespace, sourceName, dv.Namespace, dv.Name, "", tokenResourceName)
×
924
}
925

926
func getTokenResourceNameDataVolume(source *cdiv1.DataVolumeSource) string {
×
927
        if source.PVC != nil {
×
928
                return "persistentvolumeclaims"
×
929
        } else if source.Snapshot != nil {
×
930
                return "volumesnapshots"
×
931
        }
×
932

933
        return ""
×
934
}
935

936
func getTokenResourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
937
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
×
938
                return "volumesnapshots"
×
939
        }
×
940

941
        return "persistentvolumeclaims"
×
942
}
943

944
func getSourceNamePvc(sourcePvc *corev1.PersistentVolumeClaim) string {
×
945
        if v, ok := sourcePvc.Labels[common.CDIComponentLabel]; ok && v == common.CloneFromSnapshotFallbackPVCCDILabel {
×
946
                if sourcePvc.Spec.DataSourceRef != nil {
×
947
                        return sourcePvc.Spec.DataSourceRef.Name
×
948
                }
×
949
        }
950

951
        return sourcePvc.Name
×
952
}
953

954
func validateTokenData(tokenData *token.Payload, srcNamespace, srcName, targetNamespace, targetName, targetUID, tokenResourceName string) error {
×
955
        uid := tokenData.Params["uid"]
×
956
        if tokenData.Operation != token.OperationClone ||
×
957
                tokenData.Name != srcName ||
×
958
                tokenData.Namespace != srcNamespace ||
×
959
                tokenData.Resource.Resource != tokenResourceName ||
×
960
                tokenData.Params["targetNamespace"] != targetNamespace ||
×
961
                tokenData.Params["targetName"] != targetName ||
×
962
                (uid != "" && uid != targetUID) {
×
963
                return errors.New("invalid token")
×
964
        }
×
965

966
        return nil
×
967
}
968

969
// validateContentTypes compares the content type of a clone DV against its source PVC's one
970
func validateContentTypes(sourcePVC *corev1.PersistentVolumeClaim, spec *cdiv1.DataVolumeSpec) (bool, cdiv1.DataVolumeContentType, cdiv1.DataVolumeContentType) {
1✔
971
        sourceContentType := GetPVCContentType(sourcePVC)
1✔
972
        targetContentType := spec.ContentType
1✔
973
        if targetContentType == "" {
2✔
974
                targetContentType = cdiv1.DataVolumeKubeVirt
1✔
975
        }
1✔
976
        return sourceContentType == targetContentType, sourceContentType, targetContentType
1✔
977
}
978

979
// ValidateClone compares a clone spec against its source PVC to validate its creation
980
func ValidateClone(sourcePVC *corev1.PersistentVolumeClaim, spec *cdiv1.DataVolumeSpec) error {
×
981
        var targetResources corev1.ResourceRequirements
×
982

×
983
        valid, sourceContentType, targetContentType := validateContentTypes(sourcePVC, spec)
×
984
        if !valid {
×
985
                msg := fmt.Sprintf("Source contentType (%s) and target contentType (%s) do not match", sourceContentType, targetContentType)
×
986
                return errors.New(msg)
×
987
        }
×
988

989
        isSizelessClone := false
×
990
        explicitPvcRequest := spec.PVC != nil
×
991
        if explicitPvcRequest {
×
992
                targetResources = spec.PVC.Resources
×
993
        } else {
×
994
                targetResources = spec.Storage.Resources
×
995
                // The storage size in the target DV can be empty
×
996
                // when cloning using the 'Storage' API
×
997
                if _, ok := targetResources.Requests[corev1.ResourceStorage]; !ok {
×
998
                        isSizelessClone = true
×
999
                }
×
1000
        }
1001

1002
        // TODO: Spec.Storage API needs a better more complex check to validate clone size - to account for fsOverhead
1003
        // simple size comparison will not work here
1004
        if (!isSizelessClone && GetVolumeMode(sourcePVC) == corev1.PersistentVolumeBlock) || explicitPvcRequest {
×
1005
                if err := ValidateRequestedCloneSize(sourcePVC.Spec.Resources, targetResources); err != nil {
×
1006
                        return err
×
1007
                }
×
1008
        }
1009

1010
        return nil
×
1011
}
1012

1013
// ValidateSnapshotClone compares a snapshot clone spec against its source snapshot to validate its creation
1014
func ValidateSnapshotClone(sourceSnapshot *snapshotv1.VolumeSnapshot, spec *cdiv1.DataVolumeSpec) error {
×
1015
        var sourceResources, targetResources corev1.ResourceRequirements
×
1016

×
1017
        if sourceSnapshot.Status == nil {
×
1018
                return fmt.Errorf("no status on source snapshot, not possible to proceed")
×
1019
        }
×
1020
        size := sourceSnapshot.Status.RestoreSize
×
1021
        restoreSizeAvailable := size != nil && size.Sign() > 0
×
1022
        if restoreSizeAvailable {
×
1023
                sourceResources.Requests = corev1.ResourceList{corev1.ResourceStorage: *size}
×
1024
        }
×
1025

1026
        isSizelessClone := false
×
1027
        explicitPvcRequest := spec.PVC != nil
×
1028
        if explicitPvcRequest {
×
1029
                targetResources = spec.PVC.Resources
×
1030
        } else {
×
1031
                targetResources = spec.Storage.Resources
×
1032
                if _, ok := targetResources.Requests["storage"]; !ok {
×
1033
                        isSizelessClone = true
×
1034
                }
×
1035
        }
1036

1037
        if !isSizelessClone && restoreSizeAvailable {
×
1038
                // Sizes available, make sure user picked something bigger than minimal
×
1039
                if err := ValidateRequestedCloneSize(sourceResources, targetResources); err != nil {
×
1040
                        return err
×
1041
                }
×
1042
        } else if isSizelessClone && !restoreSizeAvailable {
×
1043
                return fmt.Errorf("size not specified by user/provisioner, can't tell how much needed for restore")
×
1044
        }
×
1045

1046
        return nil
×
1047
}
1048

1049
// AddAnnotation adds an annotation to an object
1050
func AddAnnotation(obj metav1.Object, key, value string) {
1✔
1051
        if obj.GetAnnotations() == nil {
2✔
1052
                obj.SetAnnotations(make(map[string]string))
1✔
1053
        }
1✔
1054
        obj.GetAnnotations()[key] = value
1✔
1055
}
1056

1057
// AddLabel adds a label to an object
1058
func AddLabel(obj metav1.Object, key, value string) {
×
1059
        if obj.GetLabels() == nil {
×
1060
                obj.SetLabels(make(map[string]string))
×
1061
        }
×
1062
        obj.GetLabels()[key] = value
×
1063
}
1064

1065
// HandleFailedPod handles pod-creation errors and updates the pod's PVC without providing sensitive information
1066
func HandleFailedPod(err error, podName string, pvc *corev1.PersistentVolumeClaim, recorder record.EventRecorder, c client.Client) error {
×
1067
        if err == nil {
×
1068
                return nil
×
1069
        }
×
1070
        // Generic reason and msg to avoid providing sensitive information
1071
        reason := ErrStartingPod
×
1072
        msg := fmt.Sprintf(MessageErrStartingPod, podName)
×
1073

×
1074
        // Error handling to fine-tune the event with pertinent info
×
1075
        if ErrQuotaExceeded(err) {
×
1076
                reason = ErrExceededQuota
×
1077
        }
×
1078

1079
        recorder.Event(pvc, corev1.EventTypeWarning, reason, msg)
×
1080

×
1081
        if isCloneSourcePod := CreateCloneSourcePodName(pvc) == podName; isCloneSourcePod {
×
1082
                AddAnnotation(pvc, AnnSourceRunningCondition, "false")
×
1083
                AddAnnotation(pvc, AnnSourceRunningConditionReason, reason)
×
1084
                AddAnnotation(pvc, AnnSourceRunningConditionMessage, msg)
×
1085
        } else {
×
1086
                AddAnnotation(pvc, AnnRunningCondition, "false")
×
1087
                AddAnnotation(pvc, AnnRunningConditionReason, reason)
×
1088
                AddAnnotation(pvc, AnnRunningConditionMessage, msg)
×
1089
        }
×
1090

1091
        AddAnnotation(pvc, AnnPodPhase, string(corev1.PodFailed))
×
1092
        if err := c.Update(context.TODO(), pvc); err != nil {
×
1093
                return err
×
1094
        }
×
1095

1096
        return err
×
1097
}
1098

1099
// GetSource returns the source string which determines the type of source. If no source or invalid source found, default to http
1100
func GetSource(pvc *corev1.PersistentVolumeClaim) string {
×
1101
        source, found := pvc.Annotations[AnnSource]
×
1102
        if !found {
×
1103
                source = ""
×
1104
        }
×
1105
        switch source {
×
1106
        case
1107
                SourceHTTP,
1108
                SourceS3,
1109
                SourceGCS,
1110
                SourceGlance,
1111
                SourceNone,
1112
                SourceRegistry,
1113
                SourceImageio,
1114
                SourceVDDK:
×
1115
        default:
×
1116
                source = SourceHTTP
×
1117
        }
1118
        return source
×
1119
}
1120

1121
// GetEndpoint returns the endpoint string which contains the full path URI of the target object to be copied.
1122
func GetEndpoint(pvc *corev1.PersistentVolumeClaim) (string, error) {
×
1123
        ep, found := pvc.Annotations[AnnEndpoint]
×
1124
        if !found || ep == "" {
×
1125
                verb := "empty"
×
1126
                if !found {
×
1127
                        verb = "missing"
×
1128
                }
×
1129
                return ep, errors.Errorf("annotation %q in pvc \"%s/%s\" is %s", AnnEndpoint, pvc.Namespace, pvc.Name, verb)
×
1130
        }
1131
        return ep, nil
×
1132
}
1133

1134
// AddImportVolumeMounts is being called for pods using PV with filesystem volume mode
1135
func AddImportVolumeMounts() []corev1.VolumeMount {
×
1136
        volumeMounts := []corev1.VolumeMount{
×
1137
                {
×
1138
                        Name:      DataVolName,
×
1139
                        MountPath: common.ImporterDataDir,
×
1140
                },
×
1141
        }
×
1142
        return volumeMounts
×
1143
}
×
1144

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

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

1160
// CreateCloneSourcePodName creates clone source pod name
1161
func CreateCloneSourcePodName(targetPvc *corev1.PersistentVolumeClaim) string {
×
1162
        return string(targetPvc.GetUID()) + common.ClonerSourcePodNameSuffix
×
1163
}
×
1164

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

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

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

1210
        if hasVolumeMounts {
×
1211
                if podSpec.SecurityContext == nil {
×
1212
                        podSpec.SecurityContext = &corev1.PodSecurityContext{}
×
1213
                }
×
1214
                podSpec.SecurityContext.FSGroup = ptr.To[int64](common.QemuSubGid)
×
1215
        }
1216
}
1217

1218
// SetNodeNameIfPopulator sets NodeName in a pod spec when the PVC is being handled by a CDI volume populator
1219
func SetNodeNameIfPopulator(pvc *corev1.PersistentVolumeClaim, podSpec *corev1.PodSpec) {
×
1220
        _, isPopulator := pvc.Annotations[AnnPopulatorKind]
×
1221
        nodeName := pvc.Annotations[AnnSelectedNode]
×
1222
        if isPopulator && nodeName != "" {
×
1223
                podSpec.NodeName = nodeName
×
1224
        }
×
1225
}
1226

1227
// CreatePvc creates PVC
1228
func CreatePvc(name, ns string, annotations, labels map[string]string) *corev1.PersistentVolumeClaim {
1✔
1229
        return CreatePvcInStorageClass(name, ns, nil, annotations, labels, corev1.ClaimBound)
1✔
1230
}
1✔
1231

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

1262
// GetAPIServerKey returns API server RSA key
1263
func GetAPIServerKey() *rsa.PrivateKey {
×
1264
        apiServerKeyOnce.Do(func() {
×
1265
                apiServerKey, _ = rsa.GenerateKey(rand.Reader, 2048)
×
1266
        })
×
1267
        return apiServerKey
×
1268
}
1269

1270
// CreateStorageClass creates storage class CR
1271
func CreateStorageClass(name string, annotations map[string]string) *storagev1.StorageClass {
1✔
1272
        return &storagev1.StorageClass{
1✔
1273
                ObjectMeta: metav1.ObjectMeta{
1✔
1274
                        Name:        name,
1✔
1275
                        Annotations: annotations,
1✔
1276
                },
1✔
1277
        }
1✔
1278
}
1✔
1279

1280
// CreateImporterTestPod creates importer test pod CR
1281
func CreateImporterTestPod(pvc *corev1.PersistentVolumeClaim, dvname string, scratchPvc *corev1.PersistentVolumeClaim) *corev1.Pod {
×
1282
        // importer pod name contains the pvc name
×
1283
        podName := fmt.Sprintf("%s-%s", common.ImporterPodName, pvc.Name)
×
1284

×
1285
        blockOwnerDeletion := true
×
1286
        isController := true
×
1287

×
1288
        volumes := []corev1.Volume{
×
1289
                {
×
1290
                        Name: dvname,
×
1291
                        VolumeSource: corev1.VolumeSource{
×
1292
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1293
                                        ClaimName: pvc.Name,
×
1294
                                        ReadOnly:  false,
×
1295
                                },
×
1296
                        },
×
1297
                },
×
1298
        }
×
1299

×
1300
        if scratchPvc != nil {
×
1301
                volumes = append(volumes, corev1.Volume{
×
1302
                        Name: ScratchVolName,
×
1303
                        VolumeSource: corev1.VolumeSource{
×
1304
                                PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
×
1305
                                        ClaimName: scratchPvc.Name,
×
1306
                                        ReadOnly:  false,
×
1307
                                },
×
1308
                        },
×
1309
                })
×
1310
        }
×
1311

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

×
1360
        ep, _ := GetEndpoint(pvc)
×
1361
        source := GetSource(pvc)
×
1362
        contentType := GetPVCContentType(pvc)
×
1363
        imageSize, _ := GetRequestedImageSize(pvc)
×
1364
        volumeMode := GetVolumeMode(pvc)
×
1365

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

1399
        if scratchPvc != nil {
×
1400
                pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
×
1401
                        Name:      ScratchVolName,
×
1402
                        MountPath: common.ScratchDataDir,
×
1403
                })
×
1404
        }
×
1405

1406
        return pod
×
1407
}
1408

1409
// CreateStorageClassWithProvisioner creates CR of storage class with provisioner
1410
func CreateStorageClassWithProvisioner(name string, annotations, labels map[string]string, provisioner string) *storagev1.StorageClass {
×
1411
        return &storagev1.StorageClass{
×
1412
                Provisioner: provisioner,
×
1413
                ObjectMeta: metav1.ObjectMeta{
×
1414
                        Name:        name,
×
1415
                        Annotations: annotations,
×
1416
                        Labels:      labels,
×
1417
                },
×
1418
        }
×
1419
}
×
1420

1421
// CreateClient creates a fake client
1422
func CreateClient(objs ...runtime.Object) client.Client {
1✔
1423
        s := scheme.Scheme
1✔
1424
        _ = cdiv1.AddToScheme(s)
1✔
1425
        _ = corev1.AddToScheme(s)
1✔
1426
        _ = storagev1.AddToScheme(s)
1✔
1427
        _ = ocpconfigv1.Install(s)
1✔
1428

1✔
1429
        return fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
1✔
1430
}
1✔
1431

1432
// ErrQuotaExceeded checked is the error is of exceeded quota
1433
func ErrQuotaExceeded(err error) bool {
×
1434
        return strings.Contains(err.Error(), "exceeded quota:")
×
1435
}
×
1436

1437
// GetContentType returns the content type. If invalid or not set, default to kubevirt
1438
func GetContentType(contentType cdiv1.DataVolumeContentType) cdiv1.DataVolumeContentType {
1✔
1439
        switch contentType {
1✔
1440
        case
1441
                cdiv1.DataVolumeKubeVirt,
1442
                cdiv1.DataVolumeArchive:
1✔
1443
        default:
1✔
1444
                // TODO - shouldn't archive be the default?
1✔
1445
                contentType = cdiv1.DataVolumeKubeVirt
1✔
1446
        }
1447
        return contentType
1✔
1448
}
1449

1450
// GetPVCContentType returns the content type of the source image. If invalid or not set, default to kubevirt
1451
func GetPVCContentType(pvc *corev1.PersistentVolumeClaim) cdiv1.DataVolumeContentType {
1✔
1452
        contentType, found := pvc.Annotations[AnnContentType]
1✔
1453
        if !found {
1✔
1454
                // TODO - shouldn't archive be the default?
×
1455
                return cdiv1.DataVolumeKubeVirt
×
1456
        }
×
1457

1458
        return GetContentType(cdiv1.DataVolumeContentType(contentType))
1✔
1459
}
1460

1461
// GetNamespace returns the given namespace if not empty, otherwise the default namespace
1462
func GetNamespace(namespace, defaultNamespace string) string {
×
1463
        if namespace == "" {
×
1464
                return defaultNamespace
×
1465
        }
×
1466
        return namespace
×
1467
}
1468

1469
// IsErrCacheNotStarted checked is the error is of cache not started
1470
func IsErrCacheNotStarted(err error) bool {
×
1471
        target := &runtimecache.ErrCacheNotStarted{}
×
1472
        return errors.As(err, &target)
×
1473
}
×
1474

1475
// GetDataVolumeTTLSeconds gets the current DataVolume TTL in seconds if GC is enabled, or < 0 if GC is disabled
1476
// Garbage collection is disabled by default
1477
func GetDataVolumeTTLSeconds(config *cdiv1.CDIConfig) int32 {
×
1478
        const defaultDataVolumeTTLSeconds = -1
×
1479
        if config.Spec.DataVolumeTTLSeconds != nil {
×
1480
                return *config.Spec.DataVolumeTTLSeconds
×
1481
        }
×
1482
        return defaultDataVolumeTTLSeconds
×
1483
}
1484

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

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

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

1519
        return "", "", ""
×
1520
}
1521

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

1532
        return pvcHonorWaitForFirstConsumer && globalHonorWaitForFirstConsumer, nil
×
1533
}
1534

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

1547
// GetRequiredSpace calculates space required taking file system overhead into account
1548
func GetRequiredSpace(filesystemOverhead float64, requestedSpace int64) int64 {
×
1549
        // the `image` has to be aligned correctly, so the space requested has to be aligned to
×
1550
        // next value that is a multiple of a block size
×
1551
        alignedSize := util.RoundUp(requestedSpace, util.DefaultAlignBlockSize)
×
1552

×
1553
        // count overhead as a percentage of the whole/new size, including aligned image
×
1554
        // and the space required by filesystem metadata
×
1555
        spaceWithOverhead := int64(math.Ceil(float64(alignedSize) / (1 - filesystemOverhead)))
×
1556
        return spaceWithOverhead
×
1557
}
×
1558

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

×
1563
        if util.ResolveVolumeMode(pvcSpec.VolumeMode) == corev1.PersistentVolumeFilesystem {
×
1564
                fsOverhead, err := GetFilesystemOverheadForStorageClass(ctx, c, pvcSpec.StorageClassName)
×
1565
                if err != nil {
×
1566
                        return resource.Quantity{}, err
×
1567
                }
×
1568
                // Parse filesystem overhead (percentage) into a 64-bit float
1569
                fsOverheadFloat, _ := strconv.ParseFloat(string(fsOverhead), 64)
×
1570

×
1571
                // Merge the previous values into a 'resource.Quantity' struct
×
1572
                requiredSpace := GetRequiredSpace(fsOverheadFloat, imgSize)
×
1573
                returnSize = *resource.NewScaledQuantity(requiredSpace, 0)
×
1574
        } else {
×
1575
                // Inflation is not needed with 'Block' mode
×
1576
                returnSize = *resource.NewScaledQuantity(imgSize, 0)
×
1577
        }
×
1578

1579
        return returnSize, nil
×
1580
}
1581

1582
// IsBound returns if the pvc is bound
1583
func IsBound(pvc *corev1.PersistentVolumeClaim) bool {
×
1584
        return pvc.Spec.VolumeName != ""
×
1585
}
×
1586

1587
// IsUnbound returns if the pvc is not bound yet
1588
func IsUnbound(pvc *corev1.PersistentVolumeClaim) bool {
×
1589
        return !IsBound(pvc)
×
1590
}
×
1591

1592
// IsImageStream returns true if registry source is ImageStream
1593
func IsImageStream(pvc *corev1.PersistentVolumeClaim) bool {
×
1594
        return pvc.Annotations[AnnRegistryImageStream] == "true"
×
1595
}
×
1596

1597
// ShouldIgnorePod checks if a pod should be ignored.
1598
// If this is a completed pod that was used for one checkpoint of a multi-stage import, it
1599
// should be ignored by pod lookups as long as the retainAfterCompletion annotation is set.
1600
func ShouldIgnorePod(pod *corev1.Pod, pvc *corev1.PersistentVolumeClaim) bool {
×
1601
        retain := pvc.ObjectMeta.Annotations[AnnPodRetainAfterCompletion]
×
1602
        checkpoint := pvc.ObjectMeta.Annotations[AnnCurrentCheckpoint]
×
1603
        if checkpoint != "" && pod.Status.Phase == corev1.PodSucceeded {
×
1604
                return retain == "true"
×
1605
        }
×
1606
        return false
×
1607
}
1608

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

1633
// ErrConnectionRefused checks for connection refused errors
1634
func ErrConnectionRefused(err error) bool {
×
1635
        return strings.Contains(err.Error(), "connection refused")
×
1636
}
×
1637

1638
// GetPodMetricsPort returns, if exists, the metrics port from the passed pod
1639
func GetPodMetricsPort(pod *corev1.Pod) (int, error) {
1✔
1640
        for _, container := range pod.Spec.Containers {
2✔
1641
                for _, port := range container.Ports {
2✔
1642
                        if port.Name == "metrics" {
2✔
1643
                                return int(port.ContainerPort), nil
1✔
1644
                        }
1✔
1645
                }
1646
        }
1647
        return 0, errors.New("Metrics port not found in pod")
1✔
1648
}
1649

1650
// GetMetricsURL builds the metrics URL according to the specified pod
1651
func GetMetricsURL(pod *corev1.Pod) (string, error) {
1✔
1652
        if pod == nil {
1✔
1653
                return "", nil
×
1654
        }
×
1655
        port, err := GetPodMetricsPort(pod)
1✔
1656
        if err != nil || pod.Status.PodIP == "" {
2✔
1657
                return "", err
1✔
1658
        }
1✔
1659
        domain := net.JoinHostPort(pod.Status.PodIP, fmt.Sprint(port))
1✔
1660
        url := fmt.Sprintf("https://%s/metrics", domain)
1✔
1661
        return url, nil
1✔
1662
}
1663

1664
// GetProgressReportFromURL fetches the progress report from the passed URL according to an specific regular expression
1665
func GetProgressReportFromURL(url string, regExp *regexp.Regexp, httpClient *http.Client) (string, error) {
×
1666
        resp, err := httpClient.Get(url)
×
1667
        if err != nil {
×
1668
                if ErrConnectionRefused(err) {
×
1669
                        return "", nil
×
1670
                }
×
1671
                return "", err
×
1672
        }
1673
        defer resp.Body.Close()
×
1674
        body, err := io.ReadAll(resp.Body)
×
1675
        if err != nil {
×
1676
                return "", err
×
1677
        }
×
1678

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

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

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

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

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

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

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

1768
// UpdateImageIOAnnotations updates the passed annotations for proper imageIO import
1769
func UpdateImageIOAnnotations(annotations map[string]string, imageio *cdiv1.DataVolumeSourceImageIO) {
×
1770
        annotations[AnnEndpoint] = imageio.URL
×
1771
        annotations[AnnSource] = SourceImageio
×
1772
        annotations[AnnSecret] = imageio.SecretRef
×
1773
        annotations[AnnCertConfigMap] = imageio.CertConfigMap
×
1774
        annotations[AnnDiskID] = imageio.DiskID
×
1775
}
×
1776

1777
// IsPVBoundToPVC checks if a PV is bound to a specific PVC
1778
func IsPVBoundToPVC(pv *corev1.PersistentVolume, pvc *corev1.PersistentVolumeClaim) bool {
1✔
1779
        claimRef := pv.Spec.ClaimRef
1✔
1780
        return claimRef != nil && claimRef.Name == pvc.Name && claimRef.Namespace == pvc.Namespace && claimRef.UID == pvc.UID
1✔
1781
}
1✔
1782

1783
// Rebind binds the PV of source to target
1784
func Rebind(ctx context.Context, c client.Client, source, target *corev1.PersistentVolumeClaim) error {
1✔
1785
        pv := &corev1.PersistentVolume{
1✔
1786
                ObjectMeta: metav1.ObjectMeta{
1✔
1787
                        Name: source.Spec.VolumeName,
1✔
1788
                },
1✔
1789
        }
1✔
1790

1✔
1791
        if err := c.Get(ctx, client.ObjectKeyFromObject(pv), pv); err != nil {
2✔
1792
                return err
1✔
1793
        }
1✔
1794

1795
        // Examine the claimref for the PV and see if it's still bound to PVC'
1796
        if pv.Spec.ClaimRef == nil {
1✔
1797
                return fmt.Errorf("PV %s claimRef is nil", pv.Name)
×
1798
        }
×
1799

1800
        if !IsPVBoundToPVC(pv, source) {
2✔
1801
                // Something is not right if the PV is neither bound to PVC' nor target PVC
1✔
1802
                if !IsPVBoundToPVC(pv, target) {
2✔
1803
                        klog.Errorf("PV bound to unexpected PVC: Could not rebind to target PVC '%s'", target.Name)
1✔
1804
                        return fmt.Errorf("PV %s bound to unexpected claim %s", pv.Name, pv.Spec.ClaimRef.Name)
1✔
1805
                }
1✔
1806
                // our work is done
1807
                return nil
1✔
1808
        }
1809

1810
        // Rebind PVC to target PVC
1811
        pv.Spec.ClaimRef = &corev1.ObjectReference{
1✔
1812
                Namespace:       target.Namespace,
1✔
1813
                Name:            target.Name,
1✔
1814
                UID:             target.UID,
1✔
1815
                ResourceVersion: target.ResourceVersion,
1✔
1816
        }
1✔
1817
        klog.V(3).Info("Rebinding PV to target PVC", "PVC", target.Name)
1✔
1818
        if err := c.Update(context.TODO(), pv); err != nil {
1✔
1819
                return err
×
1820
        }
×
1821

1822
        return nil
1✔
1823
}
1824

1825
// BulkDeleteResources deletes a bunch of resources
1826
func BulkDeleteResources(ctx context.Context, c client.Client, obj client.ObjectList, lo client.ListOption) error {
×
1827
        if err := c.List(ctx, obj, lo); err != nil {
×
1828
                if meta.IsNoMatchError(err) {
×
1829
                        return nil
×
1830
                }
×
1831
                return err
×
1832
        }
1833

1834
        sv := reflect.ValueOf(obj).Elem()
×
1835
        iv := sv.FieldByName("Items")
×
1836

×
1837
        for i := 0; i < iv.Len(); i++ {
×
1838
                obj := iv.Index(i).Addr().Interface().(client.Object)
×
1839
                if obj.GetDeletionTimestamp().IsZero() {
×
1840
                        klog.V(3).Infof("Deleting type %+v %+v", reflect.TypeOf(obj), obj)
×
1841
                        if err := c.Delete(ctx, obj); err != nil {
×
1842
                                return err
×
1843
                        }
×
1844
                }
1845
        }
1846

1847
        return nil
×
1848
}
1849

1850
// ProgressFromClaimArgs are the args for ProgressFromClaim
1851
type ProgressFromClaimArgs struct {
1852
        Client       client.Client
1853
        HTTPClient   *http.Client
1854
        Claim        *corev1.PersistentVolumeClaim
1855
        OwnerUID     string
1856
        PodNamespace string
1857
        PodName      string
1858
}
1859

1860
// ProgressFromClaim returns the progres
1861
func ProgressFromClaim(ctx context.Context, args *ProgressFromClaimArgs) (string, error) {
×
1862
        // Just set 100.0% if pod is succeeded
×
1863
        if args.Claim.Annotations[AnnPodPhase] == string(corev1.PodSucceeded) {
×
1864
                return ProgressDone, nil
×
1865
        }
×
1866

1867
        pod := &corev1.Pod{
×
1868
                ObjectMeta: metav1.ObjectMeta{
×
1869
                        Namespace: args.PodNamespace,
×
1870
                        Name:      args.PodName,
×
1871
                },
×
1872
        }
×
1873
        if err := args.Client.Get(ctx, client.ObjectKeyFromObject(pod), pod); err != nil {
×
1874
                if k8serrors.IsNotFound(err) {
×
1875
                        return "", nil
×
1876
                }
×
1877
                return "", err
×
1878
        }
1879

1880
        // This will only work when the import pod is running
1881
        if pod.Status.Phase != corev1.PodRunning {
×
1882
                return "", nil
×
1883
        }
×
1884
        url, err := GetMetricsURL(pod)
×
1885
        if err != nil {
×
1886
                return "", err
×
1887
        }
×
1888
        if url == "" {
×
1889
                return "", nil
×
1890
        }
×
1891

1892
        // We fetch the import progress from the import pod metrics
1893
        importRegExp := regexp.MustCompile("progress\\{ownerUID\\=\"" + args.OwnerUID + "\"\\} (\\d{1,3}\\.?\\d*)")
×
1894
        progressReport, err := GetProgressReportFromURL(url, importRegExp, args.HTTPClient)
×
1895
        if err != nil {
×
1896
                return "", err
×
1897
        }
×
1898
        if progressReport != "" {
×
1899
                if f, err := strconv.ParseFloat(progressReport, 64); err == nil {
×
1900
                        return fmt.Sprintf("%.2f%%", f), nil
×
1901
                }
×
1902
        }
1903

1904
        return "", nil
×
1905
}
1906

1907
// ValidateSnapshotCloneSize does proper size validation when doing a clone from snapshot operation
1908
func ValidateSnapshotCloneSize(snapshot *snapshotv1.VolumeSnapshot, pvcSpec *corev1.PersistentVolumeClaimSpec, targetSC *storagev1.StorageClass, log logr.Logger) (bool, error) {
×
1909
        restoreSize := snapshot.Status.RestoreSize
×
1910
        if restoreSize == nil {
×
1911
                return false, fmt.Errorf("snapshot has no RestoreSize")
×
1912
        }
×
1913
        targetRequest, hasTargetRequest := pvcSpec.Resources.Requests[corev1.ResourceStorage]
×
1914
        allowExpansion := targetSC.AllowVolumeExpansion != nil && *targetSC.AllowVolumeExpansion
×
1915
        if hasTargetRequest {
×
1916
                // otherwise will just use restoreSize
×
1917
                if restoreSize.Cmp(targetRequest) < 0 && !allowExpansion {
×
1918
                        log.V(3).Info("Can't expand restored PVC because SC does not allow expansion, need to fall back to host assisted")
×
1919
                        return false, nil
×
1920
                }
×
1921
        }
1922
        return true, nil
×
1923
}
1924

1925
// ValidateSnapshotCloneProvisioners validates the target PVC storage class against the snapshot class provisioner
1926
func ValidateSnapshotCloneProvisioners(ctx context.Context, c client.Client, snapshot *snapshotv1.VolumeSnapshot, storageClass *storagev1.StorageClass) (bool, error) {
×
1927
        // Do snapshot and storage class validation
×
1928
        if storageClass == nil {
×
1929
                return false, fmt.Errorf("target storage class not found")
×
1930
        }
×
1931
        if snapshot.Status == nil || snapshot.Status.BoundVolumeSnapshotContentName == nil {
×
1932
                return false, fmt.Errorf("volumeSnapshotContent name not found")
×
1933
        }
×
1934
        volumeSnapshotContent := &snapshotv1.VolumeSnapshotContent{}
×
1935
        if err := c.Get(ctx, types.NamespacedName{Name: *snapshot.Status.BoundVolumeSnapshotContentName}, volumeSnapshotContent); err != nil {
×
1936
                return false, err
×
1937
        }
×
1938
        if storageClass.Provisioner != volumeSnapshotContent.Spec.Driver {
×
1939
                return false, nil
×
1940
        }
×
1941
        // TODO: get sourceVolumeMode from volumesnapshotcontent and validate against target spec
1942
        // currently don't have CRDs in CI with sourceVolumeMode which is pretty new
1943
        // converting volume mode is possible but has security implications
1944
        return true, nil
×
1945
}
1946

1947
// GetSnapshotClassForSmartClone looks up the snapshot class based on the storage class
1948
func GetSnapshotClassForSmartClone(pvc *corev1.PersistentVolumeClaim, targetPvcStorageClassName, snapshotClassName *string, log logr.Logger, client client.Client, recorder record.EventRecorder) (string, error) {
×
1949
        logger := log.WithName("GetSnapshotClassForSmartClone").V(3)
×
1950
        // Check if relevant CRDs are available
×
1951
        if !isCsiCrdsDeployed(client, log) {
×
1952
                logger.Info("Missing CSI snapshotter CRDs, falling back to host assisted clone")
×
1953
                return "", nil
×
1954
        }
×
1955

1956
        targetStorageClass, err := GetStorageClassByNameWithK8sFallback(context.TODO(), client, targetPvcStorageClassName)
×
1957
        if err != nil {
×
1958
                return "", err
×
1959
        }
×
1960
        if targetStorageClass == nil {
×
1961
                logger.Info("Target PVC's Storage Class not found")
×
1962
                return "", nil
×
1963
        }
×
1964

1965
        vscName, err := GetVolumeSnapshotClass(context.TODO(), client, pvc, targetStorageClass.Provisioner, snapshotClassName, logger, recorder)
×
1966
        if err != nil {
×
1967
                return "", err
×
1968
        }
×
1969
        if vscName != nil {
×
1970
                if pvc != nil {
×
1971
                        logger.Info("smart-clone is applicable for datavolume", "datavolume",
×
1972
                                pvc.Name, "snapshot class", *vscName)
×
1973
                }
×
1974
                return *vscName, nil
×
1975
        }
1976

1977
        logger.Info("Could not match snapshotter with storage class, falling back to host assisted clone")
×
1978
        return "", nil
×
1979
}
1980

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

×
1986
        logEvent := func(message, vscName string) {
×
1987
                logger.Info(message, "name", vscName)
×
1988
                if pvc != nil {
×
1989
                        msg := fmt.Sprintf("%s %s", message, vscName)
×
1990
                        recorder.Event(pvc, corev1.EventTypeNormal, VolumeSnapshotClassSelected, msg)
×
1991
                }
×
1992
        }
1993

1994
        if snapshotClassName != nil {
×
1995
                vsc := &snapshotv1.VolumeSnapshotClass{}
×
1996
                if err := c.Get(context.TODO(), types.NamespacedName{Name: *snapshotClassName}, vsc); err != nil {
×
1997
                        return nil, err
×
1998
                }
×
1999
                if vsc.Driver == driver {
×
2000
                        logEvent(MessageStorageProfileVolumeSnapshotClassSelected, vsc.Name)
×
2001
                        return snapshotClassName, nil
×
2002
                }
×
2003
                return nil, nil
×
2004
        }
2005

2006
        vscList := &snapshotv1.VolumeSnapshotClassList{}
×
2007
        if err := c.List(ctx, vscList); err != nil {
×
2008
                if meta.IsNoMatchError(err) {
×
2009
                        return nil, nil
×
2010
                }
×
2011
                return nil, err
×
2012
        }
2013

2014
        var candidates []string
×
2015
        for _, vsc := range vscList.Items {
×
2016
                if vsc.Driver == driver {
×
2017
                        if vsc.Annotations[AnnDefaultSnapshotClass] == "true" {
×
2018
                                logEvent(MessageDefaultVolumeSnapshotClassSelected, vsc.Name)
×
2019
                                vscName := vsc.Name
×
2020
                                return &vscName, nil
×
2021
                        }
×
2022
                        candidates = append(candidates, vsc.Name)
×
2023
                }
2024
        }
2025

2026
        if len(candidates) > 0 {
×
2027
                sort.Strings(candidates)
×
2028
                logEvent(MessageFirstVolumeSnapshotClassSelected, candidates[0])
×
2029
                return &candidates[0], nil
×
2030
        }
×
2031

2032
        return nil, nil
×
2033
}
2034

2035
// isCsiCrdsDeployed checks whether the CSI snapshotter CRD are deployed
2036
func isCsiCrdsDeployed(c client.Client, log logr.Logger) bool {
×
2037
        version := "v1"
×
2038
        vsClass := "volumesnapshotclasses." + snapshotv1.GroupName
×
2039
        vsContent := "volumesnapshotcontents." + snapshotv1.GroupName
×
2040
        vs := "volumesnapshots." + snapshotv1.GroupName
×
2041

×
2042
        return isCrdDeployed(c, vsClass, version, log) &&
×
2043
                isCrdDeployed(c, vsContent, version, log) &&
×
2044
                isCrdDeployed(c, vs, version, log)
×
2045
}
×
2046

2047
// isCrdDeployed checks whether a CRD is deployed
2048
func isCrdDeployed(c client.Client, name, version string, log logr.Logger) bool {
×
2049
        crd := &extv1.CustomResourceDefinition{}
×
2050
        err := c.Get(context.TODO(), types.NamespacedName{Name: name}, crd)
×
2051
        if err != nil {
×
2052
                if !k8serrors.IsNotFound(err) {
×
2053
                        log.Info("Error looking up CRD", "crd name", name, "version", version, "error", err)
×
2054
                }
×
2055
                return false
×
2056
        }
2057

2058
        for _, v := range crd.Spec.Versions {
×
2059
                if v.Name == version && v.Served {
×
2060
                        return true
×
2061
                }
×
2062
        }
2063

2064
        return false
×
2065
}
2066

2067
// IsSnapshotReady indicates if a volume snapshot is ready to be used
2068
func IsSnapshotReady(snapshot *snapshotv1.VolumeSnapshot) bool {
×
2069
        return snapshot.Status != nil && snapshot.Status.ReadyToUse != nil && *snapshot.Status.ReadyToUse
×
2070
}
×
2071

2072
// GetResource updates given obj with the data of the object with the same name and namespace
2073
func GetResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
×
2074
        obj.SetNamespace(namespace)
×
2075
        obj.SetName(name)
×
2076

×
2077
        err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
×
2078
        if err != nil {
×
2079
                if k8serrors.IsNotFound(err) {
×
2080
                        return false, nil
×
2081
                }
×
2082

2083
                return false, err
×
2084
        }
2085

2086
        return true, nil
×
2087
}
2088

2089
// PatchArgs are the args for Patch
2090
type PatchArgs struct {
2091
        Client client.Client
2092
        Log    logr.Logger
2093
        Obj    client.Object
2094
        OldObj client.Object
2095
}
2096

2097
// GetAnnotatedEventSource returns resource referenced by AnnEventSource annotations
2098
func GetAnnotatedEventSource(ctx context.Context, c client.Client, obj client.Object) (client.Object, error) {
×
2099
        esk, ok := obj.GetAnnotations()[AnnEventSourceKind]
×
2100
        if !ok {
×
2101
                return obj, nil
×
2102
        }
×
2103
        if esk != "PersistentVolumeClaim" {
×
2104
                return obj, nil
×
2105
        }
×
2106
        es, ok := obj.GetAnnotations()[AnnEventSource]
×
2107
        if !ok {
×
2108
                return obj, nil
×
2109
        }
×
2110
        namespace, name, err := cache.SplitMetaNamespaceKey(es)
×
2111
        if err != nil {
×
2112
                return nil, err
×
2113
        }
×
2114
        pvc := &corev1.PersistentVolumeClaim{
×
2115
                ObjectMeta: metav1.ObjectMeta{
×
2116
                        Namespace: namespace,
×
2117
                        Name:      name,
×
2118
                },
×
2119
        }
×
2120
        if err := c.Get(ctx, client.ObjectKeyFromObject(pvc), pvc); err != nil {
×
2121
                return nil, err
×
2122
        }
×
2123
        return pvc, nil
×
2124
}
2125

2126
// OwnedByDataVolume returns true if the object is owned by a DataVolume
2127
func OwnedByDataVolume(obj metav1.Object) bool {
×
2128
        owner := metav1.GetControllerOf(obj)
×
2129
        return owner != nil && owner.Kind == "DataVolume"
×
2130
}
×
2131

2132
// CopyAllowedAnnotations copies the allowed annotations from the source object
2133
// to the destination object
2134
func CopyAllowedAnnotations(srcObj, dstObj metav1.Object) {
×
2135
        for ann, def := range allowedAnnotations {
×
2136
                val, ok := srcObj.GetAnnotations()[ann]
×
2137
                if !ok && def != "" {
×
2138
                        val = def
×
2139
                }
×
2140
                if val != "" {
×
2141
                        klog.V(1).Info("Applying annotation", "Name", dstObj.GetName(), ann, val)
×
2142
                        AddAnnotation(dstObj, ann, val)
×
2143
                }
×
2144
        }
2145
}
2146

2147
// ClaimMayExistBeforeDataVolume returns true if the PVC may exist before the DataVolume
2148
func ClaimMayExistBeforeDataVolume(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2149
        if ClaimIsPopulatedForDataVolume(pvc, dv) {
×
2150
                return true, nil
×
2151
        }
×
2152
        return AllowClaimAdoption(c, pvc, dv)
×
2153
}
2154

2155
// ClaimIsPopulatedForDataVolume returns true if the PVC is populated for the given DataVolume
2156
func ClaimIsPopulatedForDataVolume(pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) bool {
×
2157
        return pvc != nil && dv != nil && pvc.Annotations[AnnPopulatedFor] == dv.Name
×
2158
}
×
2159

2160
// AllowClaimAdoption returns true if the PVC may be adopted
2161
func AllowClaimAdoption(c client.Client, pvc *corev1.PersistentVolumeClaim, dv *cdiv1.DataVolume) (bool, error) {
×
2162
        if pvc == nil || dv == nil {
×
2163
                return false, nil
×
2164
        }
×
2165
        anno, ok := pvc.Annotations[AnnCreatedForDataVolume]
×
2166
        if ok && anno == string(dv.UID) {
×
2167
                return false, nil
×
2168
        }
×
2169
        anno, ok = dv.Annotations[AnnAllowClaimAdoption]
×
2170
        // if annotation exists, go with that regardless of featuregate
×
2171
        if ok {
×
2172
                val, _ := strconv.ParseBool(anno)
×
2173
                return val, nil
×
2174
        }
×
2175
        return featuregates.NewFeatureGates(c).ClaimAdoptionEnabled()
×
2176
}
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