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

implement overhaul of error handling

This commit is contained in:
checktheroads
2019-08-31 23:50:02 -07:00
parent d48b7130e4
commit 25359c8834
5 changed files with 214 additions and 138 deletions

View File

@@ -136,7 +136,13 @@ class Construct:
query = json.dumps({"query_type": query_type, "afi": afi, "target": target}) query = json.dumps({"query_type": query_type, "afi": afi, "target": target})
elif self.transport == "scrape": elif self.transport == "scrape":
conf_command = self.device_commands(self.device.nos, afi, query_type) conf_command = self.device_commands(self.device.nos, afi, query_type)
query = conf_command.format(target=target) afis = []
for afi in self.device.afis:
split_afi = afi.split("v")
afis.append(
"".join([split_afi[0].upper(), "v", split_afi[1], " Unicast|"])
)
query = conf_command.format(target=target, afis="".join(afis))
logger.debug(f"Constructed query: {query}") logger.debug(f"Constructed query: {query}")
return query return query
@@ -154,6 +160,12 @@ class Construct:
query = json.dumps({"query_type": query_type, "afi": afi, "target": target}) query = json.dumps({"query_type": query_type, "afi": afi, "target": target})
elif self.transport == "scrape": elif self.transport == "scrape":
conf_command = self.device_commands(self.device.nos, afi, query_type) conf_command = self.device_commands(self.device.nos, afi, query_type)
query = conf_command.format(target=target) afis = []
for afi in self.device.afis:
split_afi = afi.split("v")
afis.append(
"".join([split_afi[0].upper(), "v", split_afi[1], " Unicast|"])
)
query = conf_command.format(target=target, afis="".join(afis))
logger.debug(f"Constructed query: {query}") logger.debug(f"Constructed query: {query}")
return query return query

View File

