Added debugging infrastrcuture, working on error reporting

This commit is contained in:
Daniel Dayley 2019-12-09 11:57:22 -07:00
parent d233a12a5e
commit 8d578dc9bf
4 changed files with 98 additions and 59 deletions

View File

@ -159,7 +159,7 @@ if __name__ == "__main__":
if arguments and len(arguments) == 4 :
parser.add_argument(arguments[0],arguments[1],action=arguments[2],help=arguments[3])
parser.add_argument('-a', '--all', action='store_true', help="Return all searchable information about the subject")
parser.add_argument('-v', '--debug', action='store_true', help="Include debug information in the output")
parser.add_argument('-v', '--debug', action='count', help="Include debug information in the output. Add 'v's for more output.")
parser.add_argument('subjects', metavar='subjects', nargs='+', help='IPs or hostnames to look up.')
args = parser.parse_args()

View File

@ -14,18 +14,14 @@ class ServiceDelegate(ServiceBase) :
def startup(self) :
for requirement in ['host','username','key'] :
if requirement not in self.config or (requirement in self.config and (self.config[requirement] is '' or type(self.config[requirement]) is not str)):
self.return_payload.update({'error':'Missing required config option ' + requirement})
self.error.append('Missing required config option ' + requirement)
return
self.host = self.config['host']
self.current_auth_header = self.get_bc_auth_header(self.config['host'],self.config['username'],self.config['key'])
self._debug("Searching BlueCat for hosts...",1)
def perform_lookup(self,host_tuple) :
error = None
if self.return_payload and 'error' in self.return_payload :
error = self.return_payload['error']
self.return_payload = {}
if error :
self.return_payload.update({'error':error})
if self.current_auth_header :
object = self.search_bc_object(host_tuple)
self.return_payload.update({'object':object})
@ -39,7 +35,7 @@ class ServiceDelegate(ServiceBase) :
if self.host and self.current_auth_header :
logout = requests.get("https://" + self.host + "/Services/REST/v1/logout",headers=self.current_auth_header)
if not logout.text.endswith("successfully logged out.\"") :
self.return_payload.update({'error':'Unable to log out of BlueCat API session'})
self.error.append('Unable to log out of BlueCat API session')
def get_bc_auth_header(self,host,username,password) :

View File

