Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions internal/controller/impvm_coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,17 @@ var _ = Describe("countRunningVMs", func() {
counts := countRunningVMs(vms)
Expect(counts["n1"]).To(Equal(3))
})

It("skips Suspended (memory freed) but counts Suspending/Resuming (still resident)", func() {
vms := []impdevv1alpha1.ImpVM{
{Spec: impdevv1alpha1.ImpVMSpec{NodeName: "n1"}, Status: impdevv1alpha1.ImpVMStatus{Phase: impdevv1alpha1.VMPhaseRunning}},
{Spec: impdevv1alpha1.ImpVMSpec{NodeName: "n1"}, Status: impdevv1alpha1.ImpVMStatus{Phase: impdevv1alpha1.VMPhaseSuspended}},
{Spec: impdevv1alpha1.ImpVMSpec{NodeName: "n1"}, Status: impdevv1alpha1.ImpVMStatus{Phase: impdevv1alpha1.VMPhaseSuspending}},
{Spec: impdevv1alpha1.ImpVMSpec{NodeName: "n1"}, Status: impdevv1alpha1.ImpVMStatus{Phase: impdevv1alpha1.VMPhaseResuming}},
}
// Running + Suspending + Resuming are resident (3); Suspended is not counted.
Expect(countRunningVMs(vms)["n1"]).To(Equal(3))
})
})

// ─── filterByNodeSelector ────────────────────────────────────────────────────
Expand Down
17 changes: 12 additions & 5 deletions internal/controller/impvm_scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,20 @@ import (

const labelImpEnabled = "imp/enabled"

// sumUsedResources returns (usedVCPU, usedMemMiB) per node for active VMs.
// VMs in Failed, Succeeded, or Terminating phase are excluded.
// sumUsedResources returns (usedVCPU, usedMemMiB) per node for VMs that are
// resident on the node. VMs in Failed, Succeeded, or Terminating phase are
// excluded (vacating or gone), and so are Suspended VMs: their memory is freed
// to node-local disk, so they no longer consume schedulable capacity. This lets
// the scheduler overcommit — packing new VMs into the RAM freed by suspension.
// VMs whose class cannot be resolved are skipped (best-effort).
func sumUsedResources(ctx context.Context, c client.Client, vms []impdevv1alpha1.ImpVM) map[string][2]int64 {
result := make(map[string][2]int64)
for _, vm := range vms {
switch vm.Status.Phase {
case impdevv1alpha1.VMPhaseFailed,
impdevv1alpha1.VMPhaseSucceeded,
impdevv1alpha1.VMPhaseTerminating:
impdevv1alpha1.VMPhaseTerminating,
impdevv1alpha1.VMPhaseSuspended:
continue
}
if vm.Spec.NodeName == "" {
Expand Down Expand Up @@ -319,14 +323,17 @@ func filterSchedulable(nodes []corev1.Node) []corev1.Node {
}

// countRunningVMs counts VMs per node that are actively occupying capacity.
// Excludes Failed, Succeeded, and Terminating — all of which are vacating or already gone.
// Excludes Failed, Succeeded, and Terminating (vacating or already gone) and
// Suspended (memory freed to disk), so suspended VMs do not count against the
// per-node VM cap and the scheduler can overcommit into their freed capacity.
func countRunningVMs(vms []impdevv1alpha1.ImpVM) map[string]int {
counts := make(map[string]int)
for _, vm := range vms {
switch vm.Status.Phase {
case impdevv1alpha1.VMPhaseFailed,
impdevv1alpha1.VMPhaseSucceeded,
impdevv1alpha1.VMPhaseTerminating:
impdevv1alpha1.VMPhaseTerminating,
impdevv1alpha1.VMPhaseSuspended:
continue
}
if vm.Spec.NodeName != "" {
Expand Down
Loading