Rename remote_nodes module to node_control

- Move remote_nodes.py to node_control.py; update commands __init__
- Rename design doc and test file; refresh README references

Made-with: Cursor
This commit is contained in:
Robert McMahon 2026-04-10 16:26:36 -07:00
parent 1f3803c75a
commit e5b5010aaa
5 changed files with 28 additions and 28 deletions

View File

@ -4,7 +4,7 @@ Tools and libraries for managing and testing Umber FiWi networks.
This repository ships one Python distribution (**`fiwicontrol`** for `pip`) whose import root is **`orchestrator`**:
1. **`orchestrator.commands`** — run commands on remote rigs via OpenSSH or **`ush`**, with asyncio streaming, timeouts, repeats (`Command`), and a small registry (`CommandManager`). Implementation: `src/orchestrator/commands/remote_nodes.py`.
1. **`orchestrator.commands`** — run commands on remote rigs via OpenSSH or **`ush`**, with asyncio streaming, timeouts, repeats (`Command`), and a small registry (`CommandManager`). Implementation: `src/orchestrator/commands/node_control.py`.
2. **`orchestrator.power`** — reserved for power switching, monitoring, and discovery (Acroname, Monsoon, …). Must depend on **`commands`** only, not the reverse.
**Layout**
@ -15,12 +15,12 @@ FiWiControl/
├── README.md
├── pyproject.toml
├── docs/
│ └── remote-nodes-asyncio-design.md
│ └── node-control-asyncio-design.md
├── src/
│ └── orchestrator/
│ ├── commands/
│ │ ├── __init__.py
│ │ └── remote_nodes.py
│ │ └── node_control.py
│ └── power/
│ └── __init__.py
└── tests/
@ -29,7 +29,7 @@ FiWiControl/
## Requirements
- Python **3.11+**
- For **`sshtype="ssh"`**: **passwordless** SSH to **`root@<host>`** (non-interactive `ssh`; see `docs/remote-nodes-asyncio-design.md`).
- For **`sshtype="ssh"`**: **passwordless** SSH to **`root@<host>`** (non-interactive `ssh`; see `docs/node-control-asyncio-design.md`).
## Install (editable)
@ -47,7 +47,7 @@ from orchestrator.commands import ssh_node, Command, CommandManager
## Tests
Commands, example **`pytest`** output, and **`unittest`** entry points for **`remote_nodes`**: **`docs/remote-nodes-asyncio-design.md`** → section **“Running tests”** (top-level `##` heading near the top of the file).
Commands, example **`pytest`** output, and **`unittest`** entry points for **`node_control`**: **`docs/node-control-asyncio-design.md`** → section **“Running tests”** (top-level `##` heading near the top of the file).
Layout / import smoke (no network):
@ -60,7 +60,7 @@ Remote integration (needs **`FIWI_REMOTE_IP`** and key auth):
```bash
cd ~/Code/FiWiControl
FIWI_REMOTE_IP=192.168.1.39 pytest -q tests/test_remote_nodes.py
FIWI_REMOTE_IP=192.168.1.39 pytest -q tests/test_node_control.py
```
## Remote (Gitea)

View File

