network/cnmaestro/docs/plans/2026-05-09-cnmaestro-cleanup-upgrade-design.md
2026-05-09 16:54:28 -05:00

3.4 KiB
Raw Blame History

cnMaestro Cleanup + Bulk Firmware Upgrade Tool

Date: 2026-05-09 Status: Approved, implementation starting

Goal

Build a Python CLI for cnMaestro Cloud (cloud.cambiumnetworks.com) that:

  1. Removes devices offline for > 24 hours (configurable threshold).
  2. Submits bulk firmware upgrade jobs to bring online devices to the latest firmware available for their model.

Non-Goals

  • NetBox sync (separate concern).
  • Daemon/scheduling (use cron + --yes).
  • Firmware rollback (cnMaestro handles).
  • Custom firmware uploads (use cnMaestro UI).

Architecture

cnmaestro/
├── README.md                  # Setup, API client creation, usage
├── .env.example               # CNMAESTRO_CLIENT_ID / _SECRET / _BASE_URL
├── pyproject.toml             # uv project
├── cnmaestro/
│   ├── __init__.py
│   ├── client.py              # OAuth2 + paginated httpx wrapper
│   ├── devices.py             # list, partition, delete
│   ├── firmware.py            # latest-per-model, job submission
│   └── cli.py                 # click CLI
└── tests/
    ├── test_client.py
    ├── test_devices.py
    └── test_firmware.py

API Reference (cnMaestro Cloud v2)

  • Auth: POST /api/v2/access/token with client_credentials grant → bearer token. Cache in-memory, re-fetch on 401.
  • List devices: GET /api/v2/devices?limit=100&offset=N. Key fields: mac, name, network, tower, status ("online"/"offline"), last_sync (epoch ms or ISO 8601), product, software_version.
  • Delete device: DELETE /api/v2/devices/{mac}.
  • Firmware images: GET /api/v2/firmware. Returns image metadata with product and version fields per image.
  • Submit job: POST /api/v2/devices/jobs/firmware with { "devices": ["MAC", ...], "package": "<image-name-or-version>" }. Limit 100 MACs per job.

Behavior

cnmaestro cleanup [--dry-run] [--yes] [--threshold-hours 24] [--force]

  1. Fetch all devices (paginated).
  2. Partition: online / offline-recent / offline >24h / never-synced.
  3. Removal candidates = offline >24h never-synced.
  4. Render rich table: name, MAC, product, last_sync, tower.
  5. Show counts. If candidates > 50% of fleet, refuse unless --force.
  6. Prompt; on confirm, DELETE each with progress bar; collect failures.

cnmaestro upgrade [--dry-run] [--yes] [--product P1,P2,...]

  1. Fetch all devices, keep status=online.
  2. Fetch firmware images list.
  3. For each product, pick highest semver as target.
  4. Build groups (product, target_version) → [mac, ...] for devices where software_version != target.
  5. Render summary table.
  6. Prompt; submit one POST per group, chunked to 100 MACs. Print job IDs.

Safety Rails

  • Default to dry-run summary + interactive confirm.
  • 50%-of-fleet sanity check on cleanup.
  • Token never persisted.
  • All destructive ops appended to ./cnmaestro.log (timestamped).
  • Tests use respx to mock httpx; CI never hits live API.

Testing Strategy

  • Unit tests for partition logic (no HTTP).
  • Unit tests for "latest version per model" with fixture firmware list.
  • HTTP-mocked tests for client pagination, 401 retry, delete.
  • No live API tests in CI.

Setup (User)

  1. Log into cloud.cambiumnetworks.com.
  2. Manage Services → API Clients → Add Client.
  3. Copy client ID + secret into .env.
  4. uv sync && uv run cnmaestro cleanup --dry-run to validate.