Skip to content

Instantly share code, notes, and snippets.

@q1x
Created February 3, 2021 08:14
Show Gist options
  • Save q1x/3407b35b69485a7b35937b444b6fc94f to your computer and use it in GitHub Desktop.
Save q1x/3407b35b69485a7b35937b444b6fc94f to your computer and use it in GitHub Desktop.
Zabbix automaintenance
#!/usr/bin/env python
#
# import needed modules.
# pyzabbix is needed, see https://github.com/lukecyca/pyzabbix
#
import argparse
import ConfigParser
import os
import os.path
import time
import sys
import logging
import distutils.util
from pprint import pprint
from pyzabbix import ZabbixAPI
logger = logging.getLogger(__name__)
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.WARNING)
# define config helper function
def ConfigSectionMap(section):
dict1 = {}
options = Config.options(section)
for option in options:
try:
dict1[option] = Config.get(section, option)
if dict1[option] == -1:
DebugPrint("skip: %s" % option)
except:
print("exception on %s!" % option)
dict1[option] = None
return dict1
# set default vars
defconf = os.getenv("HOME") + "/.zbx.conf"
username = ""
password = ""
api = ""
noverify = ""
# Define commandline arguments
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,description='Creates Zabbix trigger dependencies for on-site equipment to the local default gateway.', epilog="""
This program can use .ini style configuration files to retrieve the needed API connection information.
To use this type of storage, create a conf file (the default is $HOME/.zbx.conf) that contains at least the [Zabbix API] section and any of the other parameters:
[Zabbix API]
username=johndoe
password=verysecretpassword
api=https://zabbix.mycompany.com/path/to/zabbix/frontend/
no_verify=true
""")
parser.add_argument('-u', '--username', help='User for the Zabbix api')
parser.add_argument('-p', '--password', help='Password for the Zabbix api user')
parser.add_argument('-a', '--api', help='Zabbix API URL')
parser.add_argument('--no-verify', help='Disables certificate validation when using a secure connection',action='store_true')
parser.add_argument('-c','--config', help='Config file location (defaults to $HOME/.zbx.conf)')
parser.add_argument('-v', '--verbose', help='Enables verbose output.',action='store_true')
parser.add_argument('-d', '--debug', help='Enables debug output.',action='store_true')
parser.add_argument('-n', '--noop', help='Verbose output will show what would be done, but no changes will be made.',action='store_true')
parser.add_argument('-t', '--trigger', help='Description of the trigger used to identify host online status, defaults to "Ping: {HOST.NAME} is not responding to requests"',default='Ping: {HOST.NAME} is not responding to requests',metavar='DESCRIPTION')
parser.add_argument('-P', '--predeploy', help='Name of the predeployment group, defaults to "Predeployment"',default='Predeployment',metavar='HOSTGROUP')
parser.add_argument('-A', '--alive', help='Alive period (in hours) before a host is removed from the predeployment group, defaults to 8. 0 only checks last 5 mins.',metavar='[0-24]', choices=range(0,25),default=8, type=int)
parser.add_argument('-O', '--offline', help='Name of the Offline group, defaults to "Offline"',default='Offline',metavar='HOSTGROUP')
parser.add_argument('-g', '--grace', help='Grace period (in days) before a host is moved to the offline group, defaults to 7.',metavar='[1-31]', choices=range(1,32),default=7, type=int)
parser.add_argument('groups', help='List of groups to scan for hosts.', nargs='+')
args = parser.parse_args()
# load config module
Config = ConfigParser.ConfigParser()
Config
# if configuration argument is set, test the config file
if args.config:
if os.path.isfile(args.config) and os.access(args.config, os.R_OK):
Config.read(args.config)
# if not set, try default config file
else:
if os.path.isfile(defconf) and os.access(defconf, os.R_OK):
Config.read(defconf)
# try to load available settings from config file
try:
username=ConfigSectionMap("Zabbix API")['username']
password=ConfigSectionMap("Zabbix API")['password']
api=ConfigSectionMap("Zabbix API")['api']
noverify=bool(distutils.util.strtobool(ConfigSectionMap("Zabbix API")["no_verify"]))
except:
pass
# override settings if they are provided as arguments
if args.username:
username = args.username
if args.password:
password = args.password
if args.api:
api = args.api
if args.no_verify:
noverify = args.no_verify
# test for needed params
if not username:
sys.exit("Error: API User not set")
if not password:
sys.exit("Error: API Password not set")
if not api:
sys.exit("Error: API URL is not set")
if args.verbose:
logger.setLevel(logging.INFO)
if args.debug:
logger.setLevel(logging.DEBUG)
if args.noop:
logger.info(" *** Running in NOOP mode, no changes will be made *** ")
# Setup Zabbix API connection
zapi = ZabbixAPI(api)
if noverify is True:
zapi.session.verify = False
# Login to the Zabbix API
zapi.login(username, password)
zapi.timeout=900
##################################
# Start actual API logic
##################################
# Zabbix version
zversion=zapi.apiinfo.version()
logger.info("Zabbix API version: %s", str(zversion))
timestamp = time.time()
gracestamp = timestamp-(args.grace*86400)
if args.alive == 0:
alivestamp = timestamp-300
else:
alivestamp = timestamp-(args.alive*3600)
set_offline = []
remove_offline = []
remove_predeploy = []
# Find group properties for offline maintenance group
offlinegrp = zapi.hostgroup.get(**{"output": ["groupid","name"],"filter": {"name": args.offline }})
if offlinegrp:
logger.debug("Found the following offline maintenance group: %s", offlinegrp[0])
else:
logger.error('Could not find offline maintenance group "%s".', args.offline)
sys.exit(1)
# Find group properties for predeployment maintenance group
predeploygrp = zapi.hostgroup.get(**{"output": ["groupid","name"],"filter": {"name": args.predeploy }})
if predeploygrp:
logger.debug("Found the following predeployment maintenance group: %s", predeploygrp[0])
else:
logger.error('Could not find predeployment maintenance group "%s".', args.predeploy)
sys.exit(1)
# Find hostgroups to inspect
groups = zapi.hostgroup.get(**{"output": ["groupid","name"],"filter": {"name": args.groups }})
if groups:
logger.debug("Found the following hostgroups: %s", groups)
groupids = [group['groupid'] for group in groups if 'groupid' in group] # we need a list of groupids
# Fetch matching hosts
hosts = zapi.host.get(**{"output" : "extend",
"groupids": groupids,
"selectGroups": "extend",
"selectTriggers": ['description','lastchange','state','status','value'],
"monitored_hosts": True,
"with_monitored_triggers": True
})
if hosts:
logger.info("Inspecting %s host(s)...", len(hosts))
for host in hosts:
trigger = [t for t in host['triggers'] if t['description'] == args.trigger] # Find trigger properties for the host status trigger
if trigger:
# If host is not in maintenance but has been down longer than the grace period, add it to the set_offline list.
if ( int(host['maintenance_status']) == 0 and
int(trigger[0]['status']) == 0 and
int(trigger[0]['state']) == 0 and
int(trigger[0]['value']) == 1 and
float(trigger[0]['lastchange']) < gracestamp):
logger.debug('%s (%s): Has been offline more than %s day(s), marking host for offline maintenance.', host['host'], host['name'], args.grace)
set_offline.append({'hostid': host['hostid']})
# If the host is in the predeployment maintenance group and it has been active for more than the alive threshold, add it to the remove_predeploy list
elif ( args.predeploy in [g['name'] for g in host['groups']] and
int(trigger[0]['status']) == 0 and
int(trigger[0]['state']) == 0 and
int(trigger[0]['value']) == 0 and
float(trigger[0]['lastchange']) < alivestamp):
logger.debug('%s (%s): Has been online more than %s hours(s), unmarking host from predeployment.', host['host'], host['name'], args.grace)
remove_predeploy.append(host['hostid'])
zapi.host.update(hostid=host['hostid'], inventory={"date_hw_install": trigger[0]['lastchange']})
# If the host is in the offline maintenance group and it has been active for more than the alive threshold, add it to the remove_offline list
elif ( args.offline in [g['name'] for g in host['groups']] and
int(trigger[0]['status']) == 0 and
int(trigger[0]['state']) == 0 and
int(trigger[0]['value']) == 0 and
float(trigger[0]['lastchange']) < alivestamp):
logger.debug('%s (%s): Has been online more than %s hours(s), unmarking host from offline maintenance.', host['host'], host['name'], args.grace)
remove_offline.append(host['hostid'])
else:
logger.info('%s (%s): Could not find trigger "%s" on this host!!!', host['host'], host['name'], args.trigger)
if len(set_offline) > 0:
# Set offline hosts in maintenance
logger.info('Adding %s host(s) to offline maintenance group "%s".', len(set_offline), args.offline)
if not args.noop:
try:
response = zapi.hostgroup.massadd(**{'groups': offlinegrp, 'hosts': set_offline})
except:
logger.error('Something went wrong trying to add %s host(s) to offline maintenance group "%s".',len(set_offline), args.offline)
if len(remove_offline) > 0:
# Remove online hosts from offline maintenance
logger.info('Removing %s host(s) from offline maintenance group "%s".', len(remove_offline), args.offline)
if not args.noop:
try:
response = zapi.hostgroup.massremove(**{'groupids': offlinegrp[0]['groupid'], 'hostids': remove_offline})
except:
logger.error('Something went wrong trying to remove %s host(s) from offline maintenance group "%s".',len(remove_offline), args.offline)
if len(remove_predeploy) > 0:
# Remove online hosts from predeployment maintenance
logger.info('Removing %s host(s) from predeployment maintenance group "%s".', len(remove_predeploy), args.predeploy)
if not args.noop:
try:
response = zapi.hostgroup.massremove(**{'groupids': predeploygrp[0]['groupid'], 'hostids': remove_predeploy})
except:
logger.error('Something went wrong trying to remove %s host(s) from predeployment maintenance group "%s".',len(remove_offline), args.offline)
if len(set_offline) == 0 and len(remove_offline) ==0 and len(remove_predeploy) == 0:
logger.info("No hosts found that need to be updated.")
else:
logger.info("No hosts found.")
else:
logger.error("Could not find any matching hostgroups.")
sys.exit(2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment