1
0
Fork 1
mirror of https://github.com/thatmattlove/hyperglass.git synced 2026-01-17 08:48:05 +00:00
thatmattlove-hyperglass/hyperglass/command/execute.py
2019-06-07 18:33:49 -07:00

219 lines
8.3 KiB
Python

# https://github.com/checktheroads/hyperglass
"""
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 \
connectoins or hyperglass-frr API calls, returns the output back to the front end.
"""
# Module Imports
import json
import time
import requests
import requests.exceptions
from logzero import logger
from netmiko import (
ConnectHandler,
redispatch,
NetMikoAuthenticationException,
NetMikoTimeoutException,
NetmikoAuthError,
NetmikoTimeoutError,
)
# Project Imports
from hyperglass import configuration
from hyperglass.command.construct import Construct
from hyperglass.command.validate import Validate
codes = configuration.codes()
config = configuration.general()
class Rest:
"""Executes connections to REST API devices"""
# pylint: disable=too-few-public-methods
# Dear PyLint, sometimes, people need to make their code scalable for future use. <3, -ML
def __init__(self, transport, device, cmd, target):
self.transport = transport
self.device = device
self.cmd = cmd
self.target = target
self.cred = configuration.credential(self.device["credential"])
self.query = getattr(Construct(self.device), self.cmd)(
self.transport, self.target
)
def frr(self):
"""Sends HTTP POST to router running the hyperglass-frr API"""
try:
headers = {
"Content-Type": "application/json",
"X-API-Key": self.cred["password"],
}
json_query = json.dumps(self.query)
frr_endpoint = f'http://{self.device["address"]}:{self.device["port"]}/frr'
frr_response = requests.post(frr_endpoint, headers=headers, data=json_query)
response = frr_response.text
status = frr_response.status_code
except requests.exceptions.RequestException as requests_exception:
logger.error(
f'Error connecting to device {self.device["name"]}: {requests_exception}'
)
response = config["msg_error_general"]
status = codes["danger"]
return response, status
class Netmiko:
"""Executes connections to Netmiko devices"""
# pylint: disable=too-many-instance-attributes
# Dear PyLint, I actually need all these. <3, -ML
def __init__(self, transport, device, cmd, target):
self.device = device
self.target = target
self.cred = configuration.credential(self.device["credential"])
self.params = getattr(Construct(device), cmd)(transport, target)
self.router = self.params[0]
self.nos = self.params[1]
self.command = self.params[2]
self.nm_host = {
"host": self.router,
"device_type": self.nos,
"username": self.cred["username"],
"password": self.cred["password"],
"global_delay_factor": 0.5,
}
def direct(self):
"""Connects to the router via netmiko library, return the command output"""
try:
nm_connect_direct = ConnectHandler(**self.nm_host)
response = nm_connect_direct.send_command(self.command)
status = codes["success"]
except (
NetMikoAuthenticationException,
NetMikoTimeoutException,
NetmikoAuthError,
NetmikoTimeoutError,
) as netmiko_exception:
response = config["msg_error_general"]
status = codes["danger"]
logger.error(f"{netmiko_exception}, {status}")
return response, status
def proxied(self):
"""
Connects to the proxy server via netmiko library, then logs into the router via \
standard SSH
"""
proxy_name = self.device["proxy"]
device_proxy = configuration.proxy(proxy_name)
nm_proxy = {
"host": device_proxy["address"],
"username": device_proxy["username"],
"password": device_proxy["password"],
"device_type": device_proxy["type"],
"global_delay_factor": 0.5,
}
nm_connect_proxied = ConnectHandler(**nm_proxy)
nm_ssh_command = device_proxy["ssh_command"].format(**self.nm_host) + "\n"
nm_connect_proxied.write_channel(nm_ssh_command)
time.sleep(1)
proxy_output = nm_connect_proxied.read_channel()
try:
# Accept SSH key warnings
if "Are you sure you want to continue connecting" in proxy_output:
nm_connect_proxied.write_channel("yes" + "\n")
nm_connect_proxied.write_channel(self.nm_host["password"] + "\n")
# Send password on prompt
elif "assword" in proxy_output:
nm_connect_proxied.write_channel(self.nm_host["password"] + "\n")
proxy_output += nm_connect_proxied.read_channel()
# Reclassify netmiko connection as configured device type
redispatch(nm_connect_proxied, self.nm_host["device_type"])
response = nm_connect_proxied.send_command(self.command)
status = codes["success"]
except (
NetMikoAuthenticationException,
NetMikoTimeoutException,
NetmikoAuthError,
NetmikoTimeoutError,
) as netmiko_exception:
response = config["msg_error_general"]
status = codes["danger"]
logger.error(
f'{netmiko_exception}, {status},Proxy: {self.nm_host["proxy"]}'
)
return response, status
class Execute:
"""
Ingests user input, runs blacklist check, runs prefix length check (if enabled), pulls all \
configuraiton variables for the input router.
"""
def __init__(self, lg_data):
self.input_data = lg_data
self.input_router = lg_data["router"]
self.input_cmd = lg_data["cmd"]
self.input_target = lg_data["ipprefix"]
self.device_config = configuration.device(self.input_router)
def parse(self, output):
"""Splits BGP output by AFI, returns only IPv4 & IPv6 output for protocol-agnostic \
commands (Community & AS_PATH Lookups)"""
nos = self.device_config["type"]
parsed = output
if self.input_cmd in ["bgp_community", "bgp_aspath"]:
if nos in ["cisco_ios"]:
delimiter = "For address family: "
parsed_ipv4 = output.split(delimiter)[1]
parsed_ipv6 = output.split(delimiter)[2]
parsed = delimiter + parsed_ipv4 + delimiter + parsed_ipv6
if nos in ["cisco_xr"]:
delimiter = "Address Family: "
parsed_ipv4 = output.split(delimiter)[1]
parsed_ipv6 = output.split(delimiter)[2]
parsed = delimiter + parsed_ipv4 + delimiter + parsed_ipv6
return parsed
def response(self):
"""
Initializes Execute.filter(), if input fails to pass filter, returns errors to front end. \
Otherwise, executes queries.
"""
# Return error if no query type is specified
if self.input_cmd == "Query Type":
msg = config["msg_error_querytype"]
status = codes["warning"]
return msg, status, self.input_data
validity, msg, status = getattr(Validate(self.device_config), self.input_cmd)(
self.input_target
)
if not validity:
return msg, status, self.input_data
connection = None
output = config["msg_error_general"]
info = self.input_data
if self.device_config["type"] == "frr":
connection = Rest(
"rest", self.device_config, self.input_cmd, self.input_target
)
raw_output, status = connection.frr()
output = self.parse(raw_output)
if self.device_config["type"] in configuration.scrape_list():
connection = Netmiko(
"scrape", self.device_config, self.input_cmd, self.input_target
)
if self.device_config["proxy"]:
raw_output, status = connection.proxied()
else:
raw_output, status = connection.direct()
output = self.parse(raw_output)
else:
logger.error(f"{output}, {status}, {info}")
return output, status, info