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

* Bump k8s/OpenShift/ctrl-runtime/lifecycle-sdk & make deps-update
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
* Operator: adapt for dependency bump
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
* Controller: adapt watch calls for dependency bump
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
* Controller: adapt to ctrl-runtime's cache API changes
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
* Operator: fix unit tests by deleting resources properly in fake client
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
* Controller: fix unit tests by deleting resources properly in fake client
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
* Controller: adapt to fake client honoring status subresource
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
* Fix codegen script & make generate
There are some issues in the new script, so we
will still use the deprecated one.
More context in f4d1a5431b
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
* Functests: Adapt to NamespacedName now implementing MarshalLog
ns/name -> {"name":"name","namespace":"ns"}
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
* Functests & API server: address deprecation of wait.PollImmediate
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
---------
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
837 lines
27 KiB
Go
837 lines
27 KiB
Go
package clone
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"sync"
|
|
|
|
"github.com/go-logr/logr"
|
|
snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
storagev1 "k8s.io/api/storage/v1"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/client-go/tools/record"
|
|
"sigs.k8s.io/controller-runtime/pkg/cache"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/controller"
|
|
"sigs.k8s.io/controller-runtime/pkg/handler"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
"sigs.k8s.io/controller-runtime/pkg/source"
|
|
|
|
cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
|
|
cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
|
|
)
|
|
|
|
const (
|
|
// CloneValidationFailed reports that a clone wasn't admitted by our validation mechanism (reason)
|
|
CloneValidationFailed = "CloneValidationFailed"
|
|
|
|
// MessageCloneValidationFailed reports that a clone wasn't admitted by our validation mechanism (message)
|
|
MessageCloneValidationFailed = "The clone doesn't meet the validation requirements"
|
|
|
|
// CloneWithoutSource reports that the source of a clone doesn't exists (reason)
|
|
CloneWithoutSource = "CloneWithoutSource"
|
|
|
|
// MessageCloneWithoutSource reports that the source of a clone doesn't exists (message)
|
|
MessageCloneWithoutSource = "The source %s %s doesn't exist"
|
|
|
|
// NoVolumeSnapshotClass reports that no compatible volumesnapshotclass was found (reason)
|
|
NoVolumeSnapshotClass = "NoVolumeSnapshotClass"
|
|
|
|
// MessageNoVolumeSnapshotClass reports that no compatible volumesnapshotclass was found (message)
|
|
MessageNoVolumeSnapshotClass = "No compatible volumesnapshotclass found"
|
|
|
|
// IncompatibleVolumeModes reports that the volume modes of source and target are incompatible (reason)
|
|
IncompatibleVolumeModes = "IncompatibleVolumeModes"
|
|
|
|
// MessageIncompatibleVolumeModes reports that the volume modes of source and target are incompatible (message)
|
|
MessageIncompatibleVolumeModes = "The volume modes of source and target are incompatible"
|
|
|
|
// NoVolumeExpansion reports that no volume expansion is possible (reason)
|
|
NoVolumeExpansion = "NoVolumeExpansion"
|
|
|
|
// MessageNoVolumeExpansion reports that no volume expansion is possible (message)
|
|
MessageNoVolumeExpansion = "No volume expansion is possible"
|
|
|
|
// NoProvisionerMatch reports that the storageclass provisioner does not match the volumesnapshotcontent driver (reason)
|
|
NoProvisionerMatch = "NoProvisionerMatch"
|
|
|
|
// MessageNoProvisionerMatch reports that the storageclass provisioner does not match the volumesnapshotcontent driver (message)
|
|
MessageNoProvisionerMatch = "The storageclass provisioner does not match the volumesnapshotcontent driver"
|
|
|
|
// IncompatibleProvisioners reports that the provisioners are incompatible (reason)
|
|
IncompatibleProvisioners = "IncompatibleProvisioners"
|
|
|
|
// MessageIncompatibleProvisioners reports that the provisioners are incompatible (message)
|
|
MessageIncompatibleProvisioners = "Provisioners are incompatible"
|
|
)
|
|
|
|
// Planner plans clone operations
|
|
type Planner struct {
|
|
RootObjectType client.ObjectList
|
|
OwnershipLabel string
|
|
UIDField string
|
|
Image string
|
|
PullPolicy corev1.PullPolicy
|
|
InstallerLabels map[string]string
|
|
Client client.Client
|
|
Recorder record.EventRecorder
|
|
Controller controller.Controller
|
|
GetCache func() cache.Cache
|
|
|
|
watchingCore bool
|
|
watchingSnapshots bool
|
|
watchMutex sync.Mutex
|
|
}
|
|
|
|
// Phase is the interface implemented by all clone phases
|
|
type Phase interface {
|
|
Name() string
|
|
Reconcile(context.Context) (*reconcile.Result, error)
|
|
}
|
|
|
|
// PhaseStatus contains phase status data
|
|
type PhaseStatus struct {
|
|
Progress string
|
|
Annotations map[string]string
|
|
}
|
|
|
|
// StatusReporter allows a phase to report status
|
|
type StatusReporter interface {
|
|
Status(context.Context) (*PhaseStatus, error)
|
|
}
|
|
|
|
// list of all possible (core) types created
|
|
var coreTypesCreated = []client.Object{
|
|
&corev1.PersistentVolumeClaim{},
|
|
&corev1.Pod{},
|
|
}
|
|
|
|
// all types that may have been created
|
|
var listTypesToDelete = []client.ObjectList{
|
|
&corev1.PersistentVolumeClaimList{},
|
|
&corev1.PodList{},
|
|
&snapshotv1.VolumeSnapshotList{},
|
|
}
|
|
|
|
// AddCoreWatches watches "core" types
|
|
func (p *Planner) AddCoreWatches(log logr.Logger) error {
|
|
p.watchMutex.Lock()
|
|
defer p.watchMutex.Unlock()
|
|
if p.watchingCore {
|
|
return nil
|
|
}
|
|
|
|
for _, obj := range coreTypesCreated {
|
|
if err := p.watchOwned(log, obj); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
p.watchingCore = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// ChooseStrategyArgs are args for ChooseStrategy function
|
|
type ChooseStrategyArgs struct {
|
|
Log logr.Logger
|
|
TargetClaim *corev1.PersistentVolumeClaim
|
|
DataSource *cdiv1.VolumeCloneSource
|
|
}
|
|
|
|
// ChooseStrategyResult is result returned by ChooseStrategy function
|
|
type ChooseStrategyResult struct {
|
|
Strategy cdiv1.CDICloneStrategy
|
|
FallbackReason *string
|
|
}
|
|
|
|
// ChooseStrategy picks the strategy for a clone op
|
|
func (p *Planner) ChooseStrategy(ctx context.Context, args *ChooseStrategyArgs) (*ChooseStrategyResult, error) {
|
|
if IsDataSourcePVC(args.DataSource.Spec.Source.Kind) {
|
|
args.Log.V(3).Info("Getting strategy for PVC source")
|
|
return p.computeStrategyForSourcePVC(ctx, args)
|
|
}
|
|
if IsDataSourceSnapshot(args.DataSource.Spec.Source.Kind) {
|
|
args.Log.V(3).Info("Getting strategy for Snapshot source")
|
|
return p.computeStrategyForSourceSnapshot(ctx, args)
|
|
}
|
|
return nil, fmt.Errorf("unsupported datasource")
|
|
}
|
|
|
|
// PlanArgs are args to plan clone op for populator
|
|
type PlanArgs struct {
|
|
Log logr.Logger
|
|
TargetClaim *corev1.PersistentVolumeClaim
|
|
DataSource *cdiv1.VolumeCloneSource
|
|
Strategy cdiv1.CDICloneStrategy
|
|
}
|
|
|
|
// Plan creates phases for populator clone
|
|
func (p *Planner) Plan(ctx context.Context, args *PlanArgs) ([]Phase, error) {
|
|
if args.Strategy == cdiv1.CloneStrategySnapshot {
|
|
if err := p.watchSnapshots(ctx, args.Log); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if IsDataSourcePVC(args.DataSource.Spec.Source.Kind) {
|
|
if args.Strategy == cdiv1.CloneStrategyHostAssisted {
|
|
args.Log.V(3).Info("Planning host assisted clone from PVC")
|
|
|
|
return p.planHostAssistedFromPVC(ctx, args)
|
|
} else if args.Strategy == cdiv1.CloneStrategySnapshot {
|
|
args.Log.V(3).Info("Planning snapshot clone from PVC")
|
|
|
|
return p.planSnapshotFromPVC(ctx, args)
|
|
} else if args.Strategy == cdiv1.CloneStrategyCsiClone {
|
|
args.Log.V(3).Info("Planning csi clone from PVC")
|
|
|
|
return p.planCSIClone(ctx, args)
|
|
}
|
|
}
|
|
|
|
if IsDataSourceSnapshot(args.DataSource.Spec.Source.Kind) {
|
|
if args.Strategy == cdiv1.CloneStrategyHostAssisted {
|
|
args.Log.V(3).Info("Planning host assisted clone from Snapshot")
|
|
|
|
return p.planHostAssistedFromSnapshot(ctx, args)
|
|
} else if args.Strategy == cdiv1.CloneStrategySnapshot {
|
|
args.Log.V(3).Info("Planning Smart clone from Snapshot")
|
|
|
|
return p.planSmartCloneFromSnapshot(ctx, args)
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("unknown strategy/source %s", string(args.Strategy))
|
|
}
|
|
|
|
// Cleanup cleans up after a clone op
|
|
func (p *Planner) Cleanup(ctx context.Context, log logr.Logger, owner client.Object) error {
|
|
log.V(3).Info("Cleaning up for obj", "obj", owner)
|
|
|
|
for _, lt := range listTypesToDelete {
|
|
ls, err := labels.Parse(fmt.Sprintf("%s=%s", p.OwnershipLabel, string(owner.GetUID())))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
lo := &client.ListOptions{
|
|
LabelSelector: ls,
|
|
}
|
|
if err := cc.BulkDeleteResources(ctx, p.Client, lt, lo); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Planner) watchSnapshots(ctx context.Context, log logr.Logger) error {
|
|
p.watchMutex.Lock()
|
|
defer p.watchMutex.Unlock()
|
|
if p.watchingSnapshots {
|
|
return nil
|
|
}
|
|
|
|
vsl := &snapshotv1.VolumeSnapshotList{}
|
|
lo := &client.ListOptions{Limit: 1}
|
|
if err := p.Client.List(ctx, vsl, lo); err != nil {
|
|
if meta.IsNoMatchError(err) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if err := p.watchOwned(log, &snapshotv1.VolumeSnapshot{}); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.V(3).Info("watching volumesnapshots now")
|
|
p.watchingSnapshots = true
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Planner) watchOwned(log logr.Logger, obj client.Object) error {
|
|
objList := p.RootObjectType.DeepCopyObject().(client.ObjectList)
|
|
if err := p.Controller.Watch(source.Kind(p.GetCache(), obj), handler.EnqueueRequestsFromMapFunc(
|
|
func(ctx context.Context, obj client.Object) (reqs []reconcile.Request) {
|
|
uid, ok := obj.GetLabels()[p.OwnershipLabel]
|
|
if !ok {
|
|
return
|
|
}
|
|
matchingFields := client.MatchingFields{
|
|
p.UIDField: uid,
|
|
}
|
|
if err := p.Client.List(ctx, objList, matchingFields); err != nil {
|
|
log.Error(err, "Unable to list resource", "matchingFields", matchingFields)
|
|
return
|
|
}
|
|
sv := reflect.ValueOf(objList).Elem()
|
|
iv := sv.FieldByName("Items")
|
|
for i := 0; i < iv.Len(); i++ {
|
|
o := iv.Index(i).Addr().Interface().(client.Object)
|
|
reqs = append(reqs, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: o.GetNamespace(),
|
|
Name: o.GetName(),
|
|
},
|
|
})
|
|
}
|
|
return
|
|
}),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *Planner) computeStrategyForSourcePVC(ctx context.Context, args *ChooseStrategyArgs) (*ChooseStrategyResult, error) {
|
|
res := &ChooseStrategyResult{}
|
|
|
|
if ok, err := p.validateTargetStorageClassAssignment(ctx, args); !ok || err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sourceClaim := &corev1.PersistentVolumeClaim{}
|
|
exists, err := getResource(ctx, p.Client, args.DataSource.Namespace, args.DataSource.Spec.Source.Name, sourceClaim)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !exists {
|
|
message := fmt.Sprintf(MessageCloneWithoutSource, "pvc", args.DataSource.Spec.Source.Name)
|
|
p.Recorder.Event(args.TargetClaim, corev1.EventTypeWarning, CloneWithoutSource, message)
|
|
args.Log.V(3).Info("Source PVC does not exist, cannot compute strategy")
|
|
return nil, nil
|
|
}
|
|
|
|
if err = p.validateSourcePVC(args, sourceClaim); err != nil {
|
|
p.Recorder.Event(args.TargetClaim, corev1.EventTypeWarning, CloneValidationFailed, MessageCloneValidationFailed)
|
|
args.Log.V(3).Info("Validation failed", "target", args.TargetClaim, "source", sourceClaim)
|
|
return nil, err
|
|
}
|
|
|
|
strategy := cdiv1.CloneStrategySnapshot
|
|
cs, err := GetGlobalCloneStrategyOverride(ctx, p.Client)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if cs != nil {
|
|
strategy = *cs
|
|
} else if args.TargetClaim.Spec.StorageClassName != nil {
|
|
sp := &cdiv1.StorageProfile{}
|
|
exists, err := getResource(ctx, p.Client, "", *args.TargetClaim.Spec.StorageClassName, sp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !exists {
|
|
args.Log.V(3).Info("missing storageprofile for", "name", *args.TargetClaim.Spec.StorageClassName)
|
|
}
|
|
|
|
if exists && sp.Status.CloneStrategy != nil {
|
|
strategy = *sp.Status.CloneStrategy
|
|
}
|
|
}
|
|
|
|
if strategy == cdiv1.CloneStrategySnapshot {
|
|
n, err := GetCompatibleVolumeSnapshotClass(ctx, p.Client, args.Log, p.Recorder, sourceClaim, args.TargetClaim)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if n == nil {
|
|
p.fallbackToHostAssisted(args.TargetClaim, res, NoVolumeSnapshotClass, MessageNoVolumeSnapshotClass)
|
|
return res, nil
|
|
}
|
|
}
|
|
|
|
res.Strategy = strategy
|
|
if strategy == cdiv1.CloneStrategySnapshot ||
|
|
strategy == cdiv1.CloneStrategyCsiClone {
|
|
if err := p.validateAdvancedClonePVC(ctx, args, res, sourceClaim); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (p *Planner) computeStrategyForSourceSnapshot(ctx context.Context, args *ChooseStrategyArgs) (*ChooseStrategyResult, error) {
|
|
res := &ChooseStrategyResult{}
|
|
|
|
if ok, err := p.validateTargetStorageClassAssignment(ctx, args); !ok || err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check that snapshot exists
|
|
sourceSnapshot := &snapshotv1.VolumeSnapshot{}
|
|
exists, err := getResource(ctx, p.Client, args.DataSource.Namespace, args.DataSource.Spec.Source.Name, sourceSnapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !exists {
|
|
message := fmt.Sprintf(MessageCloneWithoutSource, "snapshot", args.DataSource.Spec.Source.Name)
|
|
p.Recorder.Event(args.TargetClaim, corev1.EventTypeWarning, CloneWithoutSource, message)
|
|
args.Log.V(3).Info("Source Snapshot does not exist, cannot compute strategy")
|
|
return nil, nil
|
|
}
|
|
|
|
// Do snapshot and storage class validation
|
|
targetStorageClass, err := GetStorageClassForClaim(ctx, p.Client, args.TargetClaim)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if targetStorageClass == nil {
|
|
return nil, fmt.Errorf("target claim's storageclass doesn't exist, clone will not work")
|
|
}
|
|
valid, err := cc.ValidateSnapshotCloneProvisioners(ctx, p.Client, sourceSnapshot, targetStorageClass)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !valid {
|
|
p.fallbackToHostAssisted(args.TargetClaim, res, NoProvisionerMatch, MessageNoProvisionerMatch)
|
|
args.Log.V(3).Info("Provisioner differs, need to fall back to host assisted")
|
|
return res, nil
|
|
}
|
|
|
|
// Lastly, do size validation to determine whether to use dumb or smart cloning
|
|
valid, err = cc.ValidateSnapshotCloneSize(sourceSnapshot, &args.TargetClaim.Spec, targetStorageClass, args.Log)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !valid {
|
|
p.fallbackToHostAssisted(args.TargetClaim, res, NoVolumeExpansion, MessageNoVolumeExpansion)
|
|
return res, nil
|
|
}
|
|
res.Strategy = cdiv1.CloneStrategySnapshot
|
|
return res, nil
|
|
}
|
|
|
|
func (p *Planner) validateTargetStorageClassAssignment(ctx context.Context, args *ChooseStrategyArgs) (bool, error) {
|
|
if args.TargetClaim.Spec.StorageClassName == nil {
|
|
args.Log.V(3).Info("Target PVC has nil storage class, cannot compute strategy")
|
|
return false, nil
|
|
}
|
|
|
|
if *args.TargetClaim.Spec.StorageClassName == "" {
|
|
args.Log.V(3).Info("Target PVC has \"\" storage class, cannot compute strategy")
|
|
return false, fmt.Errorf("claim has emptystring storageclass, will not work")
|
|
}
|
|
|
|
sc, err := GetStorageClassForClaim(ctx, p.Client, args.TargetClaim)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if sc == nil {
|
|
args.Log.V(3).Info("Target PVC has no storage class, cannot compute strategy")
|
|
return false, fmt.Errorf("target storage class not found")
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (p *Planner) validateSourcePVC(args *ChooseStrategyArgs, sourceClaim *corev1.PersistentVolumeClaim) error {
|
|
_, permissive := args.TargetClaim.Annotations[cc.AnnPermissiveClone]
|
|
if permissive {
|
|
args.Log.V(3).Info("permissive clone annotation found, skipping size validation")
|
|
return nil
|
|
}
|
|
|
|
if err := cc.ValidateRequestedCloneSize(sourceClaim.Spec.Resources, args.TargetClaim.Spec.Resources); err != nil {
|
|
p.Recorder.Eventf(args.TargetClaim, corev1.EventTypeWarning, cc.ErrIncompatiblePVC, err.Error())
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Planner) validateAdvancedClonePVC(ctx context.Context, args *ChooseStrategyArgs, res *ChooseStrategyResult, sourceClaim *corev1.PersistentVolumeClaim) error {
|
|
driver, err := GetCommonDriver(ctx, p.Client, sourceClaim, args.TargetClaim)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if driver == nil {
|
|
p.fallbackToHostAssisted(args.TargetClaim, res, IncompatibleProvisioners, MessageIncompatibleProvisioners)
|
|
args.Log.V(3).Info("CSIDrivers not compatible for advanced clone")
|
|
return nil
|
|
}
|
|
|
|
if !SameVolumeMode(sourceClaim, args.TargetClaim) {
|
|
p.fallbackToHostAssisted(args.TargetClaim, res, IncompatibleVolumeModes, MessageIncompatibleVolumeModes)
|
|
args.Log.V(3).Info("volume modes not compatible for advanced clone")
|
|
return nil
|
|
}
|
|
|
|
sc, err := GetStorageClassForClaim(ctx, p.Client, args.TargetClaim)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if sc == nil {
|
|
args.Log.V(3).Info("target storage class not found")
|
|
return fmt.Errorf("target storage class not found")
|
|
}
|
|
|
|
srcCapacity, hasSrcCapacity := sourceClaim.Status.Capacity[corev1.ResourceStorage]
|
|
targetRequest, hasTargetRequest := args.TargetClaim.Spec.Resources.Requests[corev1.ResourceStorage]
|
|
allowExpansion := sc.AllowVolumeExpansion != nil && *sc.AllowVolumeExpansion
|
|
if !hasSrcCapacity || !hasTargetRequest {
|
|
return fmt.Errorf("source/target size info missing")
|
|
}
|
|
|
|
if srcCapacity.Cmp(targetRequest) < 0 && !allowExpansion {
|
|
p.fallbackToHostAssisted(args.TargetClaim, res, NoVolumeExpansion, MessageNoVolumeExpansion)
|
|
args.Log.V(3).Info("advanced clone not possible, no volume expansion")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Planner) fallbackToHostAssisted(targetClaim *corev1.PersistentVolumeClaim, res *ChooseStrategyResult, reason, message string) {
|
|
res.Strategy = cdiv1.CloneStrategyHostAssisted
|
|
res.FallbackReason = &message
|
|
p.Recorder.Event(targetClaim, corev1.EventTypeWarning, reason, message)
|
|
}
|
|
|
|
func (p *Planner) planHostAssistedFromPVC(ctx context.Context, args *PlanArgs) ([]Phase, error) {
|
|
desiredClaim := createDesiredClaim(args.DataSource.Namespace, args.TargetClaim)
|
|
|
|
hcp := &HostClonePhase{
|
|
Owner: args.TargetClaim,
|
|
Namespace: args.DataSource.Namespace,
|
|
SourceName: args.DataSource.Spec.Source.Name,
|
|
DesiredClaim: desiredClaim,
|
|
ImmediateBind: true,
|
|
OwnershipLabel: p.OwnershipLabel,
|
|
Preallocation: cc.GetPreallocation(ctx, p.Client, args.DataSource.Spec.Preallocation),
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
if args.DataSource.Spec.PriorityClassName != nil {
|
|
hcp.PriorityClassName = *args.DataSource.Spec.PriorityClassName
|
|
}
|
|
|
|
rp := &RebindPhase{
|
|
SourceNamespace: desiredClaim.Namespace,
|
|
SourceName: desiredClaim.Name,
|
|
TargetNamespace: args.TargetClaim.Namespace,
|
|
TargetName: args.TargetClaim.Name,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
return []Phase{hcp, rp}, nil
|
|
}
|
|
|
|
func (p *Planner) planHostAssistedFromSnapshot(ctx context.Context, args *PlanArgs) ([]Phase, error) {
|
|
sourceSnapshot := &snapshotv1.VolumeSnapshot{}
|
|
exists, err := getResource(ctx, p.Client, args.DataSource.Namespace, args.DataSource.Spec.Source.Name, sourceSnapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !exists {
|
|
return nil, fmt.Errorf("source claim does not exist")
|
|
}
|
|
|
|
sourceClaimForDumbClone, err := createTempSourceClaim(ctx, args.DataSource.Namespace, args.TargetClaim, sourceSnapshot, p.Client)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cfsp := &SnapshotClonePhase{
|
|
Owner: args.TargetClaim,
|
|
Namespace: args.DataSource.Namespace,
|
|
SourceName: args.DataSource.Spec.Source.Name,
|
|
DesiredClaim: sourceClaimForDumbClone,
|
|
OwnershipLabel: p.OwnershipLabel,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
pcp := &PrepClaimPhase{
|
|
Owner: args.TargetClaim,
|
|
DesiredClaim: sourceClaimForDumbClone.DeepCopy(),
|
|
Image: p.Image,
|
|
PullPolicy: p.PullPolicy,
|
|
InstallerLabels: p.InstallerLabels,
|
|
OwnershipLabel: p.OwnershipLabel,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
desiredClaim := createDesiredClaim(args.DataSource.Namespace, args.TargetClaim)
|
|
|
|
hcp := &HostClonePhase{
|
|
Owner: args.TargetClaim,
|
|
Namespace: sourceClaimForDumbClone.Namespace,
|
|
SourceName: sourceClaimForDumbClone.Name,
|
|
DesiredClaim: desiredClaim,
|
|
ImmediateBind: true,
|
|
OwnershipLabel: p.OwnershipLabel,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
if args.DataSource.Spec.PriorityClassName != nil {
|
|
hcp.PriorityClassName = *args.DataSource.Spec.PriorityClassName
|
|
}
|
|
|
|
rp := &RebindPhase{
|
|
SourceNamespace: desiredClaim.Namespace,
|
|
SourceName: desiredClaim.Name,
|
|
TargetNamespace: args.TargetClaim.Namespace,
|
|
TargetName: args.TargetClaim.Name,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
return []Phase{cfsp, pcp, hcp, rp}, nil
|
|
}
|
|
|
|
func (p *Planner) planSmartCloneFromSnapshot(ctx context.Context, args *PlanArgs) ([]Phase, error) {
|
|
sourceSnapshot := &snapshotv1.VolumeSnapshot{}
|
|
exists, err := getResource(ctx, p.Client, args.DataSource.Namespace, args.DataSource.Spec.Source.Name, sourceSnapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !exists {
|
|
return nil, fmt.Errorf("source claim does not exist")
|
|
}
|
|
|
|
desiredClaim := createDesiredClaim(args.DataSource.Namespace, args.TargetClaim)
|
|
cfsp := &SnapshotClonePhase{
|
|
Owner: args.TargetClaim,
|
|
Namespace: args.DataSource.Namespace,
|
|
SourceName: args.DataSource.Spec.Source.Name,
|
|
DesiredClaim: desiredClaim.DeepCopy(),
|
|
OwnershipLabel: p.OwnershipLabel,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
pcp := &PrepClaimPhase{
|
|
Owner: args.TargetClaim,
|
|
DesiredClaim: desiredClaim.DeepCopy(),
|
|
Image: p.Image,
|
|
PullPolicy: p.PullPolicy,
|
|
InstallerLabels: p.InstallerLabels,
|
|
OwnershipLabel: p.OwnershipLabel,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
rp := &RebindPhase{
|
|
SourceNamespace: desiredClaim.Namespace,
|
|
SourceName: desiredClaim.Name,
|
|
TargetNamespace: args.TargetClaim.Namespace,
|
|
TargetName: args.TargetClaim.Name,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
return []Phase{cfsp, pcp, rp}, nil
|
|
}
|
|
|
|
func (p *Planner) planSnapshotFromPVC(ctx context.Context, args *PlanArgs) ([]Phase, error) {
|
|
sourceClaim := &corev1.PersistentVolumeClaim{}
|
|
exists, err := getResource(ctx, p.Client, args.DataSource.Namespace, args.DataSource.Spec.Source.Name, sourceClaim)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !exists {
|
|
return nil, fmt.Errorf("source claim does not exist")
|
|
}
|
|
|
|
vsc, err := GetCompatibleVolumeSnapshotClass(ctx, p.Client, args.Log, p.Recorder, args.TargetClaim)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if vsc == nil {
|
|
return nil, fmt.Errorf("no compatible volumesnapshotclass")
|
|
}
|
|
|
|
sp := &SnapshotPhase{
|
|
Owner: args.TargetClaim,
|
|
SourceNamespace: args.DataSource.Namespace,
|
|
SourceName: args.DataSource.Spec.Source.Name,
|
|
TargetName: fmt.Sprintf("tmp-snapshot-%s", string(args.TargetClaim.UID)),
|
|
VolumeSnapshotClass: *vsc,
|
|
OwnershipLabel: p.OwnershipLabel,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
desiredClaim := createDesiredClaim(args.DataSource.Namespace, args.TargetClaim)
|
|
cfsp := &SnapshotClonePhase{
|
|
Owner: args.TargetClaim,
|
|
Namespace: args.DataSource.Namespace,
|
|
SourceName: sp.TargetName,
|
|
DesiredClaim: desiredClaim.DeepCopy(),
|
|
OwnershipLabel: p.OwnershipLabel,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
pcp := &PrepClaimPhase{
|
|
Owner: args.TargetClaim,
|
|
DesiredClaim: desiredClaim.DeepCopy(),
|
|
Image: p.Image,
|
|
PullPolicy: p.PullPolicy,
|
|
InstallerLabels: p.InstallerLabels,
|
|
OwnershipLabel: p.OwnershipLabel,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
rp := &RebindPhase{
|
|
SourceNamespace: desiredClaim.Namespace,
|
|
SourceName: desiredClaim.Name,
|
|
TargetNamespace: args.TargetClaim.Namespace,
|
|
TargetName: args.TargetClaim.Name,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
return []Phase{sp, cfsp, pcp, rp}, nil
|
|
}
|
|
|
|
func (p *Planner) planCSIClone(ctx context.Context, args *PlanArgs) ([]Phase, error) {
|
|
desiredClaim := createDesiredClaim(args.DataSource.Namespace, args.TargetClaim)
|
|
cp := &CSIClonePhase{
|
|
Owner: args.TargetClaim,
|
|
Namespace: args.DataSource.Namespace,
|
|
SourceName: args.DataSource.Spec.Source.Name,
|
|
DesiredClaim: desiredClaim.DeepCopy(),
|
|
OwnershipLabel: p.OwnershipLabel,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
pcp := &PrepClaimPhase{
|
|
Owner: args.TargetClaim,
|
|
DesiredClaim: desiredClaim.DeepCopy(),
|
|
Image: p.Image,
|
|
PullPolicy: p.PullPolicy,
|
|
InstallerLabels: p.InstallerLabels,
|
|
OwnershipLabel: p.OwnershipLabel,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
rp := &RebindPhase{
|
|
SourceNamespace: desiredClaim.Namespace,
|
|
SourceName: desiredClaim.Name,
|
|
TargetNamespace: args.TargetClaim.Namespace,
|
|
TargetName: args.TargetClaim.Name,
|
|
Client: p.Client,
|
|
Log: args.Log,
|
|
Recorder: p.Recorder,
|
|
}
|
|
|
|
return []Phase{cp, pcp, rp}, nil
|
|
}
|
|
|
|
func createDesiredClaim(namespace string, targetClaim *corev1.PersistentVolumeClaim) *corev1.PersistentVolumeClaim {
|
|
targetCpy := targetClaim.DeepCopy()
|
|
desiredClaim := &corev1.PersistentVolumeClaim{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: namespace,
|
|
Name: fmt.Sprintf("tmp-pvc-%s", string(targetClaim.UID)),
|
|
Labels: targetCpy.Labels,
|
|
Annotations: targetCpy.Annotations,
|
|
},
|
|
Spec: targetCpy.Spec,
|
|
}
|
|
desiredClaim.Spec.DataSource = nil
|
|
desiredClaim.Spec.DataSourceRef = nil
|
|
|
|
return desiredClaim
|
|
}
|
|
|
|
func createTempSourceClaim(ctx context.Context, namespace string, targetClaim *corev1.PersistentVolumeClaim, snapshot *snapshotv1.VolumeSnapshot, client client.Client) (*corev1.PersistentVolumeClaim, error) {
|
|
scName, err := getStorageClassNameForTempSourceClaim(ctx, snapshot, client)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Get the appropriate size from the snapshot
|
|
targetCpy := targetClaim.DeepCopy()
|
|
if snapshot.Status == nil || snapshot.Status.RestoreSize == nil || snapshot.Status.RestoreSize.Sign() == -1 {
|
|
return nil, fmt.Errorf("snapshot has no RestoreSize")
|
|
}
|
|
restoreSize := snapshot.Status.RestoreSize
|
|
if restoreSize.IsZero() {
|
|
reqSize := targetCpy.Spec.Resources.Requests[corev1.ResourceStorage]
|
|
restoreSize = &reqSize
|
|
}
|
|
|
|
desiredClaim := &corev1.PersistentVolumeClaim{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: namespace,
|
|
Name: fmt.Sprintf("tmp-source-pvc-%s", string(targetClaim.UID)),
|
|
Labels: targetCpy.Labels,
|
|
Annotations: targetCpy.Annotations,
|
|
},
|
|
Spec: targetCpy.Spec,
|
|
}
|
|
desiredClaim.Spec.DataSource = nil
|
|
desiredClaim.Spec.DataSourceRef = nil
|
|
desiredClaim.Spec.StorageClassName = &scName
|
|
desiredClaim.Spec.Resources.Requests[corev1.ResourceStorage] = *restoreSize
|
|
|
|
return desiredClaim, nil
|
|
}
|
|
|
|
func getStorageClassNameForTempSourceClaim(ctx context.Context, snapshot *snapshotv1.VolumeSnapshot, client client.Client) (string, error) {
|
|
var matches []string
|
|
|
|
if snapshot.Status == nil || snapshot.Status.BoundVolumeSnapshotContentName == nil {
|
|
return "", fmt.Errorf("volumeSnapshotContent name not found")
|
|
}
|
|
volumeSnapshotContent := &snapshotv1.VolumeSnapshotContent{}
|
|
if err := client.Get(ctx, types.NamespacedName{Name: *snapshot.Status.BoundVolumeSnapshotContentName}, volumeSnapshotContent); err != nil {
|
|
return "", err
|
|
}
|
|
// Attempting to get a storageClass compatible with the source snapshot
|
|
storageClasses := &storagev1.StorageClassList{}
|
|
if err := client.List(ctx, storageClasses); err != nil {
|
|
return "", err
|
|
}
|
|
for _, storageClass := range storageClasses.Items {
|
|
if storageClass.Provisioner == volumeSnapshotContent.Spec.Driver {
|
|
matches = append(matches, storageClass.Name)
|
|
}
|
|
}
|
|
if len(matches) == 0 {
|
|
return "", fmt.Errorf("unable to find a valid storage class for the temporal source claim")
|
|
}
|
|
sort.Strings(matches)
|
|
return matches[0], nil
|
|
}
|