mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
implement ability to override device driver
This commit is contained in:
@@ -49,6 +49,7 @@ routers:
|
||||
| <R/> `port` | Integer | TCP port used to connect to the device. `22` by default. |
|
||||
| <R/> `nos` | String | Network Operating System. <MiniNote>Must be a <Link to="platforms">supported platform</Link>.</MiniNote> |
|
||||
| `structured_output` | Boolean | Disabled output parsing to structured data. |
|
||||
| `driver` | String | Override the device driver. Must be 'scrapli' or 'netmiko'. |
|
||||
| <R/>`credential` | | [Device Credential Configuration](#credential) |
|
||||
| <R/>`vrfs` | | [Device VRF Configuration](#vrfs) |
|
||||
| `proxy` | | [SSH Proxy Configuration](#proxy) |
|
||||
@@ -131,11 +132,11 @@ May be set to `null` to disable IPv4 for this VRF, on the parent device.
|
||||
|
||||
May be set to `null` to disable IPv6 for this VRF, on the parent device.
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
| :-------------------- | :------ | :------ | :------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| <R/> `source_address` | String | | Device's source IPv6 address for directed queries (ping, traceroute). This address must be surrounded by quotes. Ex. "0000:0000:0000::"|
|
||||
| `force_cidr` | Boolean | `true` | Convert IP host queries to actual advertised containing prefix length |
|
||||
| `access_list` | | | [IPv6 Access List Configuration](#access_list) |
|
||||
| Parameter | Type | Default | Description |
|
||||
| :-------------------- | :------ | :------ | :-------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| <R/> `source_address` | String | | Device's source IPv6 address for directed queries (ping, traceroute). This address must be surrounded by quotes. Ex. "0000:0000:0000::" |
|
||||
| `force_cidr` | Boolean | `true` | Convert IP host queries to actual advertised containing prefix length |
|
||||
| `access_list` | | | [IPv6 Access List Configuration](#access_list) |
|
||||
|
||||
:::note
|
||||
The `force_cidr` option will ensure that a **BGP Route** query for an IP host (/32 IPv4, /128 IPv6) is converted to its containing prefix. For example, a query for `1.1.1.1` would be converted to a query for `1.1.1.0/24`. This is because not all platforms support a BGP lookup for a host (this is primary a problem with IPv6, but the option applies to both address families).
|
||||
|
@@ -2,5 +2,6 @@
|
||||
|
||||
# Local
|
||||
from .agent import AgentConnection
|
||||
from ._common import Connection
|
||||
from .ssh_netmiko import NetmikoConnection
|
||||
from .ssh_scrapli import ScrapliConnection
|
||||
|
@@ -12,19 +12,24 @@ from typing import Any, Dict, Union, Callable, Sequence
|
||||
|
||||
# Project
|
||||
from hyperglass.log import log
|
||||
from hyperglass.util import validate_nos
|
||||
from hyperglass.exceptions import DeviceTimeout, ResponseEmpty
|
||||
from hyperglass.models.api import Query
|
||||
from hyperglass.configuration import params
|
||||
|
||||
# Local
|
||||
from .drivers import AgentConnection, NetmikoConnection, ScrapliConnection
|
||||
from .drivers import Connection, AgentConnection, NetmikoConnection, ScrapliConnection
|
||||
|
||||
DRIVER_MAP = {
|
||||
"scrapli": ScrapliConnection,
|
||||
"netmiko": NetmikoConnection,
|
||||
"hyperglass_agent": AgentConnection,
|
||||
}
|
||||
|
||||
def map_driver(driver_name: str) -> Connection:
|
||||
"""Get the correct driver class based on the driver name."""
|
||||
|
||||
if driver_name == "scrapli":
|
||||
return ScrapliConnection
|
||||
|
||||
elif driver_name == "hyperglass_agent":
|
||||
return AgentConnection
|
||||
|
||||
return NetmikoConnection
|
||||
|
||||
|
||||
def handle_timeout(**exc_args: Any) -> Callable:
|
||||
@@ -44,9 +49,7 @@ async def execute(query: Query) -> Union[str, Sequence[Dict]]:
|
||||
log.debug("Received query for {}", query.json())
|
||||
log.debug("Matched device config: {}", query.device)
|
||||
|
||||
supported, driver_name = validate_nos(query.device.nos)
|
||||
|
||||
mapped_driver = DRIVER_MAP.get(driver_name, NetmikoConnection)
|
||||
mapped_driver = map_driver(query.device.driver)
|
||||
driver = mapped_driver(query.device, query)
|
||||
|
||||
timeout_args = {
|
||||
|
@@ -19,7 +19,7 @@ from pydantic import (
|
||||
|
||||
# Project
|
||||
from hyperglass.log import log
|
||||
from hyperglass.util import validate_nos, resolve_hostname
|
||||
from hyperglass.util import get_driver, validate_nos, resolve_hostname
|
||||
from hyperglass.constants import SCRAPE_HELPERS, SUPPORTED_STRUCTURED_OUTPUT
|
||||
from hyperglass.exceptions import ConfigError, UnsupportedDevice
|
||||
|
||||
@@ -28,6 +28,7 @@ from .ssl import Ssl
|
||||
from .vrf import Vrf, Info
|
||||
from ..main import HyperglassModel, HyperglassModelExtra
|
||||
from .proxy import Proxy
|
||||
from ..fields import SupportedDriver
|
||||
from .network import Network
|
||||
from .credential import Credential
|
||||
|
||||
@@ -93,6 +94,7 @@ class Device(HyperglassModel):
|
||||
display_vrfs: List[StrictStr] = []
|
||||
vrf_names: List[StrictStr] = []
|
||||
structured_output: Optional[StrictBool]
|
||||
driver: Optional[SupportedDriver]
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
"""Set the device ID."""
|
||||
@@ -130,15 +132,9 @@ class Device(HyperglassModel):
|
||||
return value
|
||||
|
||||
@validator("structured_output", pre=True, always=True)
|
||||
def validate_structured_output(cls, value, values):
|
||||
"""Validate structured output is supported on the device & set a default.
|
||||
def validate_structured_output(cls, value: bool, values: Dict) -> bool:
|
||||
"""Validate structured output is supported on the device & set a default."""
|
||||
|
||||
Raises:
|
||||
ConfigError: Raised if true on a device that doesn't support structured output.
|
||||
|
||||
Returns:
|
||||
{bool} -- True if hyperglass should return structured output for this device.
|
||||
"""
|
||||
if value is True and values["nos"] not in SUPPORTED_STRUCTURED_OUTPUT:
|
||||
raise ConfigError(
|
||||
"The 'structured_output' field is set to 'true' on device '{d}' with "
|
||||
@@ -280,6 +276,11 @@ class Device(HyperglassModel):
|
||||
vrfs.append(vrf)
|
||||
return vrfs
|
||||
|
||||
@validator("driver")
|
||||
def validate_driver(cls, value: Optional[str], values: Dict) -> Dict:
|
||||
"""Set the correct driver and override if supported."""
|
||||
return get_driver(values["nos"], value)
|
||||
|
||||
|
||||
class Devices(HyperglassModelExtra):
|
||||
"""Validation model for device configurations."""
|
||||
|
@@ -5,10 +5,12 @@ import re
|
||||
from typing import TypeVar
|
||||
|
||||
# Third Party
|
||||
from pydantic import StrictInt, StrictFloat
|
||||
from pydantic import StrictInt, StrictFloat, constr
|
||||
|
||||
IntFloat = TypeVar("IntFloat", StrictInt, StrictFloat)
|
||||
|
||||
SupportedDriver = constr(regex=r"(scrapli|netmiko|hyperglass_agent)")
|
||||
|
||||
|
||||
class StrictBytes(bytes):
|
||||
"""Custom data type for a strict byte string.
|
||||
|
@@ -6,16 +6,21 @@ import sys
|
||||
import json
|
||||
import platform
|
||||
from queue import Queue
|
||||
from typing import Dict, Union, Generator
|
||||
from typing import Dict, Union, Optional, Generator
|
||||
from asyncio import iscoroutine
|
||||
from pathlib import Path
|
||||
from ipaddress import IPv4Address, IPv6Address, ip_address
|
||||
|
||||
# Third Party
|
||||
from loguru._logger import Logger as LoguruLogger
|
||||
from netmiko.ssh_dispatcher import CLASS_MAPPER
|
||||
|
||||
# Project
|
||||
from hyperglass.log import log
|
||||
from hyperglass.constants import DRIVER_MAP
|
||||
|
||||
ALL_NOS = {*DRIVER_MAP.keys(), *CLASS_MAPPER.keys()}
|
||||
ALL_DRIVERS = {*DRIVER_MAP.values(), "netmiko"}
|
||||
|
||||
|
||||
def cpu_count(multiplier: int = 0) -> int:
|
||||
@@ -250,22 +255,30 @@ def make_repr(_class):
|
||||
|
||||
def validate_nos(nos):
|
||||
"""Validate device NOS is supported."""
|
||||
# Third Party
|
||||
from netmiko.ssh_dispatcher import CLASS_MAPPER
|
||||
|
||||
# Project
|
||||
from hyperglass.constants import DRIVER_MAP
|
||||
|
||||
result = (False, None)
|
||||
|
||||
all_nos = {*DRIVER_MAP.keys(), *CLASS_MAPPER.keys()}
|
||||
|
||||
if nos in all_nos:
|
||||
if nos in ALL_NOS:
|
||||
result = (True, DRIVER_MAP.get(nos, "netmiko"))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_driver(nos: str, driver: Optional[str]) -> str:
|
||||
"""Determine the appropriate driver for a device."""
|
||||
|
||||
if driver is None:
|
||||
# If no driver is set, use the driver map with netmiko as
|
||||
# fallback.
|
||||
return DRIVER_MAP.get(nos, "netmiko")
|
||||
elif driver in ALL_DRIVERS:
|
||||
# If a driver is set and it is valid, allow it.
|
||||
return driver
|
||||
else:
|
||||
# Otherwise, fail validation.
|
||||
raise ValueError("{} is not a supported driver.".format(driver))
|
||||
|
||||
|
||||
def current_log_level(logger: LoguruLogger) -> str:
|
||||
"""Get the current log level of a logger instance."""
|
||||
|
||||
|
Reference in New Issue
Block a user