Architecture Overview¶
Router Test Kit is structured around three abstractions: Devices (what you're testing), Connections (how you talk to them), and Plugins (how you extend either). This page walks through each layer and how they fit together.
Devices¶
A Device is a plain data container that holds the credentials and identity of a network endpoint.
Device
├── LinuxDevice – Linux/Unix hosts and VMs (default prompt: $)
├── OneOS6Device – OneOS6 routers/switches (default prompt: >)
│ └── RADIUSServer – RADIUS-authenticated Linux host
└── HostDevice – local host (runs commands via subprocess, no network)
Device objects carry no connection logic. They expose:
username,password,hostname– authentication infoDEFAULT_USERNAME,DEFAULT_PASSWORD,DEFAULT_PROMPT_SYMBOL– class-level defaults
Connection decorators use isinstance checks against these classes to enforce that a method like set_sudo() is only called on a LinuxDevice, not on a OneOS6Device.
Connections¶
Connection is an abstract base class (abc.ABC) that defines the interface every transport implementation must follow.
Connection (ABC)
├── SSHConnection – paramiko-based SSH shell session
│ └── OneOS6SSHConnection – SSHConnection + OneOS6Mixin CLI methods
├── TelnetConnection – telnetlib-based Telnet session ⚠ deprecated
│ └── OneOS6TelnetConnection – TelnetConnection + OneOS6Mixin CLI methods
└── TelnetCLIConnection – Telnet hop via an existing TelnetConnection
OneOS6Mixin — vendor-specific CLI methods¶
OneOS6Mixin provides six OneOS6-specific methods (load_config, patch_config, unload_interface, unload_config, is_config_empty, reconfigure) that are mixed into concrete connection classes via multiple inheritance:
OneOS6SSHConnection(OneOS6Mixin, SSHConnection)— recommended for OneOS6 devicesOneOS6TelnetConnection(OneOS6Mixin, TelnetConnection)— deprecated; prefer SSH
from router_test_kit import OneOS6SSHConnection, OneOS6Device
device = OneOS6Device(username="admin", password="secret")
conn = OneOS6SSHConnection(timeout=30)
conn.connect(device, "10.0.0.1")
conn.load_config("/path/to/config.cfg")
conn.reconfigure()
Connection lifecycle¶
device = LinuxDevice(username="admin", password="secret")
conn = SSHConnection(timeout=30)
conn.connect(device, "10.0.0.1") # opens transport, sets prompt_symbol
conn.write_command("show version") # sends command, reads until prompt
conn.disconnect() # closes transport, frees state
SSHConnection opens an interactive shell channel (not exec_command) so it can handle multi-step interactions like privilege escalation.
Runtime decorators¶
Three decorator functions are defined inside Connection and used by subclasses:
| Decorator | Guards against |
|---|---|
@check_occupied |
method called while the connection is already in use by another thread or hop |
@check_device_type(type) |
method called on the wrong device type (e.g. calling set_sudo on an OneOS6 device) |
@check_connection |
method called before connect() has been called |
These are applied at the method level rather than subclass level, so each method explicitly declares what it requires. The checks raise ConnectionRefusedError, TypeError, or ConnectionError respectively.
TelnetCLIConnection — Telnet hop¶
TelnetCLIConnection wraps an already-open TelnetConnection and issues a telnet <ip> command through it, acting as a second hop. On disconnect() / exit(), it writes exit to the shell and releases the parent connection back to available state.
Plugin system¶
The plugin system lets external packages register new device types without modifying the core library. Discovery uses Python's importlib.metadata entry points.
Registration¶
External packages declare device classes in their pyproject.toml:
The registered class must inherit from router_test_kit.device.Device.
Loading¶
PluginManager is a singleton. On import router_test_kit, auto_load_plugins() calls PluginManager.get_instance().load_plugins(), which:
- Calls
importlib.metadata.entry_points().select(group="router_test_kit.devices") - For each entry point: loads the class, validates it inherits
Device, registers it by name - Built-in device types (
linux,oneos6,radius,host) are registered first in_register_builtin_devices()
Plugin load failures are logged as warnings but do not prevent the package from importing.
Usage¶
from router_test_kit import get_plugin_manager
manager = get_plugin_manager()
devices = manager.get_available_devices() # dict[str, type[Device]]
# Instantiate by name (if the plugin is installed)
juniper = devices["juniper"](username="admin", password="secret")
End-to-end flow¶
1. Instantiate device: device = LinuxDevice(username="admin", password="pw")
2. Instantiate connection: conn = SSHConnection(timeout=30)
3. Connect: conn.connect(device, "10.0.0.1")
└─ sets prompt_symbol from device.DEFAULT_PROMPT_SYMBOL
└─ flushes welcome banner
4. Execute commands: output = conn.write_command("ip route show")
└─ sends bytes over the shell channel
└─ reads until prompt_symbol appears in recv buffer
5. Higher-level helpers: conn.ping("10.0.0.2") # wraps write_command
conn.set_sudo() # checks device type first
6. Disconnect: conn.disconnect()
└─ closes channel and transport
Deprecations¶
Telnet support (TelnetConnection, TelnetCLIConnection) relies on the standard-library telnetlib module, which was removed in Python 3.13. Both classes emit a DeprecationWarning on instantiation. Prefer SSHConnection for all new code. See CHANGELOG.md at the repo root for the migration roadmap.
Non-goals¶
- Test orchestration — Router Test Kit provides device fixtures. Test collection, parameterisation, and reporting are left to pytest.
- Configuration management — the library can push configs but doesn't store or diff them; that belongs in your test fixtures.
- Async I/O — all connection methods are synchronous. For concurrent device testing, use pytest-xdist or standard threading at the test layer.