@@ -24,7 +24,6 @@ from hyperglass.configuration import logzero_config # noqa: F401
from hyperglass.configuration import params from hyperglass.configuration import params
from hyperglass.configuration import proxies from hyperglass.configuration import proxies
from hyperglass.constants import Supported from hyperglass.constants import Supported
from hyperglass.constants import code
from hyperglass.constants import protocol_map from hyperglass.constants import protocol_map
from hyperglass.exceptions import AuthError, RestError, ScrapeError from hyperglass.exceptions import AuthError, RestError, ScrapeError
@@ -74,6 +73,7 @@ class Connect:
self.device_config.port, self.device_config.port,
), ),
local_bind_address=("localhost", 0), local_bind_address=("localhost", 0),
skip_tunnel_checkup=False,
) as tunnel: ) as tunnel:
logger.debug(f"Established tunnel with {self.device_config.proxy}") logger.debug(f"Established tunnel with {self.device_config.proxy}")
scrape_host = { scrape_host = {
@@ -99,16 +99,24 @@ class Connect:
NetmikoTimeoutError, NetmikoTimeoutError,
sshtunnel.BaseSSHTunnelForwarderError, sshtunnel.BaseSSHTunnelForwarderError,
) as scrape_error: ) as scrape_error:
logger.error(
f"Error connecting to device {self.device_config.location}"
)
raise ScrapeError( raise ScrapeError(
params.messages.connection_error,
device=self.device_config.location, device=self.device_config.location,
proxy=self.device_config.proxy, proxy=self.device_config.proxy,
error_msg=scrape_error, error=scrape_error,
) from None ) from None
except (NetMikoAuthenticationException, NetmikoAuthError) as auth_error: except (NetMikoAuthenticationException, NetmikoAuthError) as auth_error:
logger.error(
f"Error authenticating to device {self.device_config.location}"
)
raise AuthError( raise AuthError(
params.messages.connection_error,
device=self.device_config.location, device=self.device_config.location,
proxy=self.device_config.proxy, proxy=self.device_config.proxy,
error_msg=auth_error, error=auth_error,
) from None ) from None
else: else:
scrape_host = { scrape_host = {
@@ -134,16 +142,32 @@ class Connect:
NetmikoTimeoutError, NetmikoTimeoutError,
sshtunnel.BaseSSHTunnelForwarderError, sshtunnel.BaseSSHTunnelForwarderError,
) as scrape_error: ) as scrape_error:
logger.error(
f"Error connecting to device {self.device_config.location}"
)
raise ScrapeError( raise ScrapeError(
device=self.device_config.location, error_msg=scrape_error params.messages.connection_error,
device=self.device_config.location,
proxy=None,
error=scrape_error,
) from None ) from None
except (NetMikoAuthenticationException, NetmikoAuthError) as auth_error: except (NetMikoAuthenticationException, NetmikoAuthError) as auth_error:
logger.error(
f"Error authenticating to device {self.device_config.location}"
)
raise AuthError( raise AuthError(
device=self.device_config.location, error_msg=auth_error params.messages.connection_error,
device=self.device_config.location,
proxy=None,
error=auth_error,
) from None ) from None
if not response: if not response:
logger.error(f"No response from device {self.device_config.location}")
raise ScrapeError( raise ScrapeError(
device=self.device_config.location, error_msg="No response" params.messages.connection_error,
device=self.device_config.location,
proxy=None,
error="No response",
) )
logger.debug(f"Output for query: {self.query}:\n{response}") logger.debug(f"Output for query: {self.query}:\n{response}")
return response return response
@@ -151,6 +175,7 @@ class Connect:
async def rest(self): async def rest(self):
"""Sends HTTP POST to router running a hyperglass API agent""" """Sends HTTP POST to router running a hyperglass API agent"""
logger.debug(f"Query parameters: {self.query}") logger.debug(f"Query parameters: {self.query}")
uri = Supported.map_rest(self.device_config.nos) uri = Supported.map_rest(self.device_config.nos)
headers = { headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -163,8 +188,10 @@ class Connect:
port=self.device_config.port, port=self.device_config.port,
uri=uri, uri=uri,
) )
logger.debug(f"HTTP Headers: {headers}") logger.debug(f"HTTP Headers: {headers}")
logger.debug(f"URL endpoint: {endpoint}") logger.debug(f"URL endpoint: {endpoint}")
try: try:
http_client = httpx.AsyncClient() http_client = httpx.AsyncClient()
raw_response = await http_client.post( raw_response = await http_client.post(
@@ -172,7 +199,7 @@ class Connect:
) )
response = raw_response.text response = raw_response.text
logger.debug(f"HTTP status code: {status}") logger.debug(f"HTTP status code: {raw_response.status_code}")
logger.debug(f"Output for query {self.query}:\n{response}") logger.debug(f"Output for query {self.query}:\n{response}")
except ( except (
httpx.exceptions.ConnectTimeout, httpx.exceptions.ConnectTimeout,
@@ -193,8 +220,11 @@ class Connect:
OSError, OSError,
) as rest_error: ) as rest_error:
logger.error(f"Error connecting to device {self.device_config.location}") logger.error(f"Error connecting to device {self.device_config.location}")
logger.error(rest_error) raise RestError(
raise RestError(device=self.device_config.location, error_msg=rest_error) params.messages.connection_error,
device=self.device_config.location,
error=rest_error,
)
return response return response
@@ -206,20 +236,22 @@ class Execute:
""" """
def __init__(self, lg_data): def __init__(self, lg_data):
self.input_data = lg_data self.query_data = lg_data
self.input_location = self.input_data["location"] self.query_location = self.query_data["location"]
self.input_type = self.input_data["query_type"] self.query_type = self.query_data["query_type"]
self.input_target = self.input_data["target"] self.query_target = self.query_data["target"]
def parse(self, raw_output, nos): def parse(self, raw_output, nos):
""" """
Deprecating: see #16
Splits BGP raw output by AFI, returns only IPv4 & IPv6 output for Splits BGP raw output by AFI, returns only IPv4 & IPv6 output for
protocol-agnostic commands (Community & AS_PATH Lookups). protocol-agnostic commands (Community & AS_PATH Lookups).
""" """
logger.debug("Parsing raw output...") logger.debug("Parsing raw output...")
parsed = raw_output parsed = raw_output
if self.input_type in ("bgp_community", "bgp_aspath"): if self.query_type in ("bgp_community", "bgp_aspath"):
logger.debug(f"Parsing raw output for device type {nos}") logger.debug(f"Parsing raw output for device type {nos}")
if nos in ("cisco_ios",): if nos in ("cisco_ios",):
delimiter = "For address family: " delimiter = "For address family: "
@@ -236,33 +268,26 @@ class Execute:
Initializes Execute.filter(), if input fails to pass filter, Initializes Execute.filter(), if input fails to pass filter,
returns errors to front end. Otherwise, executes queries. returns errors to front end. Otherwise, executes queries.
""" """
device_config = getattr(devices, self.input_location) device_config = getattr(devices, self.query_location)
logger.debug(f"Received query for {self.input_data}") logger.debug(f"Received query for {self.query_data}")
logger.debug(f"Matched device config:\n{device_config}") logger.debug(f"Matched device config: {device_config}")
# Run query parameters through validity checks # Run query parameters through validity checks
validity, msg, status = getattr(Validate(device_config), self.input_type)( validation = Validate(device_config, self.query_type, self.query_target)
self.input_target valid_input = validation.validate_query()
) if valid_input:
if not validity: logger.debug(f"Validation passed for query: {self.query_data}")
logger.debug("Invalid query") pass
return (msg, status)
connection = None connect = None
output = params.messages.general output = params.messages.general
logger.debug(f"Validity: {validity}, Message: {msg}, Status: {status}")
transport = Supported.map_transport(device_config.nos) transport = Supported.map_transport(device_config.nos)
connection = Connect( connect = Connect(device_config, self.query_type, self.query_target, transport)
device_config, self.input_type, self.input_target, transport
)
if Supported.is_rest(device_config.nos): if Supported.is_rest(device_config.nos):
raw_output, status = await connection.rest() output = await connect.rest()
elif Supported.is_scrape(device_config.nos): elif Supported.is_scrape(device_config.nos):
raw_output, status = await connection.scrape() output = await connect.scrape()
output = self.parse(raw_output, device_config.nos)
logger.debug(f"Parsed output for device type {device_config.nos}:\n{output}") return output
return (output, status)

