Files
spinifex/spinifex/vm/errors.go
T
Josh Siviour ec9f0e5f2d fix(eks,ebs): EKS multi-node create-cluster + EBS/instance lifecycle hardening (#487)
* feat(ec2,ebs): harden volume attach/detach + instance lifecycle state machine

Batch of related EBS/instance-lifecycle correctness fixes verified together.

- u94vs: add compare-and-swap (casUpdate/casPut via nats KV Update revision)
  to the multi-writer lifecycle KV sites (WriteTerminatedInstance,
  WriteStoppedInstance, WriteClusterShutdown); make the teardown-reaper
  read-modify-write atomic via a merge callback so retries never re-run
  cleaner side effects.
- yi2ed: add ClaimStoppedInstance, a revision-checked delete-as-claim on the
  stopped bucket performed before Allocate/Insert/Run, so a forwarded start
  and a local fallback cannot double-start one instance onto the same volume.
- siv-473: make AttachVolume idempotent — return attached success when the
  volume is already attached to the requesting instance; a conflicting
  requested device returns InvalidParameterValue.
- siv-462: reconnect now re-asserts in-use for all attached volumes (boot and
  non-boot data volumes), not just boot, so a daemon restart no longer leaves
  stateful-pod data volumes split-brain; EFI pflash is skipped.
- 9w2mv: wait for the QMP DEVICE_DELETED event (bounded) before blockdev-del
  instead of a blind sleep, so a slow guest unplug no longer exhausts retries.
- soyah: poll (50ms) instead of fixed 1s sleeps in KillProcess and the
  viperblockd nbdkit confirm-running path, cutting volume-lifecycle latency.
- siv-481: a cross-node start forward that times out now falls back to a local
  start; this is safe because the yi2ed claim is the single serialization point.

* fix(eks): create per-account and leader KV buckets with cluster replica count

The EKS per-account KV bucket and the leader lease bucket were created at
Replicas=1 and never upgraded, since they're created lazily after
upgradeJetStreamReplicas runs at startup. On multi-node clusters this leaves
their single-replica Raft group transiently leaderless during formation,
causing KV timeouts that surface as a deterministic 503 on ListClusters.

Thread clusterSize through GetOrCreateAccountBucket/InitLeaderBucket (backed
by a new utils.GetOrCreateKVBucketWithReplicas) so both buckets are created
at max(clusterSize,1) replicas, matching every other node-count-scaled
JetStream stream.
2026-07-09 16:49:31 +10:00

56 lines
2.7 KiB
Go

package vm
import "errors"
// ErrInstanceNotFound is returned by manager methods that look up an
// instance by id when no entry exists in the running map.
var ErrInstanceNotFound = errors.New("instance not found")
// ErrInvalidTransition is returned when a lifecycle method cannot run
// because the instance's current state does not permit the target
// transition (e.g. Stop on an already-stopped instance).
var ErrInvalidTransition = errors.New("invalid state transition")
// ErrAttachmentLimitExceeded is returned by AttachVolume when the
// instance has no free /dev/sd[f-p] slot to assign.
var ErrAttachmentLimitExceeded = errors.New("attachment limit exceeded")
// ErrVolumeNotAttached is returned by DetachVolume when the supplied
// volumeID is not present in the instance's EBSRequests.
var ErrVolumeNotAttached = errors.New("volume not attached to instance")
// ErrVolumeNotDetachable is returned by DetachVolume when the target
// volume is a boot or EFI volume that cannot be hot-unplugged.
var ErrVolumeNotDetachable = errors.New("volume cannot be detached")
// ErrVolumeDeviceMismatch is returned by DetachVolume when the caller
// supplies a device name that does not match the recorded attachment.
var ErrVolumeDeviceMismatch = errors.New("volume device name mismatch")
// ErrMountAmbiguous is returned by VolumeMounter.MountOne when the
// backend acknowledged the mount but returned an empty URI. The mount may
// or may not have started serving NBD; AttachVolume must invoke UnmountOne
// defensively so a half-started backend mount is not orphaned.
var ErrMountAmbiguous = errors.New("ebs.mount succeeded with empty URI")
// ErrENINotAttached is returned by HotUnplugENI when the supplied ENI id
// is not present in the instance's ENIRequests.AttachedByENIID map.
var ErrENINotAttached = errors.New("ENI not attached to instance")
// ErrENIPipelineTimeout is returned by HotPlugENI / HotUnplugENI when the
// query-pci poll did not observe the expected materialization (attach) or
// removal (detach) within the configured budget.
var ErrENIPipelineTimeout = errors.New("ENI hot-plug pipeline timed out")
// ErrQMPUnavailable is returned by hot-plug entry points when the
// instance has no live QMPClient (daemon restart mid-launch, terminated
// VM, etc).
var ErrQMPUnavailable = errors.New("QMP unavailable for instance")
// ErrStoppedInstanceClaimed is returned by StateStore.ClaimStoppedInstance
// when a concurrent caller already claimed (or the record was otherwise
// removed from) the stopped-instance store for the given id. The caller
// lost the race and must not proceed to allocate resources or launch qemu
// for that instance.
var ErrStoppedInstanceClaimed = errors.New("stopped instance already claimed")