@ -3,7 +3,7 @@
**Status:** Current
**Package:** **`orchestrator.commands`** (FiWiControl repository).
**Implementation file:** **`src/orchestrator/commands/remote_nodes.py`**.
**Implementation file:** **`src/orchestrator/commands/node_control.py`**.
The main entry type is still named **`ssh_node`** (historical name: SSH/`ush`-oriented rig connection).
**Scope:** Asyncio-based SSH/`ush` rig control: **`ssh_node`**, **`ssh_session`**, **`Command`**, **`CommandManager`**, concurrent execution via **`asyncio`**, line-oriented streaming, and logging.
@ -15,7 +15,7 @@ The main entry type is still named **`ssh_node`** (historical name: SSH/`ush`-or
## Running tests
Integration tests for **`remote_nodes.py`** live in **`tests/test_remote_nodes.py`**. They open real **`ssh`** sessions to **`root@$FIWI_REMOTE_IP`** (default **`192.168.1.39`**). **Passwordless SSH must work** for that host.
Integration tests for **`node_control.py`** live in **`tests/test_node_control.py`**. They open real **`ssh`** sessions to **`root@$FIWI_REMOTE_IP`** (default **`192.168.1.39`**). **Passwordless SSH must work** for that host.
**From a terminal** (repository root — e.g. **`~/Code/FiWiControl`**):
@ -26,24 +26,24 @@ cd ~/Code/FiWiControl
export FIWI_REMOTE_IP=192.168.1.39
# Verbose pytest (recommended)
python -m pytest tests/test_remote_nodes.py -v
python -m pytest tests/test_node_control.py -v
# Quiet summary only
python -m pytest tests/test_remote_nodes.py -q
python -m pytest tests/test_node_control.py -q
```
**Without pytest**, the file is also a **`unittest`** script:
```bash
cd ~/Code/FiWiControl
python tests/test_remote_nodes.py --remote-ip 192.168.1.39
python tests/test_node_control.py --remote-ip 192.168.1.39
# More asyncio logging:
python tests/test_remote_nodes.py --remote-ip 192.168.1.39 --debug
python tests/test_node_control.py --remote-ip 192.168.1.39 --debug
```
**`pytest`** picks up **`pythonpath = ["src"]`** from **`pyproject.toml`**, so you do **not** need **`PYTHONPATH=src`** when you run from the repo root as above. After **`pip install -e .`**, imports resolve the same way.
**Example successful output** (line order and timings vary; captured with **`python -m pytest tests/test_remote_nodes.py -v`** on Linux):
**Example successful output** (line order and timings vary; captured with **`python -m pytest tests/test_node_control.py -v`** on Linux):
```text
============================= test session starts ==============================
@ -54,12 +54,12 @@ configfile: pyproject.toml
plugins: anyio-4.8.0
collecting ... collected 6 items
tests/test_remote_nodes.py::TestRemoteSingleRunCommands::test_dmesg_tail PASSED [ 16%]
tests/test_remote_nodes.py::TestRemoteSingleRunCommands::test_ls_home_directory PASSED [ 33%]
tests/test_remote_nodes.py::TestRemoteRepeatingCommands::test_command_manager_add_and_self_clean PASSED [ 50%]
tests/test_remote_nodes.py::TestRemoteRepeatingCommands::test_repeat_01_uname_r_every_1s_10_times PASSED [ 66%]
tests/test_remote_nodes.py::TestRemoteRepeatingCommands::test_repeat_02_pwd_and_ls_every_1s_5_times PASSED [ 83%]
tests/test_remote_nodes.py::TestRemoteRepeatingCommands::test_repeat_03_forever_can_be_stopped PASSED [100%]
tests/test_node_control.py::TestRemoteSingleRunCommands::test_dmesg_tail PASSED [ 16%]
tests/test_node_control.py::TestRemoteSingleRunCommands::test_ls_home_directory PASSED [ 33%]
tests/test_node_control.py::TestRemoteRepeatingCommands::test_command_manager_add_and_self_clean PASSED [ 50%]
tests/test_node_control.py::TestRemoteRepeatingCommands::test_repeat_01_uname_r_every_1s_10_times PASSED [ 66%]
tests/test_node_control.py::TestRemoteRepeatingCommands::test_repeat_02_pwd_and_ls_every_1s_5_times PASSED [ 83%]
tests/test_node_control.py::TestRemoteRepeatingCommands::test_repeat_03_forever_can_be_stopped PASSED [100%]
============================== 6 passed in 16.47s ==============================
```
@ -77,7 +77,7 @@ python -m pytest tests/test_package_layout.py -q
## How to use (examples)
Implementation lives in **`src/orchestrator/commands/remote_nodes.py`**. All remote I/O is **async** — call from a coroutine under **`asyncio.run()`** or your application event loop.
Implementation lives in **`src/orchestrator/commands/node_control.py`**. All remote I/O is **async** — call from a coroutine under **`asyncio.run()`** or your application event loop.
### Prerequisites (remote targets)
@ -97,7 +97,7 @@ from orchestrator.commands import Command, CommandManager, ssh_node
Equivalent submodule import (same symbols):
```python
from orchestrator.commands.remote_nodes import Command, CommandManager, ssh_node
from orchestrator.commands.node_control import Command, CommandManager, ssh_node
```
Install the package (editable: **`pip install -e .`** from the **FiWiControl** repo root; distribution name **`fiwicontrol`**). For **`pytest`** without installing, **`pythonpath = ["src"]`** is set in **`pyproject.toml`**.
@ -611,6 +611,6 @@ This section lists **behaviors to verify** in automated tests (unit, functional,
---
Implemented coverage reference: `tests/test_remote_nodes.py`
Implemented coverage reference: `tests/test_node_control.py`
*End of design document.*

View File

@ -4,7 +4,7 @@ Command execution over SSH / ``ush``: sessions, scheduling, timeouts.
Must not import from ``orchestrator.power``.
"""
from orchestrator.commands.remote_nodes import (
from orchestrator.commands.node_control import (
Command,
CommandManager,
sleep_async,

View File

@ -2,7 +2,7 @@
#
# Licensed under the Apache License, Version 2.0; see LICENSE.
#
# Remote command helpers (SSH / ush) for test rig control.
# Node command helpers (SSH / ush) for test rig control.
# Package: orchestrator.commands (FiWiControl). Public imports: from orchestrator.commands import ssh_node, ...
#
# Async-first implementation for ssh_node.
@ -12,7 +12,7 @@
# - Repeating: cmd = Command(node, cmd="...", interval=1.0, count=10); await cmd.start(); await cmd.task
# - Many: CommandManager().add_command(...) / stop_all() / list_active()
#
# Remote targets (sshtype "ssh"): passwordless SSH is required — see docs/remote-nodes-asyncio-design.md (## Running tests).
# SSH targets (sshtype "ssh"): passwordless SSH is required — see docs/node-control-asyncio-design.md (## Running tests).
from __future__ import annotations

View File

@ -5,7 +5,7 @@ import os
import sys
import unittest
from orchestrator.commands.remote_nodes import Command, CommandManager, ssh_node
from orchestrator.commands.node_control import Command, CommandManager, ssh_node
class _RemoteNodeMixin:
@ -84,7 +84,7 @@ class TestRemoteRepeatingCommands(_RemoteNodeMixin, unittest.IsolatedAsyncioTest
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Integration tests for orchestrator.commands.remote_nodes (passwordless SSH required)."
description="Integration tests for orchestrator.commands.node_control (passwordless SSH required)."
)
parser.add_argument(
"--remote-ip",
@ -100,5 +100,5 @@ if __name__ == "__main__":
else:
logging.basicConfig(level=logging.WARNING, format="%(message)s")
logging.getLogger("asyncio").setLevel(logging.WARNING)
logging.getLogger("orchestrator.commands.remote_nodes").setLevel(logging.INFO)
logging.getLogger("orchestrator.commands.node_control").setLevel(logging.INFO)
unittest.main(argv=[sys.argv[0], *remaining])