View File

@@ -85,10 +85,14 @@ def ip_validate(target):
try: try:
valid_ip = ipaddress.ip_network(target) valid_ip = ipaddress.ip_network(target)
if valid_ip.is_reserved or valid_ip.is_unspecified or valid_ip.is_loopback: if valid_ip.is_reserved or valid_ip.is_unspecified or valid_ip.is_loopback:
raise ValueError _exception = ValueError(params.messages.invalid_input)
except (ipaddress.AddressValueError, ValueError): _exception.details = {}
raise _exception
except (ipaddress.AddressValueError, ValueError) as ip_error:
logger.debug(f"IP {target} is invalid") logger.debug(f"IP {target} is invalid")
raise ValueError _exception = ValueError(ip_error)
_exception.details = {}
raise _exception
return valid_ip return valid_ip
@@ -108,16 +112,18 @@ def ip_blacklist(target):
if ipaddress.ip_network(net).version == target_ver if ipaddress.ip_network(net).version == target_ver
] ]
logger.debug( logger.debug(
f"IPv{target_ver} Blacklist Networks: {[str(n) for n in networks]}" f"IPv{target_ver} Blacklist Networks: {[str(net) for net in networks]}"
) )
for net in networks: for net in networks:
blacklist_net = ipaddress.ip_network(net) blacklist_net = ipaddress.ip_network(net)
if ( if (
blacklist_net.network_address <= target.network_address blacklist_net.network_address <= target.network_address
and blacklist_net.network_address >= target.broadcast_address and blacklist_net.broadcast_address >= target.broadcast_address
): ):
logger.debug(f"Blacklist Match Found for {target} in {net}") logger.debug(f"Blacklist Match Found for {target} in {str(net)}")
raise ValueError(params.messages.blacklist) _exception = ValueError(params.messages.blacklist)
_exception.details = {"blacklisted_net": str(net)}
raise _exception
return target return target
@@ -153,7 +159,7 @@ def ip_type_check(query_type, target, device):
if prefix_attr["length"] > max_length: if prefix_attr["length"] > max_length:
logger.debug("Failed max prefix length check") logger.debug("Failed max prefix length check")
_exception = ValueError(params.messages.max_prefix) _exception = ValueError(params.messages.max_prefix)
_exception.details = {"max_length": params.features.max_prefix} _exception.details = {"max_length": max_length}
raise _exception raise _exception
# If device NOS is listed in requires_ipv6_cidr.toml, and query is # If device NOS is listed in requires_ipv6_cidr.toml, and query is
@@ -166,7 +172,7 @@ def ip_type_check(query_type, target, device):
): ):
logger.debug("Failed requires IPv6 CIDR check") logger.debug("Failed requires IPv6 CIDR check")
_exception = ValueError(params.messages.requires_ipv6_cidr) _exception = ValueError(params.messages.requires_ipv6_cidr)
_exception.details = {"device_name": device.location} _exception.details = {"device_name": device.display_name}
raise _exception raise _exception
# If query type is ping or traceroute, and query target is in CIDR # If query type is ping or traceroute, and query target is in CIDR
@@ -174,7 +180,7 @@ def ip_type_check(query_type, target, device):
if query_type in ("ping", "traceroute") and IPType().is_cidr(target): if query_type in ("ping", "traceroute") and IPType().is_cidr(target):
logger.debug("Failed CIDR format for ping/traceroute check") logger.debug("Failed CIDR format for ping/traceroute check")
_exception = ValueError(params.messages.directed_cidr) _exception = ValueError(params.messages.directed_cidr)
_exception.details = {} _exception.details = {"query_type": getattr(params.branding.text, query_type)}
raise _exception raise _exception
return target return target
@@ -202,18 +208,20 @@ class Validate:
ip_validate(self.target) ip_validate(self.target)
except ValueError as unformatted_error: except ValueError as unformatted_error:
raise InputInvalid( raise InputInvalid(
unformatted_error, params.messages.invalid_input,
target=self.target, target=self.target,
query_type=getattr(params.branding.text, self.query_type), query_type=getattr(params.branding.text, self.query_type),
**unformatted_error.details, **unformatted_error.details,
) from None )
# If target is a member of the blacklist, return an error. # If target is a member of the blacklist, return an error.
try: try:
ip_blacklist(self.target) ip_blacklist(self.target)
except ValueError as unformatted_error: except ValueError as unformatted_error:
raise InputNotAllowed( raise InputNotAllowed(
unformatted_error, target=self.target, **unformatted_error.details params.messages.blacklist,
target=self.target,
**unformatted_error.details,
) )
# Perform further validation of a valid IP address, return an # Perform further validation of a valid IP address, return an
@@ -222,7 +230,7 @@ class Validate:
ip_type_check(self.query_type, self.target, self.device) ip_type_check(self.query_type, self.target, self.device)
except ValueError as unformatted_error: except ValueError as unformatted_error:
raise InputNotAllowed( raise InputNotAllowed(
unformatted_error, target=self.target, **unformatted_error.details str(unformatted_error), target=self.target, **unformatted_error.details
) )
return self.target return self.target
@@ -267,7 +275,7 @@ class Validate:
) )
return self.target return self.target
def valdiate_query(self): def validate_query(self):
if self.query_type in ("bgp_community", "bgp_aspath"): if self.query_type in ("bgp_community", "bgp_aspath"):
return self.validate_dual() return self.validate_dual()
else: else:

