From 9ae657a2fbb0850773cf7d942ce1266e30e7b395 Mon Sep 17 00:00:00 2001 From: tonic Date: Thu, 25 Jun 2026 19:20:25 +0800 Subject: [PATCH 1/5] feat(meta): add restore-from-hibernate annotation Operator->vk-cocoon signal: a freshly created pod flagged with vm.cocoonstack.io/restore-from-hibernate restores its VM from the :hibernate snapshot (cross-node migration) instead of cloning from the base image. Adds the key plus a ReadRestoreFromHibernate reader. --- meta/hibernate.go | 9 +++++++++ meta/keys.go | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/meta/hibernate.go b/meta/hibernate.go index c73e314..8db1c2f 100644 --- a/meta/hibernate.go +++ b/meta/hibernate.go @@ -27,3 +27,12 @@ func ReadHibernateState(pod *corev1.Pod) HibernateState { } return HibernateState(pod.Annotations[AnnotationHibernate] == annotationTrue) } + +// ReadRestoreFromHibernate reports whether the pod is flagged to restore its VM +// from the :hibernate snapshot instead of cloning from the base image. +func ReadRestoreFromHibernate(pod *corev1.Pod) bool { + if pod == nil { + return false + } + return pod.Annotations[AnnotationRestoreFromHibernate] == annotationTrue +} diff --git a/meta/keys.go b/meta/keys.go index 42a4516..65cbb3e 100644 --- a/meta/keys.go +++ b/meta/keys.go @@ -50,6 +50,10 @@ const ( AnnotationVNCPort = "vm.cocoonstack.io/vnc-port" // AnnotationHibernate signals "hibernate this VM" when set to "true". AnnotationHibernate = "vm.cocoonstack.io/hibernate" + // AnnotationRestoreFromHibernate signals that a freshly created pod must + // restore its VM from the :hibernate snapshot (cross-node migration) instead + // of cloning from the base image. Written by the operator on the rebuilt pod. + AnnotationRestoreFromHibernate = "vm.cocoonstack.io/restore-from-hibernate" // AnnotationForkFrom names a VM to fork the new VM from. AnnotationForkFrom = "vm.cocoonstack.io/fork-from" // AnnotationCloneFromDir names a host directory to clone the VM image from (vk-cocoon-specific). From 89d2211101cffb9d6dc85500dcd4a40993638705 Mon Sep 17 00:00:00 2001 From: tonic Date: Thu, 25 Jun 2026 19:42:17 +0800 Subject: [PATCH 2/5] feat(api): add CocoonSetSpec.nodeName for cross-node pinning Empty leaves scheduler placement within NodePool untouched; a value is translated operator-side into a hostname nodeAffinity (Pending if it won't fit) rather than a hard NodeName bind. Regenerates the CocoonSet CRD. --- apis/v1/cocoonset_types.go | 6 ++++++ apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/apis/v1/cocoonset_types.go b/apis/v1/cocoonset_types.go index 48709ff..d29d8bb 100644 --- a/apis/v1/cocoonset_types.go +++ b/apis/v1/cocoonset_types.go @@ -19,6 +19,12 @@ type CocoonSetSpec struct { // +kubebuilder:default=default NodePool string `json:"nodePool,omitempty"` + // NodeName pins the VM to a node (cross-node migrate). Empty = let the + // scheduler place it within NodePool and leave it alone; a value adds a + // hostname nodeAffinity so the pod lands there or stays Pending if it won't fit. + // +optional + NodeName string `json:"nodeName,omitempty"` + // +kubebuilder:validation:Required Agent AgentSpec `json:"agent"` diff --git a/apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml b/apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml index 037fba6..30db5b1 100644 --- a/apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml +++ b/apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml @@ -247,6 +247,12 @@ spec: required: - image type: object + nodeName: + description: |- + NodeName pins the VM to a node (cross-node migrate). Empty = let the + scheduler place it within NodePool and leave it alone; a value adds a + hostname nodeAffinity so the pod lands there or stays Pending if it won't fit. + type: string nodePool: default: default type: string From a4de5a595746c2f1a5d8e33e6b19342b59a5c846 Mon Sep 17 00:00:00 2001 From: tonic Date: Thu, 25 Jun 2026 20:16:33 +0800 Subject: [PATCH 3/5] feat(api): add CocoonSetPhaseMigrating Status phase the operator sets during a cross-node migration so the control plane can poll progress; back to Running when the VM is settled on the target. --- apis/v1/enums.go | 1 + 1 file changed, 1 insertion(+) diff --git a/apis/v1/enums.go b/apis/v1/enums.go index 5793d5a..f42c8ab 100644 --- a/apis/v1/enums.go +++ b/apis/v1/enums.go @@ -26,6 +26,7 @@ const ( CocoonSetPhaseScaling CocoonSetPhase = "Scaling" CocoonSetPhaseSuspending CocoonSetPhase = "Suspending" CocoonSetPhaseSuspended CocoonSetPhase = "Suspended" + CocoonSetPhaseMigrating CocoonSetPhase = "Migrating" CocoonSetPhaseFailed CocoonSetPhase = "Failed" ConnTypeSSH ConnType = "ssh" From 4cddae4a7af327956fc634714b2781893f544ce0 Mon Sep 17 00:00:00 2001 From: doge Date: Sun, 28 Jun 2026 23:51:04 +0800 Subject: [PATCH 4/5] feat(api): add macos to OSType (enum + IsValid + CRD validation) vk-cocoon's macos-support backend dispatches os=macos to the cocoon-macos QEMU/KVM provider, and cocoon-clouddesktop already offers macOS desktops, but the shared OSType only enumerated linux/windows/android -- so the generated CocoonSet CRD enum and the cocoon-webhook admission check (OSType.IsValid) rejected os=macos, blocking the feature end-to-end. Add OSMacos = "macos" to the constant set, IsValid(), and the +kubebuilder:validation:Enum marker. Downstream cocoon-webhook / cocoon-operator pick it up on rebuild. --- apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml | 2 ++ apis/v1/enums.go | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml b/apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml index 30db5b1..507748b 100644 --- a/apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml +++ b/apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml @@ -160,6 +160,7 @@ spec: - linux - windows - android + - macos type: string probePort: description: ProbePort overrides the default ICMP probe with a @@ -328,6 +329,7 @@ spec: - linux - windows - android + - macos type: string probePort: description: ProbePort overrides the default ICMP probe with diff --git a/apis/v1/enums.go b/apis/v1/enums.go index f42c8ab..2533ca7 100644 --- a/apis/v1/enums.go +++ b/apis/v1/enums.go @@ -16,6 +16,7 @@ const ( OSLinux OSType = "linux" OSWindows OSType = "windows" OSAndroid OSType = "android" + OSMacos OSType = "macos" SnapshotPolicyAlways SnapshotPolicy = "always" SnapshotPolicyMainOnly SnapshotPolicy = "main-only" @@ -43,7 +44,7 @@ const ( var ( agentModeValid = []AgentMode{AgentModeClone, AgentModeRun} toolboxModeValid = []ToolboxMode{ToolboxModeRun, ToolboxModeClone, ToolboxModeStatic} - osTypeValid = []OSType{OSLinux, OSWindows, OSAndroid} + osTypeValid = []OSType{OSLinux, OSWindows, OSAndroid, OSMacos} snapshotPolicyValid = []SnapshotPolicy{SnapshotPolicyAlways, SnapshotPolicyMainOnly, SnapshotPolicyNever} connTypeValid = []ConnType{ConnTypeSSH, ConnTypeRDP, ConnTypeVNC, ConnTypeADB} backendValid = []Backend{BackendCloudHypervisor, BackendFirecracker} @@ -58,7 +59,7 @@ type AgentMode string type ToolboxMode string // OSType defines the guest operating system type. -// +kubebuilder:validation:Enum=linux;windows;android +// +kubebuilder:validation:Enum=linux;windows;android;macos type OSType string // SnapshotPolicy defines when VM snapshots are taken. From 49feefc8bed1c143f8d785e411a9eb23f57003fc Mon Sep 17 00:00:00 2001 From: CMGS Date: Sun, 28 Jun 2026 23:57:05 +0800 Subject: [PATCH 5/5] fix(api): add Migrating to CocoonSetPhase validation enum The CocoonSetPhaseMigrating const was added but the +kubebuilder validation Enum marker (and thus the generated CRD status.phase enum) was not, so the API server rejected status.phase=Migrating and the cross-node migration state machine could never persist the phase. Add Migrating to the marker and regenerate the CRD. --- apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml | 1 + apis/v1/enums.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml b/apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml index 507748b..2c1f6a5 100644 --- a/apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml +++ b/apis/v1/crds/cocoonset.cocoonstack.io_cocoonsets.yaml @@ -530,6 +530,7 @@ spec: - Scaling - Suspending - Suspended + - Migrating - Failed type: string readyAgents: diff --git a/apis/v1/enums.go b/apis/v1/enums.go index 2533ca7..0e7dac1 100644 --- a/apis/v1/enums.go +++ b/apis/v1/enums.go @@ -67,7 +67,7 @@ type OSType string type SnapshotPolicy string // CocoonSetPhase represents the lifecycle phase of a CocoonSet. -// +kubebuilder:validation:Enum=Pending;Running;Scaling;Suspending;Suspended;Failed +// +kubebuilder:validation:Enum=Pending;Running;Scaling;Suspending;Suspended;Migrating;Failed type CocoonSetPhase string // ConnType is the connection protocol advertised for a VM. Empty