1
0
Fork 1
mirror of https://github.com/thatmattlove/hyperglass.git synced 2026-01-17 08:48:05 +00:00

Merge pull request #4 from checktheroads/develop

Add BIRD support, error handling improvements
This commit is contained in:
Matt Love 2019-06-23 14:42:24 -07:00 committed by GitHub
commit 2f2e4995f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 95 additions and 52 deletions

View file

@ -17,6 +17,7 @@
- Full frontend and backend IPv6 support
- [Netmiko](https://github.com/ktbyers/netmiko)-based connection handling for traditional network devices
- [FRRouting](https://frrouting.org/) support via [hyperglass-frr](https://github.com/checktheroads/hyperglass-frr) REST API
- [BIRD](https://bird.network.cz/) support via [hyperglass-bird](https://github.com/checktheroads/hyperglass-bird) REST API
- Customizable commands for each query type by vendor
- Clean, modern, google-esq GUI based on the [Bumla](https://bulma.io) framework
- Customizable colors, logo, web fonts, error messages, UI text
@ -41,6 +42,7 @@ Theoretically, any vendor supported by Netmiko can be supported by hyperglass. H
- Cisco Classic IOS/IOS-XE: Netmiko `cisco_ios` vendor class
- Juniper JunOS: Netmiko `junos` vendor class
- FRRouting: [hyperglass-frr](https://github.com/checktheroads/hyperglass-frr) API
- BIRD: [hyperglass-bird](https://github.com/checktheroads/hyperglass-bird) API
### Proxies
@ -48,7 +50,7 @@ Theoretically, any vendor supported by Netmiko can be supported by hyperglass. H
## Coming Soon
- [BIRD](https://bird.network.cz/) Support
- [GoBGP](https://github.com/osrg/gobgp) Support
## Acknowledgements

View file

@ -8,7 +8,6 @@ connectoins or hyperglass-frr API calls, returns the output back to the front en
import json
import time
import logging
from pprint import pprint
# Module Imports
import requests
@ -59,7 +58,7 @@ class Rest:
def frr(self):
"""Sends HTTP POST to router running the hyperglass-frr API"""
# Debug
logger.debug(f"FRR host params:\n{pprint(self.device)}")
logger.debug(f"FRR host params:\n{self.device}")
logger.debug(f"Raw query parameters: {self.query}")
# End Debug
try:
@ -70,11 +69,13 @@ class Rest:
json_query = json.dumps(self.query)
frr_endpoint = f'http://{self.device["address"]}:{self.device["port"]}/frr'
# Debug
logger.debug(f"HTTP Headers:\n{pprint(headers)}")
logger.debug(f"HTTP Headers:\n{headers}")
logger.debug(f"JSON query:\n{json_query}")
logger.debug(f"FRR endpoint: {frr_endpoint}")
# End Debug
frr_response = requests.post(frr_endpoint, headers=headers, data=json_query)
frr_response = requests.post(
frr_endpoint, headers=headers, data=json_query, timeout=3
)
response = frr_response.text
status = frr_response.status_code
# Debug
@ -83,7 +84,44 @@ class Rest:
# End Debug
except requests.exceptions.RequestException as requests_exception:
logger.error(
f'Error connecting to device {self.device["name"]}: {requests_exception}'
f"Error connecting to device {self.device}: {requests_exception}"
)
response = config["messages"]["general"]
status = codes["danger"]
return response, status
def bird(self):
"""Sends HTTP POST to router running the hyperglass-bird API"""
# Debug
logger.debug(f"BIRD host params:\n{self.device}")
logger.debug(f"Raw query parameters: {self.query}")
# End Debug
try:
headers = {
"Content-Type": "application/json",
"X-API-Key": self.cred["password"],
}
json_query = json.dumps(self.query)
bird_endpoint = (
f'http://{self.device["address"]}:{self.device["port"]}/bird'
)
# Debug
logger.debug(f"HTTP Headers:\n{headers}")
logger.debug(f"JSON query:\n{json_query}")
logger.debug(f"BIRD endpoint: {bird_endpoint}")
# End Debug
bird_response = requests.post(
bird_endpoint, headers=headers, data=json_query, timeout=3
)
response = bird_response.text
status = bird_response.status_code
# Debug
logger.debug(f"BIRD response text:\n{response}")
logger.debug(f"BIRD status code: {status}")
# End Debug
except requests.exceptions.RequestException as requests_exception:
logger.error(
f"Error connecting to device {self.device}: {requests_exception}"
)
response = config["messages"]["general"]
status = codes["danger"]
@ -115,7 +153,7 @@ class Netmiko:
def direct(self):
"""Connects to the router via netmiko library, return the command output"""
# Debug
logger.debug(f"Netmiko host: {pprint(self.nm_host)}")
logger.debug(f"Netmiko host: {self.nm_host}")
logger.debug(f"Connecting to host via Netmiko library...")
# End Debug
try:
@ -153,7 +191,7 @@ class Netmiko:
nm_connect_proxied = ConnectHandler(**nm_proxy)
nm_ssh_command = device_proxy["ssh_command"].format(**self.nm_host) + "\n"
# Debug
logger.debug(f"Netmiko proxy {proxy_name}:\n{pprint(nm_proxy)}")
logger.debug(f"Netmiko proxy {proxy_name}:\n{nm_proxy}")
logger.debug(f"Proxy SSH command: {nm_ssh_command}")
# End Debug
nm_connect_proxied.write_channel(nm_ssh_command)
@ -233,7 +271,7 @@ class Execute:
device_config = configuration.device(self.input_location)
# Debug
logger.debug(f"Received query for {self.input_data}")
logger.debug(f"Matched device config:\n{pprint(device_config)}")
logger.debug(f"Matched device config:\n{device_config}")
# End Debug
validity, msg, status = getattr(Validate(device_config), self.input_type)(
self.input_target
@ -248,7 +286,7 @@ class Execute:
logger.debug(f"Validity: {validity}, Message: {msg}, Status: {status}")
if device_config["type"] in configuration.rest_list():
connection = Rest("rest", device_config, self.input_type, self.input_target)
raw_output, status = connection.frr()
raw_output, status = getattr(connection, device_config["type"])()
output = self.parse(raw_output, device_config["type"])
## return output, status, info
return {"output": output, "status": status}

View file

@ -7,7 +7,6 @@ returns validity boolean and specific error message.
import re
import inspect
import logging
from pprint import pprint
# Module Imports
import logzero
@ -148,7 +147,7 @@ def ip_attributes(target):
def ip_type_check(query_type, target, device):
"""Checks multiple IP address related validation parameters"""
prefix_attr = ip_attributes(target)
logger.debug(f"IP Attributes:\n{pprint(prefix_attr)}")
logger.debug(f"IP Attributes:\n{prefix_attr}")
requires_ipv6_cidr = configuration.requires_ipv6_cidr(device["type"])
validity = False
msg = config["messages"]["not_allowed"].format(i=target)

View file

@ -106,10 +106,12 @@ def codes():
code_dict = {
# 200: renders standard display text
"success": 200,
# 405: Renders Bulma "warning" class notification message with message text
# 405: Renders Bulma "warning" class notification with message text
"warning": 405,
# 415: Renders Bulma "danger" class notification message with message text
# 415: Renders Bulma "danger" class notification with message text
"danger": 415,
# 504: Renders Bulma "danger" class notifiction, used for Ping/Traceroute errors
"error": 504,
}
return code_dict
@ -117,16 +119,17 @@ def codes():
def codes_reason():
"""Reusable status code descriptions"""
code_desc_dict = {
200: "Valid Query",
405: "Query Not Allowed",
415: "Query Invalid",
"200": "Valid Query",
"405": "Query Not Allowed",
"415": "Query Invalid",
"504": "Unable to reach Ping target",
}
return code_desc_dict
def rest_list():
"""Returns list of supported hyperglass API types"""
rest = ["frr"]
rest = ["frr", "bird"]
return rest
@ -403,6 +406,9 @@ def params():
branding["text"]["500"]["button"] = config["branding"]["text"]["500"].get(
"button", "Home"
)
branding["text"]["504"]["message"] = config["branding"]["text"]["504"].get(
"message", "Unable to reach <b>{target}</b>."
)
branding["logo"] = config["branding"]["logo"]
branding["logo"]["path"] = config["branding"]["logo"].get(
"path", "static/images/hyperglass-dark.png"

View file

@ -100,6 +100,8 @@ blacklist = [
# title = ""
# subtitle = ""
# button = ""
[branding.text.504]
# message = ""
[branding.logo]
# path = ""
# width = ""

View file

@ -216,7 +216,6 @@ def hyperglass_main():
logger.debug(f"Returning {value_code} response")
return Response(response["output"], response["status"])
# If 400 error, return error message and code
# ["code", "reason", "source", "type", "loc_id", "target"],
if value_code in [405, 415]:
count_errors.labels(
response["status"],

View file

@ -17,16 +17,16 @@ dropdown.addEventListener('click', function(event) {
var btn_copy = document.getElementById('btn-copy');
var clipboard = new ClipboardJS(btn_copy);
clipboard.on('success', function(e) {
console.log(e);
$('#btn-copy').addClass('is-success').addClass('is-outlined');
$('#copy-icon').removeClass('icofont-ui-copy').addClass('icofont-check');
setTimeout(function(){
$('#btn-copy').removeClass('is-success').removeClass('is-outlined');
$('#copy-icon').removeClass('icofont-check').addClass('icofont-ui-copy');
}, 1000)
console.log(e);
$('#btn-copy').addClass('is-success').addClass('is-outlined');
$('#copy-icon').removeClass('icofont-ui-copy').addClass('icofont-check');
setTimeout(function() {
$('#btn-copy').removeClass('is-success').removeClass('is-outlined');
$('#copy-icon').removeClass('icofont-check').addClass('icofont-ui-copy');
}, 1000)
});
clipboard.on('error', function(e) {
console.log(e);
console.log(e);
});
function bgpHelpASPath() {
@ -181,30 +181,20 @@ function submitForm() {
target_input.addClass('is-danger');
target_error.html(`
<br>
<article class="message is-danger is-small" style="display: block;">
<div class="message-header" style="display: block;">
Authentication Error
</div>
<div id="error" style="display: block;" class="message-body">
${response.responseText}
</div>
</article>
<div class="notification is-danger">
${response.responseText}
</div>
`);
},
},
405: function(response, code) {
clearPage();
target_error.show()
target_input.addClass('is-warning');
target_error.html(`
<br>
<article class="message is-warning is-small" style="display: block;">
<div class="message-header" style="display: block;">
Input Not Allowed
</div>
<div id="error" style="display: block;" class="message-body">
${response.responseText}
</div>
</article>
<div class="notification is-warning">
${response.responseText}
</div>
`);
},
415: function(response, code) {
@ -213,19 +203,24 @@ function submitForm() {
target_input.addClass('is-danger');
target_error.html(`
<br>
<article class="message is-danger is-small" style="display: block;">
<div class="message-header" style="display: block;">
Invalid Input
</div>
<div id="error" style="display: block;" class="message-body">
${response.responseText}
</div>
</article>
<div class="notification is-danger">
${response.responseText}
</div>
`);
},
429: function(response, code) {
clearPage();
$("#ratelimit").addClass("is-active");
},
504: function(response, code) {
clearPage();
target_error.show()
target_error.html(`
<br>
<div class="notification is-danger">
${response.responseText}
</div>
`);
}
}
})

View file

@ -100,6 +100,8 @@ timeout = 1
# title = ""
# subtitle = ""
# button = ""
[branding.text.504]
# message = ""
[branding.logo]
# path = ""
# width = ""