From 741af0779723a9fb981a3d41cbd62f2dbb139cf9 Mon Sep 17 00:00:00 2001 From: Christian Stankowic Date: Mon, 30 Mar 2015 18:46:48 +0200 Subject: [PATCH] Minor enhancements and fixes - enhanced blacklisting for preparing maintenances (case-insensitive wildcards - see issue #31) - verification log is created automatically after preparing maintenance (see issue #30) - unicodes are translated into ascii while creating snapshot reports (see issue #28) - locked systems are now excluded while creating snapshot reports by default, they can also be integrated using command-line parameters (see issue #29) - decreased verbosity of satprep_snapshot.py - fixed bug where monitoring host (auth) information are not captured correctly (see issue #33) --- satprep_prepare_maintenance.py | 33 +++++++++++++---- satprep_snapshot.py | 68 +++++++++++++++++++++++++++------- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/satprep_prepare_maintenance.py b/satprep_prepare_maintenance.py index 62b6019..6a6aeaa 100644 --- a/satprep_prepare_maintenance.py +++ b/satprep_prepare_maintenance.py @@ -17,6 +17,7 @@ from satprep_shared import schedule_downtime, get_credentials, create_snapshot, is_downtime, has_snapshot import time import os +from fnmatch import fnmatch #set logger LOGGER = logging.getLogger('satprep_prepare_maintenance') @@ -33,7 +34,17 @@ +def is_blacklisted(name, list): + #check whether system is blacklisted + for entry in list: + LOGGER.debug("Checking whether {name} is blacklisted by *{entry}*".format(name=name, entry=entry)) + if fnmatch(name.lower(), "*{seek}*".format(seek=entry.lower()) ): return True + return False + + + def verify(): + #verify snapshots and downtimes global downtimeHosts global snapshotHosts global myPrefix @@ -124,7 +135,7 @@ def verify(): def setDowntimes(): - #some globale variables + #set downtimes global defaultMonUser global defaultMonPass @@ -185,7 +196,7 @@ def setDowntimes(): def createSnapshots(): - #some globale variables + #create snapshots global defaultVirtUser global defaultVirtPass @@ -281,7 +292,7 @@ def readFile(file): if (row[repcols["system_prod"]] == "1" and options.nonprodOnly == False) \ or (row[repcols["system_prod"]] != "1" and options.prodOnly == False) \ or (options.prodOnly == False and options.nonprodOnly == False): - if row[repcols["hostname"]].lower() not in options.exclude: downtimeHosts.append(this_name) + if is_blacklisted(row[repcols["hostname"]], options.exclude) == False: downtimeHosts.append(this_name) LOGGER.debug("Downtime will be scheduled for '" + this_name + "' (P:" + row[repcols["system_prod"]] + ")") #virtualization, add custom name if defined @@ -292,7 +303,7 @@ def readFile(file): if (row[repcols["system_prod"]] == "1" and options.nonprodOnly == False) \ or (row[repcols["system_prod"]] != "1" and options.prodOnly == False) \ or (options.prodOnly == False and options.nonprodOnly == False): - if row[repcols["hostname"]].lower() not in options.exclude: snapshotHosts.append(this_name) + if is_blacklisted(row[repcols["hostname"]], options.exclude) == False: snapshotHosts.append(this_name) LOGGER.debug("Snapshot will be created for '" + this_name + "' (P:" + row[repcols["system_prod"]] + ")") else: LOGGER.debug("Script parameters are avoiding creating snapshot for '" + this_name + "' (P:" + row[repcols["system_prod"]] + ")") @@ -307,7 +318,7 @@ def readFile(file): if (row[repcols["system_prod"]] == "1" and options.nonprodOnly == False) \ or (row[repcols["system_prod"]] != "1" and options.prodOnly == False) \ or (options.prodOnly == False and options.nonprodOnly == False): - if row[repcols["hostname"]].lower() not in options.exclude: downtimeHosts.append(this_name) + if is_blacklisted(row[repcols["hostname"]], options.exclude) == False: downtimeHosts.append(this_name) LOGGER.debug("Downtime will be scheduled for '" + this_name + "' (P:" + row[repcols["system_prod"]] + ")") else: LOGGER.debug("Script parameters are avoiding scheduling downtime for '" + this_name + "' (P:" + row[repcols["system_prod"]] + ")") @@ -321,7 +332,7 @@ def readFile(file): if (row[repcols["system_prod"]] == "1" and options.nonprodOnly == False) \ or (row[repcols["system_prod"]] != "1" and options.prodOnly == False) \ or (options.prodOnly == False and options.nonprodOnly == False): - if row[repcols["hostname"]].lower() not in options.exclude: snapshotHosts.append(this_name) + if is_blacklisted(row[repcols["hostname"]], options.exclude) == False: snapshotHosts.append(this_name) LOGGER.debug("Snapshot will be created for '" + this_name + "' (P:" + row[repcols["system_prod"]] + ")") else: LOGGER.debug("Script parameters are avoiding creating snapshot for '" + this_name + "' (P:" + row[repcols["system_prod"]] + ")") @@ -353,6 +364,10 @@ def main(options): #schedule downtimes and create snapshots if options.skipMonitoring == False: setDowntimes() if options.skipSnapshot == False: createSnapshots() + #also verify + if options.dryrun == False: + LOGGER.info("Verifying preparation...") + verify() @@ -360,7 +375,7 @@ def parse_options(args=None): if args is None: args = sys.argv - # define usage, description, version and load parser + #define usage, description, version and load parser usage = "usage: %prog [options] snapshot.csv" desc = '''%prog is used to prepare maintenance for systems managed with Spacewalk, Red Hat Satellite or SUSE Manager. This includes (un)scheduling downtimes in Nagios, Icinga and Shinken and creating/removing snapshots of virtual machines. As this script uses libvirt multiple hypervisors are supported (see GitHub and libvirt documenation). Login credentials are assigned using the following shell variables: SATELLITE_LOGIN username for Satellite @@ -436,9 +451,11 @@ def parse_options(args=None): #tell user that he's a funny guy if ( - (options.skipSnapshot and options.skipMonitoring) + (options.skipSnapshot == True and options.skipMonitoring == True) or (options.prodOnly == True and options.nonprodOnly == True) + or + (options.dryrun == True and options.verifyOnly == True) ): print "Haha, you're funny." exit(1) diff --git a/satprep_snapshot.py b/satprep_snapshot.py index fc8f08e..a17463c 100755 --- a/satprep_snapshot.py +++ b/satprep_snapshot.py @@ -18,6 +18,7 @@ import xmlrpclib from optparse import OptionParser, OptionGroup from satprep_shared import check_if_api_is_supported, get_credentials +from unidecode import unidecode @@ -76,6 +77,8 @@ def parse_options(args=None): #snapOpts.add_option("-f", "--field", action="append", type="choice", dest="fields", choices=POSSIBLE_FIELDS, metavar="FIELDS", help="defines which fields should be integrated in the report (default: all available)") #-p / --include-patches snapOpts.add_option("-p", "--include-patches", action="store_true", default=False, dest="includePatches", help="defines whether package updates that are not part of an erratum shall be included (default: no)") + #-l / --include-locked + snapOpts.add_option("-l", "--include-locked", action="store_true", default=False, dest="includeLocked", help="also includes locked systems (default: no)") (options, args) = parser.parse_args(args) @@ -98,8 +101,9 @@ def main(options): sattelite_url = "http://{0}/rpc/api".format(options.server) client = xmlrpclib.Server(sattelite_url, verbose=options.debug) key = client.auth.login(username, password) - check_if_api_is_supported(client) + + if options.includeLocked: LOGGER.warning("Snapshot report will also include information about locked systems") #check whether the output directory/file is writable if os.access(os.path.dirname(options.output), os.W_OK) or os.access(os.getcwd(), os.W_OK): @@ -110,7 +114,6 @@ def main(options): writer = csv.writer(open(options.output, "w"), 'default') #create header and scan _all_ the systems - #writer.writerow(options.fields) writer.writerow(DEFAULT_FIELDS) systems = client.system.listSystems(key) #counter variable for XMLRPC timeout workaround (https://github.com/stdevel/satprep/issues/5) @@ -156,21 +159,37 @@ def process_errata(client, key, writer, system): "errata_date": "update_date" } + #break if system locked + details = client.system.getDetails(key, system["id"]) + if details["lock_status"] != False and options.includeLocked == False: + LOGGER.info("Skipping errata for locked host " + "{system[name]} (SID {system[id]})...".format( + system=system + ) + ) + return + #TODO: errata_* not working! Implemented a workaround (looking for a "nicer" way to do this) errata = client.system.getRelevantErrata(key, system["id"]) if not errata: - LOGGER.info("Host {0[name]} (SID {0[id]}) has no relevant errata.".format(system)) + LOGGER.debug("Host {0[name]} (SID {0[id]}) has no relevant errata.".format(system)) return + else: + LOGGER.info("Looking at relevant errata for host " + "{system[name]} (SID {system[id]})...".format( + system=system + ) + ) for i, erratum in enumerate(errata, start=1): - LOGGER.info("Having a look at relevant errata #{errata} " + LOGGER.debug("Having a look at relevant errata #{errata} " "for host {system[name]} (SID {system[id]})...".format( errata=i, system=system ) ) - + valueSet = [] for column in DEFAULT_FIELDS: try: @@ -178,7 +197,7 @@ def process_errata(client, key, writer, system): LOGGER.debug("Translated column '" + column + "' in '" + columnErrataMapping[column] + "'") continue except KeyError: - # Key not found - probably needs more logic. + #Key not found - probably needs more logic. LOGGER.debug("Could not find column '" + column + "' in columnErrataMapping") pass @@ -286,7 +305,7 @@ def process_errata(client, key, writer, system): and temp["SYSTEM_MONITORING_HOST"] != "" and "SYSTEM_MONITORING_HOST_AUTH" in temp and temp["SYSTEM_MONITORING_HOST_AUTH"] != ""): - temp_vmname = temp_vmname + "@" + temp["SYSTEM_MONITORING_HOST"] + ":" + temp["SYSTEM_MONITORING_HOST_AUTH"] + temp_monname = temp_monname + "@" + temp["SYSTEM_MONITORING_HOST"] + ":" + temp["SYSTEM_MONITORING_HOST_AUTH"] valueSet.append(temp_monname) else: valueSet.append("") @@ -323,6 +342,14 @@ def process_errata(client, key, writer, system): else: valueSet.append("") + #replace unicodes + for i,field in enumerate(valueSet): + if type(field) is unicode: + LOGGER.debug("Converted to ascii: {ascii}".format( + ascii=unidecode(field) + )) + valueSet[i] = str(unidecode(field)) + writer.writerow(valueSet) @@ -330,22 +357,38 @@ def process_errata(client, key, writer, system): def process_patches(client, key, writer, system): updates = client.system.listLatestUpgradablePackages(key, system["id"]) + #break if system locked + details = client.system.getDetails(key, system["id"]) + if details["lock_status"] != False and options.includeLocked == False: + LOGGER.info("Skipping patches for locked host " + "{system[name]} (SID {system[id]})...".format( + system=system + ) + ) + return + if not updates: LOGGER.debug("Host {0[name]} (SID {0[id]}) has no relevant updates.".format(system)) return + else: + LOGGER.info("Looking at relevant package updates for host " + "{system[name]} (SID {system[id]})...".format( + system=system + ) + ) for i, update in enumerate(updates, start=1): - LOGGER.info("Having a look at relevant package update " + LOGGER.debug("Having a look at relevant package update " "#{update} for host {system[name]} " "(SID {system[id]})...".format( update=i, system=system ) ) - + if client.packages.listProvidingErrata(key, update["to_package_id"]): - # We only add update information if it is not not - # already displayed as part of an erratum + #We only add update information if it is not not + #already displayed as part of an erratum LOGGER.debug("Dropping update {0[name]} " "({0[to_package_id]}) as it's already part of " "an erratum.".format(update) @@ -353,7 +396,6 @@ def process_patches(client, key, writer, system): continue valueSet = [] - #for column in options.fields: for column in DEFAULT_FIELDS: if column == "hostname": valueSet.append(system["name"]) @@ -438,7 +480,7 @@ def process_patches(client, key, writer, system): and temp["SYSTEM_MONITORING_HOST"] != "" and "SYSTEM_MONITORING_HOST_AUTH" in temp and temp["SYSTEM_MONITORING_HOST_AUTH"] != ""): - temp_vmname = temp_vmname + "@" + temp["SYSTEM_MONITORING_HOST"] + ":" + temp["SYSTEM_MONITORING_HOST_AUTH"] + temp_monname = temp_monname + "@" + temp["SYSTEM_MONITORING_HOST"] + ":" + temp["SYSTEM_MONITORING_HOST_AUTH"] valueSet.append(temp_monname) else: valueSet.append("")