View File

@@ -2,8 +2,6 @@
Custom exceptions for hyperglass Custom exceptions for hyperglass
""" """
from typing import Dict
from hyperglass.constants import code from hyperglass.constants import code
@@ -12,7 +10,17 @@ class HyperglassError(Exception):
hyperglass base exception hyperglass base exception
""" """
pass def __init__(self, message="", status=500, keywords={}):
self.message = message
self.status = status
self.keywords = keywords
def __dict__(self):
return {
"message": self.message,
"status": self.status,
"keywords": self.keywords,
}
class ConfigError(HyperglassError): class ConfigError(HyperglassError):
@@ -21,9 +29,9 @@ class ConfigError(HyperglassError):
""" """
def __init__(self, unformatted_msg, kwargs={}): def __init__(self, unformatted_msg, kwargs={}):
self.message: unformatted_msg.format(**kwargs) self.message = unformatted_msg.format(**kwargs)
self.keywords: Dict = kwargs self.keywords = [value for value in kwargs.values()]
super().__init__(self.message, self.keywords) super().__init__(message=self.message, keywords=self.keywords)
def __str__(self): def __str__(self):
return self.message return self.message
@@ -33,11 +41,11 @@ class ConfigInvalid(HyperglassError):
"""Raised when a config item fails type or option validation""" """Raised when a config item fails type or option validation"""
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.message: str = 'The value field "{field}" is invalid: {error_msg}'.format( self.message = 'The value field "{field}" is invalid: {error_msg}'.format(
**kwargs **kwargs
) )
self.keywords: Dict = kwargs self.keywords = [value for value in kwargs.values()]
super().__init__(self.message, self.keywords) super().__init__(message=self.message, keywords=self.keywords)
def __str__(self): def __str__(self):
return self.message return self.message
@@ -49,12 +57,12 @@ class ConfigMissing(HyperglassError):
""" """
def __init__(self, kwargs={}): def __init__(self, kwargs={}):
self.message: str = ( self.message = (
"{missing_item} is missing or undefined and is required to start " "{missing_item} is missing or undefined and is required to start "
"hyperglass. Please consult the installation documentation." "hyperglass. Please consult the installation documentation."
).format(**kwargs) ).format(**kwargs)
self.keywords: Dict = kwargs self.keywords = [value for value in kwargs.values()]
super().__init__(self.message, self.keywords) super().__init__(message=self.message, keywords=self.keywords)
def __str__(self): def __str__(self):
return self.message return self.message
@@ -63,11 +71,13 @@ class ConfigMissing(HyperglassError):
class ScrapeError(HyperglassError): class ScrapeError(HyperglassError):
"""Raised upon a scrape/netmiko error""" """Raised upon a scrape/netmiko error"""
def __init__(self, kwargs={}): def __init__(self, msg, kwargs={}):
self.message: str = "".format(**kwargs) self.message = msg.format(**kwargs)
self.keywords: Dict = kwargs self.status = code.target_error
self.status: int = code.target_error self.keywords = [value for value in kwargs.values()]
super().__init__(self.message, self.keywords) super().__init__(
message=self.message, status=self.status, keywords=self.keywords
)
def __str__(self): def __str__(self):
return self.message return self.message
@@ -76,11 +86,13 @@ class ScrapeError(HyperglassError):
class AuthError(HyperglassError): class AuthError(HyperglassError):
"""Raised when authentication to a device fails""" """Raised when authentication to a device fails"""
def __init__(self, kwargs={}): def __init__(self, msg, kwargs={}):
self.message: str = "".format(**kwargs) self.message = msg.format(**kwargs)
self.keywords: Dict = kwargs self.status = code.target_error
self.status: int = code.target_error self.keywords = [value for value in kwargs.values()]
super().__init__(self.message, self.keywords) super().__init__(
message=self.message, status=self.status, keywords=self.keywords
)
def __str__(self): def __str__(self):
return self.message return self.message
@@ -89,11 +101,13 @@ class AuthError(HyperglassError):
class RestError(HyperglassError): class RestError(HyperglassError):
"""Raised upon a rest API client error""" """Raised upon a rest API client error"""
def __init__(self, kwargs={}): def __init__(self, msg, kwargs={}):
self.message: str = "".format(**kwargs) self.message = msg.format(**kwargs)
self.keywords: Dict = kwargs self.status = code.target_error
self.status: int = code.target_error self.keywords = [value for value in kwargs.values()]
super().__init__(self.message, self.keywords) super().__init__(
message=self.message, status=self.status, keywords=self.keywords
)
def __str__(self): def __str__(self):
return self.message return self.message
@@ -103,10 +117,12 @@ class InputInvalid(HyperglassError):
"""Raised when input validation fails""" """Raised when input validation fails"""
def __init__(self, unformatted_msg, **kwargs): def __init__(self, unformatted_msg, **kwargs):
self.message: str = unformatted_msg.format(**kwargs) self.message = unformatted_msg.format(**kwargs)
self.keywords: Dict = kwargs self.status = code.invalid
self.status: int = code.invalid self.keywords = [value for value in kwargs.values()]
super().__init__(self.message, self.status) super().__init__(
message=self.message, status=self.status, keywords=self.keywords
)
def __str__(self): def __str__(self):
return self.message return self.message
@@ -119,25 +135,12 @@ class InputNotAllowed(HyperglassError):
""" """
def __init__(self, unformatted_msg, **kwargs): def __init__(self, unformatted_msg, **kwargs):
self.message: str = unformatted_msg.format(**kwargs) self.message = unformatted_msg.format(**kwargs)
self.keywords: Dict = kwargs self.status = code.invalid
self.status: int = code.invalid self.keywords = [value for value in kwargs.values()]
super().__init__(self.status, self.message) super().__init__(
message=self.message, status=self.status, keywords=self.keywords
def __str__(self): )
return self.message
class ParseError(HyperglassError):
"""
Raised when an ouput parser encounters an error.
"""
def __init__(self, kwargs={}):
self.message: str = "".format(**kwargs)
self.keywords: Dict = kwargs
self.status: int = code.target_error
super().__init__(self.message, self.keywords)
def __str__(self): def __str__(self):
return self.message return self.message
@@ -149,10 +152,12 @@ class UnsupportedDevice(HyperglassError):
""" """
def __init__(self, kwargs={}): def __init__(self, kwargs={}):
self.message: str = "".format(**kwargs) self.message = "".format(**kwargs)
self.keywords: Dict = kwargs self.status = code.target_error
self.status: int = code.target_error self.keywords = [value for value in kwargs.values()]
super().__init__(self.message, self.keywords) super().__init__(
message=self.message, status=self.status, keywords=self.keywords
)
def __str__(self): def __str__(self):
return self.message return self.message

