Refactor Device.type to Device.platform

This commit is contained in:
thatmattlove 2021-09-16 22:04:15 -07:00
parent cb5459a72a
commit 85d7f8876e
12 changed files with 53 additions and 52 deletions

View file

@ -17,9 +17,9 @@ class ExternalError(PrivateHyperglassError):
class UnsupportedDevice(PrivateHyperglassError):
"""Raised when an input NOS is not in the supported NOS list."""
"""Raised when an input platform is not in the supported platform list."""
def __init__(self, device_type: str) -> None:
def __init__(self, platform: str) -> None:
"""Show the unsupported device type and a list of supported drivers."""
# Third Party
from netmiko.ssh_dispatcher import CLASS_MAPPER # type: ignore
@ -29,7 +29,7 @@ class UnsupportedDevice(PrivateHyperglassError):
drivers = ("", *[*DRIVER_MAP.keys(), *CLASS_MAPPER.keys()].sort())
driver_list = "\n - ".join(drivers)
super().__init__(message=f"'{device_type}' is not supported. Must be one of:{driver_list}")
super().__init__(message=f"'{platform}' is not supported. Must be one of:{driver_list}")
class InputValidationError(PrivateHyperglassError):

View file

@ -45,14 +45,14 @@ class Construct:
# Set transport method based on NOS type
self.transport = "scrape"
if self.device.type in TRANSPORT_REST:
if self.device.platform in TRANSPORT_REST:
self.transport = "rest"
# Remove slashes from target for required platforms
if self.device.type in TARGET_FORMAT_SPACE:
if self.device.platform in TARGET_FORMAT_SPACE:
self.target = re.sub(r"\/", r" ", str(self.query.query_target))
with Formatter(self.device.type, self.query.query_type) as formatter:
with Formatter(self.device.platform, self.query.query_type) as formatter:
self.target = formatter(self.query.query_target)
def json(self, afi):
@ -105,9 +105,9 @@ class Construct:
class Formatter:
"""Modify query target based on the device's NOS requirements and the query type."""
def __init__(self, device_type: str, query_type: str) -> None:
def __init__(self, platform: str, query_type: str) -> None:
"""Initialize target formatting."""
self.device_type = device_type
self.platform = platform
self.query_type = query_type
def __enter__(self):
@ -121,10 +121,10 @@ class Formatter:
pass
def _get_formatter(self):
if self.device_type in ("juniper", "juniper_junos"):
if self.platform in ("juniper", "juniper_junos"):
if self.query_type == "bgp_aspath":
return self._juniper_bgp_aspath
if self.device_type in ("bird", "bird_ssh"):
if self.platform in ("bird", "bird_ssh"):
if self.query_type == "bgp_aspath":
return self._bird_bgp_aspath
elif self.query_type == "bgp_community":

View file

@ -57,14 +57,14 @@ class NetmikoConnection(SSHConnection):
else:
log.debug("Connecting directly to {}", self.device.name)
global_args = netmiko_device_globals.get(self.device.type, {})
global_args = netmiko_device_globals.get(self.device.platform, {})
send_args = netmiko_device_send_args.get(self.device.type, {})
send_args = netmiko_device_send_args.get(self.device.platform, {})
driver_kwargs = {
"host": host or self.device._target,
"port": port or self.device.port,
"device_type": self.device.type,
"device_type": self.device.platform,
"username": self.device.credential.username,
"global_delay_factor": params.netmiko_delay_factor,
"timeout": math.floor(params.request_timeout * 1.25),
@ -72,7 +72,7 @@ class NetmikoConnection(SSHConnection):
**global_args,
}
if "_telnet" in self.device.type:
if "_telnet" in self.device.platform:
# Telnet devices with a low delay factor (default) tend to
# throw login errors.
driver_kwargs["global_delay_factor"] = 2

View file

