host.remote_host

Abstract base for network-reached hosts.

RemoteHost is the common ancestor of every host class that talks to a target across a network — UnixHost (SSH/Telnet to a bash shell), EmbeddedHost (telnet to an RTOS shell), and any future siblings such as a Windows-host class. It is deliberately distinct from LocalHost, which runs commands on the local machine and shares no network plumbing.

History: this name used to belong to the concrete SSH/Telnet bash host. That class is now UnixHost; RemoteHost is the abstract parent. The split makes the OS family of a host explicit (lab data carries an os_type field) and gives embedded targets a place to live alongside Unix ones without lying about their shape.

RemoteHost is intentionally not a dataclass. The concrete subclasses are @dataclass(slots=True) and the field-ordering rules of dataclass inheritance (no non-default field after a default one) make a shared dataclass base awkward. Instead this base owns the behavior shared by every remote host — host naming and the SshHopTransport machinery — and declares, as bare annotations, the instance attributes those shared methods rely on. Each concrete subclass supplies the real @dataclass fields.

otto.host.remote_host.slug(value: str) str

Normalize an identity token into a URL/id-safe slug.

STABILITY CONTRACT — feeds make_host_id, which in turn feeds make_link_id (static route ids) and tunnel path hops/sentinels; changing it re-maps every id and invalidates live tunnel markers. Never change the algorithm:

  • lower-case;

  • replace every maximal run of characters outside [a-z0-9] with a single - (so spaces, _, ., :, |, /, and punctuation never reach an id);

  • strip leading/trailing -.

A value that slugs to "" (all punctuation/whitespace) is invalid — the caller reports it as a load error.

otto.host.remote_host.make_host_id(element: str, element_id: int | None, board: str | None, slot: int | None) str

Compose a host’s id from its identity fields — the single source of the id format.

Called by RemoteHost._generate_id and by host_preferences selector matching (so a selector regex matches the same string a built host reports). element/board are slugged (§ slug); the only structural delimiter is _ between the element-slug and the board-slug. A simple [a-z0-9] element slugs to itself, so its id is byte-identical to the pre-slug format.

otto.host.remote_host.OsType

Profile selector recorded on a host (the os_type field).

Built-ins: unix (UnixHost), embedded (generic EmbeddedHost), zephyr (ZephyrHost). Custom profiles add more names. The base family (unix vs embedded) is derived from the host class, not from this string.

class otto.host.remote_host.RemoteHost

Bases: BaseHost

Abstract base class for any host reached over a network.

Concrete subclasses (UnixHost, EmbeddedHost) supply the transport-specific session/transfer machinery as @dataclass fields. Do not instantiate this class directly.

The bare annotations below are the instance-attribute contract every concrete subclass must satisfy. They carry no values, so they create no slots and do not participate in the subclasses’ @dataclass field collection — they exist purely so the shared methods here (and callers holding a RemoteHost-typed reference) type-check.

ip : str

IP address of the host.

element : str

Network element to which this host belongs.

id : str

Unique identifier for this host.

logical_index : int | None

Lab-scoped position among same-slug(element) siblings (1-based, by element_id ascending), stamped by Lab._assign_logical_indices; None when the element is unique in the lab. Display/CLI sugar only — never stored, hashed, or used as a correlation key.

name : str

Human-readable name; auto-generated from element/board if not given.

creds : list[Cred]

Login credentials for this host — see creds.

resources : set[str]

Names of resources required to use this host.

log : LogMode

Standing per-host logging disposition. QUIET keeps command I/O in verbose.log but off the console; NEVER redacts it everywhere.

user : str | None

User with which to log in, or None to use the first entry in creds.

element_id : int | None

Network element identifier, or None when no disambiguation is needed.

board : str | None

Board type name, or None.

slot : int | None

Physical slot number of the board, or None.

hop : str | None

Host ID of the intermediate hop used to reach this host, or None.

os_type : str

Profile selector recorded on this host (see OsType). The base family (unix vs embedded) is derived from the host class, not this string.

os_name : str | None

Kernel/OS name (e.g. Linux, Zephyr).

os_version : str | None

OS/kernel version string, or None if unspecified.

default_dest_dir : Path

Per-host default directory that put / get resolve a relative or empty dest_dir against. Lets a fan-out helper like do_for_all_hosts pass one generic destination (Path()) and have each host land the files where its filesystem actually lives — e.g. /RAM: on a Zephyr FAT target, /lfs on a Zephyr LittleFS target. Defaults to Path() on Unix, which preserves the existing “relative path lands in the SSH user’s home” behavior.

snmp : SnmpOptions | None

Optional per-host SNMP polling config (lab snmp block), or None. When set, otto’s monitor collects this host over SNMP instead of by running shell commands. Declared on both concrete subclasses; see SnmpOptions.

max_filename_len : int

Upper bound on the basename length (including extension) accepted by the target’s filesystem. Defaults to 255 on every concrete subclass — the Linux NAME_MAX, also the cap for ext4 / XFS / Btrfs / NTFS and the typical LittleFS ceiling. Override per-host when the firmware enforces a tighter limit (e.g. 32 for a Zephyr build that sets CONFIG_FS_FATFS_MAX_LFN=32, or 12 for a stock FAT 8.3 build without LFN support). put / get reject over-limit names up front with a clear message instead of letting the device produce an opaque error like -ENOENT or File name too long.

interfaces : dict[str, Interface]

Named network devices, keyed by the netdev name (e.g. {"eth0": Interface(ip="10.0.0.5"), "eth1": Interface(ip="192.168.1.5")}). The primary address stays ip; this map is additive and optional (empty by default). Resolve a name (or pass a literal through) with address_for().

products : list[Product]

Software-under-test deployed to this host (see products).

power_control : PowerController | None

Pluggable power backend (see power_control).

async verify_connection() CommandResult

Verify the host connection by running a diagnostic command.

Subclasses override this to run an OS-appropriate probe (e.g. uname on Unix, a Zephyr kernel-info command on embedded). Called by is_reachable() to decide whether the host is up.

async is_reachable(timeout: float = 10.0) bool

Probe by attempting a connection (no command), bounded by timeout.

address_for(name_or_literal: str) str

Resolve an interface name to its address, or pass a literal through.

If name_or_literal is a key in interfaces, return that interface’s address; otherwise return the value unchanged (it is taken to be a literal address such as ip or an explicit IP). This lets a host’s snmp.address name a secondary interface without otto having to distinguish names from literals.