@ -1,13 +1,16 @@
from servicebase import ServiceBase
import requests
import sys
import re
import json
import urllib3
urllib3.disable_warnings()
class ServiceDelegate(ServiceBase) :
current_auth_header = None
return_payload = {}
hosts = None
_partition = 'Common'
def get_arguments(cls) :
return ['-cg', '--vip', 'store_true', "Return VIP information about the subject"]
@ -15,54 +18,104 @@ class ServiceDelegate(ServiceBase) :
def startup(self) :
for requirement in ['hosts','username','key'] :
if requirement not in self.config or (requirement in self.config and self.config[requirement] is ''):
self.return_payload.update({'error':'Missing required config option ' + requirement})
self.error.appendupdate('Missing required config option ' + requirement)
return
self.hosts = self.config['hosts']
#self.current_auth_header = self.get_bc_auth_header(self.config['host'],self.config['username'],self.config['key'])
self._debug("Logging into F5's and searching for hosts, this make take some time.",1)
def perform_lookup(self,host_tuple) :
error = None
if self.return_payload and 'error' in self.return_payload :
error = self.return_payload['error']
self.return_payload = {}
if error :
self.return_payload.update({'error':error})
self.return_payload.update({'lookupcandidates':self.choose_host(host_tuple)})
return self.return_payload
if self.current_auth_header :
object = self.search_bc_object(host_tuple)
self.return_payload.update({'object':object})
parent = self.search_bc_parent_object(host_tuple)
self.return_payload.update({'parent':parent})
logincandidates = self.choose_host(host_tuple)
if logincandidates == {} :
# Idk what to do here, do we give up or do we log in to all of them?
self.error.append('Unable to find an LB to log into to check this host')
return self.return_payload
for host in logincandidates.keys() :
auth_header = self.get_iq_auth_header(host,self.config['username'],self.config['key'])
if not auth_header :
self.error.append('Unable to log in to ' + host)
continue
# Get all VIPs
if 'vip' not in self.return_payload :
vips = self.get_vip(host,auth_header);
# If it's a VIP, return it and the host and let's be done
if vips :
self._debug("Got vips from " + host,3)
vip_match = [ x for x in vips if x['destination'].split('/' + self._partition + '/')[1].startswith(host_tuple[2][0])]
all_vips = [ x['destination'] for x in vips ]
if len(vip_match) > 0 :
selected_vip = vip_match[0]
self.return_payload.update({'vip': selected_vip})
pool_name = selected_vip['pool'].split('/' + self._partition + '/')[1]
pool_info = self.get_pool(host,auth_header,pool_name)
if pool_info :
self.return_payload.update(pool_info)
break
else :
self._debug("Unable to find VIP in VIPs from host " + host,2)
else :
self._debug("Unable to get VIPs from " + host,2)
# If it's a VIP, return it and the host and let's be done
# Otherwise, get all nodes.
# If it's in the nodes, we have to start all over and find where.
# Take all vips and get all pools
# Check if address is in the pool. If it is, return the node, pool, vip, and host.
if self.return_payload == {} :
self.return_payload = None
return self.return_payload
def shutdown(self) :
return
if self.host and self.current_auth_header :
logout = requests.get("https://" + self.host + "/Services/REST/v1/logout",headers=self.current_auth_header)
if not logout.text.endswith("successfully logged out.\"") :
self.return_payload.update({'error':'Unable to log out of BlueCat API session'})
def get_bc_auth_header(self,host,username,password) :
auth_token = requests.get("https://" + host + "/Services/REST/v1/login?username=" + username + "&password=" + password).text[17:71]
if auth_token.startswith('BAMAuthToken') :
return { "Authorization" : auth_token }
def get_iq_auth_header(self,host,username,password) :
assumed_login_provider = "tmos"
token = None
try :
payload = { "username" : username, "password" : password, "loginProviderName" : assumed_login_provider }
login = requests.post('https://' + host + '/mgmt/shared/authn/login', json=payload,verify=False,timeout=2)
if 'token' in json.loads(login.text) :
token = json.loads(login.text)['token']['token']
except Exception as exception:
self._debug("Exception getting auth header for host " + host + ": \n" + str(exception),2)
return None
if token :
return { "X-F5-Auth-Token" : token }
else :
return None
def search_bc_object(self,host_tuple) :
"""Searches BC for the subject."""
response = requests.get("https://" + self.host + "/Services/REST/v1/searchByObjectTypes?keyword=" + host_tuple[2][0] + "&types=IP4Address,IP6Address&start=0&count=10",headers=self.current_auth_header)
return self.clean_response(response)
def get_vip(self,host,header) :
"""Given a host and an authorization header, gets all the vips for that host"""
# Please don't give me empty auth headers, I don't want to check it everywhere
self._debug("Trying VIP info for " + host,2)
try :
vips = requests.get("https://" + host + '/mgmt/tm/ltm/virtual',headers=header,verify=False,timeout=10)
if vips.status_code == 200 :
return json.loads(vips.text)['items']
self._debug("Response not OK for " + vips.history[0].url,3)
return None
except Exception as exception :
self._debug("Exception getting VIPs for host " + host + ": \n" + str(exception),2)
return None
def search_bc_parent_object(self,host_tuple) :
"""Search for and return information about the objects parent, if any"""
if 'object' in self.return_payload and 'id' in self.return_payload['object'] :
response = requests.get("https://" + self.host + "/Services/REST/v1/getParent?entityId=" + str(self.return_payload['object']['id']), headers=self.current_auth_header)
return self.clean_response(response)
def get_pool(self,host,header,pool) :
"""Given a host, auth header, and pool, return the pool and it's members from the host"""
try :
pools = requests.get("https://" + host + "/mgmt/tm/ltm/pool/~" + self._partition + "~" + pool,headers=header,verify=False,timeout=5)
if pools.status_code == 200 :
poolobj = json.loads(pools.text)
members = requests.get("https://" + host + "/mgmt/tm/ltm/pool/~" + self._partition + "~" + pool + "/members",headers=header,verify=False,timeout=10)
if members.status_code == 200 :
membersobj = json.loads(members.text)['items']
return {'pool':poolobj,'members':membersobj}
return {'pool':poolobj}
elf._debug("Response not OK for " + pool.history[0].url,3)
return None
except Exception as exception:
self._debug("Exception getting pool for host " + host + ": \n" + str(exception),2)
return None
def choose_host(self,host_tuple) :
"""Given a host_tuple, return the host that we suspect is responsible for the VIP resides in."""
@ -88,21 +141,3 @@ class ServiceDelegate(ServiceBase) :
return hosts
return hosts
def clean_response(self,response) :
parsed = {}
try :
parsed = json.loads(response.text)
except Exception :
return parsed
if type(parsed) is list and len(parsed) > 0 :
parsed = parsed[0]
if type(parsed) is dict :
if 'properties' in parsed :
parsed["properties"] = parsed["properties"][:-1]
bad_delim_props = [x for x in parsed["properties"].split("|") if x.count('=') > 1]
if len(bad_delim_props) > 0 :
parsed.update({"unknown":bad_delim_props})
attributes = dict(x.split("=") for x in parsed["properties"].split("|") if x.count('=') == 1)
parsed.pop("properties", None)
parsed.update(attributes)
return parsed

View File

@ -1,7 +1,11 @@
import sys
class ServiceBase :
config = {}
namespace = {}
data = {}
error = []
warn = []
_status = "Ready"
_subjects = []
def __init__(self,config,namespace,subjects,dossiercopy):
@ -27,6 +31,10 @@ class ServiceBase :
self.shutdown()
return finaldictionary
def _debug(self,message,verbosity_level) :
if self.namespace.debug and self.namespace.debug >= verbosity_level :
print(message,file=sys.stderr)
@classmethod
def get_arguments(cls) :
"""Returns an array of information used to construct an argumentparser argument."""