containerized-data-importer/pkg/controller/datavolume/util.go
Arnon Gilboa 223ce37768
Fix PVC webhook rendering segfault (#3544)
* Fix PVC webhook rendering segfault

When there is no default StorageClass and no virt default StorageClass,
creating a PVC with no StorageClass specified will cause a segfault in
the webhook:

Error from server (InternalError): error when creating
"pvc_render.yaml": Internal error occurred: failed calling webhook
"pvc-mutate.cdi.kubevirt.io": failed to call webhook: Post
"https://cdi-api.cdi.svc:443/pvc-mutate?timeout=10s": EOF

In cdi-apiserver log:

panic({0x1b49360?, 0x3295550?})
        GOROOT/src/runtime/panic.go:770 +0x132
kubevirt.io/containerized-data-importer/pkg/controller/datavolume.
renderPvcSpecVolumeModeAndAccessModesAndStorageClass({0x2164d00,
0xc000156090}, {0x0, 0x0}, 0x0?, 0x0, 0xc0002e86a8, {0x1e09ed8?,
0xc0002e8530?})
        pkg/controller/datavolume/util.go:162 +0xb37

renderPvcSpecVolumeModeAndAccessModesAndStorageClass() is used from both
DV controller and PVC webhook rendering. In the PVC mutating webhook
(cdi-apiserver) we don't have logr.Logger (unlike in cdi-deployment),
but klog which has different api, and no reason to log there as we
return the failure anyway to the user when she creates the PVC. We also
don't have a DV there, and no event recorder.

Signed-off-by: Arnon Gilboa <agilboa@redhat.com>

* Rephrase rendering errors

Signed-off-by: Arnon Gilboa <agilboa@redhat.com>

* Add some utests

Signed-off-by: Arnon Gilboa <agilboa@redhat.com>

---------

Signed-off-by: Arnon Gilboa <agilboa@redhat.com>
2024-12-08 14:17:38 +01:00

687 lines
24 KiB
Go

/*
Copyright 2020 The CDI Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package datavolume
import (
"context"
"fmt"
"strconv"
"github.com/docker/go-units"
"github.com/go-logr/logr"
snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
storagev1 "k8s.io/api/storage/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
storagehelpers "k8s.io/component-helpers/storage/volume"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
"kubevirt.io/containerized-data-importer/pkg/common"
cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
"kubevirt.io/containerized-data-importer/pkg/controller/populators"
featuregates "kubevirt.io/containerized-data-importer/pkg/feature-gates"
"kubevirt.io/containerized-data-importer/pkg/util"
)
const (
// AnnOwnedByDataVolume annotation has the owner DataVolume name
AnnOwnedByDataVolume = "cdi.kubevirt.io/ownedByDataVolume"
// MessageErrStorageClassNotFound provides a const to indicate the PVC spec is missing accessMode and no storageClass to choose profile
MessageErrStorageClassNotFound = "PVC spec is missing accessMode and no storageClass to choose profile"
)
var (
// ErrStorageClassNotFound indicates the PVC spec is missing accessMode and no storageClass to choose profile
ErrStorageClassNotFound = errors.New(MessageErrStorageClassNotFound)
)
// RenderPvc renders the PVC according to StorageProfiles
func RenderPvc(ctx context.Context, client client.Client, pvc *v1.PersistentVolumeClaim) error {
if pvc.Spec.VolumeMode != nil &&
*pvc.Spec.VolumeMode == cdiv1.PersistentVolumeFromStorageProfile {
pvc.Spec.VolumeMode = nil
}
dvContentType := cc.GetPVCContentType(pvc)
if err := renderPvcSpecVolumeModeAndAccessModesAndStorageClass(client, nil, nil, nil, &pvc.Spec, dvContentType); err != nil {
return err
}
if hasCloneSourceRef(pvc) {
return renderClonePvcVolumeSizeFromSource(ctx, client, pvc)
}
isClone := pvc.Annotations[cc.AnnCloneType] != ""
return renderPvcSpecVolumeSize(client, &pvc.Spec, isClone)
}
// renderPvcSpec creates a new PVC Spec based on either the dv.spec.pvc or dv.spec.storage section
func renderPvcSpec(client client.Client, recorder record.EventRecorder, log logr.Logger, dv *cdiv1.DataVolume, pvc *v1.PersistentVolumeClaim) (*v1.PersistentVolumeClaimSpec, error) {
if dv.Spec.PVC != nil {
return dv.Spec.PVC.DeepCopy(), nil
} else if dv.Spec.Storage != nil {
return pvcFromStorage(client, recorder, log, dv, pvc)
}
return nil, errors.Errorf("datavolume one of {pvc, storage} field is required")
}
func pvcFromStorage(client client.Client, recorder record.EventRecorder, log logr.Logger, dv *cdiv1.DataVolume, pvc *v1.PersistentVolumeClaim) (*v1.PersistentVolumeClaimSpec, error) {
var pvcSpec *v1.PersistentVolumeClaimSpec
isWebhookRenderingEnabled, err := featuregates.IsWebhookPvcRenderingEnabled(client)
if err != nil {
return nil, err
}
shouldRender := !isWebhookRenderingEnabled || dv.Labels[common.PvcApplyStorageProfileLabel] != "true"
if pvc == nil {
pvcSpec = copyStorageAsPvc(dv.Spec.Storage)
if shouldRender {
if err := renderPvcSpecVolumeModeAndAccessModesAndStorageClass(client, recorder, &log, dv, pvcSpec, dv.Spec.ContentType); err != nil {
return nil, err
}
}
} else {
pvcSpec = pvc.Spec.DeepCopy()
}
if shouldRender {
isClone := dv.Spec.Source.PVC != nil || dv.Spec.Source.Snapshot != nil
if err := renderPvcSpecVolumeSize(client, pvcSpec, isClone); err != nil {
return nil, err
}
}
return pvcSpec, nil
}
// func is called from both DV controller (with recorder and log) and PVC mutating webhook (without recorder and log)
// therefore we use wrappers for log and recorder calls
func renderPvcSpecVolumeModeAndAccessModesAndStorageClass(client client.Client, recorder record.EventRecorder, log *logr.Logger,
dv *cdiv1.DataVolume, pvcSpec *v1.PersistentVolumeClaimSpec, dvContentType cdiv1.DataVolumeContentType) error {
logInfo := func(msg string, keysAndValues ...interface{}) {
if log != nil && dv != nil {
keysAndValuesWithDv := append(keysAndValues, "namespace", dv.Namespace, "name", dv.Name)
log.V(1).Info(msg, keysAndValuesWithDv...)
}
}
recordEventf := func(eventtype, reason, messageFmt string, args ...interface{}) {
if recorder != nil && dv != nil {
recorder.Eventf(dv, eventtype, reason, messageFmt, args...)
}
}
if dvContentType == cdiv1.DataVolumeArchive {
if pvcSpec.VolumeMode != nil && *pvcSpec.VolumeMode == v1.PersistentVolumeBlock {
logInfo("ContentType Archive cannot have block volumeMode")
recordEventf(v1.EventTypeWarning, cc.ErrClaimNotValid, "ContentType Archive cannot have block volumeMode")
return errors.Errorf("ContentType Archive cannot have block volumeMode")
}
pvcSpec.VolumeMode = ptr.To[v1.PersistentVolumeMode](v1.PersistentVolumeFilesystem)
}
storageClass, err := cc.GetStorageClassByNameWithVirtFallback(context.TODO(), client, pvcSpec.StorageClassName, dvContentType)
if err != nil {
return err
}
if storageClass == nil {
if err := renderPvcSpecFromAvailablePv(client, pvcSpec); err != nil {
return err
}
// Not even default storageClass on the cluster, cannot apply the defaults, verify spec is ok
if len(pvcSpec.AccessModes) == 0 {
logInfo("Cannot set accessMode for new pvc")
recordEventf(v1.EventTypeWarning, cc.ErrClaimNotValid, MessageErrStorageClassNotFound)
return ErrStorageClassNotFound
}
return nil
}
pvcSpec.StorageClassName = &storageClass.Name
// given storageClass we can apply defaults if needed
if (pvcSpec.VolumeMode == nil || *pvcSpec.VolumeMode == "") && len(pvcSpec.AccessModes) == 0 {
accessModes, volumeMode, err := getDefaultVolumeAndAccessMode(client, storageClass)
if err != nil {
logInfo("Cannot set accessMode and volumeMode for new pvc", "Error", err)
recordEventf(v1.EventTypeWarning, cc.ErrClaimNotValid,
fmt.Sprintf("Spec is missing accessMode and volumeMode, cannot get access mode from StorageProfile %s", getName(storageClass)))
return err
}
pvcSpec.AccessModes = append(pvcSpec.AccessModes, accessModes...)
pvcSpec.VolumeMode = volumeMode
} else if len(pvcSpec.AccessModes) == 0 {
accessModes, err := getDefaultAccessModes(client, storageClass, pvcSpec.VolumeMode)
if err != nil {
logInfo("Cannot set accessMode for new pvc", "Error", err)
recordEventf(v1.EventTypeWarning, cc.ErrClaimNotValid,
fmt.Sprintf("Spec is missing accessMode and cannot get access mode from StorageProfile %s", getName(storageClass)))
return err
}
pvcSpec.AccessModes = append(pvcSpec.AccessModes, accessModes...)
} else if pvcSpec.VolumeMode == nil || *pvcSpec.VolumeMode == "" {
volumeMode, err := getDefaultVolumeMode(client, storageClass, pvcSpec.AccessModes)
if err != nil {
return err
}
pvcSpec.VolumeMode = volumeMode
}
return nil
}
func renderClonePvcVolumeSizeFromSource(ctx context.Context, client client.Client, pvc *v1.PersistentVolumeClaim) error {
if size, exists := pvc.Spec.Resources.Requests[v1.ResourceStorage]; exists && !size.IsZero() {
return nil
}
if !hasCloneSourceRef(pvc) {
return nil
}
sourceNamespace, exists := pvc.Annotations[populators.AnnDataSourceNamespace]
if !exists {
sourceNamespace = pvc.Namespace
}
volumeCloneSource := &cdiv1.VolumeCloneSource{}
if exists, err := cc.GetResource(ctx, client, sourceNamespace, pvc.Spec.DataSourceRef.Name, volumeCloneSource); err != nil || !exists {
return err
}
source := volumeCloneSource.Spec.Source
if source.Kind == "VolumeSnapshot" && source.Name != "" {
sourceSnapshot := &snapshotv1.VolumeSnapshot{}
if exists, err := cc.GetResource(ctx, client, sourceNamespace, source.Name, sourceSnapshot); err != nil || !exists {
return err
}
if sourceSnapshot.Status != nil && sourceSnapshot.Status.RestoreSize != nil {
setRequestedVolumeSize(&pvc.Spec, *sourceSnapshot.Status.RestoreSize)
}
return nil
}
if source.Kind != "PersistentVolumeClaim" || source.Name == "" {
return nil
}
sourcePvc := &v1.PersistentVolumeClaim{}
if exists, err := cc.GetResource(ctx, client, sourceNamespace, source.Name, sourcePvc); err != nil || !exists {
return err
}
// We cannot fill in the PVC size when these conditions are met, where the size detection pod is used when PVC size is rendered by the controllor
sourceVolumeMode := util.ResolveVolumeMode(sourcePvc.Spec.VolumeMode)
targetVolumeMode := util.ResolveVolumeMode(pvc.Spec.VolumeMode)
isKubevirtContent := cc.GetPVCContentType(sourcePvc) == cdiv1.DataVolumeKubeVirt
isHostAssistedClone := pvc.Annotations[cc.AnnCloneType] == string(cdiv1.CloneStrategyHostAssisted)
if sourceVolumeMode == v1.PersistentVolumeFilesystem &&
targetVolumeMode == v1.PersistentVolumeBlock &&
isKubevirtContent && isHostAssistedClone {
return nil
}
sourceSC, err := cc.GetStorageClassByNameWithK8sFallback(ctx, client, sourcePvc.Spec.StorageClassName)
if err != nil || sourceSC == nil {
return err
}
targetSC, err := cc.GetStorageClassByNameWithK8sFallback(ctx, client, pvc.Spec.StorageClassName)
if err != nil || targetSC == nil {
return err
}
// If target has the source volume mode and storage class, it can request the source requested volume size.
// Otherwise try using the source capacity.
volSize := sourcePvc.Spec.Resources.Requests[v1.ResourceStorage]
if targetVolumeMode != sourceVolumeMode || targetSC.Name != sourceSC.Name {
if capacity, exists := sourcePvc.Status.Capacity[v1.ResourceStorage]; exists {
volSize = capacity
}
}
setRequestedVolumeSize(&pvc.Spec, volSize)
return nil
}
func hasCloneSourceRef(pvc *v1.PersistentVolumeClaim) bool {
dsRef := pvc.Spec.DataSourceRef
return dsRef != nil && dsRef.APIGroup != nil && *dsRef.APIGroup == cc.AnnAPIGroup && dsRef.Kind == cdiv1.VolumeCloneSourceRef && dsRef.Name != ""
}
func renderPvcSpecVolumeSize(client client.Client, pvcSpec *v1.PersistentVolumeClaimSpec, isClone bool) error {
requestedSize, found := pvcSpec.Resources.Requests[v1.ResourceStorage]
// Storage size can be empty when cloning
if !found {
if !isClone {
return errors.Errorf("PVC Spec is not valid - missing storage size")
}
setRequestedVolumeSize(pvcSpec, resource.Quantity{})
return nil
}
// Kubevirt doesn't allow disks smaller than 1MiB. Rejecting for consistency.
if requestedSize.Value() < units.MiB {
return errors.Errorf("PVC Spec is not valid - storage size should be at least 1MiB")
}
requestedSize, err := cc.InflateSizeWithOverhead(context.TODO(), client, requestedSize.Value(), pvcSpec)
if err != nil {
return err
}
setRequestedVolumeSize(pvcSpec, requestedSize)
return nil
}
func setRequestedVolumeSize(pvcSpec *v1.PersistentVolumeClaimSpec, volumeSize resource.Quantity) {
if pvcSpec.Resources.Requests == nil {
pvcSpec.Resources.Requests = v1.ResourceList{}
}
pvcSpec.Resources.Requests[v1.ResourceStorage] = volumeSize
}
func getName(storageClass *storagev1.StorageClass) string {
if storageClass != nil {
return storageClass.Name
}
return ""
}
func copyStorageAsPvc(storage *cdiv1.StorageSpec) *v1.PersistentVolumeClaimSpec {
input := storage.DeepCopy()
pvcSpec := &v1.PersistentVolumeClaimSpec{
AccessModes: input.AccessModes,
Selector: input.Selector,
Resources: input.Resources,
VolumeName: input.VolumeName,
StorageClassName: input.StorageClassName,
VolumeMode: input.VolumeMode,
DataSource: input.DataSource,
DataSourceRef: input.DataSourceRef,
}
return pvcSpec
}
// Renders the PVC spec VolumeMode and AccessModes from an available satisfying PV
func renderPvcSpecFromAvailablePv(c client.Client, pvcSpec *v1.PersistentVolumeClaimSpec) error {
if pvcSpec.StorageClassName == nil {
return nil
}
pvList := &v1.PersistentVolumeList{}
fields := client.MatchingFields{claimStorageClassNameField: *pvcSpec.StorageClassName}
if err := c.List(context.TODO(), pvList, fields); err != nil {
return err
}
for _, pv := range pvList.Items {
pvc := &v1.PersistentVolumeClaim{Spec: *pvcSpec}
if err := CheckVolumeSatisfyClaim(&pv, pvc); err == nil {
pvcSpec.VolumeMode = pv.Spec.VolumeMode
if len(pvcSpec.AccessModes) == 0 {
pvcSpec.AccessModes = pv.Spec.AccessModes
}
return nil
}
}
return nil
}
func getDefaultVolumeAndAccessMode(c client.Client, storageClass *storagev1.StorageClass) ([]v1.PersistentVolumeAccessMode, *v1.PersistentVolumeMode, error) {
if storageClass == nil {
return nil, nil, errors.Errorf("no accessMode specified and StorageClass not found")
}
storageProfile := &cdiv1.StorageProfile{}
err := c.Get(context.TODO(), types.NamespacedName{Name: storageClass.Name}, storageProfile)
if err != nil {
return nil, nil, errors.Wrap(err, "cannot get StorageProfile")
}
if len(storageProfile.Status.ClaimPropertySets) > 0 &&
len(storageProfile.Status.ClaimPropertySets[0].AccessModes) > 0 {
accessModes := storageProfile.Status.ClaimPropertySets[0].AccessModes
volumeMode := storageProfile.Status.ClaimPropertySets[0].VolumeMode
return accessModes, volumeMode, nil
}
// no accessMode configured on storageProfile
return nil, nil, errors.Errorf("no accessMode specified in StorageProfile %s", storageProfile.Name)
}
func getDefaultVolumeMode(c client.Client, storageClass *storagev1.StorageClass, pvcAccessModes []v1.PersistentVolumeAccessMode) (*v1.PersistentVolumeMode, error) {
if storageClass == nil {
// fallback to k8s defaults
return nil, nil
}
storageProfile := &cdiv1.StorageProfile{}
err := c.Get(context.TODO(), types.NamespacedName{Name: storageClass.Name}, storageProfile)
if err != nil {
return nil, errors.Wrap(err, "cannot get StorageProfile")
}
if len(storageProfile.Status.ClaimPropertySets) > 0 {
volumeMode := storageProfile.Status.ClaimPropertySets[0].VolumeMode
if len(pvcAccessModes) == 0 {
return volumeMode, nil
}
// check for volume mode matching with given pvc access modes
for _, cps := range storageProfile.Status.ClaimPropertySets {
for _, accessMode := range cps.AccessModes {
for _, pvcAccessMode := range pvcAccessModes {
if accessMode == pvcAccessMode {
return cps.VolumeMode, nil
}
}
}
}
// if not found return default volume mode for the storage class
return volumeMode, nil
}
// since volumeMode is optional - > gracefully fallback to k8s defaults,
return nil, nil
}
func getDefaultAccessModes(c client.Client, storageClass *storagev1.StorageClass, pvcVolumeMode *v1.PersistentVolumeMode) ([]v1.PersistentVolumeAccessMode, error) {
if storageClass == nil {
return nil, errors.Errorf("no accessMode specified and no StorageProfile")
}
storageProfile := &cdiv1.StorageProfile{}
err := c.Get(context.TODO(), types.NamespacedName{Name: storageClass.Name}, storageProfile)
if err != nil {
return nil, errors.Wrapf(err, "no accessMode specified and cannot get StorageProfile %s", storageClass.Name)
}
if len(storageProfile.Status.ClaimPropertySets) > 0 {
// check for access modes matching with given pvc volume mode
defaultAccessModes := []v1.PersistentVolumeAccessMode{}
for _, cps := range storageProfile.Status.ClaimPropertySets {
if cps.VolumeMode != nil && pvcVolumeMode != nil && *cps.VolumeMode == *pvcVolumeMode {
if len(cps.AccessModes) > 0 {
return cps.AccessModes, nil
}
} else if len(cps.AccessModes) > 0 && len(defaultAccessModes) == 0 {
defaultAccessModes = cps.AccessModes
}
}
// if not found return default access modes for the storage profile
if len(defaultAccessModes) > 0 {
return defaultAccessModes, nil
}
}
// no accessMode configured on storageProfile
return nil, errors.Errorf("no accessMode specified in StorageProfile %s", storageProfile.Name)
}
// storageClassCSIDriverExists returns true if the passed storage class has CSI drivers available
func storageClassCSIDriverExists(client client.Client, log logr.Logger, storageClassName *string) (bool, error) {
log = log.WithName("storageClassCSIDriverExists").V(3)
storageClass, err := cc.GetStorageClassByNameWithK8sFallback(context.TODO(), client, storageClassName)
if err != nil {
return false, err
}
if storageClass == nil {
log.Info("Target PVC's Storage Class not found")
return false, nil
}
csiDriver := &storagev1.CSIDriver{}
if err := client.Get(context.TODO(), types.NamespacedName{Name: storageClass.Provisioner}, csiDriver); err != nil {
if !k8serrors.IsNotFound(err) {
return false, err
}
return false, nil
}
return true, nil
}
// CheckPVCUsingPopulators returns true if pvc has dataSourceRef and has
// the usePopulator annotation
func CheckPVCUsingPopulators(pvc *v1.PersistentVolumeClaim) (bool, error) {
if pvc.Spec.DataSourceRef == nil {
return false, nil
}
usePopulator, ok := pvc.Annotations[cc.AnnUsePopulator]
if !ok {
return false, nil
}
boolUsePopulator, err := strconv.ParseBool(usePopulator)
if err != nil {
return false, err
}
return boolUsePopulator, nil
}
func updateDataVolumeUseCDIPopulator(syncState *dvSyncState) {
cc.AddAnnotation(syncState.dvMutated, cc.AnnUsePopulator, strconv.FormatBool(syncState.usePopulator))
}
func updateDataVolumeDefaultInstancetypeLabels(client client.Client, syncState *dvSyncState) error {
// Skip looking anything up if any default instance type labels are already present
dv := syncState.dvMutated
for _, defaultInstanceTypeLabel := range cc.DefaultInstanceTypeLabels {
if _, ok := dv.Labels[defaultInstanceTypeLabel]; ok {
return nil
}
}
if dv.Spec.Source != nil && dv.Spec.Source.PVC != nil {
pvc := &v1.PersistentVolumeClaim{}
key := types.NamespacedName{
Name: dv.Spec.Source.PVC.Name,
Namespace: dv.Spec.Source.PVC.Namespace,
}
if err := client.Get(context.TODO(), key, pvc); err != nil {
if k8serrors.IsNotFound(err) {
return nil
}
return err
}
copyDefaultInstancetypeLabels(dv, pvc.Labels)
return nil
}
if dv.Spec.Source != nil && dv.Spec.Source.Snapshot != nil {
snapshot := &snapshotv1.VolumeSnapshot{}
key := types.NamespacedName{
Name: dv.Spec.Source.Snapshot.Name,
Namespace: dv.Spec.Source.Snapshot.Namespace,
}
if err := client.Get(context.TODO(), key, snapshot); err != nil {
if k8serrors.IsNotFound(err) {
return nil
}
return err
}
copyDefaultInstancetypeLabels(dv, snapshot.Labels)
return nil
}
if dv.Spec.Source != nil && dv.Spec.Source.Registry != nil {
pvc := &v1.PersistentVolumeClaim{}
key := types.NamespacedName{
Name: dv.Name,
Namespace: dv.Namespace,
}
if err := client.Get(context.TODO(), key, pvc); err != nil {
if k8serrors.IsNotFound(err) {
return nil
}
return err
}
copyDefaultInstancetypeLabels(dv, pvc.Labels)
return nil
}
if dv.Spec.SourceRef != nil && dv.Spec.SourceRef.Namespace != nil && dv.Spec.SourceRef.Kind == cdiv1.DataVolumeDataSource {
ds := &cdiv1.DataSource{}
key := types.NamespacedName{
Name: dv.Spec.SourceRef.Name,
Namespace: *dv.Spec.SourceRef.Namespace,
}
if err := client.Get(context.TODO(), key, ds); err != nil {
if k8serrors.IsNotFound(err) {
return nil
}
return err
}
copyDefaultInstancetypeLabels(dv, ds.Labels)
return nil
}
return nil
}
func copyDefaultInstancetypeLabels(dataVolume *cdiv1.DataVolume, labels map[string]string) {
for _, defaultInstancetypeLabel := range cc.DefaultInstanceTypeLabels {
if v, ok := labels[defaultInstancetypeLabel]; ok {
cc.AddLabel(dataVolume, defaultInstancetypeLabel, v)
}
}
}
func checkDVUsingPopulators(dv *cdiv1.DataVolume) (bool, error) {
usePopulator, ok := dv.Annotations[cc.AnnUsePopulator]
if !ok {
return false, nil
}
boolUsePopulator, err := strconv.ParseBool(usePopulator)
if err != nil {
return false, err
}
return boolUsePopulator, nil
}
func dvBoundOrPopulationInProgress(dataVolume *cdiv1.DataVolume, boundCond *cdiv1.DataVolumeCondition) bool {
usePopulator, err := checkDVUsingPopulators(dataVolume)
if err != nil {
return false
}
return boundCond.Status == v1.ConditionTrue ||
(usePopulator && dataVolume.Status.Phase != cdiv1.Pending && dataVolume.Status.Phase != cdiv1.PendingPopulation)
}
func createStorageProfile(name string,
accessModes []v1.PersistentVolumeAccessMode,
volumeMode v1.PersistentVolumeMode) *cdiv1.StorageProfile {
claimPropertySets := []cdiv1.ClaimPropertySet{{
AccessModes: accessModes,
VolumeMode: &volumeMode,
}}
return createStorageProfileWithClaimPropertySets(name, claimPropertySets)
}
func createStorageProfileWithClaimPropertySets(name string,
claimPropertySets []cdiv1.ClaimPropertySet) *cdiv1.StorageProfile {
return createStorageProfileWithCloneStrategy(name, claimPropertySets, nil)
}
func createStorageProfileWithCloneStrategy(name string,
claimPropertySets []cdiv1.ClaimPropertySet,
cloneStrategy *cdiv1.CDICloneStrategy) *cdiv1.StorageProfile {
return &cdiv1.StorageProfile{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Status: cdiv1.StorageProfileStatus{
StorageClass: &name,
ClaimPropertySets: claimPropertySets,
CloneStrategy: cloneStrategy,
},
}
}
func hasAnnOwnedByDataVolume(obj metav1.Object) bool {
_, ok := obj.GetAnnotations()[AnnOwnedByDataVolume]
return ok
}
func getAnnOwnedByDataVolume(obj metav1.Object) (string, string, error) {
val := obj.GetAnnotations()[AnnOwnedByDataVolume]
return cache.SplitMetaNamespaceKey(val)
}
func setAnnOwnedByDataVolume(dest, obj metav1.Object) error {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err != nil {
return err
}
if dest.GetAnnotations() == nil {
dest.SetAnnotations(make(map[string]string))
}
dest.GetAnnotations()[AnnOwnedByDataVolume] = key
return nil
}
// CheckVolumeSatisfyClaim checks if the volume requested by the claim satisfies the requirements of the claim
// adapted from k8s.io/kubernetes/pkg/controller/volume/persistentvolume/pv_controller.go
func CheckVolumeSatisfyClaim(volume *v1.PersistentVolume, claim *v1.PersistentVolumeClaim) error {
requestedQty := claim.Spec.Resources.Requests[v1.ResourceStorage]
requestedSize := requestedQty.Value()
// check if PV's DeletionTimeStamp is set, if so, return error.
if volume.ObjectMeta.DeletionTimestamp != nil {
return fmt.Errorf("the volume is marked for deletion %q", volume.Name)
}
volumeQty := volume.Spec.Capacity[v1.ResourceStorage]
volumeSize := volumeQty.Value()
if volumeSize < requestedSize {
return fmt.Errorf("requested PV is too small")
}
// this differs from pv_controller, be loose on storage class if not specified
requestedClass := storagehelpers.GetPersistentVolumeClaimClass(claim)
if requestedClass != "" && storagehelpers.GetPersistentVolumeClass(volume) != requestedClass {
return fmt.Errorf("storageClassName does not match")
}
if storagehelpers.CheckVolumeModeMismatches(&claim.Spec, &volume.Spec) {
return fmt.Errorf("incompatible volumeMode")
}
if !storagehelpers.CheckAccessModes(claim, volume) {
return fmt.Errorf("incompatible accessMode")
}
return nil
}
func getReconcileRequest(obj client.Object) reconcile.Request {
return reconcile.Request{NamespacedName: client.ObjectKeyFromObject(obj)}
}