Lab Configuration¶
A lab is a set of hosts (and the routes between them) described in one or
more lab.json files. Each file lists every host and declared link in a
given location; each host entry declares which lab names it belongs to, so a
single file can serve multiple labs and a host can belong to more than one lab
at once. This page is the full per-host and per-link schema reference. For
repo-level settings (paths, libs, init modules) see Repository Setup.
Lab files¶
Each directory listed under the labs key in .otto/settings.toml may
contain a lab.json file. The file is a JSON object with two array
sections, hosts and links:
{
"hosts": [
{
"ip": "10.10.200.11",
"element": "carrot",
"board": "seed",
"creds": [{ "login": "vagrant", "password": "vagrant" }],
"labs": ["veggies"]
},
{
"ip": "192.0.2.1",
"element": "sprout",
"board": "seed",
"os_type": "zephyr",
"labs": ["embedded"]
}
],
"links": []
}
hosts schema is unchanged from before the lab.json cutover. Each entry
carries a labs field listing the lab names it belongs to. Pass --lab veggies (or set OTTO_LAB=veggies) and otto loads every host whose labs
field includes "veggies". links is covered in Links below;
when a file declares none, "links": [] (or omitting the key) is fine.
The host id used by get_host(), --list-hosts, and the rest of the CLI
is slug(element), plus element_id when set, plus (only when a board is
set) _ + slug(board) and then slot when set — so slot never appears
in the id without a board. See Host identity & naming below for the exact
rules, a worked example, and how the display name and CLI handles are derived
alongside it.
Per-host fields¶
Required¶
Field |
Type |
Description |
|---|---|---|
|
string |
IP address or DNS name otto will connect to. |
|
string |
Network-element name. Slugged, then combined with |
|
array of objects |
Ordered list of |
|
array of strings |
Lab names this host belongs to. Without this the host is invisible to |
Common optional¶
Field |
Type |
Description |
|---|---|---|
|
string |
Board type, included in the host id when set. |
|
integer |
Disambiguates multiple instances of the same NE; appended to the host id. |
|
string |
Pin a specific user from |
|
string |
Terminal protocol lab pin — must be in the host’s |
|
string |
File-transfer protocol lab pin — must be in the host’s |
|
array of strings |
Ordered list of term backends that may be selected for this host (gates |
|
array of strings |
Ordered list of transfer backends that may be selected for this host (gates |
|
integer |
Physical slot number of the board to which this host belongs. Appended to the host id, but only when |
|
string |
Host id of an intermediate SSH jump host. Otto opens an SSH tunnel through it and routes all subsequent connections automatically. Hops can chain. |
|
array of strings |
Free-form resource tags used by the reservation backend. |
|
boolean |
|
|
boolean |
Whether to log output to stdout and log files (default |
|
boolean |
Whether to log output to stdout (default |
|
boolean |
|
|
boolean |
|
Host identity & naming¶
There is no separate id field. element is both the human-readable name
and the id source: it is slugged — lower-cased, with every run of
characters outside [a-z0-9] (spaces, punctuation, _) collapsed to a
single -, leading/trailing - stripped — to form the canonical id. The id
is then slug(element), plus element_id when set, plus (only when a board
is set) _ + slug(board) and then slot when set: element_id can follow
the element with no board, but slot never appears without a board.
"Lab X Server" slugs to lab-x-server. Renaming element changes the
host’s id — and, transitively, the id of any declared link whose endpoints[].host names it.
When the same element string appears more than once in a lab, disambiguate
with distinct element strings, an element_id, or board/slot — any of
these changes the resulting id. Two hosts that still resolve to the same id
fail the lab load with a clear error instead of one silently overwriting the
other.
The display name (host.name) is a separate, human-friendly label: the
original-case element, a small logical number, board, and slot,
space-joined (parts omitted when absent). The logical number is only added
when the element string repeats in the lab — it counts instances in
ascending element_id order, starting at 1 — so a unique element gets no
number. Setting an explicit name on the host entry overrides this
generated label entirely.
On the CLI, wherever a host is named — the otto host <id> positional,
--hop, and docker’s --on — you can type either the canonical id or the
shorter positional handle <element-slug><logical number> (e.g. dut1 for
the first dut); tab completion offers both forms.
{
"hosts": [
{ "ip": "10.0.0.2", "element": "Lab X Server" },
{ "ip": "10.0.0.3", "element": "dut", "element_id": 47 },
{ "ip": "10.0.0.4", "element": "dut", "element_id": 103 }
]
}
|
Id |
Display name |
CLI handle(s) |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Lab X Server is the only host with that element, so it has no logical
number and no positional handle — just its id. The two dut hosts share an
element, so each is numbered by ascending element_id (47 before 103)
in both its display name and its positional handle.
Host type / OS¶
Field |
Type |
Description |
|---|---|---|
|
string |
Profile selector. Defaults to |
|
string |
Human-readable OS name (e.g. |
|
string |
OS or kernel version string (e.g. |
Embedded-only fields¶
These fields apply only to hosts with an embedded base type (e.g.
os_type: "zephyr" or os_type: "embedded"). See Embedded Hosts for full
details.
Field |
Type |
Description |
|---|---|---|
|
string |
Shell-framing dialect (e.g. |
|
string |
On-device filesystem variant ( |
|
integer |
Maximum filename length accepted by the target filesystem. |
File transfer for embedded hosts uses "console" or "tftp" — see
Embedded Hosts.
Network interfaces¶
The optional interfaces field maps a network-device name to that device’s
address, so links (below) and later impairment/capture tooling can address a
specific device directly instead of just the host’s management ip.
Field |
Type |
Description |
|---|---|---|
|
object |
Map of netdev name (e.g. |
|
object or string |
|
{
"interfaces": {
"eth0": "10.0.0.5",
"eth1": { "ip": "10.0.1.5" }
}
}
A host with no interfaces (or exactly one entry) needs no interface on a
declared-link endpoint — otto assumes it. A host with more than one entry
requires each declared-link endpoint on it to name which one; see
Links below.
Power control¶
The optional power_control block configures a pluggable power controller for
the host. The built-in "command" controller runs configured shell commands on
a controller host in the lab:
Field |
Type |
Description |
|---|---|---|
|
object or string |
Power controller spec. A string selects a registered controller by type name; an object takes the fields below. Omit to leave the host without power control. |
|
string |
Controller type name (e.g. |
|
string |
Host id of the lab host that runs the on/off/status commands. |
|
string |
Shell command to power the host on. Templated with |
|
string |
Shell command to power the host off. Same template variables. |
|
string |
Shell command to query power state (optional). |
|
string |
Substring of |
{
"power_control": {
"type": "command",
"controller": "hypervisor1",
"on_cmd": "virsh start {name}",
"off_cmd": "virsh destroy {name}",
"status_cmd": "virsh domstate {name}",
"status_on": "running"
}
}
See Host capabilities for the Power Control section, runtime API
(host.power(), host.reboot(hard=True)), and how to register a custom
controller (register_power_controller).
SNMP monitoring¶
The optional snmp block configures SNMP polling for a host’s metrics. See
the SNMP section of otto monitor.
Field |
Type |
Description |
|---|---|---|
|
string |
SNMP agent IP address. |
|
integer |
SNMP UDP port. |
|
string |
SNMP community string. |
|
array of strings |
OIDs to poll — raw dotted OIDs, named bundles (below), or a mix of both. |
Each entry in oids is either a raw dotted OID or one of otto’s built-in
named bundles, which expand to a group of related OIDs and register
their descriptors as a side effect. Bundles and raw OIDs mix freely in the
same list:
otto-core— the five core scalars (uptime, overall CPU, heap used, heap free, thread count).otto-net:N— network OIDs for interfaces0..N-1.otto-netalone (no:N) meansotto-net:1, i.e. just interface0.otto-fs:N— filesystem OIDs for filesystems0..N-1, same:Ndefault.
{
"snmp": {
"address": "10.10.200.14",
"port": 16101,
"oids": [
"otto-core",
"otto-net:2",
"otto-fs:1",
"1.3.6.1.4.1.99999.1.5.0"
]
}
}
N must be a positive integer. An unknown bundle name fails fast at
monitor startup rather than silently polling nothing. See
Per-interface and per-filesystem OIDs
in otto monitor for what each expanded OID charts.
Per-protocol option tables¶
Each of the following keys accepts an object that overrides individual
protocol fields. They merge per-key with hardcoded dataclass defaults;
product [host_preferences] values are then applied on top (product wins
over the host’s own values). See Host configuration (lab.json) for the full
connection-options reference.
Key |
Protocol |
|---|---|
|
SSH (term and hop) |
|
Telnet (term, and the embedded console) |
|
SFTP transfer |
|
SCP transfer |
|
FTP transfer |
|
Netcat transfer |
Coverage toolchain¶
The toolchain object points to the cross-toolchain binaries used by the
coverage pipeline. See Coverage Collection.
Field |
Type |
Description |
|---|---|---|
|
string |
Path to the cross-toolchain sysroot. |
|
string |
Path to |
|
string |
Path to the |
Example¶
Two real entries from the test fixture — one Unix host and one Zephyr host:
{
"hosts": [
{
"ip": "10.10.200.11",
"element": "carrot",
"os_type": "unix",
"board": "seed",
"term": "ssh",
"transfer": "scp",
"is_virtual": true,
"creds": [
{"login": "vagrant", "password": "vagrant"},
{"login": "test", "password": "Password1"}
],
"resources": [
"carrot"
],
"labs": [
"veggies"
]
},
{
"ip": "192.0.2.1",
"element": "sprout",
"os_type": "zephyr",
"os_version": "3.7",
"transfer": "console",
"filesystem": "fat-ram",
"max_filename_len": 32,
"is_virtual": true,
"hop": "basil_seed",
"snmp": {
"address": "10.10.200.14",
"port": 16101,
"community": "public",
"oids": [
"1.3.6.1.2.1.1.3.0",
"1.3.6.1.4.1.63245.1.1.0",
"1.3.6.1.4.1.63245.1.2.0",
"1.3.6.1.4.1.63245.1.3.0",
"1.3.6.1.4.1.63245.1.4.0"
]
},
"resources": [
"sprout"
],
"labs": [
"embedded"
]
}
],
"links": []
}
Links¶
A links entry in lab.json declares a data-plane route between two hosts —
distinct from the hop field’s SSH/telnet management path. It is resolved
into a runtime Link object (otto.link) at lab-load time: the edge that
otto tunnel add (see otto tunnel) rides to actually stand up traffic, and
that otto link impair (see otto link) targets to impair it:
{
"name": "data-plane-a",
"endpoints": [
{ "host": "carrot_seed", "interface": "eth1" },
{ "host": "tomato_seed", "interface": "eth1" }
],
"protocol": "udp"
}
Field |
Type |
Description |
|---|---|---|
|
array of exactly 2 objects |
The two ends of the route, each |
|
string |
Host id (see Lab files above) — must resolve to a host loaded from some lab file. |
|
string |
A key in that host’s |
|
string |
Optional; defaults to |
|
string |
Optional friendly handle; the link’s id is otherwise derived from its endpoints. |
|
string |
Optional in-path middlebox host id — a bare string, validated as a known host reference at lab load. When set, |
|
string |
Optional, reserved for a later link sub-project (management-host source attribution); accepted today but not yet acted on. |
Lab membership is derived, not authored — a link carries no labs field
of its own. It belongs to the union of both endpoints’ labs: loading lab
veggies surfaces every link with at least one endpoint in veggies, even
one whose other endpoint lives in a different lab (that far endpoint
renders as a stub/dangling node). A link can legitimately span two labs.
Product host preferences¶
Most products share a common set of connection conventions — a non-standard
SSH port, a longer connect timeout, an alternate nc binary, preferred
terminal or transfer backend. Restating those values on every host entry is
repetitive and error-prone. Move the shared values into
[host_preferences] in .otto/settings.toml.
Migration note:
[host_defaults]was removed; its option tables move under[host_preferences."<selector>".<opt>].
The [host_preferences] block is a map whose keys are Python regexes
matched (re.fullmatch) against each host’s id (e.g. carrot_seed,
router_seed_2). Under each selector, two kinds of values are allowed:
Selection lists (
term,transfer) — an ordered list of preferred backends. Otto picks the first entry that is in the host’s lab-definedvalid_terms/valid_transfersmenu; out-of-menu entries are skipped.Option tables (
ssh_options,telnet_options,sftp_options,scp_options,ftp_options,nc_options) — per-key value overrides.
# .otto/settings.toml
# Selector = Python regex matched against host id (".*" = all hosts).
# Selections (term/transfer) are ordered preferences gated by each host's
# lab menu; option tables are per-key values that win over lab.json.
[host_preferences.".*"]
term = ["telnet"]
transfer = ["nc"]
ssh_options = { connect_timeout = 5.0, keepalive_interval = 30 }
telnet_options = { cols = 200, echo_negotiation_timeout = 1.0 }
nc_options = { exec_name = "ncat", port_strategy = "proc" }
# Narrower selectors overlay specific host groups.
[host_preferences."router.*"]
telnet_options = { port = 9023 }
Valid option-table names are: ssh_options, telnet_options,
sftp_options, scp_options, ftp_options, nc_options. Unknown keys
raise at startup so typos fail loudly instead of silently no-opping.
Precedence (lowest to highest):
The hardcoded dataclass defaults in
otto.host.options.The host’s own
*_optionstable andterm/transferpin inlab.json(thevalid_*menu hard-gates selections).[host_preferences]from each repo — product values win overlab.json. Repos are applied inOTTO_SUT_DIRSorder (later repo wins); within a repo, selectors are applied in definition order (later selector wins on the same key).CLI
--term/--transfer— final word, applied at invocation time.
Merging happens per key at every option-table layer. Setting only
port on a host in lab.json still inherits connect_timeout from the
product preference, and so on down to the dataclass default.
The merge is performed at host construction time, so the resulting host
carries the fully-resolved *_options instances — nothing has to be
re-resolved at use time.
For the full *_options field reference and per-field semantics, see
Host configuration (lab.json).
Merging labs¶
Combine lab names with + to merge them:
otto --lab lab_a+lab_b test TestDevice
Hosts from all labs are merged into a single lab named lab_a+lab_b. If two
labs define the same host ID, the later lab’s definition wins. + is the same
operator the Lab objects themselves use to merge.
Exploring labs¶
otto --lab my_lab --list-labs # list all available lab names
otto --lab my_lab --list-hosts # list host IDs in the loaded lab
otto --lab my_lab --show-lab # full lab details (use -v for expanded output)