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

* 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>
215 lines
5.7 KiB
Go
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
|
|
}
|