diff --git a/hyperglass/command/__init__.py b/hyperglass/command/__init__.py
index 336b986..1b910f7 100644
--- a/hyperglass/command/__init__.py
+++ b/hyperglass/command/__init__.py
@@ -1,4 +1,5 @@
-"""
+"""Validate, construct, execute queries.
+
Constructs SSH commands or API call parameters based on front end
input, executes the commands/calls, returns the output to front end.
"""
diff --git a/hyperglass/command/construct.py b/hyperglass/command/construct.py
index a8dba84..4eadb3a 100644
--- a/hyperglass/command/construct.py
+++ b/hyperglass/command/construct.py
@@ -1,29 +1,25 @@
-"""
+"""Construct SSH command/API parameters from validated query data.
+
Accepts filtered & validated input from execute.py, constructs SSH
command for Netmiko library or API call parameters for supported
hyperglass API modules.
"""
+
# Standard Library Imports
import ipaddress
import json
import operator
import re
-# Third Party Imports
-from logzero import logger as log
-
# Project Imports
from hyperglass.configuration import commands
-from hyperglass.configuration import logzero_config # NOQA: F401
from hyperglass.constants import target_format_space
from hyperglass.exceptions import HyperglassError
+from hyperglass.util import log
class Construct:
- """
- Constructs SSH commands or REST API queries based on validated
- input parameters.
- """
+ """Construct SSH commands/REST API parameters from validated query data."""
def get_device_vrf(self):
_device_vrf = None
diff --git a/hyperglass/command/encode.py b/hyperglass/command/encode.py
index 4261a93..92e4770 100644
--- a/hyperglass/command/encode.py
+++ b/hyperglass/command/encode.py
@@ -1,3 +1,5 @@
+"""Handle JSON Web Token Encoding & Decoding."""
+
# Standard Library Imports
import datetime
@@ -9,7 +11,7 @@ from hyperglass.exceptions import RestError
async def jwt_decode(payload, secret):
- """Decode & validate an encoded JSON Web Token (JWT)"""
+ """Decode & validate an encoded JSON Web Token (JWT)."""
try:
decoded = jwt.decode(payload, secret, algorithm="HS256")
decoded = decoded["payload"]
@@ -19,7 +21,7 @@ async def jwt_decode(payload, secret):
async def jwt_encode(payload, secret, duration):
- """Encode a query to a JSON Web Token (JWT)"""
+ """Encode a query to a JSON Web Token (JWT)."""
token = {
"payload": payload,
"nbf": datetime.datetime.utcnow(),
diff --git a/hyperglass/command/execute.py b/hyperglass/command/execute.py
index fbefff1..914b795 100644
--- a/hyperglass/command/execute.py
+++ b/hyperglass/command/execute.py
@@ -1,4 +1,5 @@
-"""
+"""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 connectoins or
@@ -11,7 +12,6 @@ import re
# Third Party Imports
import httpx
import sshtunnel
-from logzero import logger as log
from netmiko import ConnectHandler
from netmiko import NetMikoAuthenticationException
from netmiko import NetmikoAuthError
@@ -20,10 +20,10 @@ from netmiko import NetMikoTimeoutException
# Project Imports
from hyperglass.command.construct import Construct
+from hyperglass.command.encode import jwt_decode
+from hyperglass.command.encode import jwt_encode
from hyperglass.command.validate import Validate
-from hyperglass.command.encode import jwt_decode, jwt_encode
from hyperglass.configuration import devices
-from hyperglass.configuration import logzero_config # noqa: F401
from hyperglass.configuration import params
from hyperglass.constants import Supported
from hyperglass.constants import protocol_map
@@ -32,6 +32,7 @@ from hyperglass.exceptions import DeviceTimeout
from hyperglass.exceptions import ResponseEmpty
from hyperglass.exceptions import RestError
from hyperglass.exceptions import ScrapeError
+from hyperglass.util import log
class Connect:
@@ -219,7 +220,6 @@ class Connect:
"""Sends HTTP POST to router running a hyperglass API agent"""
log.debug(f"Query parameters: {self.query}")
- # uri = Supported.map_rest(self.device.nos)
headers = {"Content-Type": "application/json"}
http_protocol = protocol_map.get(self.device.port, "https")
endpoint = "{protocol}://{addr}:{port}/query".format(
diff --git a/hyperglass/command/validate.py b/hyperglass/command/validate.py
index d9c9ccb..1cde1b7 100644
--- a/hyperglass/command/validate.py
+++ b/hyperglass/command/validate.py
@@ -1,4 +1,5 @@
-"""
+"""Validate query data.
+
Accepts raw input data from execute.py, passes it through specific
filters based on query type, returns validity boolean and specific
error message.
@@ -7,15 +8,12 @@ error message.
import ipaddress
import re
-# Third Party Imports
-from logzero import logger as log
-
# Project Imports
-from hyperglass.configuration import logzero_config # noqa: F401
from hyperglass.configuration import params
from hyperglass.exceptions import HyperglassError
from hyperglass.exceptions import InputInvalid
from hyperglass.exceptions import InputNotAllowed
+from hyperglass.util import log
class IPType:
diff --git a/hyperglass/configuration/__init__.py b/hyperglass/configuration/__init__.py
index f25fb7e..34705b7 100644
--- a/hyperglass/configuration/__init__.py
+++ b/hyperglass/configuration/__init__.py
@@ -1,24 +1,22 @@
-"""
-Imports configuration varibles from configuration files and returns
-default values if undefined.
-"""
+"""Import configuration files and returns default values if undefined."""
# Standard Library Imports
from pathlib import Path
# Third Party Imports
-import logzero
import yaml
-from logzero import logger as log
from pydantic import ValidationError
# Project Imports
from hyperglass.configuration.models import commands as _commands
from hyperglass.configuration.models import params as _params
from hyperglass.configuration.models import routers as _routers
+from hyperglass.constants import LOG_HANDLER
+from hyperglass.constants import LOG_LEVELS
from hyperglass.exceptions import ConfigError
from hyperglass.exceptions import ConfigInvalid
from hyperglass.exceptions import ConfigMissing
+from hyperglass.util import log
# Project Directories
working_dir = Path(__file__).resolve().parent
@@ -82,19 +80,14 @@ except ValidationError as validation_errors:
)
-# Logzero Configuration
-log_level = 20
+# Logging Config
if params.general.debug:
- log_level = 10
-log_format = (
- "%(color)s[%(asctime)s.%(msecs)03d %(module)s:%(funcName)s:%(lineno)d "
- "%(levelname)s]%(end_color)s %(message)s"
-)
-date_format = "%Y-%m-%d %H:%M:%S"
-logzero_formatter = logzero.LogFormatter(fmt=log_format, datefmt=date_format)
-logzero_config = logzero.setup_default_logger(
- formatter=logzero_formatter, level=log_level
-)
+ _log_level = "DEBUG"
+ LOG_HANDLER["level"] = _log_level
+ log.remove()
+ log.configure(handlers=[LOG_HANDLER], levels=LOG_LEVELS)
+
+log.debug("Debugging Enabled")
def build_frontend_networks():
diff --git a/hyperglass/configuration/models/_utils.py b/hyperglass/configuration/models/_utils.py
index c07a0f0..98006d4 100644
--- a/hyperglass/configuration/models/_utils.py
+++ b/hyperglass/configuration/models/_utils.py
@@ -1,6 +1,4 @@
-"""
-Utility Functions for Pydantic Models
-"""
+"""Utility Functions for Pydantic Models."""
# Standard Library Imports
import re
@@ -10,10 +8,17 @@ from pydantic import BaseSettings
def clean_name(_name):
- """
- Converts any "desirable" seperators to underscore, then
- removes all characters that are unsupported in Python class
- variable names. Also removes leading numbers underscores.
+ """Remove unsupported characters from field names.
+
+ Converts any "desirable" seperators to underscore, then removes all
+ characters that are unsupported in Python class variable names.
+ Also removes leading numbers underscores.
+
+ Arguments:
+ _name {str} -- Initial field name
+
+ Returns:
+ {str} -- Cleaned field name
"""
_replaced = re.sub(r"[\-|\.|\@|\~|\:\/|\s]", "_", _name)
_scrubbed = "".join(re.findall(r"([a-zA-Z]\w+|\_+)", _replaced))
@@ -21,12 +26,15 @@ def clean_name(_name):
class HyperglassModel(BaseSettings):
- """Base model for all hyperglass configuration models"""
+ """Base model for all hyperglass configuration models."""
pass
class Config:
- """Default pydantic configuration"""
+ """Default Pydantic configuration.
+
+ See https://pydantic-docs.helpmanual.io/usage/model_config
+ """
validate_all = True
extra = "forbid"
@@ -35,11 +43,11 @@ class HyperglassModel(BaseSettings):
class HyperglassModelExtra(HyperglassModel):
- """Model for hyperglass configuration models with dynamic fields"""
+ """Model for hyperglass configuration models with dynamic fields."""
pass
class Config:
- """Default pydantic configuration"""
+ """Default pydantic configuration."""
extra = "allow"
diff --git a/hyperglass/configuration/models/branding.py b/hyperglass/configuration/models/branding.py
index 60991d2..4155420 100644
--- a/hyperglass/configuration/models/branding.py
+++ b/hyperglass/configuration/models/branding.py
@@ -1,10 +1,4 @@
-"""
-Defines models for all Branding variables.
-
-Imports config variables and overrides default class attributes.
-
-Validates input for overridden parameters.
-"""
+"""Validate branding configuration variables."""
# Third Party Imports
from pydantic import constr
diff --git a/hyperglass/configuration/models/commands.py b/hyperglass/configuration/models/commands.py
index bb8ffb1..0e9f803 100644
--- a/hyperglass/configuration/models/commands.py
+++ b/hyperglass/configuration/models/commands.py
@@ -1,10 +1,5 @@
-"""
-Defines models for all config variables.
+"""Validate command configuration variables."""
-Imports config variables and overrides default class attributes.
-
-Validates input for overridden parameters.
-"""
# Disable string length warnings so I can actually read these commands
# flake8: noqa: E501
diff --git a/hyperglass/configuration/models/credentials.py b/hyperglass/configuration/models/credentials.py
index d36734d..abe22f7 100644
--- a/hyperglass/configuration/models/credentials.py
+++ b/hyperglass/configuration/models/credentials.py
@@ -1,10 +1,4 @@
-"""
-Defines models for Credential config variables.
-
-Imports config variables and overrides default class attributes.
-
-Validates input for overridden parameters.
-"""
+"""Validate credential configuration variables."""
# Third Party Imports
from pydantic import SecretStr
@@ -15,14 +9,14 @@ from hyperglass.configuration.models._utils import clean_name
class Credential(HyperglassModel):
- """Model for per-credential config in devices.yaml"""
+ """Model for per-credential config in devices.yaml."""
username: str
password: SecretStr
class Credentials(HyperglassModel):
- """Base model for credentials class"""
+ """Base model for credentials class."""
@classmethod
def import_params(cls, input_params):
diff --git a/hyperglass/configuration/models/features.py b/hyperglass/configuration/models/features.py
index c4d623e..a8ba358 100644
--- a/hyperglass/configuration/models/features.py
+++ b/hyperglass/configuration/models/features.py
@@ -1,10 +1,5 @@
-"""
-Defines models for all Features variables.
+"""Validate feature configuration variables."""
-Imports config variables and overrides default class attributes.
-
-Validates input for overridden parameters.
-"""
# Standard Library Imports
from math import ceil
@@ -16,20 +11,20 @@ from hyperglass.configuration.models._utils import HyperglassModel
class Features(HyperglassModel):
- """Class model for params.features"""
+ """Validation model for params.features."""
class BgpRoute(HyperglassModel):
- """Class model for params.features.bgp_route"""
+ """Validation model for params.features.bgp_route."""
enable: bool = True
class BgpCommunity(HyperglassModel):
- """Class model for params.features.bgp_community"""
+ """Validation model for params.features.bgp_community."""
enable: bool = True
class Regex(HyperglassModel):
- """Class model for params.features.bgp_community.regex"""
+ """Validation model for params.features.bgp_community.regex."""
decimal: str = r"^[0-9]{1,10}$"
extended_as: str = r"^([0-9]{0,5})\:([0-9]{1,5})$"
@@ -38,12 +33,12 @@ class Features(HyperglassModel):
regex: Regex = Regex()
class BgpAsPath(HyperglassModel):
- """Class model for params.features.bgp_aspath"""
+ """Validation model for params.features.bgp_aspath."""
enable: bool = True
class Regex(HyperglassModel):
- """Class model for params.bgp_aspath.regex"""
+ """Validation model for params.bgp_aspath.regex."""
mode: constr(regex="asplain|asdot") = "asplain"
asplain: str = r"^(\^|^\_)(\d+\_|\d+\$|\d+\(\_\.\+\_\))+$"
@@ -54,17 +49,17 @@ class Features(HyperglassModel):
regex: Regex = Regex()
class Ping(HyperglassModel):
- """Class model for params.features.ping"""
+ """Validation model for params.features.ping."""
enable: bool = True
class Traceroute(HyperglassModel):
- """Class model for params.features.traceroute"""
+ """Validation model for params.features.traceroute."""
enable: bool = True
class Cache(HyperglassModel):
- """Class model for params.features.cache"""
+ """Validation model for params.features.cache."""
redis_id: int = 0
timeout: int = 120
@@ -74,7 +69,7 @@ class Features(HyperglassModel):
)
class MaxPrefix(HyperglassModel):
- """Class model for params.features.max_prefix"""
+ """Validation model for params.features.max_prefix."""
enable: bool = False
ipv4: int = 24
@@ -84,12 +79,12 @@ class Features(HyperglassModel):
)
class RateLimit(HyperglassModel):
- """Class model for params.features.rate_limit"""
+ """Validation model for params.features.rate_limit."""
redis_id: int = 1
class Query(HyperglassModel):
- """Class model for params.features.rate_limit.query"""
+ """Validation model for params.features.rate_limit.query."""
rate: int = 5
period: str = "minute"
@@ -101,7 +96,7 @@ class Features(HyperglassModel):
button: str = "Try Again"
class Site(HyperglassModel):
- """Class model for params.features.rate_limit.site"""
+ """Validation model for params.features.rate_limit.site."""
rate: int = 60
period: str = "minute"
diff --git a/hyperglass/configuration/models/general.py b/hyperglass/configuration/models/general.py
index 7d0a0d3..6952096 100644
--- a/hyperglass/configuration/models/general.py
+++ b/hyperglass/configuration/models/general.py
@@ -1,10 +1,5 @@
-"""
-Defines models for General config variables.
+"""Validate general configuration variables."""
-Imports config variables and overrides default class attributes.
-
-Validates input for overridden parameters.
-"""
# Standard Library Imports
from typing import List
@@ -13,7 +8,7 @@ from hyperglass.configuration.models._utils import HyperglassModel
class General(HyperglassModel):
- """Class model for params.general"""
+ """Validation model for params.general."""
debug: bool = False
primary_asn: str = "65001"
diff --git a/hyperglass/configuration/models/messages.py b/hyperglass/configuration/models/messages.py
index 45c3053..b98a079 100644
--- a/hyperglass/configuration/models/messages.py
+++ b/hyperglass/configuration/models/messages.py
@@ -1,17 +1,11 @@
-"""
-Defines models for Messages config variables.
-
-Imports config variables and overrides default class attributes.
-
-Validates input for overridden parameters.
-"""
+"""Validate error message configuration variables."""
# Project Imports
from hyperglass.configuration.models._utils import HyperglassModel
class Messages(HyperglassModel):
- """Class model for params.messages"""
+ """Validation model for params.messages."""
no_input: str = "{field} must be specified."
acl_denied: str = "{target} is a member of {denied_network}, which is not allowed."
diff --git a/hyperglass/configuration/models/networks.py b/hyperglass/configuration/models/networks.py
index 5118f8d..5d74981 100644
--- a/hyperglass/configuration/models/networks.py
+++ b/hyperglass/configuration/models/networks.py
@@ -1,10 +1,4 @@
-"""
-Defines models for Networks config variables.
-
-Imports config variables and overrides default class attributes.
-
-Validates input for overridden parameters.
-"""
+"""Validate network configuration variables."""
# Project Imports
from hyperglass.configuration.models._utils import HyperglassModel
@@ -12,14 +6,14 @@ from hyperglass.configuration.models._utils import clean_name
class Network(HyperglassModel):
- """Model for per-network/asn config in devices.yaml"""
+ """Validation Model for per-network/asn config in devices.yaml."""
name: str
display_name: str
class Networks(HyperglassModel):
- """Base model for networks class"""
+ """Base model for networks class."""
@classmethod
def import_params(cls, input_params):
diff --git a/hyperglass/configuration/models/params.py b/hyperglass/configuration/models/params.py
index 5100b62..731f349 100644
--- a/hyperglass/configuration/models/params.py
+++ b/hyperglass/configuration/models/params.py
@@ -1,10 +1,4 @@
-"""
-Defines models for all Params variables.
-
-Imports config variables and overrides default class attributes.
-
-Validates input for overridden parameters.
-"""
+"""Configuration validation entry point."""
# Project Imports
from hyperglass.configuration.models._utils import HyperglassModel
@@ -15,7 +9,7 @@ from hyperglass.configuration.models.messages import Messages
class Params(HyperglassModel):
- """Base model for params"""
+ """Validation model for all configuration variables."""
general: General = General()
features: Features = Features()
diff --git a/hyperglass/configuration/models/proxies.py b/hyperglass/configuration/models/proxies.py
index d8f39ca..d50f6b0 100644
--- a/hyperglass/configuration/models/proxies.py
+++ b/hyperglass/configuration/models/proxies.py
@@ -1,10 +1,4 @@
-"""
-Defines models for Router config variables.
-
-Imports config variables and overrides default class attributes.
-
-Validates input for overridden parameters.
-"""
+"""Validate SSH proxy configuration variables."""
# Third Party Imports
from pydantic import validator
@@ -17,7 +11,7 @@ from hyperglass.exceptions import UnsupportedDevice
class Proxy(HyperglassModel):
- """Model for per-proxy config in devices.yaml"""
+ """Validation model for per-proxy config in devices.yaml."""
name: str
address: str
@@ -26,17 +20,17 @@ class Proxy(HyperglassModel):
nos: str = "linux_ssh"
@validator("nos")
- def supported_nos(cls, v): # noqa: N805
+ def supported_nos(cls, value): # noqa: N805
"""
Validates that passed nos string is supported by hyperglass.
"""
- if not v == "linux_ssh":
- raise UnsupportedDevice(f'"{v}" device type is not supported.')
- return v
+ if not value == "linux_ssh":
+ raise UnsupportedDevice(f'"{value}" device type is not supported.')
+ return value
class Proxies(HyperglassModel):
- """Base model for proxies class"""
+ """Validation model for SSH proxy configuration."""
@classmethod
def import_params(cls, input_params):
diff --git a/hyperglass/configuration/models/routers.py b/hyperglass/configuration/models/routers.py
index 22b3336..729926b 100644
--- a/hyperglass/configuration/models/routers.py
+++ b/hyperglass/configuration/models/routers.py
@@ -1,10 +1,5 @@
-"""
-Defines models for Router config variables.
+"""Validate router configuration variables."""
-Imports config variables and overrides default class attributes.
-
-Validates input for overridden parameters.
-"""
# Standard Library Imports
import re
from typing import List
@@ -12,7 +7,6 @@ from typing import Union
# Third Party Imports
from pydantic import validator
-from logzero import logger as log
# Project Imports
from hyperglass.configuration.models._utils import HyperglassModel
@@ -22,14 +16,15 @@ from hyperglass.configuration.models.commands import Command
from hyperglass.configuration.models.credentials import Credential
from hyperglass.configuration.models.networks import Network
from hyperglass.configuration.models.proxies import Proxy
-from hyperglass.configuration.models.vrfs import Vrf, DefaultVrf
+from hyperglass.configuration.models.vrfs import DefaultVrf, Vrf
from hyperglass.constants import Supported
from hyperglass.exceptions import ConfigError
from hyperglass.exceptions import UnsupportedDevice
+from hyperglass.util import log
class Router(HyperglassModel):
- """Model for per-router config in devices.yaml."""
+ """Validation model for per-router config in devices.yaml."""
name: str
address: str
@@ -129,7 +124,7 @@ class Router(HyperglassModel):
class Routers(HyperglassModelExtra):
- """Base model for devices class."""
+ """Validation model for device configurations."""
hostnames: List[str] = []
vrfs: List[str] = []
diff --git a/hyperglass/configuration/models/vrfs.py b/hyperglass/configuration/models/vrfs.py
index a7d9023..6cdbe57 100644
--- a/hyperglass/configuration/models/vrfs.py
+++ b/hyperglass/configuration/models/vrfs.py
@@ -1,10 +1,5 @@
-"""
-Defines models for VRF config variables.
+"""Validate VRF configuration variables."""
-Imports config variables and overrides default class attributes.
-
-Validates input for overridden parameters.
-"""
# Standard Library Imports
from ipaddress import IPv4Address
from ipaddress import IPv4Network
@@ -25,7 +20,7 @@ from hyperglass.exceptions import ConfigError
class DeviceVrf4(HyperglassModel):
- """Model for AFI definitions"""
+ """Validation model for IPv4 AFI definitions."""
vrf_name: str
source_address: IPv4Address
@@ -46,7 +41,7 @@ class DeviceVrf4(HyperglassModel):
class DeviceVrf6(HyperglassModel):
- """Model for AFI definitions"""
+ """Validation model for IPv6 AFI definitions."""
vrf_name: str
source_address: IPv6Address
@@ -67,7 +62,7 @@ class DeviceVrf6(HyperglassModel):
class Vrf(HyperglassModel):
- """Model for per VRF/afi config in devices.yaml"""
+ """Validation model for per VRF/afi config in devices.yaml."""
name: str
display_name: str
@@ -94,16 +89,21 @@ class Vrf(HyperglassModel):
class DefaultVrf(HyperglassModel):
+ """Validation model for default routing table VRF."""
name: str = "default"
display_name: str = "Global"
access_list = [{"allow": IPv4Network("0.0.0.0/0")}, {"allow": IPv6Network("::/0")}]
class DefaultVrf4(HyperglassModel):
+ """Validation model for IPv4 default routing table VRF definition."""
+
vrf_name: str = "default"
source_address: IPv4Address = IPv4Address("127.0.0.1")
class DefaultVrf6(HyperglassModel):
+ """Validation model for IPv6 default routing table VRF definition."""
+
vrf_name: str = "default"
source_address: IPv6Address = IPv6Address("::1")
diff --git a/hyperglass/constants.py b/hyperglass/constants.py
index e16fb1e..da37482 100644
--- a/hyperglass/constants.py
+++ b/hyperglass/constants.py
@@ -1,11 +1,25 @@
-"""
-Global Constants for hyperglass
-"""
+"""Constant definitions used throughout the application."""
+import sys
protocol_map = {80: "http", 8080: "http", 443: "https", 8443: "https"}
target_format_space = ("huawei", "huawei_vrpv8")
+LOG_FMT = (
+ "[{level}] {time:YYYYMMDD} | {time:HH:mm:ss} {name} "
+ "| {function} → {message}"
+)
+LOG_LEVELS = [
+ {"name": "DEBUG", "no": 10, "color": ""},
+ {"name": "INFO", "no": 20, "color": ""},
+ {"name": "SUCCESS", "no": 25, "color": ""},
+ {"name": "WARNING", "no": 30, "color": ""},
+ {"name": "ERROR", "no": 40, "color": ""},
+ {"name": "CRITICAL", "no": 50, "color": ""},
+]
+
+LOG_HANDLER = {"sink": sys.stdout, "format": LOG_FMT, "level": "INFO"}
+
class Supported:
"""
diff --git a/hyperglass/exceptions.py b/hyperglass/exceptions.py
index 8f3e7b9..4fb903a 100644
--- a/hyperglass/exceptions.py
+++ b/hyperglass/exceptions.py
@@ -1,137 +1,161 @@
-"""
-Custom exceptions for hyperglass
-"""
+"""Custom exceptions for hyperglass."""
+
+import json as _json
+from hyperglass.util import log
class HyperglassError(Exception):
- """hyperglass base exception"""
+ """hyperglass base exception."""
- def __init__(self, message="", alert="warning", keywords=[]):
+ def __init__(self, message="", alert="warning", keywords=None):
+ """Initialize the hyperglass base exception class.
+
+ Keyword Arguments:
+ message {str} -- Error message (default: {""})
+ alert {str} -- Error severity (default: {"warning"})
+ keywords {list} -- 'Important' keywords (default: {None})
+ """
self.message = message
self.alert = alert
- self.keywords = keywords
+ self.keywords = keywords or []
+ if self.alert == "warning":
+ log.error(repr(self))
+ elif self.alert == "danger":
+ log.critical(repr(self))
+ else:
+ log.info(repr(self))
def __str__(self):
+ """Return the instance's error message.
+
+ Returns:
+ {str} -- Error Message
+ """
return self.message
+ def __repr__(self):
+ """Return the instance's severity & error message in a string.
+
+ Returns:
+ {str} -- Error message with code
+ """
+ return f"[{self.alert.upper()}] {self.message}"
+
def __dict__(self):
+ """Return the instance's attributes as a dictionary.
+
+ Returns:
+ {dict} -- Exception attributes in dict
+ """
return {"message": self.message, "alert": self.alert, "keywords": self.keywords}
+ def json(self):
+ """Return the instance's attributes as a JSON object.
-class ConfigError(HyperglassError):
+ Returns:
+ {str} -- Exception attributes as JSON
+ """
+ return _json.dumps(self.__dict__())
+
+ @property
+ def message(self):
+ """Return the instance's `message` attribute.
+
+ Returns:
+ {str} -- Error Message
+ """
+ return self.message
+
+ @property
+ def alert(self):
+ """Return the instance's `alert` attribute.
+
+ Returns:
+ {str} -- Alert name
+ """
+ return self.alert
+
+ @property
+ def keywords(self):
+ """Return the instance's `keywords` attribute.
+
+ Returns:
+ {list} -- Keywords List
+ """
+ return self.keywords
+
+
+class _UnformattedHyperglassError(HyperglassError):
+ """Base exception class for freeform error messages."""
+
+ def __init__(self, unformatted_msg, alert="warning", **kwargs):
+ """Format error message with keyword arguments.
+
+ Keyword Arguments:
+ message {str} -- Error message (default: {""})
+ alert {str} -- Error severity (default: {"warning"})
+ keywords {list} -- 'Important' keywords (default: {None})
+ """
+ self.message = unformatted_msg.format(**kwargs)
+ self.alert = alert
+ self.keywords = list(kwargs.values())
+ super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
+
+
+class ConfigError(_UnformattedHyperglassError):
"""Raised for generic user-config issues."""
- def __init__(self, unformatted_msg, **kwargs):
- self.message = unformatted_msg.format(**kwargs)
- self.keywords = [value for value in kwargs.values()]
- super().__init__(message=self.message, keywords=self.keywords)
+
+class ConfigInvalid(_UnformattedHyperglassError):
+ """Raised when a config item fails type or option validation."""
+
+ message = 'The value field "{field}" is invalid: {error_msg}'
-class ConfigInvalid(HyperglassError):
- """Raised when a config item fails type or option validation"""
+class ConfigMissing(_UnformattedHyperglassError):
+ """Raised when a required config file or item is missing or undefined."""
- def __init__(self, **kwargs):
- self.message = 'The value field "{field}" is invalid: {error_msg}'.format(
- **kwargs
- )
- self.keywords = [value for value in kwargs.values()]
- super().__init__(message=self.message, keywords=self.keywords)
+ message = (
+ "{missing_item} is missing or undefined and is required to start "
+ "hyperglass. Please consult the installation documentation."
+ )
-class ConfigMissing(HyperglassError):
- """
- Raised when a required config file or item is missing or undefined.
- """
+class ScrapeError(_UnformattedHyperglassError):
+ """Raised when a scrape/netmiko error occurs."""
- def __init__(self, **kwargs):
- self.message = (
- "{missing_item} is missing or undefined and is required to start "
- "hyperglass. Please consult the installation documentation."
- ).format(**kwargs)
- self.keywords = [value for value in kwargs.values()]
- super().__init__(message=self.message, keywords=self.keywords)
+ alert = "danger"
-class ScrapeError(HyperglassError):
- """Raised upon a scrape/netmiko error"""
+class AuthError(_UnformattedHyperglassError):
+ """Raised when authentication to a device fails."""
- def __init__(self, msg, **kwargs):
- self.message = msg.format(**kwargs)
- self.alert = "danger"
- self.keywords = [value for value in kwargs.values()]
- super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
+ alert = "danger"
-class AuthError(HyperglassError):
- """Raised when authentication to a device fails"""
+class RestError(_UnformattedHyperglassError):
+ """Raised upon a rest API client error."""
- def __init__(self, msg, **kwargs):
- self.message = msg.format(**kwargs)
- self.alert = "danger"
- self.keywords = [value for value in kwargs.values()]
- super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
+ alert = "danger"
-class RestError(HyperglassError):
- """Raised upon a rest API client error"""
-
- def __init__(self, msg, **kwargs):
- self.message = msg.format(**kwargs)
- self.alert = "danger"
- self.keywords = [value for value in kwargs.values()]
- super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
-
-
-class InputInvalid(HyperglassError):
- """Raised when input validation fails"""
-
- def __init__(self, unformatted_msg, **kwargs):
- self.message = unformatted_msg.format(**kwargs)
- self.alert = "warning"
- self.keywords = [value for value in kwargs.values()]
- super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
-
-
-class InputNotAllowed(HyperglassError):
- """
- Raised when input validation fails due to a blacklist or
- requires_ipv6_cidr check
- """
-
- def __init__(self, unformatted_msg, **kwargs):
- self.message = unformatted_msg.format(**kwargs)
- self.alert = "warning"
- self.keywords = [value for value in kwargs.values()]
- super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
-
-
-class ResponseEmpty(HyperglassError):
- """
- Raised when hyperglass is able to connect to the device and execute
- a valid query, but the response is empty.
- """
-
- def __init__(self, unformatted_msg, **kwargs):
- self.message = unformatted_msg.format(**kwargs)
- self.alert = "warning"
- self.keywords = [value for value in kwargs.values()]
- super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
-
-
-class UnsupportedDevice(HyperglassError):
- """Raised when an input NOS is not in the supported NOS list."""
-
- def __init__(self, **kwargs):
- self.message = "".format(**kwargs)
- self.keywords = [value for value in kwargs.values()]
- super().__init__(message=self.message, keywords=self.keywords)
-
-
-class DeviceTimeout(HyperglassError):
+class DeviceTimeout(_UnformattedHyperglassError):
"""Raised when the connection to a device times out."""
- def __init__(self, msg, **kwargs):
- self.message = msg.format(**kwargs)
- self.alert = "danger"
- self.keywords = [value for value in kwargs.values()]
- super().__init__(message=self.message, alert=self.alert, keywords=self.keywords)
+ alert = "danger"
+
+
+class InputInvalid(_UnformattedHyperglassError):
+ """Raised when input validation fails."""
+
+
+class InputNotAllowed(_UnformattedHyperglassError):
+ """Raised when input validation fails due to a configured check."""
+
+
+class ResponseEmpty(_UnformattedHyperglassError):
+ """Raised when hyperglass can connect to the device but the response is empty."""
+
+
+class UnsupportedDevice(_UnformattedHyperglassError):
+ """Raised when an input NOS is not in the supported NOS list."""
diff --git a/hyperglass/hyperglass.py b/hyperglass/hyperglass.py
index 80188e3..f5979a5 100644
--- a/hyperglass/hyperglass.py
+++ b/hyperglass/hyperglass.py
@@ -1,4 +1,4 @@
-"""Hyperglass Front End"""
+"""Hyperglass Front End."""
# Standard Library Imports
import operator
@@ -8,7 +8,6 @@ from pathlib import Path
# Third Party Imports
import aredis
import stackprinter
-from logzero import logger as log
from prometheus_client import CONTENT_TYPE_LATEST
from prometheus_client import CollectorRegistry
from prometheus_client import Counter
@@ -27,7 +26,6 @@ from sanic_limiter import get_remote_address
# Project Imports
from hyperglass.command.execute import Execute
from hyperglass.configuration import devices
-from hyperglass.configuration import logzero_config # noqa: F401
from hyperglass.configuration import params
from hyperglass.constants import Supported
from hyperglass.exceptions import AuthError
@@ -39,6 +37,7 @@ from hyperglass.exceptions import ResponseEmpty
from hyperglass.exceptions import RestError
from hyperglass.exceptions import ScrapeError
from hyperglass.render import render_html
+from hyperglass.util import log
stackprinter.set_excepthook()
diff --git a/hyperglass/render/html.py b/hyperglass/render/html.py
index 3968bcb..320bbb1 100644
--- a/hyperglass/render/html.py
+++ b/hyperglass/render/html.py
@@ -1,20 +1,18 @@
-"""
-Renders Jinja2 & Sass templates for use by the front end application
-"""
+"""Renders Jinja2 & Sass templates for use by the front end application."""
+
# Standard Library Imports
from pathlib import Path
# Third Party Imports
import jinja2
import yaml
-from logzero import logger as log
from markdown2 import Markdown
# Project Imports
-from hyperglass.configuration import logzero_config # NOQA: F401
from hyperglass.configuration import networks
from hyperglass.configuration import params
from hyperglass.exceptions import HyperglassError
+from hyperglass.util import log
# Module Directories
working_directory = Path(__file__).resolve().parent
@@ -190,7 +188,7 @@ def generate_markdown(section, file_name=None):
def render_html(template_name, **kwargs):
- """Renders Jinja2 HTML templates"""
+ """Render Jinja2 HTML templates."""
details_name_list = ["footer", "bgp_aspath", "bgp_community"]
details_dict = {}
for details_name in details_name_list:
diff --git a/hyperglass/render/webassets.py b/hyperglass/render/webassets.py
index 3885c25..d335d39 100644
--- a/hyperglass/render/webassets.py
+++ b/hyperglass/render/webassets.py
@@ -1,6 +1,5 @@
-"""
-Renders Jinja2 & Sass templates for use by the front end application
-"""
+"""Renders Jinja2 & Sass templates for use by the front end application."""
+
# Standard Library Imports
import json
import subprocess
@@ -8,15 +7,14 @@ from pathlib import Path
# Third Party Imports
import jinja2
-from logzero import logger as log
# Project Imports
-from hyperglass.configuration import frontend_networks
from hyperglass.configuration import frontend_devices
+from hyperglass.configuration import frontend_networks
from hyperglass.configuration import frontend_params
-from hyperglass.configuration import logzero_config # NOQA: F401
from hyperglass.configuration import params
from hyperglass.exceptions import HyperglassError
+from hyperglass.util import log
# Module Directories
working_directory = Path(__file__).resolve().parent
diff --git a/hyperglass/util.py b/hyperglass/util.py
new file mode 100644
index 0000000..8cfcd6b
--- /dev/null
+++ b/hyperglass/util.py
@@ -0,0 +1,45 @@
+"""Utility fuctions."""
+
+# Third Party Imports
+from loguru import logger as _loguru_logger
+
+# Project Imports
+from hyperglass.constants import LOG_HANDLER
+from hyperglass.constants import LOG_LEVELS
+from hyperglass.exceptions import ConfigInvalid
+
+_loguru_logger.remove()
+_loguru_logger.configure(handlers=[LOG_HANDLER], levels=LOG_LEVELS)
+
+log = _loguru_logger
+
+
+async def check_redis(host, port):
+ """Validate if Redis is running.
+
+ Arguments:
+ host {str} -- IP address or hostname of Redis server
+ port {[type]} -- TCP port of Redis server
+
+ Raises:
+ ConfigInvalid: Raised if redis server is unreachable
+
+ Returns:
+ {bool} -- True if running, False if not
+ """
+ import asyncio
+ from socket import gaierror
+
+ try:
+ _reader, _writer = await asyncio.open_connection(str(host), int(port))
+ except gaierror:
+ raise ConfigInvalid(
+ "Redis isn't running: {host}:{port} is unreachable/unresolvable.",
+ alert="danger",
+ host=host,
+ port=port,
+ )
+ if _reader or _writer:
+ return True
+ else:
+ return False