mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
Improve driver layout
This commit is contained in:
@@ -1 +1,6 @@
|
||||
"""Individual transport driver classes & subclasses."""
|
||||
|
||||
# Project
|
||||
from hyperglass.execution.drivers.agent import AgentConnection
|
||||
from hyperglass.execution.drivers.ssh_netmiko import NetmikoConnection
|
||||
from hyperglass.execution.drivers.ssh_scrapli import ScrapliConnection
|
||||
|
@@ -92,6 +92,7 @@ class AgentConnection(Connection):
|
||||
)
|
||||
log.debug(f"Decoded Response: {decoded}")
|
||||
responses += (decoded,)
|
||||
|
||||
elif raw_response.status_code == 204:
|
||||
raise ResponseEmpty(
|
||||
params.messages.no_output,
|
||||
|
@@ -1,41 +1,18 @@
|
||||
"""Execute validated & constructed query on device.
|
||||
|
||||
Accepts input from front end application, validates the input and
|
||||
returns errors if input is invalid. Passes validated parameters to
|
||||
construct.py, which is used to build & run the Netmiko connections or
|
||||
hyperglass-frr API calls, returns the output back to the front end.
|
||||
"""
|
||||
"""Common Classes or Utilities for SSH Drivers."""
|
||||
|
||||
# Standard Library
|
||||
import math
|
||||
from typing import Callable, Iterable
|
||||
|
||||
# Third Party
|
||||
from netmiko import (
|
||||
ConnectHandler,
|
||||
NetmikoAuthError,
|
||||
NetmikoTimeoutError,
|
||||
NetMikoTimeoutException,
|
||||
NetMikoAuthenticationException,
|
||||
)
|
||||
from typing import Callable
|
||||
|
||||
# Project
|
||||
from hyperglass.log import log
|
||||
from hyperglass.exceptions import AuthError, ScrapeError, DeviceTimeout
|
||||
from hyperglass.exceptions import ScrapeError
|
||||
from hyperglass.configuration import params
|
||||
from hyperglass.compat._sshtunnel import BaseSSHTunnelForwarderError, open_tunnel
|
||||
from hyperglass.execution.drivers._common import Connection
|
||||
|
||||
|
||||
class SSHConnection(Connection):
|
||||
"""Connect to target device via specified transport.
|
||||
|
||||
scrape_direct() directly connects to devices via SSH
|
||||
|
||||
scrape_proxied() connects to devices via an SSH proxy
|
||||
|
||||
rest() connects to devices via HTTP for RESTful API communication
|
||||
"""
|
||||
"""Base class for SSH drivers."""
|
||||
|
||||
def setup_proxy(self) -> Callable:
|
||||
"""Return a preconfigured sshtunnel.SSHTunnelForwarder instance."""
|
||||
@@ -69,73 +46,3 @@ class SSHConnection(Connection):
|
||||
)
|
||||
|
||||
return opener
|
||||
|
||||
async def netmiko(self, host: str = None, port: int = None) -> Iterable:
|
||||
"""Connect directly to a device.
|
||||
|
||||
Directly connects to the router via Netmiko library, returns the
|
||||
command output.
|
||||
"""
|
||||
if host is not None:
|
||||
log.debug(
|
||||
"Connecting to {} via proxy {} [{}]",
|
||||
self.device.name,
|
||||
self.device.proxy.name,
|
||||
f"{host}:{port}",
|
||||
)
|
||||
else:
|
||||
log.debug("Connecting directly to {}", self.device.name)
|
||||
|
||||
netmiko_args = {
|
||||
"host": host or self.device.address,
|
||||
"port": port or self.device.port,
|
||||
"device_type": self.device.nos,
|
||||
"username": self.device.credential.username,
|
||||
"password": self.device.credential.password.get_secret_value(),
|
||||
"global_delay_factor": params.netmiko_delay_factor,
|
||||
"timeout": math.floor(params.request_timeout * 1.25),
|
||||
"session_timeout": math.ceil(params.request_timeout - 1),
|
||||
}
|
||||
|
||||
try:
|
||||
nm_connect_direct = ConnectHandler(**netmiko_args)
|
||||
|
||||
responses = ()
|
||||
|
||||
for query in self.query:
|
||||
raw = nm_connect_direct.send_command(query)
|
||||
responses += (raw,)
|
||||
log.debug(f'Raw response for command "{query}":\n{raw}')
|
||||
|
||||
nm_connect_direct.disconnect()
|
||||
|
||||
except (NetMikoTimeoutException, NetmikoTimeoutError) as scrape_error:
|
||||
log.error(str(scrape_error))
|
||||
raise DeviceTimeout(
|
||||
params.messages.connection_error,
|
||||
device_name=self.device.display_name,
|
||||
proxy=None,
|
||||
error=params.messages.request_timeout,
|
||||
)
|
||||
except (NetMikoAuthenticationException, NetmikoAuthError) as auth_error:
|
||||
log.error(
|
||||
"Error authenticating to device {loc}: {e}",
|
||||
loc=self.device.name,
|
||||
e=str(auth_error),
|
||||
)
|
||||
|
||||
raise AuthError(
|
||||
params.messages.connection_error,
|
||||
device_name=self.device.display_name,
|
||||
proxy=None,
|
||||
error=params.messages.authentication_error,
|
||||
)
|
||||
if not responses:
|
||||
raise ScrapeError(
|
||||
params.messages.connection_error,
|
||||
device_name=self.device.display_name,
|
||||
proxy=None,
|
||||
error=params.messages.no_response,
|
||||
)
|
||||
|
||||
return responses
|
||||
|
97
hyperglass/execution/drivers/ssh_netmiko.py
Normal file
97
hyperglass/execution/drivers/ssh_netmiko.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""Netmiko-Specific Classes & Utilities.
|
||||
|
||||
https://github.com/ktbyers/netmiko
|
||||
"""
|
||||
|
||||
# Standard Library
|
||||
import math
|
||||
from typing import Iterable
|
||||
|
||||
# Third Party
|
||||
from netmiko import (
|
||||
ConnectHandler,
|
||||
NetmikoAuthError,
|
||||
NetmikoTimeoutError,
|
||||
NetMikoTimeoutException,
|
||||
NetMikoAuthenticationException,
|
||||
)
|
||||
|
||||
# Project
|
||||
from hyperglass.log import log
|
||||
from hyperglass.exceptions import AuthError, ScrapeError, DeviceTimeout
|
||||
from hyperglass.configuration import params
|
||||
from hyperglass.execution.drivers.ssh import SSHConnection
|
||||
|
||||
|
||||
class NetmikoConnection(SSHConnection):
|
||||
"""Handle a device connection via Netmiko."""
|
||||
|
||||
async def collect(self, host: str = None, port: int = None) -> Iterable:
|
||||
"""Connect directly to a device.
|
||||
|
||||
Directly connects to the router via Netmiko library, returns the
|
||||
command output.
|
||||
"""
|
||||
if host is not None:
|
||||
log.debug(
|
||||
"Connecting to {} via proxy {} [{}]",
|
||||
self.device.name,
|
||||
self.device.proxy.name,
|
||||
f"{host}:{port}",
|
||||
)
|
||||
else:
|
||||
log.debug("Connecting directly to {}", self.device.name)
|
||||
|
||||
netmiko_args = {
|
||||
"host": host or self.device.address,
|
||||
"port": port or self.device.port,
|
||||
"device_type": self.device.nos,
|
||||
"username": self.device.credential.username,
|
||||
"password": self.device.credential.password.get_secret_value(),
|
||||
"global_delay_factor": params.netmiko_delay_factor,
|
||||
"timeout": math.floor(params.request_timeout * 1.25),
|
||||
"session_timeout": math.ceil(params.request_timeout - 1),
|
||||
}
|
||||
|
||||
try:
|
||||
nm_connect_direct = ConnectHandler(**netmiko_args)
|
||||
|
||||
responses = ()
|
||||
|
||||
for query in self.query:
|
||||
raw = nm_connect_direct.send_command(query)
|
||||
responses += (raw,)
|
||||
log.debug(f'Raw response for command "{query}":\n{raw}')
|
||||
|
||||
nm_connect_direct.disconnect()
|
||||
|
||||
except (NetMikoTimeoutException, NetmikoTimeoutError) as scrape_error:
|
||||
log.error(str(scrape_error))
|
||||
raise DeviceTimeout(
|
||||
params.messages.connection_error,
|
||||
device_name=self.device.display_name,
|
||||
proxy=None,
|
||||
error=params.messages.request_timeout,
|
||||
)
|
||||
except (NetMikoAuthenticationException, NetmikoAuthError) as auth_error:
|
||||
log.error(
|
||||
"Error authenticating to device {loc}: {e}",
|
||||
loc=self.device.name,
|
||||
e=str(auth_error),
|
||||
)
|
||||
|
||||
raise AuthError(
|
||||
params.messages.connection_error,
|
||||
device_name=self.device.display_name,
|
||||
proxy=None,
|
||||
error=params.messages.authentication_error,
|
||||
)
|
||||
if not responses:
|
||||
raise ScrapeError(
|
||||
params.messages.connection_error,
|
||||
device_name=self.device.display_name,
|
||||
proxy=None,
|
||||
error=params.messages.no_response,
|
||||
)
|
||||
|
||||
return responses
|
@@ -16,8 +16,17 @@ from hyperglass.util import validate_nos
|
||||
from hyperglass.exceptions import DeviceTimeout, ResponseEmpty
|
||||
from hyperglass.configuration import params, devices
|
||||
from hyperglass.api.models.query import Query
|
||||
from hyperglass.execution.drivers.ssh import SSHConnection
|
||||
from hyperglass.execution.drivers.agent import AgentConnection
|
||||
from hyperglass.execution.drivers import (
|
||||
AgentConnection,
|
||||
NetmikoConnection,
|
||||
ScrapliConnection,
|
||||
)
|
||||
|
||||
DRIVER_MAP = {
|
||||
"scrapli": ScrapliConnection,
|
||||
"netmiko": NetmikoConnection,
|
||||
"hyperglass_agent": AgentConnection,
|
||||
}
|
||||
|
||||
|
||||
def handle_timeout(**exc_args: Any) -> Callable:
|
||||
@@ -40,15 +49,8 @@ async def execute(query: Query) -> Union[str, Dict]:
|
||||
|
||||
supported, driver_name = validate_nos(device.nos)
|
||||
|
||||
driver_map = {
|
||||
"scrapli": SSHConnection,
|
||||
"netmiko": SSHConnection,
|
||||
"hyperglass_agent": AgentConnection,
|
||||
}
|
||||
|
||||
mapped_driver = driver_map.get(driver_name, SSHConnection)
|
||||
mapped_driver = DRIVER_MAP.get(driver_name, NetmikoConnection)
|
||||
driver = mapped_driver(device, query)
|
||||
connector = getattr(driver, driver_name)
|
||||
|
||||
timeout_args = {
|
||||
"unformatted_msg": params.messages.connection_error,
|
||||
@@ -65,9 +67,11 @@ async def execute(query: Query) -> Union[str, Dict]:
|
||||
if device.proxy:
|
||||
proxy = driver.setup_proxy()
|
||||
with proxy() as tunnel:
|
||||
response = await connector(tunnel.local_bind_host, tunnel.local_bind_port)
|
||||
response = await driver.collect(
|
||||
tunnel.local_bind_host, tunnel.local_bind_port
|
||||
)
|
||||
else:
|
||||
response = await connector()
|
||||
response = await driver.collect()
|
||||
|
||||
output = await driver.parsed_response(response)
|
||||
|
||||
|
Reference in New Issue
Block a user