diff --git a/hyperglass/constants.py b/hyperglass/constants.py index 8135e9b..d35c699 100644 --- a/hyperglass/constants.py +++ b/hyperglass/constants.py @@ -68,8 +68,12 @@ SCRAPE_HELPERS = { } DRIVER_MAP = { - "cisco_ios": "netmiko", - "juniper": "netmiko", + "arista_eos": "scrapli", + "cisco_ios": "scrapli", + "cisco_xe": "scrapli", + "cisco_xr": "scrapli", + "cisco_nxos": "scrapli", + "juniper": "scrapli", "frr": "hyperglass_agent", "bird": "hyperglass_agent", } diff --git a/hyperglass/execution/drivers/ssh_scrapli.py b/hyperglass/execution/drivers/ssh_scrapli.py new file mode 100644 index 0000000..3867319 --- /dev/null +++ b/hyperglass/execution/drivers/ssh_scrapli.py @@ -0,0 +1,137 @@ +"""Scrapli-Specific Classes & Utilities. + +https://github.com/carlmontanari/scrapli +""" + +# Standard Library +import math +from typing import Iterable + +# Third Party +from scrapli.driver import AsyncGenericDriver +from scrapli.exceptions import ( + ScrapliTimeout, + ScrapliException, + KeyVerificationFailed, + ScrapliAuthenticationFailed, +) +from scrapli.driver.core import ( + AsyncEOSDriver, + AsyncNXOSDriver, + AsyncIOSXEDriver, + AsyncIOSXRDriver, + AsyncJunosDriver, +) + +# Project +from hyperglass.log import log +from hyperglass.exceptions import ( + AuthError, + ScrapeError, + DeviceTimeout, + UnsupportedDevice, +) +from hyperglass.configuration import params +from hyperglass.execution.drivers.ssh import SSHConnection + +SCRAPLI_DRIVER_MAP = { + "cisco_ios": AsyncIOSXEDriver, + "cisco_nxos": AsyncNXOSDriver, + "cisco_xr": AsyncIOSXRDriver, + "juniper": AsyncJunosDriver, + "arista_eos": AsyncEOSDriver, +} + + +def _map_driver(nos: str) -> AsyncGenericDriver: + driver = SCRAPLI_DRIVER_MAP.get(nos) + if driver is None: + raise UnsupportedDevice("{nos} is not supported by scrapli.", nos=nos) + return driver + + +class ScrapliConnection(SSHConnection): + """Handle a device connection via Scrapli.""" + + 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. + """ + driver = _map_driver(self.device.nos) + + 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) + + driver_kwargs = { + "host": host or self.device.address, + "port": port or self.device.port, + "auth_username": self.device.credential.username, + "auth_password": self.device.credential.password.get_secret_value(), + "timeout_transport": math.floor(params.request_timeout * 1.25), + "transport": "asyncssh", + "auth_strict_key": False, + "ssh_known_hosts_file": False, + "ssh_config_file": False, + } + + driver = driver(**driver_kwargs) + driver.logger = log.bind(logger_name=f"scrapli.driver-{driver._host}") + + try: + responses = () + + async with driver as connection: + + for query in self.query: + raw = await connection.send_command(query) + responses += (raw.result,) + log.debug(f'Raw response for command "{query}":\n{raw.result}') + + except ScrapliTimeout as err: + log.error(err) + raise DeviceTimeout( + params.messages.connection_error, + device_name=self.device.display_name, + proxy=None, + error=params.messages.request_timeout, + ) + except (ScrapliAuthenticationFailed, KeyVerificationFailed) as err: + log.error( + "Error authenticating to device {loc}: {e}", + loc=self.device.name, + e=str(err), + ) + + raise AuthError( + params.messages.connection_error, + device_name=self.device.display_name, + proxy=None, + error=params.messages.authentication_error, + ) + except ScrapliException as err: + log.error(err) + raise ScrapeError( + params.messages.connection_error, + device_name=self.device.display_name, + proxy=None, + error=params.messages.no_response, + ) + + if not responses: + raise ScrapeError( + params.messages.connection_error, + device_name=self.device.display_name, + proxy=None, + error=params.messages.no_response, + ) + + return responses