cocoon-operator

CocoonHibernation reconcile loop

Spec.Desire What the reconciler does Terminal phase
Hibernate meta.HibernateState(true).Apply on the target pod, then poll until vk-cocoon reports lifecycle-state=hibernated with lifecycle-observed-generation covering the pod’s current CocoonSet generation stamp, AND Registry.HasManifest(vmName, meta.HibernateSnapshotTag) sees the snapshot — or hibernateTimeout (3 minutes) trips. The timeout starts when the CR enters Hibernating, which happens on the first pass even when the registry probe fails, and a probe that keeps failing counts against it, so an unreachable registry ends in Failed instead of starving the phase behind the requeue backoff. vk writes state and observed-generation atomically, so the gate rejects both a stale tag from a prior suspend cycle and a lagging informer snapshot of a prior round. A probe error (transport / 5xx / auth) surfaces as a returned error so controller-runtime logs + retries with backoff. Hibernated
Wake Clear meta.HibernateState if the annotation is still set (idempotent — the patch is skipped once already cleared, so requeue cycles don’t spam informer events), then wait for the container to be Running with a freshly written VMID, settle Active, and drop the hibernation snapshot tag from the registry. A failed tag delete is returned and retried on later passes of the settled CR, so a registry that refuses it neither holds the CR out of Active nor leaks the tag. A wake that does not complete within wakeTimeout (5 minutes) is escalated to Phase=Failed with the timeout in the Ready condition message. Active

Reverse desires serialize: a desire flipped back mid-transition (Hibernate while Waking, Wake while Hibernating) first drives the in-flight transition to its terminal phase, then starts the reversal. Without this, the previous round’s still-present tag and lifecycle annotations would read as the new round’s completion.

On CR deletion the reconciler runs a finalizer (cocoonhibernation.cocoonset.cocoonstack.io/finalizer) that clears the :hibernate tag from the registry (if Status.VMName is set) before removing itself — unless another live CocoonHibernation still tracks the same VM (matched by Status.VMName or by the immutable spec.podRef, so a fresh duplicate that never reconciled still counts), in which case the last holder GCs the tag. This guard is a cache-backed backstop for installs missing cocoon-webhook, not a transactional guarantee; the webhook’s one-CR-per-pod rule is the real invariant. The cleanup is best-effort: a registry failure is logged at error level but never wedges CR deletion, so a snapshot can be orphaned if the registry is unreachable at that moment (and reclaimed at CocoonSet teardown GC).

There is no cocoon-vm-snapshots ConfigMap bridge — the registry is the single source of truth for hibernation state. Failure paths set Phase=Failed with the reason in the Ready condition rather than requeueing silently; the reconciler retries from Failed on its next pass, so a hibernate or wake that cannot complete cycles once per phase timeout instead of spinning; a podRef that names no pod is re-checked every 5 s and reports Pending, except that a CR already in Hibernated or Waking keeps that phase because it carries the restore intent. Both Hibernate and Wake Failed phases are recoverable: on re-entry from a non-deadline phase the reconciler refreshes the Ready condition’s LastTransitionTime so the budget resets cleanly (without the override, apimeta.SetStatusCondition would preserve the stale timestamp across the False → False transition and the recovered phase would trip the deadline on the next reconcile). Each retry emits a RetryRequested Normal Event so the recovery is visible in kubectl describe.

See Observability for the full Event reason and metrics list.