containerized-data-importer/pkg/controller/clone/prep-claim.go
alromeros 4c911fed27
Fix PVC deletion logic in populators (#3362)
* Fix PVC' deletion logic in CDI populators

This commit ensures that we only proceed with PVC' deletion after confirming that the target PVC is rebound.

Signed-off-by: Alvaro Romero <alromero@redhat.com>

* Refactor IsBound so we check PVC status instead of spec

PVC status should be the definitive source to know whether the PVC is bound or not. This commit updates our IsBound function to use status instead of spec.

It also updates unit tests and related code to match the new behavior.

Signed-off-by: Alvaro Romero <alromero@redhat.com>

* Add check for lost target PVC in populators

Signed-off-by: Alvaro Romero <alromero@redhat.com>

---------

Signed-off-by: Alvaro Romero <alromero@redhat.com>
2024-08-22 22:22:35 +02:00

215 lines
5.7 KiB
Go

package clone
import (
"context"
"fmt"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"kubevirt.io/containerized-data-importer/pkg/common"
cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
"kubevirt.io/containerized-data-importer/pkg/util"
)
// PrepClaimPhaseName is the name of the prep claim phase
const PrepClaimPhaseName = "PrepClaim"
// PrepClaimPhase is responsible for prepping a PVC for rebind
type PrepClaimPhase struct {
Owner client.Object
DesiredClaim *corev1.PersistentVolumeClaim
Image string
PullPolicy corev1.PullPolicy
InstallerLabels map[string]string
OwnershipLabel string
Client client.Client
Log logr.Logger
Recorder record.EventRecorder
}
var _ Phase = &PrepClaimPhase{}
// Name returns the name of the phase
func (p *PrepClaimPhase) Name() string {
return PrepClaimPhaseName
}
// Reconcile ensures that a pvc is bound and resized if necessary
func (p *PrepClaimPhase) Reconcile(ctx context.Context) (*reconcile.Result, error) {
actualClaim := &corev1.PersistentVolumeClaim{}
pvcExists, err := getResource(ctx, p.Client, p.DesiredClaim.Namespace, p.DesiredClaim.Name, actualClaim)
if err != nil {
return nil, err
}
if !pvcExists {
return nil, fmt.Errorf("claim %s/%s does not exist", p.DesiredClaim.Namespace, p.DesiredClaim.Name)
}
podName := fmt.Sprintf("prep-%s", string(p.Owner.GetUID()))
pod := &corev1.Pod{}
podExists, err := getResource(ctx, p.Client, p.DesiredClaim.Namespace, podName, pod)
if err != nil {
return nil, err
}
podRequired := false
requestedSize, hasRequested := p.DesiredClaim.Spec.Resources.Requests[corev1.ResourceStorage]
currentSize, hasCurrent := actualClaim.Spec.Resources.Requests[corev1.ResourceStorage]
actualSize, hasActual := actualClaim.Status.Capacity[corev1.ResourceStorage]
if !hasRequested || !hasCurrent {
return nil, fmt.Errorf("requested PVC sizes missing")
}
p.Log.V(3).Info("Expand sizes", "req", requestedSize, "cur", currentSize, "act", actualSize)
if !hasActual {
if cc.IsBound(actualClaim) {
return nil, fmt.Errorf("actual PVC size missing")
}
p.Log.V(3).Info("prep pod required to force bind")
podRequired = true
} else {
if currentSize.Cmp(requestedSize) < 0 {
p.Log.V(3).Info("Updating resource requests to", "size", requestedSize)
actualClaim.Spec.Resources.Requests[corev1.ResourceStorage] = requestedSize
if err := p.Client.Update(ctx, actualClaim); err != nil {
return nil, err
}
// come back once pvc is updated
return &reconcile.Result{}, nil
}
if actualSize.Cmp(requestedSize) < 0 {
p.Log.V(3).Info("prep pod required to do resize")
podRequired = true
}
}
p.Log.V(3).Info("Prep status", "podRequired", podRequired, "podExists", podExists)
if !podRequired && !podExists {
// all done finally
return nil, nil
}
if podExists && pod.Status.Phase == corev1.PodSucceeded {
p.Log.V(3).Info("Prep pod succeeded, deleting")
if err := p.Client.Delete(ctx, pod); err != nil {
return nil, err
}
}
if podRequired && !podExists {
p.Log.V(3).Info("creating prep pod")
if err := p.createPod(ctx, podName, actualClaim); err != nil {
return nil, err
}
}
// pod is running
return &reconcile.Result{}, nil
}
func (p *PrepClaimPhase) createPod(ctx context.Context, name string, pvc *corev1.PersistentVolumeClaim) error {
resourceRequirements, err := cc.GetDefaultPodResourceRequirements(p.Client)
if err != nil {
return err
}
imagePullSecrets, err := cc.GetImagePullSecrets(p.Client)
if err != nil {
return err
}
workloadNodePlacement, err := cc.GetWorkloadNodePlacement(ctx, p.Client)
if err != nil {
return err
}
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: pvc.Namespace,
Annotations: map[string]string{
cc.AnnCreatedBy: "yes",
},
Labels: map[string]string{
common.CDILabelKey: common.CDILabelValue,
common.CDIComponentLabel: "cdi-populator-prep",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "dummy",
Image: p.Image,
ImagePullPolicy: p.PullPolicy,
Command: []string{"/bin/bash"},
Args: []string{"-c", "echo", "'hello cdi'"},
},
},
ImagePullSecrets: imagePullSecrets,
RestartPolicy: corev1.RestartPolicyOnFailure,
Volumes: []corev1.Volume{
{
Name: cc.DataVolName,
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: pvc.Name,
},
},
},
},
NodeSelector: workloadNodePlacement.NodeSelector,
Tolerations: workloadNodePlacement.Tolerations,
Affinity: workloadNodePlacement.Affinity,
},
}
util.SetRecommendedLabels(pod, p.InstallerLabels, "cdi-controller")
if pvc.Spec.VolumeMode != nil && *pvc.Spec.VolumeMode == corev1.PersistentVolumeBlock {
pod.Spec.Containers[0].VolumeDevices = cc.AddVolumeDevices()
} else {
pod.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{
{
Name: cc.DataVolName,
MountPath: common.ClonerMountPath,
},
}
}
if resourceRequirements != nil {
pod.Spec.Containers[0].Resources = *resourceRequirements
}
if pvc.Annotations[cc.AnnSelectedNode] != "" {
pod.Spec.NodeName = pvc.Annotations[cc.AnnSelectedNode]
}
if p.OwnershipLabel != "" {
AddOwnershipLabel(p.OwnershipLabel, pod, p.Owner)
}
cc.CopyAllowedAnnotations(pvc, pod)
cc.SetRestrictedSecurityContext(&pod.Spec)
if err := p.Client.Create(ctx, pod); err != nil {
return err
}
return nil
}