1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

Improve driver layout

This commit is contained in:
checktheroads
2020-07-23 17:47:54 -07:00
parent 224d41c2aa
commit 3b513e2a60
5 changed files with 123 additions and 109 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View 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

View File

@@ -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)