diff --git a/images/virtualization-artifact/pkg/common/pod/pod.go b/images/virtualization-artifact/pkg/common/pod/pod.go index f7836d4c57..b9a9afe578 100644 --- a/images/virtualization-artifact/pkg/common/pod/pod.go +++ b/images/virtualization-artifact/pkg/common/pod/pod.go @@ -17,9 +17,14 @@ limitations under the License. package pod import ( + "context" + "slices" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" ) // MakeOwnerReference makes owner reference from a Pod @@ -108,6 +113,46 @@ func IsPodComplete(pod *corev1.Pod) bool { return pod != nil && pod.Status.Phase == corev1.PodSucceeded } +func GetLastPodEvent(ctx context.Context, clientObject client.Client, pod *corev1.Pod) (*corev1.Event, error) { + if pod == nil { + return nil, nil + } + + eventList := &corev1.EventList{} + err := clientObject.List(ctx, eventList, &client.ListOptions{ + Namespace: pod.Namespace, + FieldSelector: fields.SelectorFromSet(fields.Set{ + "involvedObject.name": pod.Name, + "involvedObject.kind": "Pod", + }), + }) + if err != nil { + return nil, err + } + + if len(eventList.Items) == 0 { + return nil, nil + } + + last := slices.MaxFunc(eventList.Items, func(a, b corev1.Event) int { + return a.LastTimestamp.Compare(b.LastTimestamp.Time) + }) + + return &last, nil +} + +func IsContainerCreating(pod *corev1.Pod) bool { + if pod.Status.Phase != corev1.PodPending { + return false + } + for _, cs := range pod.Status.ContainerStatuses { + if cs.State.Waiting != nil && cs.State.Waiting.Reason == "ContainerCreating" { + return true + } + } + return false +} + // QemuSubGID is the gid used as the qemu group in fsGroup const QemuSubGID = int64(107) diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/lifecycle.go b/images/virtualization-artifact/pkg/controller/vm/internal/lifecycle.go index 347068a476..8be9053a9d 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/lifecycle.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/lifecycle.go @@ -23,13 +23,13 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" virtv1 "kubevirt.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/deckhouse/virtualization-controller/pkg/common/annotations" + ccpod "github.com/deckhouse/virtualization-controller/pkg/common/pod" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/service" "github.com/deckhouse/virtualization-controller/pkg/controller/vm/internal/state" @@ -103,7 +103,6 @@ func (h *LifeCycleHandler) Handle(ctx context.Context, s state.VirtualMachineSta } log := logger.FromContext(ctx).With(logger.SlogHandler(nameLifeCycleHandler)) - h.syncRunning(ctx, changed, kvvm, kvvmi, pod, log) return reconcile.Result{}, nil } @@ -123,10 +122,10 @@ func (h *LifeCycleHandler) syncRunning(ctx context.Context, vm *v1alpha2.Virtual return } - if volumeError := h.checkPodVolumeErrors(ctx, vm, log); volumeError != nil { + if volumeError := h.checkVMPodVolumeErrors(ctx, vm, log); volumeError != nil { cb.Status(metav1.ConditionFalse). Reason(vmcondition.ReasonPodNotStarted). - Message(volumeError.Error()) + Message(fmt.Sprintf("Error attaching block devices to virtual machine: %s", volumeError.Error())) conditions.SetCondition(cb, &vm.Status.Conditions) return } @@ -138,7 +137,7 @@ func (h *LifeCycleHandler) syncRunning(ctx context.Context, vm *v1alpha2.Virtual if podScheduled.Message != "" { cb.Status(metav1.ConditionFalse). Reason(vmcondition.ReasonPodNotStarted). - Message(fmt.Sprintf("%s: %s", podScheduled.Reason, podScheduled.Message)) + Message(fmt.Sprintf("Could not schedule the virtual machine: %s: %s", podScheduled.Reason, podScheduled.Message)) conditions.SetCondition(cb, &vm.Status.Conditions) } @@ -210,11 +209,12 @@ func (h *LifeCycleHandler) syncRunning(ctx context.Context, vm *v1alpha2.Virtual } else { vm.Status.Node = "" } + cb.Reason(vmcondition.ReasonVirtualMachineNotRunning).Status(metav1.ConditionFalse) conditions.SetCondition(cb, &vm.Status.Conditions) } -func (h *LifeCycleHandler) checkPodVolumeErrors(ctx context.Context, vm *v1alpha2.VirtualMachine, log *slog.Logger) error { +func (h *LifeCycleHandler) checkVMPodVolumeErrors(ctx context.Context, vm *v1alpha2.VirtualMachine, log *slog.Logger) error { var podList corev1.PodList err := h.client.List(ctx, &podList, &client.ListOptions{ Namespace: vm.Namespace, @@ -224,51 +224,20 @@ func (h *LifeCycleHandler) checkPodVolumeErrors(ctx context.Context, vm *v1alpha }) if err != nil { log.Error("Failed to list pods", "error", err) - return nil + return err } - for i := range podList.Items { - if volumeErr := h.getPodVolumeError(ctx, &podList.Items[i], log); volumeErr != nil { - return volumeErr + for _, pod := range podList.Items { + if !ccpod.IsContainerCreating(&pod) { + continue } - } - - return nil -} - -func isContainerCreating(pod *corev1.Pod) bool { - if pod.Status.Phase != corev1.PodPending { - return false - } - for _, cs := range pod.Status.ContainerStatuses { - if cs.State.Waiting != nil && cs.State.Waiting.Reason == "ContainerCreating" { - return true + lastEvent, err := ccpod.GetLastPodEvent(ctx, h.client, &pod) + if err != nil { + log.Error("Failed to get last pod event", "error", err) + return err } - } - return false -} - -func (h *LifeCycleHandler) getPodVolumeError(ctx context.Context, pod *corev1.Pod, log *slog.Logger) error { - if !isContainerCreating(pod) { - return nil - } - - eventList := &corev1.EventList{} - err := h.client.List(ctx, eventList, &client.ListOptions{ - Namespace: pod.Namespace, - FieldSelector: fields.SelectorFromSet(fields.Set{ - "involvedObject.name": pod.Name, - "involvedObject.kind": "Pod", - }), - }) - if err != nil { - log.Error("Failed to list pod events", "error", err) - return nil - } - - for _, e := range eventList.Items { - if e.Type == corev1.EventTypeWarning && (e.Reason == watcher.ReasonFailedAttachVolume || e.Reason == watcher.ReasonFailedMount) { - return fmt.Errorf("%s: %s", e.Reason, e.Message) + if lastEvent != nil && (lastEvent.Reason == watcher.ReasonFailedAttachVolume || lastEvent.Reason == watcher.ReasonFailedMount) { + return fmt.Errorf("failed to attach volume: %s: %s", lastEvent.Reason, lastEvent.Message) } }