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:
commit
2f2e4995f1
8 changed files with 95 additions and 52 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@ blacklist = [
|
|||
# title = ""
|
||||
# subtitle = ""
|
||||
# button = ""
|
||||
[branding.text.504]
|
||||
# message = ""
|
||||
[branding.logo]
|
||||
# path = ""
|
||||
# width = ""
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
`);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@ timeout = 1
|
|||
# title = ""
|
||||
# subtitle = ""
|
||||
# button = ""
|
||||
[branding.text.504]
|
||||
# message = ""
|
||||
[branding.logo]
|
||||
# path = ""
|
||||
# width = ""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue