mirror of
https://github.com/kubevirt/containerized-data-importer.git
synced 2025-06-03 06:30:22 +00:00

* Allow StorageProfile to use a specific VolumeSnapshotClass Signed-off-by: Arnon Gilboa <agilboa@redhat.com> * Use PVCs common snapshot class in GetCompatibleVolumeSnapshotClass Signed-off-by: Arnon Gilboa <agilboa@redhat.com> * Add VolumeSnapshotClass selection logs Signed-off-by: Arnon Gilboa <agilboa@redhat.com> * CR fixes Signed-off-by: Arnon Gilboa <agilboa@redhat.com> * More CR fixes Signed-off-by: Arnon Gilboa <agilboa@redhat.com> --------- Signed-off-by: Arnon Gilboa <agilboa@redhat.com>
341 lines
8.8 KiB
Go
341 lines
8.8 KiB
Go
package clone
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/go-logr/logr"
|
|
corev1 "k8s.io/api/core/v1"
|
|
storagev1 "k8s.io/api/storage/v1"
|
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
"k8s.io/client-go/tools/record"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
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/util"
|
|
)
|
|
|
|
const (
|
|
// PendingPhaseName is the phase when the clone is pending
|
|
PendingPhaseName = "Pending"
|
|
|
|
// SucceededPhaseName is the phase when the clone is succeeded
|
|
SucceededPhaseName = "Succeeded"
|
|
|
|
// ErrorPhaseName is the phase when the clone is in error
|
|
ErrorPhaseName = "Error"
|
|
)
|
|
|
|
// IsDataSourcePVC checks for PersistentVolumeClaim source kind
|
|
func IsDataSourcePVC(kind string) bool {
|
|
return kind == "PersistentVolumeClaim"
|
|
}
|
|
|
|
// IsDataSourceSnapshot checks for Snapshot source kind
|
|
func IsDataSourceSnapshot(kind string) bool {
|
|
return kind == "VolumeSnapshot"
|
|
}
|
|
|
|
// AddCommonLabels adds common labels to a resource
|
|
func AddCommonLabels(obj metav1.Object) {
|
|
if obj.GetLabels() == nil {
|
|
obj.SetLabels(make(map[string]string))
|
|
}
|
|
obj.GetLabels()[common.CDILabelKey] = common.CDILabelValue
|
|
}
|
|
|
|
// AddCommonClaimLabels adds common labels to a pvc
|
|
func AddCommonClaimLabels(pvc *corev1.PersistentVolumeClaim) {
|
|
AddCommonLabels(pvc)
|
|
if util.ResolveVolumeMode(pvc.Spec.VolumeMode) == corev1.PersistentVolumeFilesystem {
|
|
pvc.GetLabels()[common.KubePersistentVolumeFillingUpSuppressLabelKey] = common.KubePersistentVolumeFillingUpSuppressLabelValue
|
|
}
|
|
}
|
|
|
|
// AddOwnershipLabel adds owner label
|
|
func AddOwnershipLabel(label string, obj, owner metav1.Object) {
|
|
if obj.GetLabels() == nil {
|
|
obj.SetLabels(make(map[string]string))
|
|
}
|
|
obj.GetLabels()[label] = string(owner.GetUID())
|
|
}
|
|
|
|
// IsSourceClaimReadyArgs are arguments for IsSourceClaimReady
|
|
type IsSourceClaimReadyArgs struct {
|
|
Target client.Object
|
|
SourceNamespace string
|
|
SourceName string
|
|
Client client.Client
|
|
Log logr.Logger
|
|
Recorder record.EventRecorder
|
|
}
|
|
|
|
// IsSourceClaimReady checks that PVC exists, is bound, and is not being used
|
|
func IsSourceClaimReady(ctx context.Context, args *IsSourceClaimReadyArgs) (bool, error) {
|
|
claim := &corev1.PersistentVolumeClaim{}
|
|
exists, err := getResource(ctx, args.Client, args.SourceNamespace, args.SourceName, claim)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if !exists {
|
|
return false, nil
|
|
}
|
|
|
|
if claim.Status.Phase != corev1.ClaimBound {
|
|
return false, nil
|
|
}
|
|
|
|
pods, err := cc.GetPodsUsingPVCs(ctx, args.Client, args.SourceNamespace, sets.New(args.SourceName), true)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
for _, pod := range pods {
|
|
args.Log.V(1).Info("Source PVC is being used by pod", "namespace", args.SourceNamespace, "name", args.SourceName, "pod", pod.Name)
|
|
args.Recorder.Eventf(args.Target, corev1.EventTypeWarning, cc.CloneSourceInUse,
|
|
"pod %s/%s using PersistentVolumeClaim %s", pod.Namespace, pod.Name, args.SourceName)
|
|
}
|
|
|
|
if len(pods) > 0 {
|
|
return false, nil
|
|
}
|
|
|
|
return cdiv1.IsPopulated(claim, dataVolumeGetter(ctx, args.Client))
|
|
}
|
|
|
|
// GetGlobalCloneStrategyOverride returns the global clone strategy override
|
|
func GetGlobalCloneStrategyOverride(ctx context.Context, c client.Client) (*cdiv1.CDICloneStrategy, error) {
|
|
cr, err := cc.GetActiveCDI(ctx, c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if cr == nil {
|
|
return nil, fmt.Errorf("no active CDI")
|
|
}
|
|
|
|
if cr.Spec.CloneStrategyOverride == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return cr.Spec.CloneStrategyOverride, nil
|
|
}
|
|
|
|
// GetStorageClassForClaim returns the storageclass for a PVC
|
|
func GetStorageClassForClaim(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim) (*storagev1.StorageClass, error) {
|
|
if pvc.Spec.StorageClassName == nil || *pvc.Spec.StorageClassName == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
sc := &storagev1.StorageClass{}
|
|
exists, err := getResource(ctx, c, "", *pvc.Spec.StorageClassName, sc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if exists {
|
|
return sc, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func getSnapshotClassForClaim(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim) (*string, error) {
|
|
sc, err := cc.GetStorageClassByNameWithK8sFallback(ctx, c, pvc.Spec.StorageClassName)
|
|
if err != nil || sc == nil {
|
|
return nil, err
|
|
}
|
|
|
|
sp := &cdiv1.StorageProfile{}
|
|
exists, err := getResource(ctx, c, "", sc.Name, sp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if exists {
|
|
return sp.Status.SnapshotClass, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// GetDriverFromVolume returns the CSI driver name for a PVC
|
|
func GetDriverFromVolume(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim) (*string, error) {
|
|
if pvc.Spec.VolumeName == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
pv := &corev1.PersistentVolume{}
|
|
exists, err := getResource(ctx, c, "", pvc.Spec.VolumeName, pv)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !exists || pv.Spec.ClaimRef == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
if pv.Spec.ClaimRef.Namespace != pvc.Namespace ||
|
|
pv.Spec.ClaimRef.Name != pvc.Name {
|
|
return nil, fmt.Errorf("pvc does not match volume claim ref")
|
|
}
|
|
|
|
if pv.Spec.CSI == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return &pv.Spec.CSI.Driver, nil
|
|
}
|
|
|
|
// GetCommonDriver returns the name of the CSI driver shared by all PVCs
|
|
func GetCommonDriver(ctx context.Context, c client.Client, pvcs ...*corev1.PersistentVolumeClaim) (*string, error) {
|
|
var result *string
|
|
|
|
for _, pvc := range pvcs {
|
|
driver, err := GetDriverFromVolume(ctx, c, pvc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if driver == nil {
|
|
sc, err := GetStorageClassForClaim(ctx, c, pvc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if sc == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
driver = &sc.Provisioner
|
|
}
|
|
|
|
if result == nil {
|
|
result = driver
|
|
}
|
|
|
|
if *result != *driver {
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func getCommonSnapshotClass(ctx context.Context, c client.Client, pvcs ...*corev1.PersistentVolumeClaim) (*string, error) {
|
|
var result *string
|
|
|
|
for _, pvc := range pvcs {
|
|
sc, err := getSnapshotClassForClaim(ctx, c, pvc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if sc == nil {
|
|
return nil, nil
|
|
}
|
|
if result == nil {
|
|
result = sc
|
|
} else if *result != *sc {
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GetCompatibleVolumeSnapshotClass returns a VolumeSnapshotClass name that works for all PVCs
|
|
// Note the last PVC passed is considered the target PVC for logs and events
|
|
func GetCompatibleVolumeSnapshotClass(ctx context.Context, c client.Client, log logr.Logger, recorder record.EventRecorder, pvcs ...*corev1.PersistentVolumeClaim) (*string, error) {
|
|
targetClaim := pvcs[len(pvcs)-1]
|
|
driver, err := GetCommonDriver(ctx, c, pvcs...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if driver == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
snapshotClassName, err := getCommonSnapshotClass(ctx, c, pvcs...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cc.GetVolumeSnapshotClass(context.TODO(), c, targetClaim, *driver, snapshotClassName, log, recorder)
|
|
}
|
|
|
|
// SameVolumeMode returns true if all pvcs have the same volume mode
|
|
func SameVolumeMode(pvc1 *corev1.PersistentVolumeClaim, others ...*corev1.PersistentVolumeClaim) bool {
|
|
vm := util.ResolveVolumeMode(pvc1.Spec.VolumeMode)
|
|
for _, pvc := range others {
|
|
if util.ResolveVolumeMode(pvc.Spec.VolumeMode) != vm {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func getResource(ctx context.Context, c client.Client, namespace, name string, obj client.Object) (bool, error) {
|
|
obj.SetNamespace(namespace)
|
|
obj.SetName(name)
|
|
|
|
err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
|
|
if err != nil {
|
|
if k8serrors.IsNotFound(err) {
|
|
return false, nil
|
|
}
|
|
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func dataVolumeGetter(ctx context.Context, c client.Client) func(name, namespace string) (*cdiv1.DataVolume, error) {
|
|
return func(name, namespace string) (*cdiv1.DataVolume, error) {
|
|
obj := &cdiv1.DataVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: namespace,
|
|
Name: name,
|
|
},
|
|
}
|
|
|
|
err := c.Get(ctx, client.ObjectKeyFromObject(obj), obj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return obj, nil
|
|
}
|
|
}
|
|
|
|
func checkQuotaExceeded(r record.EventRecorder, owner client.Object, err error) {
|
|
if cc.ErrQuotaExceeded(err) {
|
|
r.Event(owner, corev1.EventTypeWarning, cc.ErrExceededQuota, err.Error())
|
|
}
|
|
}
|
|
|
|
func isClaimBoundOrWFFC(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim) (bool, error) {
|
|
if cc.IsBound(pvc) {
|
|
return true, nil
|
|
}
|
|
|
|
sc, err := GetStorageClassForClaim(ctx, c, pvc)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if sc == nil {
|
|
return false, fmt.Errorf("no storageclass for pvc")
|
|
}
|
|
|
|
if sc.VolumeBindingMode != nil && *sc.VolumeBindingMode == storagev1.VolumeBindingWaitForFirstConsumer {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|