What a placement move does on its hosts
What a move does on its hosts
A move needs its source to answer: it stops the source's units, copies its
states and fences its store over the source's channel. When the source does
not answer on any SSH path (switched off, asleep, off the network), each of
those steps stands until its caller gives up, so the service stays placed on
a host nobody reaches. Two commands take from such a host instead, each run
on the host that takes, each refused while the source answers on any path
(then it is moved), and each recorded in placement history:
stado placement role take SERVICE --reason WHY [--check] [--json]takes a role of the host Stado process. This host must hold the registry authority (stado placement move --authority); its own object API serves the role, and the registry from its last-known-good copy is written there. What the silent store held and this one does not is named asnot_recovered.stado placement take SERVICE... --reason WHY [--check] [--json]takes a placement profile. Each SSH path of the source is probed once under the channel's own connect bound; one that answers refuses it (<source>, where <profile> is placed, answers over <path>; move it with its states instead: stado placement move <services> --to-host <here>). SSH closed is not the units stopped, so each probe URL the profile declares for the source is then asked at every network name the source is known by (each SSH path's host and each declared host name, with the URL's own port and path), within the refresh interval this host's resolver declares; an HTTP answer of any status refuses it, since a copy started here would serve beside the source and the two would write diverging state (<source> still serves <service> at <url>, although no SSH path of it answers; … stop it on <source>, or move the profile once <source> answers over SSH). A resolver that declares no refresh interval refuses it too, naming that. This host must be one of the profile's hosts (placement profile "<profile>" does not support destination "<here>"; prepare it with stado placement standby …) and must hold every required state, since nothing can copy it from the source (<here> holds none of $HOME/<path>, which <profile> requires, and <source> does not answer to give it; put it on <here> first, or move the profile once <source> answers); an optional state it lacks is listed asnot_recovered. For every state it holds, the receipt gives the age of this host's copy, the newest modification time of the file or of anything in the tree (state_age:{"<state>": {"modified", "seconds_ago"}},<state>: <here>'s copy was last written <time> (<n>s ago); what <source> took after that is not in itin text): every write the source took after it is lost. A routing unit on the source refuses it, since nothing can retire it there; routes on other hosts are applied as a move applies them. The profile's units then start here in its start order, every probe must answer 2xx, and one registry commit places the profile here, the same change a move commits. The source's units are not stopped: the receipt names them underunretired_source(still on <source>: <unit>; retire it there before <source> serves again). A profile carrying the fleet vault is refused, since its owner key cannot be rotated without the source.--checkreads the source's paths and services and this host's states, and starts or writes nothing (state: would_take);--jsonprints{"state", "profile", "from_host", "to_host", "reason", "state_age", "not_recovered", "unretired_source", "registry_generation"}. Real journey:tests/placement/take_profile.rs.
A destination that already holds its own file or tree at a state path keeps
it. The move writes the source's state over that path behind a backup, and
once the move commits the backup is renamed to
<path>.replaced-by-placement-<transaction> beside the moved state instead of
being deleted, because what the destination held there was never part of the
source. The receipt lists each kept path under kept_replaced_state (kept what <host> held there before the move: <path> in text). Every backup is
settled at once and listed in the profile's order. A backup that could
not be settled is listed under unsettled_state with its cause and stays
under its .pre-stado-placement-<transaction> name; the move itself is
committed either way. A path the destination did not hold leaves nothing
behind, and a rollback before the commit restores the destination's own
file in place as before.
Every step of a move is its own wait on stderr, opened before the step starts
and closed with how long it took: czekam: placement move <transaction> of <profile>: <step>; gdzie: <host>; rodzaj: host; …, then koniec czekania: …; trwalo: <seconds>s or blad czekania: …; przyczyna: <cause>. The steps are
claim the transaction and commit the placement (on the registry),
preflight (on both hosts), copy <state> while it serves, fence <unit>,
read what changed in <state> and retire <unit> (on the source), and
write <state>, apply routes and start <unit> (on the destination); each
health probe is its own wait on its unit answering 2xx. The preflight and
every copy <state> while it serves run before claim the transaction: an
open transaction refuses the profile's services to every consumer, so the
first copy is made while they still resolve and answer. The claim is then laid
on the registry as it stands after the copy, and refused, with nothing
claimed or fenced, when the profile is no longer placed on the source. After
the fence each state is read again into the same copy, compared by content
(rsync --checksum), so rsync sends only what changed in between and the
services are refused for that difference, not for the whole copy. Every copy
of a profile runs at once on the source, then every write <state> at once on
the destination, each its own wait, so a profile with several stores stands
its longest transfer rather than their sum; when one of them fails the others
still end before the rollback starts, and the failure reported is the first
one in the profile's order. The copies this machine staged are removed once
the states are installed, when the move rolls back and when it fails before
its claim. A move that stands names the step it stands on, stado waits
lists it however the move's stderr was kept, and a later move's wait on the
same step opens with the time it is due, measured from the longest one that
ended.
The receipt lists every step the move ran with its host and time: --json
carries steps: [{step, host, seconds, succeeded}], including each probe as
probe <service>, and the text prints one line per step, <step> on <host>: <seconds>s, with (failed) on the step that ended without its answer. A
move that fails names the same lines in its error after steps the move ran:,
so the step that held a long move is read from its own receipt. The preflight
asks the source and the destination about each unit at once, so a slow source
no longer delays the destination's answer; when both refuse, the source's
refusal is the one reported. The running source's health probes, the
existence of every state file on the source and every routing unit are each
asked at once too, and the first refusal in the profile's order is the one
reported; a state read that fails refuses the move whether or not that state
is required. Before the source is fenced the preflight also measures what the
states take on the source (du -sk, at once for every state) and what the
destination's volume under each state root has free (df -Pk of the nearest
existing directory of the state's path), and refuses when the states written
under one root need more: <destination>: the volume the move writes <states> to has <free> KiB free, and they take <need> KiB on <source>; free space there, or declare a larger work root, before moving. Every state, a file as
much as a directory, is copied through the machine running the move with
rsync -a over the host's channel, never inside a host script's text, so its
size is also checked against the free space under that machine's staging
directory (its work root's placement, or ~/.stado/placement): <host>: <states> is copied through <dir> on this machine, which has <free> KiB free, and it takes <need> KiB on <source>; …; a machine the registry does not name
is not measured, which is said on stderr. A state is pushed to
<path>.placement-<transaction>.stage beside its path on the destination and
renamed over it, after the destination's previous file or tree was renamed to
the backup the rollback restores; a file state is installed owner-only (mode
600). A state that is not the kind the profile declares (a directory where a
file is declared, or the reverse) is refused naming its path. A file state
under the home is a file of that account: before it is pushed, each mention
of the source account's home in it, followed by /, a quote or the end of a
line, is rewritten in the copy to the destination account's home, and the
move prints <destination>: <n> mention(s) of <source home> in <path> now name <destination home> on stderr. Copied verbatim, a Weles worker.env
named STADO_BIN=<source home>/.stado/bin/stado on the destination, and the
moved service exited on start. A directory state, a work-rooted state and a
file that is not UTF-8 are carried byte for byte. The move's progress lines
are printed on stderr; with --json, stdout carries only its report.
A reading that returns no figure, or one that is not a number, refuses the
move naming the host.
A profile moved by a Stado that did not yet rewrite homes runs with its file states still naming the source account's home. They are rewritten in place, on the host the profile runs on, with:
stado placement rehome <profile> --host <host> --from-home <source home> [--json]
Each home-rooted file state of the profile is read, every mention of
<source home> is rewritten as a move rewrites it, and the file is installed
through the same stage and rename, owner-only; the file it replaces is kept
beside it as <path>.replaced-by-placement-<id>. One line per state reads
<state> <outcome> <mentions> <kept path or ->, the outcome being
rewritten, unchanged (no mention), absent (the host holds none) or
carried_as_is (a directory or work-rooted state, which is never
rewritten). Refused: a profile the registry does not hold, a host the profile
does not run on (<profile> runs on <placed>, not <host>), a --from-home
that is not an absolute path, and a --from-home equal to the host's own
home. Restart the profile's services afterwards so they read the rewritten
files.
Source: this website