Concepts¶
Four ideas explain nearly all of this library’s behaviour: the model, the backend, dispatch without fallback, and verification status.
Models¶
A switch model is a frozen netgear_switch.registry.SwitchModel record: its
key, display name, class, port and PoE-port counts, the backends it exposes, its
SNMP vendor OID subtree, and a few per-model protocol dialect flags. The whole
registry lives in src/netgear_switch/registry.py, and get_model resolves a
key (or an alias, such as s3300 for gsm7228ps) to the record.
Models fall into three classes, and the class decides which protocols are available:
- Fully Managed (
FULLY_MANAGED) M4300, GSM7252PS. Full SNMP with the Netgear
4526.10vendor subtree, a FASTPATH CLI over SSH and telnet, and a web UI.- Smart Managed Pro (
SMART_MANAGED_PRO) S3300/GSM7228PS, GS728TPP, XS748T. SNMP and a web UI; the S3300 also has a FASTPATH CLI, over telnet on the non-standard port 60000.
- Plus (
PLUS) GS110EMX, GS305EP, GS105PE. No SNMP at all. Managed by NSDP — Netgear’s UDP discovery-and-config protocol — and by a web UI.
The registry is the single source of truth for these facts, and every table in Support matrix is generated from it.
Backends¶
A backend is the protocol an operation travels over:
Backend |
What it is |
|---|---|
Standard MIBs plus Netgear vendor subtrees. Synchronously via the net-snmp command-line tools; asynchronously via pysnmp. |
|
The Netgear Switch Discovery Protocol, over UDP. The only management protocol besides the web UI on Plus switches. |
|
The switch’s own web UI, scraped and driven as a browser would. 5 distinct login schemes and 7 page dialects across the fleet. |
|
The FASTPATH command line. One command surface reached over three
transports; |
Backend parity is the design aim: an operation should work on every backend a model has, so that if SNMP writes are locked down on your network, or the web UI is the only port through a firewall, you can pick a different protocol and get the same answer.
Where that does not hold, the reason is recorded rather than glossed over. Some are limits proven against the hardware — the GS728TPP’s SNMP agent refuses every documented way of creating a VLAN, while its own web UI creates one without complaint — and others are pages or commands nobody has built yet. Support matrix marks each one. Either way the library refuses by name; it never substitutes a protocol you did not ask for.
Dispatch: exactly one backend, every time¶
switch.get_vlans() # the model's default backend
switch.get_vlans(backend=Backend.HTTP) # exactly HTTP, or an error
await switch.get_vlans() # the model's default backend
await switch.get_vlans(backend=Backend.HTTP) # exactly HTTP, or an error
When you name a backend, that backend runs. If the model does not have it,
you get UnsupportedCapabilityError immediately. If it has it but cannot serve
that operation, you get UnsupportedCapabilityError naming the backend you
asked for.
When you do not name one, resolve_backend picks the first
backend the model declares, in the fixed order SNMP → NSDP → HTTP → SSH →
TELNET → CONSOLE. The choice depends only on the model, never on the
operation: the facade does not probe one backend, catch its refusal, and try
the next. If the default cannot serve the operation, the error says so and
names the other backends you could pass:
UnsupportedCapabilityError: model 'gs110emx': the default backend NSDP cannot
serve this operation: NSDP has no PoE status tag (measured by an exhaustive
NSDP tag sweep of a live GS110EMX); pass backend=Backend.<HTTP> to use
another backend
Warning
The library will never answer over a protocol you did not get. This was
not always true, and the cost of the old behaviour is why the rule is
absolute. SyncSwitch used to loop over
SNMP → NSDP → HTTP, silently
returning the next backend’s answer when one raised. That hid a real defect
for months — HttpReader.get_vlans returned no untagged ports at all on
the managed switches, and nobody noticed because SNMP quietly answered
instead. Worse, every past “verified over HTTP” claim became worthless,
because the HTTP path may never have run. On a write it is worse still: an
operator who deliberately restricted SNMP write access could have had their
change pushed over another protocol without being told.
Choosing a default per facade¶
Pass backend= to the constructor to change the default for every call:
web_only = SyncSwitch(
get_model("gsm7252ps"), host="10.1.5.22",
http_password="...", backend=Backend.HTTP,
)
web_only = AsyncSwitch(
get_model("gsm7252ps"), host="10.1.5.22",
http_password="...", backend=Backend.HTTP,
)
Per-call backend= still overrides it.
Verification status¶
This project distinguishes what has been measured from what has been assumed, and the distinction is visible in the API.
verifiedFalsemarks a model registered from a specification sheet, with no device of that kind ever reachable from this project. Such a model is excluded from every support table — see Switch models — because putting it in a matrix would assert per-backend behaviour nobody has observed. It stays in the registry only so a caller can construct a facade for it, and nothing here treats it as evidence of anything.HttpModelSpec.reads_verified,CliModelSpec.reads_verified/writes_verifiedPer-model, per-backend flags recording whether that backend’s output has been cross-verified against live hardware. While a flag is
Falsethe facade refuses to dispatch to that backend rather than return output nobody has checked.netgear_switch.capabilities.Support.UNVERIFIEDis the corresponding verdict.
Errors¶
All errors derive from NetgearSwitchError. The distinctions that matter:
Error |
Means |
|---|---|
This backend genuinely cannot do this, for this model. Never “not implemented yet” — a missing implementation is a bug to fix, not a device limitation to document. |
|
A credential is missing or wrong. An SNMP agent silently drops an unauthorised request, so a wrong write community looks exactly like an unreachable host. |
|
The write was sent and accepted, but reading back did not show the intended value. |
|
The port is in this switch’s |
|
Web-UI login failed, or a page did not look like what the model’s dialect expects (usually a session that expired). |
|
The switch’s CLI rejected a command; the message carries what it said. |
NotImplementedError appears in exactly one place — certificate upload on a
model whose real mechanism is known but not wired to that backend. It is
deliberately not UnsupportedCapabilityError, because the hardware can do it;
see Support matrix.
Synchronous and asynchronous¶
SyncSwitch and
AsyncSwitch expose the same operations with the same
arguments and the same semantics. They share the model registry, the parsers and
the backend-resolution seam in src/netgear_switch/_dispatch.py; only the
transports differ. tests/test_facade_equivalence.py asserts they stay in
step.
Important
One backend is synchronous-only: the CLI. All three CLI transports —
SSH, telnet and the serial console — are blocking, and none has an async
twin, so AsyncSwitch has no CLI backend at all.
Asking for one raises rather than quietly blocking the event loop:
UnsupportedCapabilityError: model 'gsm7252ps' CLI reads are not available
via the async facade (CLI is synchronous ...)
That also rules out
upload_certificate_scp() on the async
facade, since the operation is CLI-based by nature. Use
SyncSwitch for those, or wrap the call in
asyncio.to_thread().
The support tables in Support matrix describe the
synchronous facade. Every SNMP, NSDP and HTTP entry holds for both; the CLI
columns apply to SyncSwitch only.