feat: add phone and media roles, and declare transport and reboot timing
Two endpoint device types had nowhere to go and were filed under
AccessPointDriver for want of anywhere better — a Yealink desk phone and a Sonos
speaker. netOrk reads the access-point role to decide what appears in its
wireless page, its AP profile pickers and its SSID drift view, so both showed up
in all three. PhoneDriver and MediaDriver give them an honest home; each
declares the surface its one existing driver actually implements, so the
contract is real rather than aspirational.
DeviceTypeDriver also gains two class attributes for facts netOrk kept as
hardcoded driver-name sets on its own side (netork#113):
USES_SSH whether netOrk reaches the device over SSH or a REST
API — transport, which is why it is not a role
REBOOT_SETTLE_SECONDS how long a reboot takes before polling is worth
attempting again
Both are driver facts and belong with the driver. A new driver is handled
correctly without anyone remembering to extend a list in netOrk.
This commit is contained in:
@@ -27,6 +27,8 @@ Role bases -- what a device *is*:
|
||||
* :class:`~napalm_device_types.hypervisor.HypervisorDriver`
|
||||
* :class:`~napalm_device_types.os.OSDriver`
|
||||
* :class:`~napalm_device_types.storage.StorageDriver`
|
||||
* :class:`~napalm_device_types.phone.PhoneDriver`
|
||||
* :class:`~napalm_device_types.media.MediaDriver`
|
||||
* :class:`~napalm_device_types.residential_gateway.ResidentialGatewayDriver`
|
||||
|
||||
Function classes -- what a device *can do*. Shared behaviour lives here once
|
||||
@@ -60,8 +62,10 @@ from napalm_device_types.firewall_rules import FirewallRuleMixin
|
||||
from napalm_device_types.health_metrics import HealthMetricsMixin
|
||||
from napalm_device_types.interface_filter import InterfaceFilterMixin
|
||||
from napalm_device_types.mac_acl import MacAclMixin
|
||||
from napalm_device_types.media import MediaDriver
|
||||
from napalm_device_types.nat_vpn import NatVpnMixin
|
||||
from napalm_device_types.packages import PackageManagementMixin
|
||||
from napalm_device_types.phone import PhoneDriver
|
||||
from napalm_device_types.ping_sweep import PingSweepMixin, driver_supports_ping
|
||||
from napalm_device_types.roles import primary_role_of, role_keys_of, roles_of
|
||||
from napalm_device_types.services import ServiceControlMixin
|
||||
@@ -82,9 +86,11 @@ __all__ = [
|
||||
"HypervisorDriver",
|
||||
"InterfaceFilterMixin",
|
||||
"MacAclMixin",
|
||||
"MediaDriver",
|
||||
"NatVpnMixin",
|
||||
"OSDriver",
|
||||
"PackageManagementMixin",
|
||||
"PhoneDriver",
|
||||
"PingSweepMixin",
|
||||
"PortSpec",
|
||||
"ResidentialGatewayDriver",
|
||||
|
||||
@@ -80,5 +80,15 @@ class DeviceTypeDriver(PingSweepMixin, NetworkDriver):
|
||||
SSH_FINGERPRINT: list[FingerprintRule] = []
|
||||
HTTP_FINGERPRINT: list[FingerprintRule] = []
|
||||
OUI_PREFIXES: list[str] = []
|
||||
|
||||
#: Whether netOrk reaches this device over SSH. False for drivers that talk
|
||||
#: to a REST API instead -- which decides whether an SSH credential is worth
|
||||
#: asking for, and whether an SSH-shaped error message would even make sense.
|
||||
USES_SSH: bool = True
|
||||
|
||||
#: How long a reboot takes before the device is worth polling again, in
|
||||
#: seconds. Hypervisors and general-purpose OS hosts run through a full
|
||||
#: init sequence; a switch or an access point is back in half the time.
|
||||
REBOOT_SETTLE_SECONDS: int = 45
|
||||
# Format: "AA:BB:CC" — first 3 octets of MAC, uppercase, colon-separated.
|
||||
# A match contributes fixed weight 6.0 to the fingerprint score.
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Abstract base class for networked media players.
|
||||
|
||||
Usage::
|
||||
|
||||
from napalm_device_types import MediaDriver
|
||||
|
||||
class SonosDriver(MediaDriver):
|
||||
...
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Dict, List
|
||||
|
||||
from napalm_device_types.base import DeviceTypeDriver
|
||||
|
||||
|
||||
class MediaDriver(DeviceTypeDriver):
|
||||
"""
|
||||
Abstract intermediate driver for speakers, streamers and media renderers
|
||||
(e.g. Sonos, Chromecast, Squeezebox, UPnP/DLNA renderers).
|
||||
|
||||
Like :class:`~napalm_device_types.phone.PhoneDriver`, this exists so an
|
||||
endpoint stops being filed under ``AccessPointDriver`` for want of anywhere
|
||||
better. A speaker has no SSIDs, no radio configuration and no AP profile; it
|
||||
has a transport state and a volume.
|
||||
"""
|
||||
|
||||
#: Stable key netOrk exposes as ``device_class``. The order in which a
|
||||
#: driver lists its role bases is the ranking; see
|
||||
#: :func:`napalm_device_types.roles.primary_role_of`.
|
||||
ROLE: str = "media"
|
||||
TYPE_LABEL: str = "Media"
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
def get_playback_state(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Returns the transport state of the renderer.
|
||||
|
||||
* state (string) - ``"playing"``, ``"paused"``, ``"stopped"``, or ``"transitioning"``
|
||||
* source (string) - input or service currently selected
|
||||
"""
|
||||
...
|
||||
|
||||
def get_volume(self) -> int:
|
||||
"""Returns the current output volume, 0-100."""
|
||||
...
|
||||
|
||||
def set_volume(self, level: int) -> None:
|
||||
"""Sets the output volume, 0-100."""
|
||||
...
|
||||
|
||||
def get_zone_info(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Returns the zones or groups this renderer participates in.
|
||||
|
||||
Each entry contains:
|
||||
|
||||
* name (string) - zone/room name
|
||||
* coordinator (bool) - whether this device leads the group
|
||||
* members (list) - names of the other devices in the group
|
||||
"""
|
||||
...
|
||||
@@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Abstract base class for IP telephony endpoints.
|
||||
|
||||
Usage::
|
||||
|
||||
from napalm_device_types import PhoneDriver
|
||||
|
||||
class YealinkDriver(PhoneDriver):
|
||||
...
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Dict, List
|
||||
|
||||
from napalm_device_types.base import DeviceTypeDriver
|
||||
|
||||
|
||||
class PhoneDriver(DeviceTypeDriver):
|
||||
"""
|
||||
Abstract intermediate driver for desk phones, conference units and DECT
|
||||
bases (e.g. Yealink T-series, Snom, Grandstream, Fanvil).
|
||||
|
||||
A phone is an endpoint, not infrastructure. It was worth separating from
|
||||
``AccessPointDriver`` — which some phone drivers used to inherit for want of
|
||||
anywhere better — because netOrk offers access points for AP profiles,
|
||||
wireless management and SSID drift, none of which a desk phone should appear
|
||||
in. A WiFi-capable phone still reports its radio through its own methods; it
|
||||
just is not an access point.
|
||||
"""
|
||||
|
||||
#: Stable key netOrk exposes as ``device_class``. The order in which a
|
||||
#: driver lists its role bases is the ranking; see
|
||||
#: :func:`napalm_device_types.roles.primary_role_of`.
|
||||
ROLE: str = "phone"
|
||||
TYPE_LABEL: str = "Phone"
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
def get_sip_accounts(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Returns the SIP registrations configured on the phone.
|
||||
|
||||
Each entry contains:
|
||||
|
||||
* account (string) - display label or line key
|
||||
* user (string) - SIP user / extension
|
||||
* server (string) - registrar host
|
||||
* registered (bool) - whether the registration is currently active
|
||||
|
||||
Example::
|
||||
|
||||
[
|
||||
{
|
||||
"account": "Line 1",
|
||||
"user": "201",
|
||||
"server": "pbx.example.com",
|
||||
"registered": True,
|
||||
}
|
||||
]
|
||||
"""
|
||||
...
|
||||
|
||||
def get_call_status(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Returns what the phone is doing right now.
|
||||
|
||||
* state (string) - ``"idle"``, ``"ringing"``, ``"talking"``, or ``"held"``
|
||||
* remote (string) - remote party, empty string when idle
|
||||
* duration (int) - seconds in the current state; ``0`` when idle
|
||||
"""
|
||||
...
|
||||
@@ -22,7 +22,9 @@ from napalm_device_types import (
|
||||
DeviceTypeDriver,
|
||||
FirewallDriver,
|
||||
HypervisorDriver,
|
||||
MediaDriver,
|
||||
OSDriver,
|
||||
PhoneDriver,
|
||||
ResidentialGatewayDriver,
|
||||
StorageDriver,
|
||||
SwitchDriver,
|
||||
@@ -33,7 +35,9 @@ ROLE_BASES = [
|
||||
AccessPointDriver,
|
||||
FirewallDriver,
|
||||
HypervisorDriver,
|
||||
MediaDriver,
|
||||
OSDriver,
|
||||
PhoneDriver,
|
||||
ResidentialGatewayDriver,
|
||||
StorageDriver,
|
||||
SwitchDriver,
|
||||
@@ -78,6 +82,8 @@ class TestDeclaredMethodsDoNotExistAtRuntime:
|
||||
(AccessPointDriver, "get_wireless_config"),
|
||||
(OSDriver, "get_processes"),
|
||||
(ResidentialGatewayDriver, "get_wan_status"),
|
||||
(PhoneDriver, "get_sip_accounts"),
|
||||
(MediaDriver, "get_playback_state"),
|
||||
],
|
||||
)
|
||||
def test_absent_until_a_driver_implements_it(self, base, method):
|
||||
|
||||
Reference in New Issue
Block a user