| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  | """Scrapli-Specific Classes & Utilities.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | https://github.com/carlmontanari/scrapli | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Standard Library | 
					
						
							|  |  |  | import math | 
					
						
							| 
									
										
										
										
											2020-10-18 12:15:13 -07:00
										 |  |  | from typing import Sequence | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | # 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 | 
					
						
							| 
									
										
										
										
											2020-10-05 12:10:27 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-11 13:14:57 -07:00
										 |  |  | # Local | 
					
						
							| 
									
										
										
										
											2020-10-05 12:10:27 -07:00
										 |  |  | from .ssh import SSHConnection | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | SCRAPLI_DRIVER_MAP = { | 
					
						
							| 
									
										
										
										
											2020-10-18 12:15:13 -07:00
										 |  |  |     "arista_eos": AsyncEOSDriver, | 
					
						
							| 
									
										
										
										
											2021-02-06 00:19:29 -07:00
										 |  |  |     "bird_ssh": AsyncGenericDriver, | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  |     "cisco_ios": AsyncIOSXEDriver, | 
					
						
							|  |  |  |     "cisco_nxos": AsyncNXOSDriver, | 
					
						
							|  |  |  |     "cisco_xr": AsyncIOSXRDriver, | 
					
						
							| 
									
										
										
										
											2021-02-06 00:19:29 -07:00
										 |  |  |     "frr_ssh": AsyncGenericDriver, | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  |     "juniper": AsyncJunosDriver, | 
					
						
							| 
									
										
										
										
											2020-10-18 12:15:13 -07:00
										 |  |  |     "tnsr": AsyncGenericDriver, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | driver_global_args = { | 
					
						
							|  |  |  |     # Per-NOS driver keyword arguments | 
					
						
							|  |  |  |     "tnsr": {"comms_prompt_pattern": r"\S+\s\S+[\#\>]"}, | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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.""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-18 12:15:13 -07:00
										 |  |  |     async def collect(self, host: str = None, port: int = None) -> Sequence: | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  |         """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) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-18 12:15:13 -07:00
										 |  |  |         global_args = driver_global_args.get(self.device.nos, {}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  |         driver_kwargs = { | 
					
						
							| 
									
										
										
										
											2020-07-30 01:30:01 -07:00
										 |  |  |             "host": host or self.device._target, | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  |             "port": port or self.device.port, | 
					
						
							|  |  |  |             "auth_username": self.device.credential.username, | 
					
						
							| 
									
										
										
										
											2021-02-25 21:02:53 -07:00
										 |  |  |             "timeout_ops": math.floor(params.request_timeout * 1.25), | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  |             "transport": "asyncssh", | 
					
						
							|  |  |  |             "auth_strict_key": False, | 
					
						
							|  |  |  |             "ssh_known_hosts_file": False, | 
					
						
							| 
									
										
										
										
											2020-10-18 12:15:13 -07:00
										 |  |  |             **global_args, | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-11 14:39:17 -07:00
										 |  |  |         if self.device.credential._method == "password": | 
					
						
							|  |  |  |             # Use password auth if no key is defined. | 
					
						
							|  |  |  |             driver_kwargs[ | 
					
						
							|  |  |  |                 "auth_password" | 
					
						
							|  |  |  |             ] = self.device.credential.password.get_secret_value() | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             # Otherwise, use key auth. | 
					
						
							|  |  |  |             driver_kwargs["auth_private_key"] = self.device.credential.key.as_posix() | 
					
						
							|  |  |  |             if self.device.credential._method == "encrypted_key": | 
					
						
							|  |  |  |                 # If the key is encrypted, use the password field as the | 
					
						
							|  |  |  |                 # private key password. | 
					
						
							|  |  |  |                 driver_kwargs[ | 
					
						
							|  |  |  |                     "auth_private_key_passphrase" | 
					
						
							|  |  |  |                 ] = self.device.credential.password.get_secret_value() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  |         driver = driver(**driver_kwargs) | 
					
						
							|  |  |  |         driver.logger = log.bind(logger_name=f"scrapli.driver-{driver._host}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             responses = () | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async with driver as connection: | 
					
						
							| 
									
										
										
										
											2020-10-05 12:10:27 -07:00
										 |  |  |                 await connection.get_prompt() | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  |                 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, | 
					
						
							| 
									
										
										
										
											2021-04-22 22:29:13 -07:00
										 |  |  |                 device_name=self.device.name, | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  |                 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, | 
					
						
							| 
									
										
										
										
											2021-04-22 22:29:13 -07:00
										 |  |  |                 device_name=self.device.name, | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  |                 proxy=None, | 
					
						
							|  |  |  |                 error=params.messages.authentication_error, | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         except ScrapliException as err: | 
					
						
							|  |  |  |             log.error(err) | 
					
						
							|  |  |  |             raise ScrapeError( | 
					
						
							|  |  |  |                 params.messages.connection_error, | 
					
						
							| 
									
										
										
										
											2021-04-22 22:29:13 -07:00
										 |  |  |                 device_name=self.device.name, | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  |                 proxy=None, | 
					
						
							|  |  |  |                 error=params.messages.no_response, | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not responses: | 
					
						
							|  |  |  |             raise ScrapeError( | 
					
						
							|  |  |  |                 params.messages.connection_error, | 
					
						
							| 
									
										
										
										
											2021-04-22 22:29:13 -07:00
										 |  |  |                 device_name=self.device.name, | 
					
						
							| 
									
										
										
										
											2020-07-23 17:48:13 -07:00
										 |  |  |                 proxy=None, | 
					
						
							|  |  |  |                 error=params.messages.no_response, | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return responses |