Fidelity
This page describes what mudflaps models faithfully and, just as importantly, what it deliberately does not.
Machine lifecycle
A machine moves through transient states into a resting state. mudflaps sets the transient state as soon as a request arrives, then advances to the resting state after a short, configurable delay driven by an injected clock. In tests the clock is a fake, so transitions are instantaneous and deterministic.
stateDiagram-v2
[*] --> creating: POST machines
[*] --> created: POST machines (skip_launch)
creating --> starting
starting --> started
starting --> failed: unpullable image
started --> stopping: POST stop
stopping --> stopped
stopped --> starting: POST start
started --> restarting: POST restart
restarting --> started
started --> suspending: POST suspend
suspending --> suspended
suspended --> starting: POST start (resume)
started --> replacing: POST update
replacing --> started: new instance_id
started --> destroying: DELETE
stopped --> destroying: DELETE
destroying --> [*]: reaped
The states the emulator actually enters fall into three groups:
- Resting:
created,started,stopped,suspended, andfailed(see Deploy-failure injection). - Transient:
creating,starting,stopping,restarting,suspending,replacing,destroying. - Terminal:
destroyed(a destroyed machine is reaped from the store), andreplacedfor a superseded version.
A skip_launch create rests directly at created rather than booting. A
destroyed machine is removed, so a subsequent operation returns 404 rather
than resurrecting it.
Deploy-failure injection
Real deploys don't always succeed: a machine flaps accepts can still fail to
pull its image at boot ("unable to pull image, not found, canceling deploy") and
never reach started. mudflaps models the Machines-API state layer, not an image
registry, so that failure can't arise on its own — but a client's deploy-failure
handling still needs testing.
A machine whose config.image is the sentinel mudflaps/unpullable (bare,
or with a :tag/@digest) settles into failed instead of started on its
next boot transition — create, start, restart, or an update to that image. The
decision is read from the machine's latest config at the moment the boot
transition fires, so an update back to a real image before then still boots
normally.
For a client that polls .../wait?state=started, this is a deploy that never
comes up: the wait returns 408 (it never falsely reports started), and a
GET of the machine shows state: failed. Everything stays deterministic on the
injected clock. Real flaps has no such magic image; the sentinel is the single
injection point that lets an applier exercise its failure/cancel/rollback path
offline.
Leases and nonces
A lease grants its holder exclusive rights to mutate a machine. The holder
proves ownership by echoing the lease nonce in the fly-machine-lease-nonce
header.
- Acquire (
POST .../lease) returns a nonce and an expiry. - A second acquire while a lease is held returns
409. - A mutating operation (update, start, stop, restart, suspend, cordon, destroy)
by a caller that does not present the held nonce is rejected
409. - Leases carry a TTL and expire on the injected clock; after expiry the machine is unguarded again.
- Refresh and release require the matching nonce.
The wait endpoint
GET /v1/apps/{app}/machines/{id}/wait?state=&version=&timeout= blocks until the
machine reaches a target state, then returns 200 {"ok":true}. state may be
repeated — any of the requested states satisfies the wait — and defaults to
started. version scopes the wait to a specific machine version
(instance_id); instance_id is also accepted. If the timeout elapses first it
returns 408. The timeout is given in seconds and clamped to the range
[1s, 60s], matching the real service.
Version churn
Updating a machine mints a fresh instance_id unique to the new version,
synchronously, so the update response already carries the new identity (only the
boot to started is async). The prior version is marked replaced, and mudflaps
keeps the instance-ID history internally.
What mudflaps does not do
mudflaps is an API emulator, not a virtualization platform. It does not:
- Run real microVMs or execute container images.
- Pull, resolve, or validate images beyond storing the reference you send.
- Provide real networking, private IPs that route, or service load balancing.
- Enforce quotas, billing, regions, or authentication.
- Persist across restarts; all state is in memory.
These boundaries are intentional. The goal is a faithful model of the API's stateful behavior, which is what a client needs to test against, without the weight of the real platform.