Workflow concepts
The command reference documents each command’s own arguments. This page
covers the cross-cutting behaviour that sits behind those commands — how fan-out
works, how reference hosts are drawn from the pool, and how locking keeps
concurrent testers from colliding. It applies equally to the mtui REPL and the
mtui-mcp tools, since both dispatch through the same engine.
Templates and the active template
Loading a testreport with load_template adds it to the
session as a template; previously loaded templates stay loaded and the new one
becomes active. Each template owns its own reference hosts and its own report.
list_templates shows the loaded set (the active one
marked *), switch changes the active one, and
unload drops one (closing only its host connections).
Session-level commands (load_template, unload, switch, list_templates,
list_refhosts, updates, config, whoami, set_log_level, help, quit)
take no template flag: each runs exactly once, never fans out, and accepts
neither -T nor --all-templates.
Fan-out across templates
When more than one template is loaded, a command falls into one of two groups:
- Read/annotate commands (
list_*,show_*,openqa_*,checkers,checkout,export,set_workflow,comment, …) fan out across every loaded template by default — none of them mutates shared remote state, and re-running one is always safe. Each template’s output is prefixed with an=== <RRID> ===banner so results stay attributable. - Host-mutating and remote-write commands (
update,prepare,downgrade,install,uninstall,set_repo,run,reboot,lock,unlock,add_host,remove_host,get,put,approve,reject,assign,unassign,request_review,commit) never implicitly fan out: bare, they act on exactly one template — the active one in the REPL, or the sole loaded one — and refuse rather than guess when several are loaded headlessly and none is named.
Scope a single call three ways:
-T <RRID>/--template <RRID>— act on one loaded template only.--all-templates— force fan-out explicitly, on either group. Mutually exclusive with-T.--all-templates=false— suppress fan-out on a read/annotate command, narrowing it to the same single-template resolution the other group uses by default.
A headless caller (MCP) with several templates loaded and neither flag named
gets AmbiguousTemplate, not a silent guess — the error names the
loaded RRIDs and how to scope the call.
Failure handling is per-template: if a fanned-out command fails on one template it
keeps running on the rest and reports an aggregate failure at the end. When a
host-phase command (one that accepts -t) fans out without explicit -t hosts,
a loaded template with no connected host is skipped with a warning rather than
counted as a failure — so an unscoped lock/run/… still succeeds on the
templates that do have hosts. If every template is host-less the command ran
nowhere and fails with “No refhosts defined”. Naming a disconnected host with
-t <host> is still a per-template failure, and a -T-scoped call keeps
single-template error behaviour.
regenerate also runs exactly once, on the active template
by default; -T names another loaded one.
Over MCP the same model applies through per-call template= / all_templates=
parameters — see MCP server.
Reference-host pool selection
When add_host connects hosts from a testplatform (and during
the autoconnect that runs when a template loads), mtui connects one reference
host per test-target slot — a slot being a unique (product, version, arch, addons)
combination — rather than every matching candidate. So a testplatform that
resolves to several interchangeable hosts on the same architecture does not fan a
command out across all of them.
- The host drawn for each slot is chosen at random among the free candidates, spreading load across interchangeable refhosts.
- If the chosen host fails to connect, mtui automatically tries another
candidate from the same slot; only when every candidate for a slot is
unreachable or claimed does
add_hostname the slot and why in its result and leave that slot unconnected. - Under fan-out, each test-target slot draws a distinct free host from the
shared pool, arbitrated in-process so two templates never collide on the same
host. When every candidate for a slot is busy, the claim queues on the
exhausted pool according to
[lock] wait/[lock] wait_poll(see Configuration).
To search the inventory without connecting, use
list_refhosts — it reads the same source offline (no
SSH, no lock, no loaded report).
Product-drift check on connect
When a host connects, mtui checks that the products actually installed on it (from
/etc/products.d) match what refhosts.yml records for that host. Any drift — a
wrong or wrong-version base product, a wrong architecture, missing/unexpected
addons, or a dangling baseproduct symlink — is logged as a WARNING and the
host is kept (the check never aborts a connect). The qa product is ignored,
and hosts not listed in refhosts.yml are skipped silently.
Host state
Each connected host has a state, set with
set_host_state:
enabled— runs all issued commands (the default).disabled— runs nothing and prints nothing.
Commands designed to run in parallel (like run) always fan out
concurrently across enabled hosts.
Locking
Two independent locks coordinate concurrent testers on a shared fleet. Both use
the same on-host wire format (timestamp:user:pid[:comment]), so concurrent
mtui processes — and any other fleet tooling that reads the same files —
cooperate rather than clobber one another.
- Operation / zypper lock (
/var/lock/mtui.lock, PID-based) — guards serialized repository transactions (enabling/disabling the test repo, install/update/prepare/downgrade). Set it explicitly withlock; it is applied automatically around the flows that need it. Enabled locks are removed when the session exits. To make the lock effective for other sessions too, give it a comment (lock -c "<why>"). Such a reservation is never rewritten or released byrun,update,installorprepareon the host; onlyunlockremoves it. Templates loaded in the same process, and the sessions of onemtui-mcpserver, hold the operation lock against each other like separate users: a second template’srunorupdatewaits for (or, at[lock] wait = 0, refuses on) the first one’s hold. - Pool-claim lock (
/var/lock/mtui-pool.lock, RRID-based) — taken during host-pool selection to reserve a host for a template. List and remove them with the-p/--poolflag onlist_locks/unlock.
By default, lock/unlock act on the whole active template’s hosts; pass
-t <host> (repeatable) to scope either to a subset. Plain unlock releases
only the operation lock this session took: a lock it never took — another
user’s, or a live sibling template’s on a refhost shared inside the same
process — is left alone and reported skipped. A foreign lock points at
list_locks or --force; a sibling’s is named as held by this mtui process and
is released from the template that took it. unlock -f force-removes a lock held by another user or
session regardless of who took it. mtui also reaps a pre-existing lock older
than [lock] stale_age on connect (almost always left over from a crashed
session); see Configuration for reap_stale, stale_age,
wait, and wait_poll. The one exception is
list_refhosts --free: its connect is a read-only
survey, so it never reaps. Its lock column reports one of free, locked,
pool (free but pool-claimed), locked+pool, unreachable (the host could
not be probed), or unknown (the probe never completed for it); --json
keeps this vocabulary and adds a pool key: the claiming RRID, or null when
unclaimed.
Product-Increment autolock
When testing a Product Increment (PI) and [lock] pi_autolock is enabled (the
default), mtui locks each reference host — with a comment naming the request —
as it connects, for the life of the loaded report; the lock is released when
the report is unloaded or the session quits, matching how every non-PI
report’s pool claims already behave. A host added later with add_host
(including a pool-selected host, with no -t) is locked too, since the lock
comment is seeded on load and every connect path applies it. Review actions
(assign/unassign/approve/reject) do not lock or unlock hosts
themselves. A reboot clears /var/lock, so the per-host
testing lock is re-applied after the host comes back.
Assignment context on assign
After the assignment succeeds, assign prints best-effort
context from the TeReGen report API: the update’s live priority and deadline
and, when the report already carries assignment state, who currently holds or
has decided each review group (one line per group). This is informational only
and never blocks the action — its absence can also mean the lookup simply
failed, and the server caches the state for a few minutes, so a line may lag by
up to ~5 minutes.
Kernel export file names
A kernel export saves each openQA job’s install log as
<host>-zypper-<arch>.log and each LTP result as
<host>-<arch>-<test>.json. When several jobs of one export would write the
same file, each keeps its own file with the job ID inserted before the
extension (<host>-zypper-<arch>-job-<id>.log); a name only one job claims
never changes. Files from an earlier export are not deleted, so a plain
<host>-zypper-<arch>.log written before this naming existed can remain next to
the job-ID files.