@ -72,7 +72,7 @@ class ScrapliConnection(SSHConnection):
command output.
"""
params = use_state("params")
driver = _map_driver(self.device.type)
driver = _map_driver(self.device.platform)
if host is not None:
log.debug(
@ -84,7 +84,7 @@ class ScrapliConnection(SSHConnection):
else:
log.debug("Connecting directly to {}", self.device.name)
global_args = driver_global_args.get(self.device.type, {})
global_args = driver_global_args.get(self.device.platform, {})
driver_kwargs = {
"host": host or self.device._target,

View file

@ -55,8 +55,8 @@ class Commands(HyperglassModel, extra="allow", validate_all=False):
def import_params(cls, **input_params):
"""Import loaded YAML, initialize per-command definitions."""
obj = Commands()
for device_type, cmds in input_params.items():
cmd_set = _DEVICE_TYPE_MAP.get(device_type, CommandGroup)
for platform, cmds in input_params.items():
cmd_set = _DEVICE_TYPE_MAP.get(platform, CommandGroup)
cmds = cmd_set(**cmds)
setattr(obj, device_type, cmds)
setattr(obj, platform, cmds)
return obj

View file

@ -15,7 +15,7 @@ from hyperglass.util import (
get_driver,
get_fmt_keys,
resolve_hostname,
validate_device_type,
validate_platform,
)
from hyperglass.settings import Settings
from hyperglass.constants import SCRAPE_HELPERS, SUPPORTED_STRUCTURED_OUTPUT
@ -45,7 +45,7 @@ class Device(HyperglassModelWithId, extra="allow"):
display_name: Optional[StrictStr]
port: StrictInt = 22
ssl: Optional[Ssl]
type: StrictStr
platform: StrictStr
commands: List[Directive]
structured_output: Optional[StrictBool]
driver: Optional[SupportedDriver]
@ -156,15 +156,15 @@ class Device(HyperglassModelWithId, extra="allow"):
"""Validate structured output is supported on the device & set a default."""
if value is True:
if values["type"] not in SUPPORTED_STRUCTURED_OUTPUT:
if values["platform"] not in SUPPORTED_STRUCTURED_OUTPUT:
raise ConfigError(
"The 'structured_output' field is set to 'true' on device '{d}' with "
+ "NOS '{n}', which does not support structured output",
+ "platform '{p}', which does not support structured output",
d=values["name"],
n=values["type"],
p=values["platform"],
)
return value
elif value is None and values["type"] in SUPPORTED_STRUCTURED_OUTPUT:
elif value is None and values["platform"] in SUPPORTED_STRUCTURED_OUTPUT:
value = True
else:
value = False
@ -185,31 +185,32 @@ class Device(HyperglassModelWithId, extra="allow"):
@root_validator(pre=True)
def validate_device_commands(cls, values: Dict) -> Dict:
"""Validate & rewrite device type, set default commands."""
"""Validate & rewrite device platform, set default commands."""
_type = values.get("type")
if _type is None:
# Ensure device type is defined.
raise ValueError(
f"Device {values['name']} is missing a 'type' (Network Operating System) property."
platform = values.get("platform")
if platform is None:
# Ensure device platform is defined.
raise ConfigError(
"Device '{device}' is missing a 'platform' (Network Operating System) property",
device={values["name"]},
)
if _type in SCRAPE_HELPERS.keys():
if platform in SCRAPE_HELPERS.keys():
# Rewrite NOS to helper value if needed.
_type = SCRAPE_HELPERS[_type]
platform = SCRAPE_HELPERS[platform]
# Verify device type is supported by hyperglass.
supported, _ = validate_device_type(_type)
# Verify device platform is supported by hyperglass.
supported, _ = validate_platform(platform)
if not supported:
raise UnsupportedDevice(_type)
raise UnsupportedDevice(platform)
values["type"] = _type
values["platform"] = platform
commands = values.get("commands")
if commands is None:
# If no commands are defined, set commands to the NOS.
inferred = values["type"]
inferred = values["platform"]
# If the _telnet prefix is added, remove it from the command
# profile so the commands are the same regardless of
@ -224,7 +225,7 @@ class Device(HyperglassModelWithId, extra="allow"):
@validator("driver")
def validate_driver(cls, value: Optional[str], values: Dict) -> Dict:
"""Set the correct driver and override if supported."""
return get_driver(values["type"], value)
return get_driver(values["platform"], value)
class Devices(HyperglassModel, extra="allow"):

View file

@ -24,7 +24,7 @@ class Proxy(HyperglassModel):
address: Union[IPv4Address, IPv6Address, StrictStr]
port: StrictInt = 22
credential: Credential
type: StrictStr = "linux_ssh"
platform: StrictStr = "linux_ssh"
def __init__(self: "Proxy", **kwargs: Any) -> None:
"""Check for legacy fields."""
@ -48,13 +48,13 @@ class Proxy(HyperglassModel):
)
return value
@validator("type", pre=True, always=True)
@validator("platform", pre=True, always=True)
def validate_type(cls: "Proxy", value: Any, values: Dict[str, Any]) -> str:
"""Validate device type."""
if value != "linux_ssh":
raise UnsupportedDevice(
"Proxy '{p}' uses type '{t}', which is currently unsupported.",
"Proxy '{p}' uses platform '{t}', which is currently unsupported.",
p=values["name"],
t=value,
)

View file

@ -7,8 +7,8 @@ from typing import Any, Dict, Tuple
from hyperglass.log import log
LEGACY_FIELDS: Dict[str, Tuple[Tuple[str, str], ...]] = {
"Device": (("nos", "type"),),
"Proxy": (("nos", "type"),),
"Device": (("nos", "platform"),),
"Proxy": (("nos", "platform"),),
}

View file

@ -63,10 +63,10 @@ class DirectivePlugin(BaseModel):
directives: Sequence[str] = ()
class DeviceTypePlugin(BaseModel):
"""Plugin associated with specific device types.
class PlatformPlugin(BaseModel):
"""Plugin associated with specific device platform.
Should always be subclassed with `HyperglassPlugin`.
"""
device_types: Sequence[str] = ()
platforms: Sequence[str] = ()

