├── LICENSE ├── README.md ├── python_zabbix_assistant.py ├── make_graph_url ├── screen_creator └── zabbix_tool /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Brian Gallew 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Zabbix Tools 2 | =========== 3 | 4 | # zabbix_tool 5 | Tool for CLI interaction with the Zabbix API. 6 | 7 | It provides for "simple" CLI access to various Zabbix objects. It is far 8 | from complete, yet is very useful in it's current state. 9 | 10 | # screen_creator 11 | 12 | Useful for creating screens in Zabbix. Can add all of the graphs from a 13 | single host to the screen, as well as adding a single named graph from 14 | every host in a hostgroup. 15 | 16 | This uses the zabbix\_api.py from 17 | https://github.com/gescheit/scripts/blob/master/zabbix/zabbix\_api.py. 18 | Someone else has packaged a stale version of that as zabbix\_api in the 19 | Python Cheese Shop, but it's stale. 20 | 21 | # Configuration 22 | 23 | Each of the above tools expects you to have a config file called ~/.zabbix 24 | 25 | The Config file should look like this: 26 | ``` 27 | -------------- CUT HERE ----------------- 28 | [zabbix] 29 | username='zabbix-api-user' 30 | password='not-your-password' 31 | url='http://zabbix-api.example.com/' 32 | validate_certs='True' 33 | 34 | [dev] 35 | username='zabbix-dev-api-user' 36 | password='not-mine-either' 37 | url='https://zabbix-dev.example.com/' 38 | validate_certs='' 39 | -------------- CUT HERE ----------------- 40 | ``` 41 | 42 | The "zabbix" section is the default used by the scripts, though you can 43 | specify the section to use on the command line. 44 | -------------------------------------------------------------------------------- /python_zabbix_assistant.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | 3 | import os, json, socket, struct, logging, time, urllib2, platform, threading 4 | 5 | # Some day this will use "real" Zabbix auth and become more complicated. 6 | ZABBIX_API_SERVER = 'zabbix-api.example.com' 7 | ZABBIX_KEY = 'xyzzy-plugh-plover' 8 | 9 | class ZabbixActiveSender(object): 10 | '''This will send values to Zabbix via the same mechanism zabbix_sender uses. 11 | ''' 12 | def __init__(self, zabbixserver=None, prefix = None, no_send=False, clienthost=platform.node()): 13 | self.lock = threading.Lock() 14 | self.zabbixserver = zabbixserver 15 | self.clienthost = clienthost # This better be the FQDN 16 | logging.debug('ZAS.__init__: clienthost="%s"', self.clienthost) 17 | self.prefix = prefix 18 | self.no_send = no_send 19 | self.check_for_proxy() 20 | self.clear() 21 | logging.debug('ZAS.__init__: zabbixserver="%s"', str(self.zabbixserver)) 22 | if os.environ.has_key('http_proxy'): 23 | del os.environ['http_proxy'] 24 | logging.debug('ZAS.__init__: removed http_proxy from the environment') 25 | return 26 | 27 | def check_for_proxy(self): 28 | '''If the zabbixserver was specified, we trust that (e.g. for 29 | zabbix-stage.llnw.net). Otherwise, we go looking for the proxy 30 | that owns us. 31 | ''' 32 | if self.zabbixserver: return 33 | postdata = { 34 | "key": ZABBIX_KEY, 35 | "method": "proxymap.get", 36 | "params": { 37 | "hostnames": [ os.uname()[1] ], 38 | "output": "json" 39 | } 40 | } 41 | data = urllib2.urlopen('http://'+ZABBIX_API_SERVER+'/llnw/api_jsonrpc.php',json.dumps(postdata)) 42 | self.zabbixserver = json.load(data)['result'][0]['proxy'] 43 | return 44 | 45 | def __call__(self, key, value): 46 | '''Load up our dictionary with data preparatory to sending it on to Zabbix. 47 | If we're debugging, we'll immediately send each value, which makes it a 48 | *lot* easier to figure out where we've got problems.''' 49 | logging.debug('ZAS.__call__: %30s\t%s', key, value) 50 | if self.prefix: key = self.prefix+key 51 | self.lock.acquire() 52 | self.data.append({"key":key, "value":value, "host":self.clienthost, "clock":int(time.time())}) 53 | self.lock.release() 54 | if logging.root.level < logging.INFO: self.send() 55 | return 56 | 57 | def send(self): 58 | '''Ship the data to Zabbix. Call as often as you like, though of course 59 | it's more efficient to call it just once after you've accumulated 60 | all of the data you'd like to send. 61 | 62 | The magic in here with structs is required to format everything for 63 | Zabbix' wire protocol. If this breaks, it's time to dive into the 64 | Zabbix internals again. 65 | 66 | ''' 67 | self.lock.acquire() 68 | if not self.data: 69 | self.lock.release() 70 | return # Nothing to send! 71 | if not self.no_send: 72 | response = {"request":"agent data", "clock":int(time.time()), "data":self.data} 73 | string = json.dumps(response) 74 | logging.debug(string) 75 | string_to_send = 'ZBXD\x01%s%s' % (struct.pack(' '2.3.99': 333 | params['rowspan'] = 0 334 | params['colspan'] = 0 335 | for param in [self._HEIGHT, self._WIDTH]: 336 | value = getattr(self.options, param, None) 337 | if value: 338 | params[param] = value 339 | self.z.screenitem.create(params) 340 | return 341 | 342 | def add_graphs_to_screen(self, graphs): 343 | '''Add graphs directly to a screen object. 344 | :param graphs: list of Zabbix graph objects 345 | :returns: None or an error message 346 | ''' 347 | # Get the current screen configuration 348 | existing_items = self.z.screenitem.get({self._SCREENIDS: self.screen[self._SCREENID], 349 | self._OUTPUT: self._EXTEND}) 350 | # Create a list of all valid coordinates in the graph, sorted for a 351 | # HORIZONTAL layout, because switching to VERTICAL is then really 352 | # easy. 353 | graph_matrix = [] 354 | for y in xrange(int(self.screen[self._VSIZE])): 355 | for x in xrange(int(self.screen[self._HSIZE])): 356 | graph_matrix.append((x, y)) 357 | # sort for VERTICAL if the user wants it. 358 | if self.options.vertical: 359 | graph_matrix.sort() 360 | 361 | # This lookup table is created so that we don't have to repeatedly 362 | # iterate over the list of graphs. 363 | lookup_table = {} 364 | for graph in graphs: 365 | lookup_table[graph[self._GRAPHID]] = graph 366 | 367 | # Add each item from the existing item set into the matrix. While 368 | # we're at it, ensure we remove them all from the set of graphs to 369 | # be added (no duplicates!). 370 | for item in existing_items: 371 | graph_matrix.remove((int(item['x']), int(item['y']))) 372 | if item[self._RESOURCEID] in lookup_table: 373 | logging.info('add_graphs_to_screen: Removing existing item from add list: %s', item[ 374 | self._RESOURCEID]) 375 | graphs.remove(lookup_table[item[self._RESOURCEID]]) 376 | 377 | # Check if screen can not fit provided graphs and increase vsize. 378 | max_graphs = int(self.screen[self._HSIZE])*int(self.screen[self._VSIZE]) 379 | if not self.options.vsize and max_graphs-len(existing_items) < len(graphs): 380 | self.options.vsize = int(self.screen[self._VSIZE])+math.ceil(len(graphs)/float(self.screen[self._HSIZE])) 381 | logging.info('add_graphs_to_screen: Setting screen vsize to %d' % self.options.vsize) 382 | self.update_screen_settings() 383 | # Current graph_matrix is wrong now, so call again. 384 | return self.add_graphs_to_screen(graphs) 385 | 386 | # Iterate through the matrix looking for empty graph locations. 387 | for (x, y) in graph_matrix: 388 | if not graphs: 389 | break 390 | value = graphs.pop(0) 391 | self.add_screenitem(self.screen[self._SCREENID], 392 | value[self._GRAPHID], 393 | x, 394 | y) 395 | if not graphs: 396 | return # We're all done! 397 | return '%d graphs were not added because the screen is too small (%s x %s)' % (len(graphs), 398 | self.screen[ 399 | self._HSIZE], 400 | self.screen[self._VSIZE]) 401 | 402 | def add_all_template(self): 403 | '''Add all of the graphs associated with the template to the screen. 404 | ''' 405 | if not self.template: # Check to see if we're using a template 406 | return # Bail if not 407 | 408 | # Get the graph set for the host in question. 409 | filter = re.compile(self.options.filter) 410 | graphs = self.z.graph.get( 411 | {self._TEMPLATEIDS: self.template[self._TEMPLATEID], self._OUTPUT: self._EXTEND}) 412 | graphs.sort(key=operator.itemgetter(self._NAME)) 413 | logging.info('add_all_host: Got %d graphs', len(graphs)) 414 | graphs = [x for x in graphs if filter.search(x[self._NAME])] 415 | if not graphs: 416 | return 'All graphs filtered out' 417 | 418 | return self.add_graphs_to_screen(graphs) 419 | 420 | def add_all_host(self): 421 | '''Add all of the graphs associated with the host to the screen. 422 | ''' 423 | if not self.options.add_all_host: # Check to see if we're adding host graphs 424 | return # Bail if not 425 | if self.template: 426 | logging.warn( 427 | "--add-all-host isn't valid with --template, ignoring it") 428 | return 429 | 430 | # Get our host information 431 | host = self.get_host(self.options.add_all_host) 432 | if not host: 433 | return 'Unable to find host %s' % self.options.add_all_host 434 | logging.debug('add_all_host: hostid is %s', host[self._HOSTID]) 435 | 436 | # Get the graph set for the host in question. 437 | filter = re.compile(self.options.filter) 438 | graphs = self.z.graph.get( 439 | {self._HOSTIDS: host[self._HOSTID], self._OUTPUT: self._EXTEND}) 440 | graphs.sort(key=operator.itemgetter(self._NAME)) 441 | logging.info('add_all_host: Got %d graphs', len(graphs)) 442 | graphs = [x for x in graphs if filter.search(x[self._NAME])] 443 | if not graphs: 444 | return 'All graphs filtered out' 445 | 446 | return self.add_graphs_to_screen(graphs) 447 | 448 | def add_all_group(self): 449 | '''Add all of the graphs associated with the given hostgroup to the given 450 | screen. 451 | :returns: None or an error message 452 | ''' 453 | if not self.options.add_all_group: # Check to see if we're really doing this 454 | return # Bail if not 455 | if self.template: 456 | logging.warn( 457 | "--add-all-group isn't valid with --template, ignoring it") 458 | return 459 | # Get our host information 460 | hostlist = self.get_hostgroup_hosts(self.options.add_all_group[0]) 461 | if not hostlist: 462 | return 'Unable to find hostgroup %s' % self.options.add_all_group[0] 463 | 464 | filter = re.compile(self.options.filter) 465 | 466 | hostlist = [x[self._HOSTID] for x in hostlist if filter.search( 467 | x[self._NAME]) or filter.search(x[self._HOST])] 468 | if not hostlist: 469 | return 'All hosts filtered out' 470 | logging.debug('add_all_group: hosts are %s', str(hostlist)) 471 | 472 | # Get the graph set for the hosts in question. 473 | graphs = self.z.graph.get({self._HOSTIDS: hostlist, self._OUTPUT: self._EXTEND, 474 | self._SELECTHOSTS: self._EXTEND, 475 | # 1.8 support, "yay" 476 | 'select_hosts': self._EXTEND, 477 | self._FILTER: {self._NAME: self.options.add_all_group[1]}}) 478 | logging.info('add_all_group: Got %d graphs', len(graphs)) 479 | logging.debug('add_all_group: graphs: %s', str(graphs)) 480 | 481 | # This oddity is to sort the graphs, because we'd *like* the graphs 482 | # to be ordered by hostname. Of course, if there are "holes" in 483 | # the screen, they will play hob with this ordering, but that's a 484 | # different problem. 485 | sorted_graphs = [] 486 | for hostid in hostlist: 487 | sorted_graphs.extend( 488 | [x for x in graphs if x[self._HOSTS][0][self._HOSTID] == hostid]) 489 | logging.debug( 490 | 'add_all_group: Sorted into %d graphs', len(sorted_graphs)) 491 | 492 | return self.add_graphs_to_screen(sorted_graphs) 493 | 494 | 495 | def parse_command_line(): 496 | '''Handle command-line options 497 | :returns: options, Client 498 | ''' 499 | parser = argparse.ArgumentParser(epilog=CONFIG_HELP, 500 | formatter_class=argparse.RawTextHelpFormatter) 501 | parser.add_argument("-c", "--config", dest="config", 502 | default=os.environ['HOME'] + '/.zabbix', 503 | help="CONFIG file", metavar="CONFIG") 504 | parser.add_argument("-s", "--section", dest="section", default='zabbix', 505 | help="Section of config file to use file", metavar="SECTION") 506 | parser.add_argument("-d", "--debug", action='store_true', default=False, 507 | help="Enable debugging messages") 508 | parser.add_argument("-v", "--verbose", action='store_true', default=False, 509 | help="Enable verbose messages") 510 | parser.add_argument("--delete", action='store_true', default=False, 511 | help="Delete a screen") 512 | parser.add_argument("--add-all-host", 513 | help="Add all of the graphs from the given host") 514 | parser.add_argument("--add-all-group", nargs=2, metavar=('GROUP', 'GRAPH'), 515 | help="For all of the hosts in the given group, add the given graph") 516 | parser.add_argument("--hsize", type=int, 517 | help="Set the width of the screen (in graph objects)") 518 | parser.add_argument("--vsize", type=int, 519 | help="Set the height of the screen (in graph objects)") 520 | parser.add_argument("--height", default=0, type=int, 521 | help="The height of all graphs to be added") 522 | parser.add_argument("--vertical", default=False, action='store_true', 523 | help="Add graphs to screen by columns") 524 | parser.add_argument("--width", default=0, type=int, 525 | help="The width of all graphs to be added") 526 | parser.add_argument("--filter", default='.*', 527 | help="REGEX filter graph names for hosts, host names for hostgroups.") 528 | parser.add_argument("--rename", help="Change the name of the screen") 529 | parser.add_argument("-t", "--template", default=None, 530 | help="Working with the give template") 531 | parser.add_argument("screen", help="The screen") 532 | 533 | options = parser.parse_args() 534 | if options.debug: 535 | logging.basicConfig(level=logging.DEBUG) 536 | elif options.verbose: 537 | logging.basicConfig(level=logging.INFO) 538 | else: 539 | logging.basicConfig(level=logging.WARNING) 540 | config = ConfigParser.ConfigParser() 541 | try: 542 | config.readfp(open(options.config)) 543 | except ConfigParser.Error, config_exception: 544 | msg = 'Unable to parse the configuration file(%s): %s\n\n' 545 | print msg % (options.config, config_exception) 546 | raise 547 | if not options.section in config.sections(): 548 | raise SystemExit('No [zabbix] section in the configuration file') 549 | 550 | for (key, value) in config.items(options.section, 1): 551 | setattr(options, key, eval(value)) 552 | 553 | return options, Client(options) # Cheating! 554 | 555 | 556 | def main(): 557 | '''Program entry point''' 558 | options, tool = parse_command_line() 559 | 560 | # If we're deleting the screen, don't do anything else! 561 | if options.delete: 562 | tool.delete_screen() 563 | exit() 564 | 565 | screen = tool.get_screen(options.screen) 566 | 567 | if not screen: 568 | screen = tool.create() 569 | else: 570 | screen = tool.update_screen_settings() 571 | for fn in [tool.add_all_host, tool.add_all_group, tool.add_all_template]: 572 | result = fn() 573 | if result: 574 | logging.fatal(result) 575 | exit(1) 576 | print screen 577 | return 578 | 579 | if __name__ == '__main__': 580 | main() 581 | -------------------------------------------------------------------------------- /zabbix_tool: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Zabbix API Python CLI tool 3 | print_hostgroup_cpu written by Christoph Haas 4 | ''' 5 | 6 | from __future__ import print_function 7 | 8 | # https://github.com/gescheit/scripts/blob/master/zabbix/zabbix_api.py 9 | import zabbix_api 10 | import sys 11 | import os 12 | import logging 13 | import ConfigParser 14 | import operator 15 | import re 16 | from pprint import pprint 17 | from pydoc import render_doc 18 | from optparse import OptionParser 19 | from types import StringTypes 20 | 21 | CONFIG = 'config' 22 | LOGLEVEL = 'loglevel' 23 | NAME = 'name' 24 | PASSWORD = 'password' 25 | URL = 'url' 26 | VERBOSE = 'verbose' 27 | FILTER = 'filter' 28 | SECTION = 'section' 29 | 30 | CONFIG_HELP = ''' 31 | The Config file used by %s should look like this: 32 | -------------- CUT HERE ----------------- 33 | [zabbix] 34 | username='zabbix-api-user' 35 | password='not-your-password' 36 | url='http://zabbix-api.example.com/' 37 | validate_certs='True' 38 | 39 | [dev] 40 | username='zabbix-dev-api-user' 41 | password='not-mine-either' 42 | url='https://zabbix-dev.example.com/' 43 | validate_certs='' 44 | -------------- CUT HERE ----------------- 45 | 46 | By default, %s will use the [zabbix] paragraph, but if you use the 47 | -s/--section option, you can tell %s to use a different section. 48 | ''' % (sys.argv[0], sys.argv[0], sys.argv[0]) 49 | 50 | 51 | class Tool(object): 52 | 53 | '''Zabbix API CLI tool. All public functions should return a list 54 | which will be printed and summarized.''' 55 | _APPLICATION = 'application' 56 | 57 | _APPLICATIONID = 'applicationid' 58 | _APPLICATIONIDS = 'applicationids' 59 | _DESCRIPTION = 'description' 60 | _DISCOVERYRULE = 'discoveryrule' 61 | _ERROR = 'error' 62 | _EXTEND = 'extend' 63 | _FILTER = 'filter' 64 | _GRAPH = 'graph' 65 | _GRAPHID = 'graphid' 66 | _GRAPHIDS = 'graphids' 67 | _GROUPID = 'groupid' 68 | _GROUPIDS = 'groupids' 69 | _HEIGHT = 'height' 70 | _HOST = 'host' 71 | _HOSTGROUP = 'hostgroup' 72 | _HOSTID = 'hostid' 73 | _HOSTIDS = 'hostids' 74 | _HOSTS = 'hosts' 75 | _HSIZE = 'hsize' 76 | _ITEM = 'item' 77 | _ITEMID = 'itemid' 78 | _ITEMIDS = 'itemids' 79 | _ITEMPROTOTYPE = 'itemprototype' 80 | _KEY = 'key_' 81 | _LIMIT = 'limit' 82 | _NAME = 'name' 83 | _OUTPUT = 'output' 84 | _PROXY = 'proxy' 85 | _PROXYID = 'proxyid' 86 | _PROXYIDS = 'proxyids' 87 | _RESOURCEID = 'resourceid' 88 | _RESULT = 'result' 89 | _SCREENID = 'screenid' 90 | _SCREENIDS = 'screenids' 91 | _SEARCH = 'search' 92 | _SEARCHWILDCARDSENABLED = 'searchwildcardsenabled' 93 | _SELECTHOSTS = 'selectHosts' 94 | _SELECTTRIGGERS = 'selectTriggers' 95 | _SORTFIELD = 'sortfield' 96 | _SORTORDER = 'sortorder' 97 | _STATUS = 'status' 98 | _TEMPLATE = 'template' 99 | _TEMPLATEID = 'templateid' 100 | _TEMPLATEIDS = 'templateids' 101 | _TEMPLATES = 'templates' 102 | _TRIGGER = 'trigger' 103 | _TRIGGERIDS = 'triggerids' 104 | _VALUE_TYPE = 'value_type' 105 | _VSIZE = 'vsize' 106 | _WIDTH = 'width' 107 | 108 | _TRIGGERPROTOTYPE = 'triggerprototype' 109 | _USERMACRO = 'usermacro' 110 | 111 | def login(self, url, username, password, validate_certs, timeout=60, loglevel=logging.ERROR, **_): 112 | '''Connect to Zabbix server''' 113 | self.z = zabbix_api.ZabbixAPI(server=url, timeout=timeout, validate_certs=validate_certs) 114 | self.z.set_log_level(loglevel) 115 | self.z.login(user=username, password=password) 116 | return 117 | 118 | def print_hostgroup_cpu(self, hg): 119 | '''This mostly a demonstration function that will print the 120 | 5-minute load average for all of the hosts in a single 121 | hostgroup.''' 122 | item_name = 'system.cpu.load[,avg5]' 123 | 124 | for host in self.get_hostgroup_hosts(hg): 125 | hostname = host[self._HOST] 126 | print("Host:", hostname) 127 | print("Host-ID:", host[self._HOSTID]) 128 | 129 | item = self.z.item.get({ 130 | self._OUTPUT: self._EXTEND, 131 | self._HOSTIDS: host[self._HOSTID], 132 | self._FILTER: {self._KEY: item_name}}) 133 | if item: 134 | print(item[0]['lastvalue']) 135 | print("Item-ID:", item[0][self._ITEMID]) 136 | 137 | # Get history 138 | lastvalue = self.z.history.get({ 139 | 'history': item[0]['value_type'], 140 | self._ITEMIDS: item[0][self._ITEMID], 141 | self._OUTPUT: self._EXTEND, 142 | # Sort by timestamp from new to old 143 | self._SORTFIELD: 'clock', 144 | self._SORTORDER: 'DESC', 145 | self._LIMIT: 1, # Get only the first (=newest) entry 146 | }) 147 | 148 | # CAVEAT! The history.get function must be told which type the 149 | # values are (float, text, etc.). The item.value_type contains 150 | # the number that needs to be passed to history.get. 151 | if lastvalue: 152 | lastvalue = lastvalue[0]['value'] 153 | 154 | print("Last value:", lastvalue) 155 | 156 | else: 157 | print("No item....") 158 | 159 | print("---------------------------") 160 | return [] 161 | 162 | def __generic_getter(self, object_type, object_key, id_attribute, 163 | filter_attribute='name', output='extend', **params): 164 | '''This is a generic getter. Hopefully, it will reduce most of the other getters to one-liners. 165 | ''' 166 | logging.debug("Tool.__generic_getter: %s %s %s %s %s %s", object_type, object_key, 167 | id_attribute, filter_attribute, output, params) 168 | thing = getattr(self.z, object_type) 169 | params[self._OUTPUT] = output 170 | try: 171 | int(object_key) 172 | params[id_attribute] = int(object_key) 173 | except: 174 | # Really, this is mostly to handle item and itemprototypes neatly. 175 | if isinstance(filter_attribute, StringTypes): 176 | params[self._FILTER] = {filter_attribute: object_key} 177 | else: 178 | intermediate_results = [] 179 | for key_name in filter_attribute: 180 | params[self._FILTER] = {key_name: object_key} 181 | intermediate_results.extend(thing.get(params)) 182 | return intermediate_results 183 | 184 | return thing.get(params) 185 | 186 | def get_hostgroup(self, identifier): 187 | '''Look up a hostgroup by name or ID number. Returns a list with a single element''' 188 | return self.__generic_getter(self._HOSTGROUP, identifier, self._GROUPIDS) 189 | 190 | def get_host(self, identifier): 191 | '''Look up a host by name or ID number. Returns a list with a single element''' 192 | return self.__generic_getter(self._HOST, identifier, self._HOSTIDS) 193 | 194 | def delete_host(self, identifier): 195 | '''Look up a host by name or ID number, then delete it. Returns a list with a single element''' 196 | hostids = [x[self._HOSTID] for x in self.__generic_getter( 197 | self._HOST, identifier, self._HOSTIDS)] 198 | return self.z.host.delete(hostids) 199 | 200 | def search_host(self, identifier): 201 | '''Search for hosts by partial name. Returns a list.''' 202 | return self.z.host.get({self._SEARCH: {self._NAME: identifier}, self._OUTPUT: self._EXTEND}) 203 | 204 | def get_proxy(self, identifier): 205 | '''Look up a proxy by name or ID number. Returns a list with a single element''' 206 | return self.__generic_getter(self._PROXY, identifier, self._PROXYIDS, self._HOST) 207 | 208 | def get_proxy_interfaces(self, identifier): 209 | '''Look up a proxy by name or ID number. Returns a list with a single element''' 210 | return self.__generic_getter(self._PROXY, identifier, self._PROXYIDS, self._HOST, selectInterfaces=self._EXTEND) 211 | 212 | def get_hosts_assigned_to_proxy(self, identifier): 213 | '''List hosts assigned to a proxy. Takes the name of a proxy (e.g. YVR01).''' 214 | # First, the the proxyid as a list 215 | return self.__generic_getter(self._PROXY, identifier, None, self._HOST, 216 | selectHosts=self._EXTEND)[0][self._HOSTS] 217 | 218 | def get_item(self, identifier, *hostgroups): 219 | '''Look up an item by name or ID number. Returns a list with a single element''' 220 | if hostgroups: 221 | return self.__generic_getter(self._ITEM, identifier, self._ITEMIDS, [self._NAME, self._KEY], group=hostgroups[0]) 222 | else: 223 | return self.__generic_getter(self._ITEM, identifier, self._ITEMIDS, [self._NAME, self._KEY]) 224 | 225 | def get_item_host(self, identifier, host): 226 | '''Look up an item by name or ID number for a given host. Returns a list with a single element''' 227 | hosts = [x[self._HOSTID] for x in self.get_host(host)] 228 | return self.__generic_getter(self._ITEM, identifier, self._ITEMIDS, [self._NAME, self._KEY], hostids=hosts) 229 | 230 | def get_usermacros(self, identifier, filter_expression=None): 231 | '''Look up a host's macros by hostid. Returns a list''' 232 | if filter_expression: 233 | return self.__generic_getter(self._USERMACRO, identifier, self._HOSTIDS, 234 | filter={self._MACRO: filter_expression}) 235 | else: 236 | return self.__generic_getter(self._USERMACRO, identifier, self._HOSTIDS) 237 | 238 | def get_template(self, identifier): 239 | '''Look up a template by name or ID number. Returns a list with a single element''' 240 | return self.__generic_getter(self._TEMPLATE, identifier, self._TEMPLATEIDS) 241 | 242 | def get_application(self, identifier, limit=0): 243 | '''Look up an appliation by name or ID number. Returns a list.''' 244 | return self.__generic_getter(self._APPLICATION, identifier, self._APPLICATIONIDS, limit=limit) 245 | 246 | def get_discovery_rule(self, identifier, limit=0): 247 | '''Look up a discovery rule by name or ID number. Returns a list.''' 248 | return self.__generic_getter(self._DISCOVERYRULE, identifier, self._ITEMIDS, limit=limit) 249 | 250 | def get_trigger(self, identifier): 251 | '''Look up a trigger by name or ID number. Returns a list.''' 252 | return self.__generic_getter(self._TRIGGER, identifier, self._TRIGGERIDS) 253 | 254 | def get_graph(self, identifier): 255 | '''Look up a graph by name or ID number. Returns a list.''' 256 | return self.__generic_getter(self._GRAPH, identifier, self._GRAPHIDS) 257 | 258 | def get_graphs(self, match_name=''): 259 | '''List all graphs, fitering via the (optional) parameter''' 260 | hosts = list(set([x[self._HOSTID] for x in self.__generic_getter(self._HOST, '', self._HOSTIDS)])) 261 | return [{self._NAME: x[self._NAME], self._GRAPHID: x[self._GRAPHID]} for x in self.z.graph.get({self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND, self._SEARCH: {self._NAME: match_name}})] 262 | 263 | def delete_graph(self, identifier): 264 | '''Look up a graph by name or ID number, then delete it. Returns a list with a single element''' 265 | graphids = [x[self._GRAPHID] for x in self.__generic_getter( 266 | self._GRAPH, identifier, self._GRAPHIDS)] 267 | return self.z.graph.delete(graphids) 268 | 269 | def get_item_prototype(self, identifier, limit=0): 270 | '''Look up an itemprototype by name or ID number. Returns a list''' 271 | return self.__generic_getter(self._ITEMPROTOTYPE, identifier, self._ITEMIDS, [self._NAME, self._KEY], limit=limit) 272 | 273 | def get_trigger_prototype(self, identifier, limit=0): 274 | '''Look up an triggerprototype by name or ID number. Returns a list''' 275 | return self.__generic_getter(self._TRIGGERPROTOTYPE, identifier, self._TRIGGERIDS, limit=limit) 276 | 277 | def get_hostgroups(self, match_name=''): 278 | '''List all hostgroups, filtering via the (optional) parameter''' 279 | return self.z.hostgroup.get({self._SEARCH: {self._NAME: match_name}, self._OUTPUT: self._EXTEND}) 280 | 281 | def get_hostgroup_hosts(self, hg): 282 | '''Look up all of the hosts which belong to a given hostgroup.''' 283 | hg = [x[self._GROUPID] for x in self.get_hostgroup(hg)] 284 | if not hg: 285 | return hg 286 | hosts = [] 287 | for x in self.z.hostgroup.get({self._GROUPIDS: hg, 288 | self._SELECTHOSTS: self._EXTEND}): 289 | hosts.extend(x[self._HOSTS]) 290 | hosts.sort(key=operator.itemgetter(self._HOST)) 291 | return hosts 292 | 293 | def apply_template_to_hostgroup_members(self, template, hostgroup, doit=False): 294 | '''Given a template and a hostgroup, the template will be added to 295 | each host within the hostgroup, assuming a third parameter (which 296 | defaults to False) is set to some true value. Otherwise, it will 297 | simply tell you what it *would* have done.''' 298 | template = self.get_template(template) 299 | hostlist = self.get_hostgroup_hosts(hostgroup) 300 | result = [] 301 | if hostlist and doit: 302 | for host in hostlist: 303 | result.extend(self.link_template_to_host(template, host)) 304 | return result 305 | print('would have applied %s to the following %d hosts:' % 306 | (str(template), len(hostlist))) 307 | return hostlist 308 | 309 | def get_host_applications(self, hostname): 310 | '''List all of the applications represented on the given host.''' 311 | hosts = [x[self._HOSTID] for x in self.get_host(hostname)] 312 | return self.z.application.get({self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND}) 313 | 314 | def get_host_discovery_rule(self, hostname, discoveryrule): 315 | '''Get the actual discovery rule objects associated with a hostname 316 | and discovery rule name.''' 317 | hosts = [x[self._HOSTID] for x in self.get_host(hostname)] 318 | return self.z.discoveryrule.get({self._FILTER: {self._NAME: discoveryrule}, 319 | self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND}) 320 | 321 | def get_host_application_items(self, hostname, application): 322 | '''Given a hostname and an application name, return the list if 323 | items which match both.''' 324 | hosts = [x[self._HOSTID] for x in self. get_host(hostname)] 325 | apps = [x[self._APPLICATIONID] for x in self.z.application.get( 326 | {self._FILTER: {self._NAME: application}, self._HOSTIDS: hosts})] 327 | return self.z.item.get({self._HOSTIDS: hosts, self._APPLICATIONIDS: apps, self._OUTPUT: self._EXTEND}) 328 | 329 | def get_host_application_triggers(self, hostname, application): 330 | '''Given a hostname and an application name, return the list if 331 | triggers which match both.''' 332 | hosts = [x[self._HOSTID] for x in self.get_host(hostname)] 333 | apps = self.z.application.get( 334 | {self._FILTER: {self._NAME: application}, self._HOSTIDS: hosts}) 335 | return self.z.trigger.get({self._HOSTIDS: hosts, self._APPLICATIONIDS: apps}) 336 | 337 | def delete_host_application_items(self, hostname, application, doit=False): 338 | '''Given a hostname and an application name, delete all the items 339 | which match both if the final parameter is True (defaults to 340 | False).''' 341 | items = [x[self._ITEMID] 342 | for x in self.get_host_application_items(hostname, application)] 343 | if not items: 344 | return None 345 | if doit: 346 | return self.z.item.delete(items) 347 | print('would have deleted the following %d items:' % len(items)) 348 | pprint(items) 349 | return [] 350 | 351 | def delete_host_application_triggers(self, hostname, application, doit=False): 352 | '''Given a hostname and an application name, delete all the triggers 353 | which match both if the final parameter is True (defaults to 354 | False).''' 355 | triggers = [x[self._ITEMID] 356 | for x in self.get_host_application_triggers(hostname, application)] 357 | if not triggers: 358 | return None 359 | if doit: 360 | return self.z.trigger.delete(triggers) 361 | print('would have deleted the following %d triggers:' % len(triggers)) 362 | pprint(triggers) 363 | return [] 364 | 365 | def delete_host_application(self, hostname, application, doit=False): 366 | '''Given a hostname and an application name, delete all the 367 | triggers and items which match both if the final parameter is True 368 | (defaults to False).''' 369 | self.delete_host_application_items(hostname, application, doit) 370 | self.delete_host_application_triggers(hostname, application, doit) 371 | hosts = [x[self._HOSTID] for x in self.get_host(hostname)] 372 | apps = [x[self._APPLICATIONID] for x in self.z.application.get( 373 | {self._HOSTIDS: hosts, self._FILTER: {self._NAME: application}})] 374 | if not apps: 375 | return None 376 | if doit: 377 | return self.z.application.delete(apps) 378 | print('would have deleted the following %d applications:' % len(apps)) 379 | return apps 380 | 381 | def get_host_with_template_and_no_proxy(self, template): 382 | '''Return a list of hosts with the given template and proxy ID.''' 383 | template_objects = self.get_template(template) 384 | if not template_objects: 385 | print('No such template: %s' % template) 386 | return [] 387 | template_id = template_objects[0][self._TEMPLATEID] 388 | host_list = self.z.host.get( 389 | {self._TEMPLATEIDS: template_id, self._OUTPUT: self._EXTEND}) 390 | print(host_list[0]) 391 | return [x for x in host_list if x['proxy_hostid'] == u'0'] 392 | 393 | def assign_proxy_hosts(self, proxyname, *host_list): 394 | '''Assign hosts to a proxy. Takes the name of a proxy (e.g. YVR01) 395 | and a list of hostnames.''' 396 | if not host_list: 397 | parser.print_help( 398 | "no hostname(s) passed in for adding to %s" % proxyname) 399 | exit() 400 | # First, the the proxyid as a list 401 | proxy = self.z.proxy.get( 402 | {self._FILTER: {self._HOST: proxyname}, self._OUTPUT: self._EXTEND})[0][self._PROXYID] 403 | # Now update the hosts from the CLI 404 | results = [] 405 | for a in host_list: 406 | host = self.z.host.get( 407 | {self._FILTER: {self._NAME: a}, self._OUTPUT: self._EXTEND})[0] 408 | host['proxy_hostid'] = proxy 409 | results.append(self.z.host.update(host)) 410 | return results 411 | 412 | def switch_proxy(self, source, dest): 413 | '''Move all hosts currently assigned to proxy1 over to proxy2.. 414 | Takes the name of a source proxy and destination proxy (e.g. YVR01 415 | YVR02).''' 416 | # First, the the proxyid as a list 417 | dest = self.z.proxy.get( 418 | {self._FILTER: {self._HOST: dest}, self._OUTPUT: self._EXTEND})[0][self._PROXYID] 419 | # Now update the hosts from the CLI 420 | results = [] 421 | for host in self.get_hosts_assigned_to_proxy(source): 422 | host['proxy_hostid'] = dest 423 | results.append(self.z.host.update(host)) 424 | return results 425 | 426 | def get_item_history(self, i, limit=None): 427 | '''Get the history of data associated with one or more items. 428 | WARNING: if you pass in the name of an item, you'll probably crush the 429 | database as it tries to retrieve the history of every item with that name. 430 | ''' 431 | items = self.get_item(i) 432 | params = { 433 | 'history': items[0]['value_type'], 434 | self._ITEMIDS: [x[self._ITEMID] for x in items], 435 | self._OUTPUT: self._EXTEND, 436 | # Sort by timestamp from new to old 437 | self._SORTFIELD: 'clock', 438 | self._SORTORDER: 'DESC', 439 | } 440 | if limit: 441 | params[self._LIMIT] = limit 442 | return self.z.history.get(params) 443 | 444 | def get_host_items(self, hostname): 445 | '''Return all of the items for a single host.''' 446 | hosts = [x[self._HOSTID] for x in self.get_host(hostname)] 447 | return self.z.item.get({self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND}) 448 | 449 | def get_hostgroup_item(self, item, hostgroup): 450 | '''Return all of the items of the given name within a hostgroup''' 451 | hostgroup = self.get_hostgroup(hostgroup)[0][self._GROUPID] 452 | return self.z.item.get({self._FILTER: {self._NAME: item}, 453 | self._OUTPUT: self._EXTEND, 454 | self._GROUPIDS: [hostgroup, ]}) 455 | 456 | def get_host_graphs(self, hostname): 457 | '''Return all of the items for a single host.''' 458 | hosts = [x[self._HOSTID] for x in self.get_host(hostname)] 459 | return self.z.graph.get({self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND}) 460 | 461 | def get_items_with_template(self, item, template): 462 | '''Return matching items from hosts with the given template''' 463 | hosts = [x[self._HOSTID] for x in self.get_template_hosts(template)] 464 | return self.z.item.get({self._FILTER: {self._NAME: item}, self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND}) 465 | 466 | def mass_host_item_update(self, hostname): 467 | '''Special-purpose function written specifically to transition from the 468 | Mikoomi MySQL templates to a home-grown template without data loss. 469 | This will rename the item keys from the Mikoomi standard to the new 470 | one. NEVER USE THIS. 471 | ''' 472 | results = [] 473 | hosts = [x[self._HOSTID] for x in self.get_host(hostname)] 474 | for item in self.z.item.get({self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND, 'templated': 0}): 475 | if not 'mysql' == item[self._KEY][:5]: 476 | continue 477 | new_key = item[self._KEY].replace(' tables', '').replace('[', '.').replace(']', '').lower().replace( 478 | 'mysql50', 'mysql').replace('mysql51', 'mysql').replace('mysql55', 'mysql').replace( 479 | 'mysql56', 'mysql').replace(' ', '_').replace(',', '_').replace( 480 | '.setting.', '.var.').replace('mysql.status.state_', 'mysql.plist.state_') 481 | if new_key.count('.') < 2 and not new_key in [ 482 | 'mysql.auto_increment_max', 483 | 'mysql.errors', 484 | 'mysql.errors_error', 485 | 'mysql.errors_error_warn', 486 | 'mysql.errors_warn', 487 | ]: 488 | new_key = new_key.replace('mysql.', 'mysql.status.') 489 | if item[self._KEY] != new_key: 490 | try: 491 | results.append( 492 | self.z.item.update({self._ITEMID: item[self._ITEMID], self._KEY: new_key.lower()})) 493 | except: 494 | results.append("duplicate key: " + new_key) 495 | return results 496 | 497 | def mass_rename_host_items(self, host, match_re, replacement): 498 | '''Look up a host by name or ID number. Rename all items that match the RE by substituting the replacement.''' 499 | replacer = re.compile(match_re) 500 | results = [] 501 | for item in self.get_host_items(host): 502 | if item.has_key(self._NAME): 503 | new_name = replacer.sub(replacement, item[self._NAME]) 504 | if new_name != item[self._NAME]: 505 | results.append( 506 | self.z.item.update({self._ITEMID: item[self._ITEMID], self._NAME: new_name})) 507 | new_name = replacer.sub(replacement, item[self._DESCRIPTION]) 508 | if new_name != item[self._DESCRIPTION]: 509 | results.append(self.z.item.update( 510 | {self._ITEMID: item[self._ITEMID], self._DESCRIPTION: new_name})) 511 | return results 512 | 513 | def delete_items_with_template(self, item, template, doit=False): 514 | '''Delete matching items from hosts with the given template''' 515 | hosts = [x[self._HOSTID] for x in self.get_template_hosts(template)] 516 | items = self.z.item.get( 517 | {self._FILTER: {self._NAME: item}, self._HOSTIDS: hosts, self._OUTPUT: self._EXTEND}) 518 | if not items: 519 | return [] 520 | itemids = [x[self._ITEMID] for x in items] 521 | if not itemids: 522 | return [] 523 | if doit: 524 | print(self.z.item.delete(itemids)) 525 | return [] 526 | print('would have deleted the following %d items:' % len(items)) 527 | return items 528 | 529 | def delete_host_item(self, hostname, item, doit=False): 530 | '''From a given host, Delete the item or items that matched the passed in value (ID or name).''' 531 | hosts = [x[self._HOSTID] for x in self.get_host(hostname)] 532 | items = [x[self._ITEMID] 533 | for x in self.get_item(item) if x[self._HOSTID] in hosts] 534 | if not items: 535 | return None 536 | if doit: 537 | return self.z.item.delete(items) 538 | print('would have deleted the following %d items:' % len(items)) 539 | return items 540 | 541 | def delete_items(self, item, doit=False): 542 | '''Delete the item or items that match the passed in value (ID or name).''' 543 | items = [x[self._ITEMID] for x in self.get_item(item)] 544 | if not items: 545 | return [] 546 | if doit: 547 | return self.z.item.delete(items) 548 | print('would have deleted the following %d items:' % len(items)) 549 | pprint(items) 550 | return items 551 | 552 | def get_unsupported_item_hosts(self, item, error_string): 553 | '''One-off function for looking for a particular item. Do not use.''' 554 | all_items = self.z.item.get({self._FILTER: {self._NAME: item, self._ERROR: error_string}, 555 | self._OUTPUT: self._EXTEND}) 556 | for a in all_items: 557 | print(a[self._NAME], a[self._ERROR], a['key_']) 558 | items = [x[self._ITEMID] for x in all_items] 559 | return self.z.host.get({self._ITEMIDS: items, self._OUTPUT: self._EXTEND}) 560 | 561 | def get_template_items(self, template): 562 | '''Look up a template by name or ID number. Returns a list of items belonging to 563 | that template (including linked items)''' 564 | template_objects = [x[self._TEMPLATEID] 565 | for x in self.get_template(template)] 566 | return self.z.item.get({self._HOSTIDS: template_objects, self._OUTPUT: self._EXTEND}) 567 | 568 | def mass_rename_template_items(self, template, match_re, replacement): 569 | '''Look up a template by name or ID number. Rename all items that match the RE 570 | by substituting the replacement.''' 571 | replacer = re.compile(match_re) 572 | results = [] 573 | for item in self.get_template_items(template): 574 | if item.has_key(self._NAME): 575 | new_name = replacer.sub(replacement, item[self._NAME]) 576 | if new_name != item[self._NAME]: 577 | results.append( 578 | self.z.item.update({self._ITEMID: item[self._ITEMID], self._NAME: new_name})) 579 | new_name = replacer.sub(replacement, item[self._DESCRIPTION]) 580 | if new_name != item[self._DESCRIPTION]: 581 | results.append(self.z.item.update( 582 | {self._ITEMID: item[self._ITEMID], self._DESCRIPTION: new_name})) 583 | return results 584 | 585 | def mass_rename_template_item_keys(self, template, match_re, replacement): 586 | '''Look up a template by name or ID number. Change all the keys that match 587 | the RE by substituting the replacement.''' 588 | replacer = re.compile(match_re) 589 | results = [] 590 | for item in self.get_template_items(template): 591 | new_key = replacer.sub(replacement, item[self._KEY]) 592 | if new_key != item[self._KEY]: 593 | results.append( 594 | self.z.item.update({self._ITEMID: item[self._ITEMID], self._KEY: new_key})) 595 | return results 596 | 597 | def mass_template_item_update(self, template, match_re): 598 | '''NEVER USE THIS.''' 599 | matcher = re.compile(match_re) 600 | results = [] 601 | for item in self.get_template_items(template): 602 | if matcher.search(item[self._KEY]): 603 | new_key = item[self._KEY].replace('[', '.').replace(']', '') 604 | results.append( 605 | self.z.item.update({self._ITEMID: item[self._ITEMID], self._KEY: new_key.lower()})) 606 | return results 607 | 608 | def mass_template_item_replace_attribute(self, template, attribute, old_value, new_value, filter_re=None): 609 | '''USING THIS CAN DESTROY YOUR TEMPLATE. 610 | 611 | Change ATTRIBUTE on all (or filtered) items from OLD_VALUE to NEW_VALUE. 612 | :param template: template to update (name or id) 613 | :param attribute: attribute of each item to update 614 | :param old_value: current value of the attribute (for limiting purposes) 615 | :param new_value: new value of the attribute 616 | :param filter_re: (optional) only update items with name/description matching this. 617 | ''' 618 | results = [] 619 | if filter_re: 620 | filter_re = re.compile(filter_re) 621 | for item in self.get_template_items(template): 622 | if filter_re: 623 | if not filter_re.search(item.get(self._NAME, '')) and not filter_re.search(item.get(self._DESCRIPTION, '')): 624 | continue 625 | if item[attribute] == old_value: 626 | results.append( 627 | self.z.item.update({self._ITEMID: item[self._ITEMID], attribute: new_value})) 628 | return results 629 | 630 | def get_template_hosts(self, t): 631 | '''Look up all the hosts that use the given template.''' 632 | template = [x[self._TEMPLATEID] for x in self.get_template(t)] 633 | return self.z.host.get({self._TEMPLATEIDS: template, self._OUTPUT: self._EXTEND}) 634 | 635 | def search_template(self, term, **kwargs): 636 | '''Search for templates matching the given fragment. Returns a list''' 637 | params = {self._SEARCH: {self._NAME: term, self._SEARCHWILDCARDSENABLED: True}, 638 | self._OUTPUT: self._EXTEND} 639 | params.update(kwargs) 640 | data = self.z.template.get(params) 641 | return data 642 | 643 | def link_template_to_host(self, template, host): 644 | '''Link the given template to the given host. Takes names or IDs.''' 645 | template = self.get_template(template) 646 | host = self.get_host(host) 647 | return self.z.template.massadd({self._TEMPLATES: template, self._HOSTS: host}) 648 | 649 | def link_template_parent_to_child(self, parent, child): 650 | '''Makes the child template a linked template of the parent template.''' 651 | pid = self.get_template(parent) 652 | if not pid: 653 | raise Exception('No such template: %s' % pid) 654 | pid = pid[0] 655 | cid = self.get_template(child) 656 | if not cid: 657 | raise Exception('No such template: %s' % cid) 658 | pid[self._TEMPLATES] = cid 659 | pprint(pid) 660 | return self.z.template.update(pid) 661 | 662 | def get_template_triggers_without_urls(self, group): 663 | '''Lists Triggers with an empty URL field. Pass in the name or ID of a Templates hostgroup''' 664 | tmpl_hg = self.get_hostgroup(group)[0]['groupid'] 665 | 666 | output = [] 667 | tmpls = self.z.template.get( 668 | {self._GROUPIDS: tmpl_hg, self._OUTPUT: self._EXTEND, self._SELECTTRIGGERS: self._EXTEND}) 669 | for tmpl in tmpls: 670 | for trig in tmpl['triggers']: 671 | if trig['url'] == '': 672 | output.append(tmpl['name'] + ' - ' + trig['description']) 673 | return output 674 | 675 | def add_item_to_template(self, h, **kwargs): 676 | '''Create a new item. Takes a template and dictionary of values.''' 677 | h = self.get_template(h)[0] 678 | kwargs[self._HOSTID] = h[self._TEMPLATEID] 679 | return self.z.item.create(kwargs) 680 | 681 | def get_application_items(self, app, limit=10): 682 | '''Look up all the items which are part of the given application, limit 10 by default.''' 683 | appids = [x[self._APPLICATIONID] for x in self.get_application(app)] 684 | return self.z.item.get({self._APPLICATIONIDS: appids, self._OUTPUT: self._EXTEND, self._LIMIT: limit}) 685 | 686 | def delete_application_from_hosts(self, application, limit=1, doit=False): 687 | '''Delete an application from some number of hosts, limited to one 688 | by default, and also limited to dry-run by default.''' 689 | apps = self.get_application(application) 690 | limit = int(limit) 691 | count = 0 692 | resultset = [] 693 | while apps and (count < limit): 694 | a = apps.pop(0) 695 | h = self.get_host(a[self._HOSTID]) 696 | if not h: 697 | continue # It was a template 698 | count += 1 699 | resultset.append( 700 | self.delete_host_application(h[0][self._NAME], application, doit)) 701 | return resultset 702 | 703 | def delete_discovery_rule(self, hg, doit=False): 704 | '''Delete a discovery rule from all hosts/templates. Defaults to dry-run only.''' 705 | try: 706 | int(hg) 707 | rules = [hg, ] 708 | except: 709 | rules = [x[self._ITEMID] for x in self.get_discovery_rule(hg)] 710 | if not rules: 711 | return None 712 | if doit: 713 | return self.z.discoveryrule.delete(rules) 714 | print('would have deleted the following %d rules:' % len(rules)) 715 | return rules 716 | 717 | def delete_discovery_rule_hosts(self, hg, limit=1, doit=False): 718 | '''Deletes the indicated discovery rule from hosts, leaving the template untouched.''' 719 | rules = self.get_discovery_rule(hg) 720 | limit = int(limit) 721 | count = 0 722 | resultset = [] 723 | while rules and (count < limit): 724 | a = rules.pop(0) 725 | h = self.get_host(a[self._HOSTID]) 726 | if not h: 727 | continue # It was a template 728 | count += 1 729 | resultset.append(self.delete_discovery_rule(a[self._ITEMID], doit)) 730 | return resultset 731 | 732 | def get_trigger_hosts(self, trigger): 733 | '''List all the hosts with the given trigger.''' 734 | triggers = self.get_trigger(trigger) 735 | return self.z.trigger.get({self._TRIGGERIDS: triggers, self._OUTPUT: self._EXTEND}) 736 | 737 | def delete_trigger(self, trigger, doit=False): 738 | '''Delete the given trigger. Defaults to dry-run only.''' 739 | try: 740 | int(trigger) 741 | triggers = [trigger, ] 742 | except: 743 | triggers = [x[self._ITEMID] for x in self.get_trigger(trigger)] 744 | if not triggers: 745 | return None 746 | if doit: 747 | return self.z.trigger.delete(triggers) 748 | print('would have deleted the following %d triggers:' % len(triggers)) 749 | return triggers 750 | 751 | def find_trigger(self, term, limit=10): 752 | '''Search trigger descriptions for a given string.''' 753 | return self.z.trigger.get({self._SEARCH: {self._DESCRIPTION: term, self._SEARCHWILDCARDSENABLED: True}, 754 | self._OUTPUT: self._EXTEND, self._LIMIT: limit}) 755 | 756 | def special_lld_in_template(self, template_name, identifier, lld_name): 757 | '''Add a new LLD rule with the requisite, e.g. 758 | 759 | zabbix_tool special_lld_in_template test-template-discovery-geek bletchley 'Discovery - Unlikely Mountpoints' 760 | 761 | (with the current code) will create a discovery rule in 762 | test-template-discovery-geek that will use BLETCHLEY as the 763 | differentiator in the key, with 8 items and 4 triggers. Assuming, 764 | of course, that it all completes successfully. There is a great 765 | deal of contention on the `ids` table in pre-2.2. 766 | :param template_name: Name of the template to be modified 767 | :param key: unique value used to differentiate this LLD from others 768 | :param lld_name: Name for the LLD to be added 769 | :returns: (lld object, itemprototypes object list, triggerprototypes object list) 770 | ''' 771 | template = self.get_template(template_name)[0] 772 | lld = self.z.discoveryrule.create({ 773 | u'key_': "vfs.fs.discovery_llnw[%s]" % identifier, 774 | self._NAME: lld_name, 775 | self._HOSTID: template[self._HOSTID], 776 | 'type': "0", 777 | 'delay': 21600, 778 | 'filter': r'{#%s}:^\/d\d+' % identifier.upper(), 779 | 'status': '1', # 1 is disabled 780 | 'interfaceid': "0", 781 | }) 782 | lld = self.get_discovery_rule(lld[self._ITEMIDS][0], 1)[0] 783 | 784 | itemprototypes = self.z.itemprototype.create([ 785 | {'ruleid': lld[self._ITEMID], 786 | 'delay': '1800', 787 | 'hostid': lld[self._HOSTID], 788 | 'interfaceid': "0", 789 | 'key_': 'vfs.fs.size[{#%s},free]' % identifier.upper(), 790 | 'name': 'Free disk space on {#%s}' % identifier.upper(), 791 | 'type': '0', 792 | 'value_type': '3', 793 | 'history': '7', 794 | 'units': 'B'}, 795 | {'ruleid': lld[self._ITEMID], 796 | 'hostid': lld[self._HOSTID], 797 | 'interfaceid': "0", 798 | 'type': '0', 799 | 'history': '7', 800 | 'delay': '1800', 801 | 'key_': 'vfs.fs.size[{#%s},pfree]' % identifier.upper(), 802 | 'name': 'Free disk space on {#%s} in %%' % identifier.upper(), 803 | 'units': '%', 804 | 'value_type': '0'}, 805 | {'ruleid': lld[self._ITEMID], 806 | 'hostid': lld[self._HOSTID], 807 | 'interfaceid': "0", 808 | 'type': '0', 809 | 'history': '7', 810 | 'delay': '900', 811 | 'key_': 'vfs.fs.inode[{#%s},pfree]' % identifier.upper(), 812 | 'name': 'Free number of inodes on {#%s} in %%' % identifier.upper(), 813 | 'units': '%', 814 | 'value_type': '0'}, 815 | {'ruleid': lld[self._ITEMID], 816 | 'hostid': lld[self._HOSTID], 817 | 'interfaceid': "0", 818 | 'type': '0', 819 | 'history': '7', 820 | 'delay': '600', 821 | 'key_': 'mountpoint[{#%s}]' % identifier.upper(), 822 | 'name': 'Mount point status for {#%s}' % identifier.upper(), 823 | 'units': '', 824 | 'value_type': '3'}, 825 | {'ruleid': lld[self._ITEMID], 826 | 'hostid': lld[self._HOSTID], 827 | 'interfaceid': "0", 828 | 'type': '0', 829 | 'history': '7', 830 | 'delay': '21600', 831 | 'key_': 'vfs.fs.size[{#%s},total]' % identifier.upper(), 832 | 'name': 'Total disk space on {#%s}' % identifier.upper(), 833 | 'units': 'B', 834 | 'value_type': '3'}, 835 | {'ruleid': lld[self._ITEMID], 836 | 'hostid': lld[self._HOSTID], 837 | 'interfaceid': "0", 838 | 'type': '0', 839 | 'history': '7', 840 | 'delay': '21600', 841 | 'key_': 'vfs.fs.inode[{#%s},total]' % identifier.upper(), 842 | 'name': 'Total number of inodes on {#%s}' % identifier.upper(), 843 | 'units': '', 844 | 'value_type': '3'}, 845 | {'ruleid': lld[self._ITEMID], 846 | 'hostid': lld[self._HOSTID], 847 | 'interfaceid': "0", 848 | 'type': '0', 849 | 'history': '7', 850 | 'delay': '1800', 851 | 'key_': 'vfs.fs.size[{#%s},used]' % identifier.upper(), 852 | 'name': 'Used disk space on {#%s}' % identifier.upper(), 853 | 'units': 'B', 854 | 'value_type': '3'}, 855 | {'ruleid': lld[self._ITEMID], 856 | 'hostid': lld[self._HOSTID], 857 | 'interfaceid': "0", 858 | 'type': '0', 859 | 'history': '7', 860 | 'delay': '600', 861 | 'key_': 'vfs.fs.size[{#%s},pused]' % identifier.upper(), 862 | 'name': 'Used disk space on {#%s} in %%' % identifier.upper(), 863 | 'units': '%', 864 | 'value_type': '0'} 865 | ]) 866 | 867 | triggerprototypes = self.z.triggerprototype.create([ 868 | {'description': 'directory {#%s} does not exist on {HOSTNAME}' % identifier.upper(), 869 | 'expression': '{%s:mountpoint[{#%s}].last(0)}=2' % (template[self._HOST], identifier.upper()), 870 | 'priority': 1}, 871 | {'description': 'directory {#%s} is not a moint point on {HOSTNAME}' % identifier.upper(), 872 | 'expression': '{%s:mountpoint[{#%s}].last(0)}=1' % (template[self._HOST], identifier.upper()), 873 | 'priority': 1}, 874 | {'description': 'Disk utilization on {#%s} above 98%% on {HOSTNAME}' % identifier.upper(), 875 | 'expression': '{%s:vfs.fs.size[{#%s},pused].last(0)}>98' % (template[self._HOST], identifier.upper()), 876 | 'priority': 1}, 877 | {'description': 'Free Inodes on {#%s} below 10%% on {HOSTNAME}' % identifier.upper(), 878 | 'expression': '{%s:vfs.fs.inode[{#%s},pfree].last(0)}<10' % (template[self._HOST], identifier.upper()), 879 | 'priority': 1}, # Information 880 | ]) 881 | 882 | return (lld, itemprototypes, triggerprototypes) 883 | 884 | 885 | class MyParser(OptionParser): 886 | 887 | '''Standard parser with a non-re-formatted epilog 888 | ''' 889 | 890 | def format_epilog(self, formatter): 891 | return self.epilog 892 | 893 | 894 | def parse_config_file(option_set): 895 | '''Parse the config file out here. 896 | ''' 897 | config = ConfigParser.ConfigParser() 898 | try: 899 | config.read(option_set[CONFIG]) 900 | except Exception, e: 901 | raise Exception('Unable to parse the configuration file(%s): %s\n\n' % 902 | (option_set[CONFIG], e)) 903 | if not option_set[SECTION] in config.sections(): 904 | raise SystemExit( 905 | 'No [%s] section in the configuration file' % option_set[SECTION]) 906 | 907 | for (option_key, option_value) in config.items(option_set[SECTION], 1): 908 | option_set[option_key] = option_value.strip('"').strip("'") 909 | 910 | if __name__ == '__main__': 911 | tool = Tool() 912 | # Handle command-line options 913 | parser = MyParser(epilog=render_doc(tool, '%s') + CONFIG_HELP) 914 | parser.add_option("-c", "--config", dest="config", default=os.environ['HOME'] + '/.zabbix', 915 | help="CONFIG file", metavar="CONFIG") 916 | parser.add_option("-s", "--section", dest="section", default='zabbix', 917 | help="Section of config file to use file", metavar="SECTION") 918 | parser.add_option("-l", "--loglevel", 919 | dest=LOGLEVEL, default=logging.ERROR, type="int", 920 | help="Set logging level (0-50), defaults to 40 (only errors)") 921 | parser.add_option("--filter", 922 | dest=FILTER, action="append", 923 | help="Only print the named field of the returned items") 924 | 925 | (options, args) = parser.parse_args() 926 | options = vars(options) 927 | parse_config_file(options) 928 | tool.login(**options) # Cheating! 929 | if not args: 930 | parser.print_help() 931 | exit() 932 | func = args.pop(0) 933 | try: 934 | cmd = getattr(tool, func) 935 | except: 936 | print('No such function: %s' % func) 937 | parser.print_help() 938 | exit() 939 | 940 | try: 941 | kwargs = {} 942 | func_args = [] 943 | for arg in args: 944 | if '=' in arg: 945 | key, value = arg.split('=', 1) 946 | kwargs[key] = value 947 | else: 948 | func_args.append(arg) 949 | func_args = tuple(func_args) 950 | function_result = cmd(*func_args, **kwargs) 951 | except TypeError: 952 | print(render_doc(cmd)) 953 | exit() 954 | if options[FILTER]: 955 | for name in function_result: 956 | filtered_result = ' '.join( 957 | [name.get(xxxx, None) for xxxx in options[FILTER]]) 958 | if filtered_result: 959 | print(filtered_result) 960 | else: 961 | print('********* Results ********') 962 | pprint(function_result) 963 | print(len(function_result)) 964 | --------------------------------------------------------------------------------