View File

@@ -17,6 +17,7 @@ from sanic import Sanic
from sanic import response from sanic import response
from sanic.exceptions import NotFound from sanic.exceptions import NotFound
from sanic.exceptions import ServerError from sanic.exceptions import ServerError
from sanic.exceptions import InvalidUsage
from sanic_limiter import Limiter from sanic_limiter import Limiter
from sanic_limiter import RateLimitExceeded from sanic_limiter import RateLimitExceeded
from sanic_limiter import get_remote_address from sanic_limiter import get_remote_address
@@ -27,10 +28,16 @@ from hyperglass.command.execute import Execute
from hyperglass.configuration import devices from hyperglass.configuration import devices
from hyperglass.configuration import logzero_config # noqa: F401 from hyperglass.configuration import logzero_config # noqa: F401
from hyperglass.configuration import params from hyperglass.configuration import params
from hyperglass.configuration import display_networks
from hyperglass.constants import Supported from hyperglass.constants import Supported
from hyperglass.constants import code from hyperglass.constants import code
from hyperglass.exceptions import HyperglassError from hyperglass.exceptions import (
HyperglassError,
AuthError,
ScrapeError,
RestError,
InputInvalid,
InputNotAllowed,
)
logger.debug(f"Configuration Parameters:\n {params.dict()}") logger.debug(f"Configuration Parameters:\n {params.dict()}")
@@ -117,6 +124,35 @@ async def metrics(request):
) )
@app.exception(InvalidUsage)
async def handle_ui_errors(request, exception):
"""Renders full error page for invalid URI"""
client_addr = get_remote_address(request)
error = exception.args[0]
status = error["status"]
logger.info(error)
count_errors.labels(
status,
code.get_reason(status),
client_addr,
request.json["query_type"],
request.json["location"],
request.json["target"],
).inc()
logger.error(f'Error: {error["message"]}, Source: {client_addr}')
return response.json(
{"output": error["message"], "status": status, "keywords": error["keywords"]},
status=status,
)
@app.exception(ServerError)
async def handle_missing(request, exception):
"""Renders full error page for invalid URI"""
logger.error(f"Error: {exception}")
return response.json(exception, status=code.invalid)
@app.exception(NotFound) @app.exception(NotFound)
async def handle_404(request, exception): async def handle_404(request, exception):
"""Renders full error page for invalid URI""" """Renders full error page for invalid URI"""
@@ -128,15 +164,6 @@ async def handle_404(request, exception):
return response.html(html, status=404) return response.html(html, status=404)
@app.exception(ServerError)
async def handle_408(request, exception):
"""Renders full error page for invalid URI"""
client_addr = get_remote_address(request)
count_notfound.labels(exception, path, client_addr).inc()
logger.error(f"Error: {exception}, Source: {client_addr}")
return response.html(exception, status=408)
@app.exception(RateLimitExceeded) @app.exception(RateLimitExceeded)
async def handle_429(request, exception): async def handle_429(request, exception):
"""Renders full error page for too many site queries""" """Renders full error page for too many site queries"""
@@ -196,17 +223,17 @@ async def hyperglass_main(request):
# Return error if no target is specified # Return error if no target is specified
if not lg_data["target"]: if not lg_data["target"]:
logger.debug("No input specified") logger.debug("No input specified")
return response.html(params.messages.no_input, status=code.invalid) raise handle_missing(request, params.messages.no_input)
# Return error if no location is selected # Return error if no location is selected
if lg_data["location"] not in devices.hostnames: if lg_data["location"] not in devices.hostnames:
logger.debug("No selection specified") logger.debug("No selection specified")
return response.html(params.messages.no_location, status=code.invalid) raise handle_missing(request, params.messages.no_input)
# Return error if no query type is selected # Return error if no query type is selected
if not Supported.is_supported_query(lg_data["query_type"]): if not Supported.is_supported_query(lg_data["query_type"]):
logger.debug("No query specified") logger.debug("No query specified")
return response.html(params.messages.no_query_type, status=code.invalid) raise handle_missing(request, params.messages.no_input)
# Get client IP address for Prometheus logging & rate limiting # Get client IP address for Prometheus logging & rate limiting
client_addr = get_remote_address(request) client_addr = get_remote_address(request)
@@ -233,13 +260,22 @@ async def hyperglass_main(request):
# Pass request to execution module # Pass request to execution module
starttime = time.time() starttime = time.time()
try:
cache_value = await Execute(lg_data).response() cache_value = await Execute(lg_data).response()
except (
AuthError,
RestError,
ScrapeError,
InputInvalid,
InputNotAllowed,
) as backend_error:
raise InvalidUsage(backend_error.__dict__())
endtime = time.time() endtime = time.time()
elapsedtime = round(endtime - starttime, 4)
if not cache_value: if not cache_value:
raise handle_408(params.messages.request_timeout) raise handle_ui_errors(request, params.messages.request_timeout)
elapsedtime = round(endtime - starttime, 4)
logger.debug( logger.debug(
f"Execution for query {cache_key} took {elapsedtime} seconds to run." f"Execution for query {cache_key} took {elapsedtime} seconds to run."
@@ -255,21 +291,11 @@ async def hyperglass_main(request):
cache_response = await r_cache.get(cache_key) cache_response = await r_cache.get(cache_key)
# Serialize stringified tuple response from cache # Serialize stringified tuple response from cache
serialized_response = literal_eval(cache_response) # serialized_response = literal_eval(cache_response)
response_output, response_status = serialized_response # response_output, response_status = serialized_response
response_output = cache_response
logger.debug(f"Cache match for: {cache_key}, returning cached entry") logger.debug(f"Cache match for: {cache_key}, returning cached entry")
logger.debug(f"Cache Output: {response_output}") logger.debug(f"Cache Output: {response_output}")
logger.debug(f"Cache Status Code: {response_status}")
# If error, increment Prometheus metrics return response.json({"output": response_output}, status=200)
if response_status in [405, 415, 504]:
count_errors.labels(
response_status,
code.get_reason(response_status),
client_addr,
lg_data["query_type"],
lg_data["location"],
lg_data["target"],
).inc()
return response.json({"output": response_output}, status=response_status)