View file

@ -120,7 +120,7 @@ class BGPRoutePluginJuniper(OutputPlugin):
"""Coerce a Juniper route table in XML format to a standard BGP Table structure."""
__hyperglass_builtin__: bool = PrivateAttr(True)
device_types: Sequence[str] = ("juniper",)
platforms: Sequence[str] = ("juniper",)
directives: Sequence[str] = (
"__hyperglass_juniper_bgp_route__",
"__hyperglass_juniper_bgp_aspath__",
@ -132,7 +132,7 @@ class BGPRoutePluginJuniper(OutputPlugin):
should_process = all(
(
isinstance(output, (list, tuple)),
device.type in self.device_types,
device.platform in self.platforms,
device.structured_output is True,
device.has_directives(*self.directives),
)

View file

@ -8,7 +8,7 @@ from hyperglass.log import log
from hyperglass.types import Series
# Local
from ._base import DirectivePlugin, DeviceTypePlugin, HyperglassPlugin
from ._base import DirectivePlugin, PlatformPlugin, HyperglassPlugin
if TYPE_CHECKING:
# Project
@ -18,7 +18,7 @@ if TYPE_CHECKING:
OutputType = Union["OutputDataModel", Series[str]]
class OutputPlugin(HyperglassPlugin, DirectivePlugin, DeviceTypePlugin):
class OutputPlugin(HyperglassPlugin, DirectivePlugin, PlatformPlugin):
"""Plugin to interact with device command output."""
def process(self, output: OutputType, device: "Device") -> OutputType:

View file

@ -220,7 +220,7 @@ def repr_from_attrs(obj: object, attrs: Series[str]) -> str:
return f"{obj.__class__.__name__}({','.join(pairs)})"
def validate_device_type(_type: str) -> t.Tuple[bool, t.Union[None, str]]:
def validate_platform(_type: str) -> t.Tuple[bool, t.Union[None, str]]:
"""Validate device type is supported."""
result = (False, None)