From a515b47c46639fc3d129063de62fae082660f208 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sun, 23 Jun 2019 11:50:33 -0700 Subject: [PATCH 1/9] Remove pprint for debugging --- hyperglass/command/execute.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/hyperglass/command/execute.py b/hyperglass/command/execute.py index dbc67ce..248828f 100644 --- a/hyperglass/command/execute.py +++ b/hyperglass/command/execute.py @@ -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,7 +69,7 @@ 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 @@ -115,7 +114,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 +152,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 +232,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 From 06c1eff3971b75b51b38d7537b584e166b2c8d5c Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sun, 23 Jun 2019 11:51:02 -0700 Subject: [PATCH 2/9] Remove pprint for debugging --- hyperglass/command/validate.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hyperglass/command/validate.py b/hyperglass/command/validate.py index 52d4c60..4344e17 100644 --- a/hyperglass/command/validate.py +++ b/hyperglass/command/validate.py @@ -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) From c6b0e0026989450723edf51267fa551ae04c3667 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sun, 23 Jun 2019 14:05:06 -0700 Subject: [PATCH 3/9] Add BIRD suppoprt & HTTP connection error handling --- hyperglass/command/execute.py | 45 ++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/hyperglass/command/execute.py b/hyperglass/command/execute.py index 248828f..9133753 100644 --- a/hyperglass/command/execute.py +++ b/hyperglass/command/execute.py @@ -73,7 +73,9 @@ class Rest: 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 @@ -82,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"] @@ -247,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} From c2b8508eebb454cdace906301ad8f3b175f9165f Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sun, 23 Jun 2019 14:05:20 -0700 Subject: [PATCH 4/9] Add 504 error options --- hyperglass/configuration/__init__.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/hyperglass/configuration/__init__.py b/hyperglass/configuration/__init__.py index 5565edd..9cfe9fe 100644 --- a/hyperglass/configuration/__init__.py +++ b/hyperglass/configuration/__init__.py @@ -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 {target}." + ) branding["logo"] = config["branding"]["logo"] branding["logo"]["path"] = config["branding"]["logo"].get( "path", "static/images/hyperglass-dark.png" From 926e9767ee7f48348476605ecf795e37edd43c63 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sun, 23 Jun 2019 14:05:43 -0700 Subject: [PATCH 5/9] Add 504 error options --- hyperglass/configuration/configuration.toml.example | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hyperglass/configuration/configuration.toml.example b/hyperglass/configuration/configuration.toml.example index b7b33e7..2f8758b 100644 --- a/hyperglass/configuration/configuration.toml.example +++ b/hyperglass/configuration/configuration.toml.example @@ -100,6 +100,8 @@ blacklist = [ # title = "" # subtitle = "" # button = "" +[branding.text.504] +# message = "" [branding.logo] # path = "" # width = "" From 27195c4a6405cda52cf667d08868d1bdd0330389 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sun, 23 Jun 2019 14:06:08 -0700 Subject: [PATCH 6/9] Remove extraneous comment --- hyperglass/hyperglass.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hyperglass/hyperglass.py b/hyperglass/hyperglass.py index 778c1f8..5aa2a04 100644 --- a/hyperglass/hyperglass.py +++ b/hyperglass/hyperglass.py @@ -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"], From d8b0ca92cb8d0befe2180065564ebd4ec0027700 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sun, 23 Jun 2019 14:06:32 -0700 Subject: [PATCH 7/9] Convert error messages to bulma notification class --- hyperglass/static/js/hyperglass.js | 61 ++++++++++++++---------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/hyperglass/static/js/hyperglass.js b/hyperglass/static/js/hyperglass.js index f1d9cfe..a5a9abe 100644 --- a/hyperglass/static/js/hyperglass.js +++ b/hyperglass/static/js/hyperglass.js @@ -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(`
-
-
- Authentication Error -
-
- ${response.responseText} -
-
+
+ ${response.responseText} +
`); - }, + }, 405: function(response, code) { clearPage(); target_error.show() target_input.addClass('is-warning'); target_error.html(`
-
-
- Input Not Allowed -
-
- ${response.responseText} -
-
+
+ ${response.responseText} +
`); }, 415: function(response, code) { @@ -213,19 +203,24 @@ function submitForm() { target_input.addClass('is-danger'); target_error.html(`
-
-
- Invalid Input -
-
- ${response.responseText} -
-
+
+ ${response.responseText} +
`); }, 429: function(response, code) { clearPage(); $("#ratelimit").addClass("is-active"); + }, + 504: function(response, code) { + clearPage(); + target_error.show() + target_error.html(` +
+
+ ${response.responseText} +
+ `); } } }) From 403dee88e21dee0a717024c05895a3977ef2e01e Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sun, 23 Jun 2019 14:24:47 -0700 Subject: [PATCH 8/9] Add new 504 message to CI config --- tests/configuration.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/configuration.toml b/tests/configuration.toml index e00684c..28d89f7 100644 --- a/tests/configuration.toml +++ b/tests/configuration.toml @@ -100,6 +100,8 @@ timeout = 1 # title = "" # subtitle = "" # button = "" +[branding.text.504] +# message = "" [branding.logo] # path = "" # width = "" From 4c1e8b4c89b46cd3f07ab300062c0309152352dc Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sun, 23 Jun 2019 14:33:22 -0700 Subject: [PATCH 9/9] README Updates for BIRD Support --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b781cb4..6c81b3a 100644 --- a/README.md +++ b/README.md @@ -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