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
13 changes: 12 additions & 1 deletion api/v1alpha1/impvm_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,16 @@ type ImpVMSpec struct {

// DesiredState is the requested run state. When set to "Suspended" the agent
// snapshots the VM to node-local storage and frees its memory; setting it back
// to "Running" resumes it from that snapshot on the same node.
// to "Running" resumes it from that snapshot on the same node. "ScaleToZero"
// makes suspension automatic: idle → suspend, first packet → resume.
// +optional
// +kubebuilder:default=Running
DesiredState VMDesiredState `json:"desiredState,omitempty"`

// IdleTimeout is how long a ScaleToZero VM must see no traffic before the agent
// auto-suspends it. Ignored unless desiredState is "ScaleToZero". Defaults to 5m.
// +optional
IdleTimeout *metav1.Duration `json:"idleTimeout,omitempty"`
}

// UserDataSource references a ConfigMap containing cloud-init user-data.
Expand Down Expand Up @@ -187,6 +193,11 @@ type ImpVMStatus struct {
// +optional
SuspendedAt *metav1.Time `json:"suspendedAt,omitempty"`

// LastActivityTime is the most recent time the agent observed traffic on the
// VM's interface. Used by ScaleToZero idle detection; observability only.
// +optional
LastActivityTime *metav1.Time `json:"lastActivityTime,omitempty"`

// RestartCount is the cumulative number of times this VM has been restarted.
// +optional
RestartCount int32 `json:"restartCount,omitempty"`
Expand Down
7 changes: 6 additions & 1 deletion api/v1alpha1/shared_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,19 @@ const (

// VMDesiredState is the operator/user-requested run state for an ImpVM.
// The agent drives the observed Phase toward this target.
// +kubebuilder:validation:Enum=Running;Suspended
// +kubebuilder:validation:Enum=Running;Suspended;ScaleToZero
type VMDesiredState string

const (
// VMDesiredStateRunning keeps the VM running (default).
VMDesiredStateRunning VMDesiredState = "Running"
// VMDesiredStateSuspended requests the VM be snapshotted and its memory freed.
VMDesiredStateSuspended VMDesiredState = "Suspended"
// VMDesiredStateScaleToZero is a mode, not a fixed target: the agent suspends
// the VM when it has been idle (no traffic) for spec.idleTimeout and resumes it
// automatically on the first inbound packet. status.Phase cycles Running↔Suspended
// underneath while desiredState stays ScaleToZero.
VMDesiredStateScaleToZero VMDesiredState = "ScaleToZero"
)

// Arch is the CPU architecture for a VM class.
Expand Down
9 changes: 9 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions charts/imp-crds/templates/impvms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ spec:
required:
- name
type: object
desiredState:
default: Running
description: |-
DesiredState is the requested run state. When set to "Suspended" the agent
snapshots the VM to node-local storage and frees its memory; setting it back
to "Running" resumes it from that snapshot on the same node. "ScaleToZero"
makes suspension automatic: idle → suspend, first packet → resume.
enum:
- Running
- Suspended
- ScaleToZero
type: string
env:
description: Env sets environment variables inside the VM via the
guest agent.
Expand Down Expand Up @@ -280,6 +292,14 @@ spec:
x-kubernetes-map-type: atomic
type: object
type: array
expireAfter:
description: |-
ExpireAfter is the maximum wall-clock runtime from first Running transition.
0 or unset disables automatic expiration. Minimum enabled value is 60s.
type: string
x-kubernetes-validations:
- message: expireAfter must be 0 (disabled) or at least 60s
rule: duration(self) == duration('0s') || duration(self) >= duration('60s')
guestAgent:
description: GuestAgent controls guest agent injection. Overrides
defaults when set.
Expand All @@ -289,6 +309,11 @@ spec:
to true when omitted.
type: boolean
type: object
idleTimeout:
description: |-
IdleTimeout is how long a ScaleToZero VM must see no traffic before the agent
auto-suspends it. Ignored unless desiredState is "ScaleToZero". Defaults to 5m.
type: string
image:
description: |-
Image is the OCI image used as the VM rootfs. CMD/ENTRYPOINT from the
Expand Down Expand Up @@ -494,6 +519,13 @@ spec:
type: integer
type: object
type: object
rescheduleOnNodeLoss:
description: |-
RescheduleOnNodeLoss opts this VM into automatic rescheduling when its
assigned node becomes unhealthy. Only takes effect when spec.lifecycle is
"persistent". The VM is NOT rescheduled if any PersistentVolumeClaim is
owned by it (PVCs cannot follow the VM to a new node).
type: boolean
restartPolicy:
description: |-
RestartPolicy overrides restart policy for this specific VM.
Expand Down Expand Up @@ -706,9 +738,21 @@ spec:
(informational).
format: date-time
type: string
expiresAt:
description: |-
ExpiresAt is the computed expiration timestamp (RunningAt + Spec.ExpireAfter).
Empty when expiration is disabled or VM has not reached Running yet.
format: date-time
type: string
ip:
description: IP is the IP address assigned to the VM by the ImpNetwork.
type: string
lastActivityTime:
description: |-
LastActivityTime is the most recent time the agent observed traffic on the
VM's interface. Used by ScaleToZero idle detection; observability only.
format: date-time
type: string
nextRetryAfter:
description: NextRetryAfter is the earliest time the controller will
attempt the next restart.
Expand All @@ -728,6 +772,9 @@ spec:
- Succeeded
- Failed
- RetryExhausted
- Suspending
- Suspended
- Resuming
type: string
restartCount:
description: RestartCount is the cumulative number of times this VM
Expand All @@ -754,6 +801,17 @@ spec:
Used to detect and time out stuck start attempts.
format: date-time
type: string
suspendSnapshotPath:
description: |-
SuspendSnapshotPath is the node-local directory holding the VM's suspend
snapshot (vm.state + vm.mem). Set when the VM is Suspended; the resume path
restores from here. Empty when the VM is not suspended.
type: string
suspendedAt:
description: SuspendedAt is the time the VM last transitioned to phase
Suspended.
format: date-time
type: string
type: object
type: object
served: true
Expand Down
15 changes: 14 additions & 1 deletion config/crd/bases/imp.dev_impvms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,12 @@ spec:
description: |-
DesiredState is the requested run state. When set to "Suspended" the agent
snapshots the VM to node-local storage and frees its memory; setting it back
to "Running" resumes it from that snapshot on the same node.
to "Running" resumes it from that snapshot on the same node. "ScaleToZero"
makes suspension automatic: idle → suspend, first packet → resume.
enum:
- Running
- Suspended
- ScaleToZero
type: string
env:
description: Env sets environment variables inside the VM via the
Expand Down Expand Up @@ -306,6 +308,11 @@ spec:
to true when omitted.
type: boolean
type: object
idleTimeout:
description: |-
IdleTimeout is how long a ScaleToZero VM must see no traffic before the agent
auto-suspends it. Ignored unless desiredState is "ScaleToZero". Defaults to 5m.
type: string
image:
description: |-
Image is the OCI image used as the VM rootfs. CMD/ENTRYPOINT from the
Expand Down Expand Up @@ -739,6 +746,12 @@ spec:
ip:
description: IP is the IP address assigned to the VM by the ImpNetwork.
type: string
lastActivityTime:
description: |-
LastActivityTime is the most recent time the agent observed traffic on the
VM's interface. Used by ScaleToZero idle detection; observability only.
format: date-time
type: string
nextRetryAfter:
description: NextRetryAfter is the earliest time the controller will
attempt the next restart.
Expand Down
10 changes: 10 additions & 0 deletions internal/webhook/v1alpha1/impvm_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,5 +173,15 @@ func validateImpVM(vm *impdevv1alpha1.ImpVM) field.ErrorList {
}
}

// idleTimeout gates ScaleToZero auto-suspend; too small a value thrashes
// against the resume latency, so floor it at 10s.
if vm.Spec.IdleTimeout != nil && vm.Spec.IdleTimeout.Duration < 10*time.Second {
errs = append(errs, field.Invalid(
field.NewPath("spec", "idleTimeout"),
vm.Spec.IdleTimeout.Duration.String(),
"idleTimeout must be at least 10s",
))
}

return errs
}
23 changes: 23 additions & 0 deletions internal/webhook/v1alpha1/impvm_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,29 @@ func TestImpVMWebhook_ValidateCreate_Valid_ClassRef(t *testing.T) {
}
}

func TestImpVMWebhook_ValidateCreate_IdleTimeoutTooSmall(t *testing.T) {
wh := &ImpVMWebhook{}
vm := newVM("", "my-class", "my-image")
vm.Spec.IdleTimeout = &metav1.Duration{Duration: 5 * time.Second}

_, err := wh.ValidateCreate(context.Background(), vm)
if err == nil {
t.Fatal("expected error for idleTimeout below 10s, got nil")
}
}

func TestImpVMWebhook_ValidateCreate_IdleTimeoutValid(t *testing.T) {
wh := &ImpVMWebhook{}
vm := newVM("", "my-class", "my-image")
vm.Spec.DesiredState = impdevv1alpha1.VMDesiredStateScaleToZero
vm.Spec.IdleTimeout = &metav1.Duration{Duration: 2 * time.Minute}

_, err := wh.ValidateCreate(context.Background(), vm)
if err != nil {
t.Errorf("expected no error for valid ScaleToZero+idleTimeout, got: %v", err)
}
}

func TestImpVMWebhook_ValidateCreate_Valid_TemplateRef(t *testing.T) {
wh := &ImpVMWebhook{}
vm := newVM("my-template", "", "") // templateRef only, no image required
Expand Down
Loading