# -*- coding: utf-8 -*- # # pynag - Python Nagios plug-in and configuration environment # Copyright (C) 2011 Pall Sigurdsson # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ This module provides a high level Object-Oriented wrapper around pynag.Parsers. Example: >>> from pynag.Model import Service, Host >>> >>> all_services = Service.objects.all >>> my_service = all_services[0] >>> print(my_service.host_name) # doctest: +SKIP localhost >>> >>> example_host = Host.objects.filter(host_name="host.example.com") >>> canadian_hosts = Host.objects.filter(host_name__endswith=".ca") >>> >>> for i in canadian_hosts: ... i.alias = "this host is located in Canada" ... i.save() # doctest: +SKIP """ from __future__ import absolute_import import os import re import subprocess import time import getpass from pynag.Model import macros from pynag.Model import all_attributes from pynag.Utils import paths from pynag.Utils import bytes2str import pynag.Control.Command import pynag.errors import pynag.Parsers.config_parser import pynag.Parsers.status_dat import pynag.Utils import six from six.moves import filter from six.moves import map # Path To Nagios configuration file cfg_file = None # '/etc/nagios/nagios.cfg' # Were new objects are written by default pynag_directory = None # This is the config parser that we use internally, if cfg_file is changed, then config # will be recreated whenever a parse is called. config = pynag.Parsers.config_parser.Config(cfg_file=cfg_file) #: eventhandlers -- A list of Model.EventHandlers object. # Event handler is responsible for passing notification whenever something # important happens in the model. # # For example FileLogger class is an event handler responsible for logging to # file whenever something has been written. eventhandlers = [] try: from collections import defaultdict except ImportError: from pynag.Utils import defaultdict # Default value returned when a macro cannot be found _UNRESOLVED_MACRO = '' # We know that a macro is a custom variable macro if the name # of the macro starts with this prefix: _CUSTOM_VARIABLE_PREFIX = '_' class ModelError(pynag.errors.PynagError): """Base class for errors in this module.""" class InvalidMacro(ModelError): """Raised when a method is inputted with an invalid macro.""" class ObjectRelations(object): """ Static container for objects and their respective neighbours """ # c['contact_name'] = [host1.get_id(),host2.get_id()] contact_hosts = defaultdict(set) # c['contact_name'] = ['contactgroup1','contactgroup2'] contact_contactgroups = defaultdict(set) # c['contact_name'] = ['service1.get_id()','service2.get_id()'] contact_services = defaultdict(set) # c['contactgroup_name'] = ['contact1.contact_name','contact2.contact_name','contact3.contact_name'] contactgroup_contacts = defaultdict(set) # c['contactgroup_name'] = ['contactgroup1','contactgroup2','contactgroup3'] contactgroup_contactgroups = defaultdict(set) # c['contactgroup_name'] = ['host1.get_id()', 'host2.get_id()'] contactgroup_hosts = defaultdict(set) # c['contactgroup_name'] = ['service1.get_id()', 'service2.get_id()'] contactgroup_services = defaultdict(set) # c['host_name'] = [service1.id, service2.id,service3.id] host_services = defaultdict(set) # c['host_name'] = ['contactgroup1', 'contactgroup2'] host_contact_groups = defaultdict(set) # c['host_name'] = ['contact1','contact2'] host_contacts = defaultdict(set) # c['host_name'] = '['hostgroup1.hostgroup_name','hostgroup2.hostgroup_name'] host_hostgroups = defaultdict(set) # c['host_name'] = '['service1.get_id()','service2.get_id()'] host_services = defaultdict(set) # c['hostgroup_name'] = ['host_name1','host_name2'] hostgroup_hosts = defaultdict(set) # c['hostgroup_name'] = ['hostgroup1','hostgroup2'] hostgroup_hostgroups = defaultdict(set) # c['hostgroup_name'] = ['service1.get_id()','service2.get_id()'] hostgroup_services = defaultdict(set) # c['service.get_id()'] = '['contactgroup_name1','contactgroup_name2'] service_contact_groups = defaultdict(set) # c['service.get_id()'] = '['contact_name1','contact_name2'] service_contacts = defaultdict(set) # c['service.get_id()'] = '['hostgroup_name1','hostgroup_name2'] service_hostgroups = defaultdict(set) # c['service.get_id()'] = '['servicegroup_name1','servicegroup_name2'] service_servicegroups = defaultdict(set) # c['service.get_id()'] = ['host_name1','host_name2'] service_hosts = defaultdict(set) # c['servicegroup_name'] = ['service1.get_id()', ['service2.get_id()'] servicegroup_services = defaultdict(set) # c['servicegroup_name'] = ['servicegroup1','servicegroup2','servicegroup3'] servicegroup_servicegroups = defaultdict(set) # c[command_name] = '['service.get_id()','service.get_id()'] command_service = defaultdict(set) # c[command_name] = '['host_name1','host_name2'] command_host = defaultdict(set) # use['host']['host_name1'] = ['host_name2','host_name3'] # use['contact']['contact_name1'] = ['contact_name2','contact_name3'] _defaultdict_set = lambda: defaultdict(set) use = defaultdict(_defaultdict_set) # contactgroup_subgroups['contactgroup_name'] = ['group1_name','group2_name'] contactgroup_subgroups = defaultdict(set) # hostgroup_subgroups['hostgroup_name'] = ['group1_name','group2_name'] hostgroup_subgroups = defaultdict(set) # servicegroup_subgroups['servicegroup_name'] = ['servicegroup1_name','servicegroup2_name'] servicegroup_subgroups = defaultdict(set) # servicegroup_members['servicegroup_name'] = ['service1_shortname','service2_shortname'] servicegroup_members = defaultdict(set) @staticmethod def reset(): """ Runs clear() on every member attribute in ObjectRelations """ for k, v in ObjectRelations.__dict__.items(): if isinstance(v, defaultdict): v.clear() @staticmethod def _get_subgroups(group_name, dictname): """ Helper function that lets you get all sub-group members of a particular group For example this call: _get_all_group-members('admins', ObjectRelations.contactgroup_contacgroups) Will return recursively go through contactgroup_members of 'admins' and return a list of all subgroups """ subgroups = dictname[group_name].copy() checked_groups = set() while len(subgroups) > 0: i = subgroups.pop() if i not in checked_groups: checked_groups.add(i) subgroups.update(dictname[i]) return checked_groups @staticmethod def resolve_regex(): """ If any object relations are a regular expression, then expand them into a full list """ self = ObjectRelations expand = self._expand_regex shortnames = ObjectFetcher._cached_shortnames host_names = list(shortnames['host'].keys()) hostgroup_names = list(shortnames['hostgroup'].keys()) expand(self.hostgroup_hosts, host_names) expand(self.host_hostgroups, hostgroup_names) expand(self.service_hostgroups, hostgroup_names) @staticmethod def _expand_regex(dictionary, full_list): """ Replaces any regex found in dictionary.values() or dictionary.keys() **INPLACE** Example with ObjectRelations.hostgroup_hosts >>> hostnames = set(['localhost','remotehost', 'not_included']) >>> hostgroup_hosts = {'hostgroup1': set([ '.*host' ]), 'hostgroup2' : set(['localhost','remotehost']), } >>> ObjectRelations._expand_regex(dictionary=hostgroup_hosts, full_list=hostnames) >>> hostgroup_hosts['hostgroup1'] == hostgroup_hosts['hostgroup2'] True >>> hostgroup_hosts['hostgroup1'] == set(['localhost','remotehost']) True """ if config.get_cfg_value('use_regexp_matching') == "0": return if config.get_cfg_value('use_true_regexp_matching') == "1": always_use_regex = True else: always_use_regex = False is_regex = lambda x: x is not None and (always_use_regex or '*' in x or '?' in x or '+' in x or '\.' in x) # Strip None entries from full_list full_list = [x for x in full_list if x is not None] # Strip None entries from dictionary # If any keys in the dictionary are regex, expand it, i.e.: # if dictionary = { '.*':[1], 'localhost':[],'remotehost':[] } # then do: # dictionary['localhost'].update( [1] ) # dictionary['remotehost'].update( [1] ) # del dictionary['.*'] regex_keys = list(filter(is_regex, list(dictionary.keys()))) for key in regex_keys: if key == '*': expanded_list = regex_keys else: regex = re.compile(key) expanded_list = list(filter(regex.search, regex_keys)) for i in expanded_list: if i == key: # No need to react if regex resolved to itself continue dictionary[i].update(dictionary[key]) if key not in expanded_list: # Only remove the regex if it did not resolve to itself. del dictionary[key] # If dictionary.values() has any regex, expand it like so: # full_list = [1,2,3] # if dictionary = {'localhost':[ '.*' ]} # then change it so that: # dictionary = { 'localhost':[1,2,3] } for key, value in dictionary.items(): regex_members = list(filter(is_regex, value)) if len(regex_members) == 0: continue # no changes need to be made if isinstance(value, list): value = set(value) #new_value = value.copy() for i in regex_members: if i == '*': # Nagios allows * instead of a valid regex, lets adjust to that expanded_list = full_list else: regex = re.compile(i) expanded_list = list(filter(regex.search, full_list)) value.remove(i) value.update(expanded_list) #dictionary[key] = new_value @staticmethod def resolve_contactgroups(): """ Update all contactgroup relations to take into account contactgroup.contactgroup_members """ groups = list(ObjectRelations.contactgroup_contactgroups.keys()) for group in groups: subgroups = ObjectRelations._get_subgroups(group, ObjectRelations.contactgroup_contactgroups) ObjectRelations.contactgroup_subgroups[group] = subgroups # Loop through every subgroup and apply its attributes to ours for subgroup in subgroups: for i in ObjectRelations.contactgroup_contacts[subgroup]: ObjectRelations.contact_contactgroups[i].add(group) ObjectRelations.contactgroup_contacts[group].update(ObjectRelations.contactgroup_contacts[subgroup]) @staticmethod def resolve_hostgroups(): """ Update all hostgroup relations to take into account hostgroup.hostgroup_members """ groups = list(ObjectRelations.hostgroup_hostgroups.keys()) for group in groups: subgroups = ObjectRelations._get_subgroups(group, ObjectRelations.hostgroup_hostgroups) ObjectRelations.hostgroup_subgroups[group] = subgroups # Loop through every subgroup and apply its attributes to ours for subgroup in subgroups: for i in ObjectRelations.hostgroup_hosts[subgroup]: ObjectRelations.host_hostgroups[i].add(group) ObjectRelations.hostgroup_hosts[group].update(ObjectRelations.hostgroup_hosts[subgroup]) @staticmethod def resolve_servicegroups(): """ Update all servicegroup relations to take into account servicegroup.servicegroup_members """ # Before we do anything, resolve servicegroup.members into actual services ObjectRelations._resolve_servicegroup_members() groups = list(ObjectRelations.servicegroup_servicegroups.keys()) for group in groups: subgroups = ObjectRelations._get_subgroups(group, ObjectRelations.servicegroup_servicegroups) ObjectRelations.servicegroup_subgroups[group] = subgroups # Loop through every subgroup and apply its attributes to ours for subgroup in subgroups: for i in ObjectRelations.servicegroup_services[subgroup]: ObjectRelations.service_servicegroups[i].add(group) ObjectRelations.servicegroup_services[group].update(ObjectRelations.servicegroup_services[subgroup]) @staticmethod def _resolve_servicegroup_members(): """ Iterates through all servicegroup.members, and updates servicegroup_services and service_servicegroups. This happens post-parse (instead of inside Servicegroup._do_relations() because when parsing Servicegroup you only know host_name/service_description of the service that belongs to the group. However the relations we update work on Service.get_id() because not all services that belong to servicegroups have a valid host_name/service_description pair (templates) """ for servicegroup, members in ObjectRelations.servicegroup_members.items(): for shortname in members: try: service = Service.objects.get_by_shortname(shortname, cache_only=True) service_id = service.get_id() ObjectRelations.servicegroup_services[servicegroup].add(service_id) ObjectRelations.service_servicegroups[service_id].add(servicegroup) except Exception: # If there is an error looking up any service, we ignore it and # don't display it in a list of related services pass class ObjectFetcher(object): """ This class is a wrapper around pynag.Parsers.config. Is responsible for fetching dict objects from config.data and turning into high ObjectDefinition objects Internal variables: * _cached_objects = List of every ObjectDefinition * _cached_id[o.get_id()] = o * _cached_shortnames[o.object_type][o.get_shortname()] = o * _cached_names[o.object_type][o.name] = o * _cached_object_type[o.object_type].append( o ) """ _cached_objects = [] _cached_ids = {} _cached_shortnames = defaultdict(dict) _cached_names = defaultdict(dict) _cached_object_type = defaultdict(list) _cache_only = False def __init__(self, object_type): self.object_type = object_type @pynag.Utils.synchronized(pynag.Utils.rlock) def get_all(self, cache_only=False): """ Return all object definitions of specified type""" if not cache_only and self.needs_reload(): self.reload_cache() if self.object_type is not None: return ObjectFetcher._cached_object_type[self.object_type] else: return ObjectFetcher._cached_objects all = property(get_all) @pynag.Utils.synchronized(pynag.Utils.rlock) def reload_cache(self): """Reload configuration cache""" # clear object list ObjectFetcher._cached_objects = [] ObjectFetcher._cached_ids = {} ObjectFetcher._cached_shortnames = defaultdict(dict) ObjectFetcher._cached_names = defaultdict(dict) ObjectFetcher._cached_object_type = defaultdict(list) global config # If global variable cfg_file has been changed, lets create a new ConfigParser object if config is None or config.cfg_file != cfg_file: config = pynag.Parsers.config_parser.Config(cfg_file) if config.needs_reparse(): config.parse() # Reset our list of how objects are related to each other ObjectRelations.reset() # Fetch all objects from config_parser.config for object_type, objects in config.data.items(): # change "all_host" to just "host" object_type = object_type[len("all_"):] Class = string_to_class.get(object_type, ObjectDefinition) for i in objects: i = Class(item=i) ObjectFetcher._cached_objects.append(i) ObjectFetcher._cached_object_type[object_type].append(i) ObjectFetcher._cached_ids[i.get_id()] = i ObjectFetcher._cached_shortnames[i.object_type][i.get_shortname()] = i if i.name is not None: ObjectFetcher._cached_names[i.object_type][i.name] = i i._do_relations() ObjectRelations.resolve_contactgroups() ObjectRelations.resolve_hostgroups() ObjectRelations.resolve_servicegroups() ObjectRelations.resolve_regex() return True @pynag.Utils.synchronized(pynag.Utils.rlock) def needs_reload(self): """ Returns true if configuration files need to be reloaded/reparsed """ if not ObjectFetcher._cached_objects: return True if config is None: return True if self._cache_only: return False return config.needs_reparse() def get_by_id(self, id, cache_only=False): """ Get one specific object :returns: ObjectDefinition :raises: ValueError if object is not found """ if not cache_only and self.needs_reload(): self.reload_cache() str_id = str(id).strip() return ObjectFetcher._cached_ids[str_id] def get_by_shortname(self, shortname, cache_only=False): """ Get one specific object by its shortname (i.e. host_name for host, etc) :param shortname: shortname of the object. i.e. host_name, command_name, etc. :param cache_only: If True, dont check if configuration files have changed since last parse :returns: ObjectDefinition :raises: ValueError if object is not found """ if cache_only is False and self.needs_reload(): self.reload_cache() shortname = str(shortname).strip() return ObjectFetcher._cached_shortnames[self.object_type][shortname] def get_by_name(self, object_name, cache_only=False): """ Get one specific object by its object_name (i.e. name attribute) :returns: ObjectDefinition :raises: ValueError if object is not found """ if not cache_only and self.needs_reload(): self.reload_cache() object_name = str(object_name).strip() return ObjectFetcher._cached_names[self.object_type][object_name] def get_object_types(self): """ Returns a list of all discovered object types """ if config is None or config.needs_reparse(): self.reload_cache() return config.get_object_types() def filter(self, **kwargs): """ Returns all objects that match the selected filter Example: Get all services where host_name is examplehost.example.com >>> Service.objects.filter(host_name='examplehost.example.com') # doctest: +SKIP Get service with host_name=examplehost.example.com and service_description='Ping' >>> Service.objects.filter(host_name='examplehost.example.com', ... service_description='Ping') # doctest: +SKIP Get all services that are registered but without a host_name >>> Service.objects.filter(host_name=None,register='1') # doctest: +SKIP Get all hosts that start with 'exampleh' >>> Host.objects.filter(host_name__startswith='exampleh') # doctest: +SKIP Get all hosts that end with 'example.com' >>> Service.objects.filter(host_name__endswith='example.com') # doctest: +SKIP Get all contactgroups that contain 'dba' >>> Contactgroup.objects.filter(host_name__contains='dba') # doctest: +SKIP Get all hosts that are not in the 'testservers' hostgroup >>> Host.objects.filter(hostgroup_name__notcontains='testservers') # doctest: +SKIP Get all services with non-empty name >>> Service.objects.filter(name__isnot=None) # doctest: +SKIP Get all hosts that have an address: >>> Host.objects.filter(address_exists=True) # doctest: +SKIP """ return pynag.Utils.grep(self.all, **kwargs) class ObjectDefinition(object): """ Holds one instance of one particular Object definition Example: >>> objects = ObjectDefinition.objects.all >>> my_object = ObjectDefinition( dict ) # doctest: +SKIP """ object_type = None objects = ObjectFetcher(None) def __init__(self, item=None, filename=None, **kwargs): self.__object_id__ = None # When we are saving, it is useful to know if we are already expecting # This object to exist in file or not. self._filename_has_changed = False # if item is empty, we are creating a new object if item is None: item = config.get_new_item(object_type=self.object_type, filename=filename) self.is_new = True else: self.is_new = False # store the object_type (i.e. host,service,command, etc) self.object_type = item['meta']['object_type'] # self.data -- This dict stores all effective attributes of this objects self._original_attributes = item #: _changes - This dict contains any changed (but yet unsaved) attributes of this object self._changes = {} #: _defined_attributes - All attributes that this item has defined self._defined_attributes = item['meta']['defined_attributes'] #: _inherited_attributes - All attributes that this object has inherited via 'use' self._inherited_attributes = item['meta']['inherited_attributes'] #: _meta - Various metadata about the object self._meta = item['meta'] #: _macros - A dict object that resolves any particular Nagios Macro (i.e. $HOSTADDR$) self._macros = {} #: __argument_macros - A dict object that resolves $ARG* macros self.__argument_macros = {} # Any kwargs provided will be added to changes: for k, v in kwargs.items(): self[k] = v def get_attribute(self, attribute_name): """Get one attribute from our object definition :param attribute_name: A attribute such as *host_name* """ return self[attribute_name] def set_attribute(self, attribute_name, attribute_value): """ Set (but does not save) one attribute in our object :param attribute_name: A attribute such as *host_name* :param attribute_value: The value you would like to set """ self[attribute_name] = attribute_value def attribute_is_empty(self, attribute_name): """ Check if the attribute is empty :param attribute_name: A attribute such as *host_name* :returns: True or False """ attr = self.get_attribute(attribute_name) if not attr or attr.strip() in '+-!': return True else: return False def is_dirty(self): """Returns true if any attributes has been changed on this object, and therefore it needs saving""" return len(list(self._changes.keys())) != 0 def is_registered(self): """ Returns true if object is enabled (registered) """ if not 'register' in self: return True if str(self['register']) == "1": return True return False def is_defined(self, attribute_name): """ Returns True if attribute_name is defined in this object """ return attribute_name in self._defined_attributes def __cmp__(self, other): return cmp(self.get_description(), other.get_description()) def __eq__(self, other): return self.get_description() == other.get_description() def __lt__(self, other): return self.get_description() < other.get_description() def __gt__(self, other): return self.get_description() > other.get_description() def __hash__(self): return hash(self.get_id()) def __setitem__(self, key, item): # Special handle for macros if pynag.Utils.is_macro(key): self.set_macro(key, item) elif self[key] != item: self._changes[key] = item self._event(level="debug", message="attribute changed: %s = %s" % (key, item)) def __getitem__(self, key): if key == 'id': return self.get_id() elif key == 'description': return self.get_description() elif key == 'shortname': return self.get_shortname() elif key == 'effective_command_line': return self.get_effective_command_line() elif key == 'register' and key not in self: return "1" elif key == 'meta': return self._meta elif key in self._changes: return self._changes[key] elif key in self._defined_attributes: return self._defined_attributes[key] elif key in self._inherited_attributes: return self._inherited_attributes[key] elif key in self._meta: return self._meta[key] else: return None def __contains__(self, item): """ Returns true if item is in ObjectDefinition """ if item in list(self.keys()): return True if item in list(self._meta.keys()): return True return False def has_key(self, key): """ Same as key in self """ return key in self def keys(self): all_keys = ['meta', 'id', 'shortname', 'effective_command_line'] for k in self._changes.keys(): if k not in all_keys: all_keys.append(k) for k in self._defined_attributes.keys(): if k not in all_keys: all_keys.append(k) for k in self._inherited_attributes.keys(): if k not in all_keys: all_keys.append(k) # for k in self._meta.keys(): # if k not in all_keys: all_keys.append(k) return all_keys def items(self): return [(x, self[x]) for x in list(self.keys())] def get_id(self): """ Return a unique ID for this object""" #object_type = self['object_type'] #shortname = self.get_description() #object_name = self['name'] if not self.__object_id__: filename = self._original_attributes['meta']['filename'] object_id = (filename, sorted(frozenset(list(self._defined_attributes.items())))) object_id = str(object_id) self.__object_id__ = str(hash(object_id)) # this is good when troubleshooting ID issues: # definition = self._original_attributes['meta']['raw_definition'] # object_id = str((filename, definition)) #self.__object_id__ = object_id return self.__object_id__ def get_suggested_filename(self): """Get a suitable configuration filename to store this object in :returns: filename, eg str('/etc/nagios/pynag/templates/hosts.cfg') """ # Invalid characters that might potentially mess with our path # | / ' " are all invalid. So is any whitespace invalid_chars = '[/\s\'\"\|]' object_type = re.sub(invalid_chars, '', self.object_type) description = re.sub(invalid_chars, '', self.get_description()) # if pynag_directory is undefined, use "/pynag" dir under nagios.cfg if pynag_directory: destination_directory = pynag_directory else: main_config = config.cfg_file or paths.find_main_configuration_file() main_config = os.path.abspath(main_config) destination_directory = os.path.dirname(main_config) # By default assume this is the filename path = "%s/%ss/%s.cfg" % (destination_directory, object_type, description) # Services go to same file as their host if object_type == "service" and self.get('host_name'): try: host = Host.objects.get_by_shortname(self.host_name) return host.get_filename() except Exception: pass # templates go to the template directory if not self.is_registered(): path = "%s/templates/%ss.cfg" % (pynag_directory, object_type) # Filename of services should match service description or name elif object_type == 'service': filename = self.name or self.service_description or "untitled" filename = re.sub(invalid_chars, '', filename) path = "%s/%ss/%s.cfg" % (pynag_directory, object_type, filename) return path @pynag.Utils.synchronized(pynag.Utils.rlock) def save(self, filename=None): """Saves any changes to the current object to its configuration file :param filename: * If filename is provided, save a copy of this object in that file. * If filename is None, either save to current file (in case of existing objects) or let pynag guess a location for it in case of new objects. :returns: * In case of existing objects, return number of attributes changed. * In case of new objects, return True """ # Let event-handlers know we are about to save an object self._event(level='pre_save', message="%s '%s'." % (self.object_type, self['shortname'])) number_of_changes = len(list(self._changes.keys())) filename = filename or self.get_filename() or self.get_suggested_filename() self.set_filename(filename) # If this is a new object, we save it with config.item_add() if self.is_new is True or self._filename_has_changed: for k in list(self._changes.keys()): v = self._changes.get(k, None) if v is not None: # Dont save anything if attribute is None self._defined_attributes[k] = v self._original_attributes[k] = v del self._changes[k] self.is_new = False self._filename_has_changed = False self._event(level='write', message="Added new %s: %s" % (self.object_type, self.get_description())) config.item_add(self._original_attributes, self.get_filename()) # If we get here, we are making modifications to an object else: number_of_changes = 0 for field_name in list(self._changes.keys()): new_value = self._changes.get(field_name, None) save_result = config.item_edit_field( item=self._original_attributes, field_name=field_name, new_value=new_value ) if save_result is True: del self._changes[field_name] self._event(level='write', message="%s changed from '%s' to '%s'" % (field_name, self[field_name], new_value)) # Setting new_value to None, is a signal to remove the attribute # Therefore we remove it from our internal data structure if new_value is None: self._defined_attributes.pop(field_name, None) self._original_attributes.pop(field_name, None) else: self._defined_attributes[field_name] = new_value self._original_attributes[field_name] = new_value number_of_changes += 1 else: raise Exception( "Failure saving object. filename=%s, object=%s" % (self.get_filename(), self['shortname'])) # this piece of code makes sure that when we current object contains all current info self.reload_object() self._event(level='save', message="%s '%s' saved." % (self.object_type, self['shortname'])) return number_of_changes def reload_object(self): """ Re-applies templates to this object (handy when you have changed the use attribute """ old_me = config.get_new_item(self.object_type, self.get_filename()) old_me['meta']['defined_attributes'] = self._defined_attributes for k, v in self._defined_attributes.items(): old_me[k] = v for k, v in self._changes.items(): old_me[k] = v i = config._apply_template(old_me) new_me = self.__class__(item=i) self._defined_attributes = new_me._defined_attributes self._original_attributes = new_me._original_attributes self._inherited_attributes = new_me._inherited_attributes self._meta = new_me._meta self.__object_id__ = None @pynag.Utils.synchronized(pynag.Utils.rlock) def rewrite(self, str_new_definition=None): """Rewrites this Object Definition in its configuration files. :param str_new_definition: The actual string that will be written in the configuration file. If str_new_definition is *None*, then we will use *self.__str__()* :returns: True on success """ self._event(level='pre_save', message="Object definition is being rewritten") if self.is_new is True: self.save() if str_new_definition is None: str_new_definition = str(self) config.item_rewrite(self._original_attributes, str_new_definition) self['meta']['raw_definition'] = str_new_definition self._event(level='write', message="Object definition rewritten") # this piece of code makes sure that when we current object contains all current info new_me = config.parse_string(str_new_definition) if new_me: new_me = new_me[0] self._defined_attributes = new_me['meta']['defined_attributes'] self.reload_object() self._event(level='save', message="Object definition was rewritten") return True def delete(self, recursive=False, cleanup_related_items=True): """ Deletes this object definition from its configuration files. :param recursive: If True, look for items that depend on this object and delete them as well (for example, if you delete a host, delete all its services as well) :param cleanup_related_items: If True, look for related items and remove references to this one. (for example, if you delete a host, remove its name from all hostgroup.members entries) """ self._event(level="pre_save", message="%s '%s' will be deleted." % (self.object_type, self.get_shortname())) if recursive is True: # Recursive does not have any meaning for a generic object, this should subclassed. pass result = config.item_remove(self._original_attributes) self._event(level="write", message="%s '%s' was deleted." % (self.object_type, self.get_shortname())) self._event(level="save", message="%s '%s' was deleted." % (self.object_type, self.get_shortname())) return result def move(self, filename): """Move this object definition to a new file. It will be deleted from current file. This is the same as running: >>> self.copy(filename=filename) # doctest: +SKIP >>> self.delete() # doctest: +SKIP :returns: The new object definition """ new_me = self.copy(filename=filename) self.delete() return new_me def copy(self, recursive=False, filename=None, **args): """ Copies this object definition with any unsaved changes to a new configuration object Arguments: filename: If specified, new object will be saved in this file. recursive: If true, also find any related children objects and copy those **args: Any argument will be treated a modified attribute in the new definition. Examples: myhost = Host.objects.get_by_shortname('myhost.example.com') # Copy this host to a new one myhost.copy( host_name="newhost.example.com", address="127.0.0.1") # Copy this host and all its services: myhost.copy(recursive=True, host_name="newhost.example.com", address="127.0.0.1") Returns: * A copy of the new ObjectDefinition * A list of all copies objects if recursive is True """ if args == {} and filename is None: raise ValueError('To copy an object definition you need at least one new attribute') new_object = string_to_class[self.object_type](filename=filename) for k, v in self._defined_attributes.items(): new_object[k] = v for k, v in self._changes.items(): new_object[k] = v for k, v in args.items(): new_object[k] = v new_object.save() return new_object def rename(self, shortname): """ Change the shortname of this object Most objects that inherit this one, should also be responsible for updating related objects about the rename. Args: shortname: New name for this object Returns: None """ if not self.object_type: raise Exception("Don't use this object on ObjectDefinition. Only sub-classes") if not shortname: raise Exception("You must provide a valid shortname if you intend to rename this object") attribute = '%s_name' % self.object_type self.set_attribute(attribute, shortname) self.save() def get_related_objects(self): """ Returns a list of ObjectDefinition that depend on this object Object can "depend" on another by a 'use' or 'host_name' or similar attribute Returns: List of ObjectDefinition objects """ result = [] if self['name'] is not None: tmp = ObjectDefinition.objects.filter(use__has_field=self['name'], object_type=self['object_type']) for i in tmp: result.append(i) return result def __str__(self): return_buffer = "define %s {\n" % self.object_type fields = list(self._defined_attributes.keys()) for i in self._changes.keys(): if i not in fields: fields.append(i) fields.sort() interesting_fields = ['service_description', 'use', 'name', 'host_name'] for i in interesting_fields: if i in fields: fields.remove(i) fields.insert(0, i) for key in fields: if key == 'meta' or key in list(self['meta'].keys()): continue value = self[key] return_buffer += " %-30s %s\n" % (key, value) return_buffer += "}\n" return return_buffer def __repr__(self): return "%s: %s" % (self['object_type'], self.get_description()) def get(self, value, default=None): """ self.get(x) == self[x] """ result = self[value] if result is None: return default else: return result def get_description(self): """ Returns a human friendly string describing current object. It will try the following in order: * return self.name (get the generic name) * return self get_shortname() * return "Untitled $object_type" """ return self.name or self.get_shortname() or "Untitled %s" % self.object_type def get_shortname(self): """ Returns shortname of an object in string format. For the confused, nagios documentation refers to shortnames usually as <object_type>_name. * In case of Host it returns host_name * In case of Command it returns command_name * etc * Special case for services it returns "host_name/service_description" Returns None if no attribute can be found to use as a shortname """ return self.get("%s_name" % self.object_type, None) def get_filename(self): """ Get name of the config file which defines this object """ if self._meta['filename'] is None: return None return os.path.normpath(self._meta['filename']) def set_filename(self, filename): """ Set name of the config file which this object will be written to on next save. """ if filename != self.get_filename(): self._filename_has_changed = True if filename is None: self._meta['filename'] = filename else: self._meta['filename'] = os.path.normpath(filename) def _get_custom_variable_macro(self, macro): """Get the value for a specific custom variable macro for this object. Args: macro: String. Macro name in the format of $_{OBJECTTYPE}{VARNAME}$, e.g. $HOSTMACADDRESS$ Returns: String. The real value for this custom variable macro. Emptystring if macro cannot be resolved. Raises: InvalidMacro if macro parameter is not a valid custom variable macro for this object type. """ if not pynag.Utils.is_macro(macro): raise InvalidMacro("%s does not look like a valid macro" % macro) # Remove leading and trailing $ from the macro name: macro = macro[1:-1] # We expect '_{OBJECTTYPE}{VARIABLENAME}' expected_prefix = _CUSTOM_VARIABLE_PREFIX + self.object_type.upper() if not macro.startswith(expected_prefix): raise InvalidMacro("%s does not look like a custom macro for %s" % (macro, self.object_type)) variable_name = macro.replace(expected_prefix, '') # We want to be case insensitive variable_name = variable_name.upper() # custom variable attribute names are all prefixed variable_name = _CUSTOM_VARIABLE_PREFIX + variable_name for attribute_name in self.keys(): if attribute_name.upper() == variable_name: return self.get(attribute_name) else: return _UNRESOLVED_MACRO def get_macro(self, macroname, host_name=None, contact_name=None): """ Take macroname (e.g. $USER1$) and return its actual value Arguments: macroname -- Macro that is to be resolved. For example $HOSTADDRESS$ host_name -- Optionally specify host (use this for services that -- don't define host specifically for example ones that only -- define hostgroups Returns: (str) Actual value of the macro. For example "$HOSTADDRESS$" becomes "127.0.0.1" """ if not pynag.Utils.is_macro(macroname): return _UNRESOLVED_MACRO if macroname.startswith('$ARG'): # Command macros handled in a special function return self._get_command_macro(macroname, host_name=host_name) if macroname.startswith('$USER'): # $USERx$ macros are supposed to be private, but we will display them anyway return config.get_resource(macroname) if macroname.startswith('$HOST') or macroname.startswith('$_HOST'): return self._get_host_macro(macroname, host_name=host_name) if macroname.startswith('$SERVICE') or macroname.startswith('$_SERVICE'): return self._get_service_macro(macroname) if macroname.startswith('$CONTACT') or macroname.startswith('$_CONTACT'): return self._get_contact_macro(macroname, contact_name=contact_name) return _UNRESOLVED_MACRO def set_macro(self, macroname, new_value): """ Update a macro (custom variable) like $ARG1$ intelligently Returns: None Notes: You are responsible for calling .save() after modifying the object Examples: >>> s = Service() >>> s.check_command = 'okc-execute!arg1!arg2' >>> s.set_macro('$ARG1$', 'modified1') >>> s.check_command 'okc-execute!modified1!arg2' >>> s.set_macro('$ARG5$', 'modified5') >>> s.check_command 'okc-execute!modified1!arg2!!!modified5' >>> s.set_macro('$_SERVICE_TEST$', 'test') >>> s['__TEST'] 'test' """ if not pynag.Utils.is_macro(macroname): raise ValueError("Macros must be of the format $<macroname>$") if macroname.startswith('$ARG'): if self.check_command is None: raise ValueError("cant save %s, when there is no check_command defined" % macroname) # split check command into an array # in general the following will apply: # $ARG0$ = c[0] # $ARG1$ = c[1] # etc... c = self._split_check_command_and_arguments(self.check_command) arg_number = int(macroname[4:-1]) # Special hack, if c array is to short for our value, we will make the array longer while arg_number >= len(c): c.append('') # Lets save our attribute c[arg_number] = new_value self.check_command = '!'.join(c) elif macroname.startswith('$_HOST'): macroname = "_" + macroname[6:-1] self[macroname] = new_value elif macroname.startswith('$_SERVICE'): macroname = "_" + macroname[9:-1] self[macroname] = new_value else: raise ValueError("No support for macro %s" % macroname) def get_all_macros(self): """Returns {macroname:macrovalue} hash map of this object's macros""" if self['check_command'] is None: return {} c = self['check_command'] c = self._split_check_command_and_arguments(c) command_name = c.pop(0) command = Command.objects.get_by_shortname(command_name) regex = re.compile("(\$\w+\$)") macronames = regex.findall(command['command_line']) # Add all custom macros to our list: for i in self.keys(): if not i.startswith(_CUSTOM_VARIABLE_PREFIX): continue if self.object_type == 'service': i = '$_SERVICE%s$' % (i[1:]) elif self.object_type == 'host': i = '$_HOST%s$' % (i[1:]) macronames.append(i) # Nagios is case-insensitive when it comes to macros, but it always displays # them in upper-case: macronames = [name.upper() for name in macronames] result = {} for i in macronames: result[i] = self.get_macro(i) return result def get_effective_command_line(self, host_name=None): """Return a string of this objects check_command with all macros (i.e. $HOSTADDR$) resolved""" if self['check_command'] is None: return None c = self['check_command'] c = self._split_check_command_and_arguments(c) command_name = c.pop(0) try: command = Command.objects.get_by_shortname(command_name, cache_only=True) except ValueError: return None return self._resolve_macros(command.command_line, host_name=host_name) def get_effective_notification_command_line(self, host_name=None, contact_name=None): """Get this objects notifications with all macros (i.e. $HOSTADDR$) resolved :param host_name: Simulate notification using this host. If None: Use first valid host (used for services) :param contact_name: Simulate notification for this contact. If None: use first valid contact for the service :returns: string of this objects notifications """ if contact_name is None: contacts = self.get_effective_contacts() if len(contacts) == 0: raise ModelError('Cannot calculate notification command for object with no contacts') else: contact = contacts[0] else: contact = Contact.objects.get_by_shortname(contact_name) notification_command = contact.service_notification_commands if not notification_command: return None command_name = notification_command.split('!').pop(0) try: command = Command.objects.get_by_shortname(command_name) except ValueError: return None return self._resolve_macros(command.command_line, host_name=host_name) def _resolve_macros(self, string, host_name=None): """Resolves every $NAGIOSMACRO$ within the string :param string: Arbitary string that contains macros :param host_name: Optionally supply host_name if this service does not define it :returns: string with every $NAGIOSMACRO$ resolved to actual value Example: >>> host = Host() >>> host.address = "127.0.0.1" >>> host._resolve_macros('check_ping -H $HOSTADDRESS$') 'check_ping -H 127.0.0.1' """ if not string: return _UNRESOLVED_MACRO regex = re.compile("(\$\w+\$)") get_macro = lambda x: self.get_macro(x.group(), host_name=host_name) result = regex.sub(get_macro, string) return result def run_check_command(self, host_name=None): """Run the check_command defined by this service. Returns return_code,stdout,stderr""" command = self.get_effective_command_line(host_name=host_name) if command is None: return None proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) stdout, stderr = proc.communicate('through stdin to stdout') stdout = bytes2str(stdout) stderr = bytes2str(stderr) return proc.returncode, stdout, stderr def _split_check_command_and_arguments(self, check_command): """ Split a nagios "check_command" string into a tuple >>> check_command = "check_ping!warning!critical" >>> o = ObjectDefinition() >>> o._split_check_command_and_arguments(check_command) ['check_ping', 'warning', 'critical'] >>> complex_check_command = "check_ping!warning with \! in it!critical" >>> o._split_check_command_and_arguments(complex_check_command) ['check_ping', 'warning with \\\\! in it', 'critical'] """ if check_command in (None, ''): return [] if '\!' in check_command: check_command = check_command.replace('\!', 'ESCAPE_EXCL_MARK') tmp = check_command.split('!') result = [x.replace('ESCAPE_EXCL_MARK', '\!') for x in tmp] return result def _get_command_macro(self, macroname, check_command=None, host_name=None): """Resolve any command argument ($ARG1$) macros from check_command""" if not pynag.Utils.is_macro(macroname): return _UNRESOLVED_MACRO if check_command is None: check_command = self.check_command if check_command is None: return _UNRESOLVED_MACRO all_args = {} c = self._split_check_command_and_arguments(check_command) c.pop(0) # First item is the command, we dont need it for i, v in enumerate(c): name = '$ARG%s$' % str(i + 1) all_args[name] = v result = all_args.get(macroname, _UNRESOLVED_MACRO) # Our $ARGx$ might contain macros on its own, so lets resolve macros in it: result = self._resolve_macros(result, host_name=host_name) return result def _get_service_macro(self, macroname): if not pynag.Utils.is_macro(macroname): return _UNRESOLVED_MACRO if macroname.startswith('$_SERVICE'): return self._get_custom_variable_macro(macroname) elif macroname in macros.STANDARD_SERVICE_MACROS: attr = macros.STANDARD_SERVICE_MACROS[macroname] return self.get(attr, _UNRESOLVED_MACRO) elif macroname.startswith('$SERVICE'): name = macroname[8:-1].lower() return self.get(name, _UNRESOLVED_MACRO) return _UNRESOLVED_MACRO def _get_host_macro(self, macroname, host_name=None): if not pynag.Utils.is_macro(macroname): return _UNRESOLVED_MACRO if macroname.startswith('$_HOST'): return self._get_custom_variable_macro(macroname) elif macroname == '$HOSTADDRESS$' and not self.address: return self._get_host_macro('$HOSTNAME$') elif macroname == '$HOSTDISPLAYNAME$' and not self.display_name: return self._get_host_macro('$HOSTNAME$') elif macroname in macros.STANDARD_HOST_MACROS: attr = macros.STANDARD_HOST_MACROS[macroname] return self.get(attr, _UNRESOLVED_MACRO) elif macroname.startswith('$HOST'): name = macroname[5:-1].lower() return self.get(name, _UNRESOLVED_MACRO) return _UNRESOLVED_MACRO def _get_contact_macro(self, macroname, contact_name=None): if not pynag.Utils.is_macro(macroname): return _UNRESOLVED_MACRO # If contact_name is not specified, get first effective contact and resolve macro for that contact if not contact_name: contacts = self.get_effective_contacts() if len(contacts) == 0: return _UNRESOLVED_MACRO contact = contacts[0] else: contact = Contact.objects.get_by_shortname(contact_name) return contact._get_contact_macro(macroname) def get_effective_children(self, recursive=False): """ Get a list of all objects that inherit this object via "use" attribute :param recursive: If true, include grandchildren as well :returns: A list of ObjectDefinition objects """ if not self.name: return [] name = self.name children = self.objects.filter(use__has_field=name) if recursive is True: for i in children: grandchildren = i.get_effective_children(recursive) for grandchild in grandchildren: if grandchild not in children: children.append(grandchild) return children def get_effective_parents(self, recursive=False, cache_only=False): """ Get all objects that this one inherits via "use" attribute Arguments: recursive - If true include grandparents in list to be returned Returns: a list of ObjectDefinition objects """ if not self.use: return [] results = [] use = pynag.Utils.AttributeList(self.use) for parent_name in use: parent = self.objects.get_by_name(parent_name, cache_only=cache_only) if parent not in results: results.append(parent) if recursive is True: for i in results: grandparents = i.get_effective_parents(recursive=True, cache_only=cache_only) for gp in grandparents: if gp not in results: results.append(gp) return results def get_attribute_tuple(self): """ Returns all relevant attributes in the form of: (attribute_name,defined_value,inherited_value) """ result = [] for k in self.keys(): inher = defin = None if k in self._inherited_attributes: inher = self._inherited_attributes[k] if k in self._defined_attributes: defin = self._defined_attributes[k] result.append((k, defin, inher)) return result def get_parents(self): """ Out-dated, use get_effective_parents instead. Kept here for backwards compatibility """ return self.get_effective_parents() def unregister(self, recursive=True): """ Short for self['register'] = 0 ; self.save() """ self['register'] = 0 self.save() if recursive is True: for i in self.get_related_objects(): i.unregister() def attribute_appendfield(self, attribute_name, value): """Convenient way to append value to an attribute with a comma seperated value Example: >>> myservice = Service() >>> myservice.attribute_appendfield(attribute_name="contact_groups", value="alladmins") >>> myservice.contact_groups '+alladmins' >>> myservice.attribute_appendfield(attribute_name="contact_groups", value='webmasters') >>> print(myservice.contact_groups) +alladmins,webmasters """ aList = AttributeList(self[attribute_name]) # If list was empty before, add a + to it so we are appending to parent if len(aList.fields) == 0: aList.operator = '+' if value not in aList.fields: aList.fields.append(value) self[attribute_name] = str(aList) return def attribute_removefield(self, attribute_name, value): """Convenient way to remove value to an attribute with a comma seperated value Example: >>> myservice = Service() >>> myservice.contact_groups = "+alladmins,localadmins" >>> myservice.attribute_removefield(attribute_name="contact_groups", value='localadmins') >>> print(myservice.contact_groups) +alladmins >>> myservice.attribute_removefield(attribute_name="contact_groups", value="alladmins") >>> print(myservice.contact_groups) None """ aList = AttributeList(self[attribute_name]) if value in aList.fields: aList.fields.remove(value) if not aList.fields: # If list is empty, lets remove the attribute self[attribute_name] = None else: self[attribute_name] = str(aList) return def attribute_replacefield(self, attribute_name, old_value, new_value): """Convenient way to replace field within an attribute with a comma seperated value Example: >>> myservice = Service() >>> myservice.contact_groups = "+alladmins,localadmins" >>> myservice.attribute_replacefield(attribute_name="contact_groups", old_value='localadmins', new_value="webmasters") >>> print(myservice.contact_groups) +alladmins,webmasters """ aList = AttributeList(self[attribute_name]) if old_value in aList.fields: i = aList.fields.index(old_value) aList.fields[i] = new_value self[attribute_name] = str(aList) return def _get_effective_attribute(self, attribute_name): """This helper function returns specific attribute, from this object or its templates This is handy for fields that effectively are many_to_many values. for example, "contactroups +group1,group2,group3" Fields that are known to use this format are: contacts, contactgroups, hostgroups, servicegroups, members,contactgroup_members """ result = [] tmp = self[attribute_name] if tmp is not None: result.append(tmp) if tmp is None or tmp.startswith('+'): for parent in self.get_parents(): result.append(parent._get_effective_attribute(attribute_name)) if parent[attribute_name] is not None and not parent[attribute_name].startswith('+'): break return_value = [] for value in result: value = value.strip('+') if value == '': continue if value not in return_value: return_value.append(value) tmp = ','.join(return_value) tmp = tmp.replace(',,', ',') return tmp def _event(self, level=None, message=None): """ Pass informational message about something that has happened within the Model """ for i in eventhandlers: if level == 'write': i.write(object_definition=self, message=message) elif level == 'save': i.save(object_definition=self, message=message) elif level == 'pre_save': i.pre_save(object_definition=self, message=message) else: i.debug(object_definition=self, message=message) def _do_relations(self): """ Discover all related objects (f.e. services that belong to this host, etc ObjectDefinition only has relations via 'use' paramameter. Subclasses should extend this. """ parents = AttributeList(self.use) for i in parents.fields: ObjectRelations.use[self.object_type][i].add(self.get_id()) class Host(ObjectDefinition): object_type = 'host' objects = ObjectFetcher('host') def acknowledge(self, sticky=1, notify=1, persistent=0, author='pynag', comment='acknowledged by pynag', recursive=False, timestamp=None): if timestamp is None: timestamp = int(time.time()) if recursive is True: pass # Its here for compatibility but we are not using recursive so far. pynag.Control.Command.acknowledge_host_problem(host_name=self.host_name, sticky=sticky, notify=notify, persistent=persistent, author=author, comment=comment, timestamp=timestamp, command_file=config.get_cfg_value('command_file') ) def downtime(self, start_time=None, end_time=None, trigger_id=0, duration=7200, author=None, comment='Downtime scheduled by pynag', recursive=False): """ Put this object in a schedule downtime. Arguments: start_time -- When downtime should start. If None, use time.time() (now) end_time -- When scheduled downtime should end. If None use start_time + duration duration -- Alternative to end_time, downtime lasts for duration seconds. Default 7200 seconds. trigger_id -- trigger_id>0 means that this downtime should trigger another downtime with trigger_id. author -- name of the contact scheduling downtime. If None, use current system user comment -- Comment that will be put in with the downtime recursive -- Also schedule same downtime for all service of this host. Returns: None because commands sent to nagios have no return values Raises: ModelError if this does not look an active object. """ if self.register == '0': raise ModelError('Cannot schedule a downtime for unregistered object') if not self.host_name: raise ModelError('Cannot schedule a downtime for host with no host_name') if start_time is None: start_time = time.time() if duration is None: duration = 7200 duration = int(duration) if end_time is None: end_time = start_time + duration if author is None: author = getpass.getuser() arguments = { 'host_name': self.host_name, 'start_time': start_time, 'end_time': end_time, 'fixed': '1', 'trigger_id': trigger_id, 'duration': duration, 'author': author, 'comment': comment, } if recursive is True: pynag.Control.Command.schedule_host_svc_downtime(**arguments) else: pynag.Control.Command.schedule_host_downtime(**arguments) def get_effective_services(self): """ Returns a list of all Service that belong to this Host """ get_object = lambda x: Service.objects.get_by_id(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.host_services[self.host_name]) services = list(map(get_object, list_of_shortnames)) # Look for services that define hostgroup_name that we belong to for hg in self.get_effective_hostgroups(): services += hg.get_effective_services() return services def get_effective_contacts(self): """ Returns a list of all Contact that belong to this Host """ get_object = lambda x: Contact.objects.get_by_shortname(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.host_contacts[self.host_name]) return list(map(get_object, list_of_shortnames)) def get_effective_contact_groups(self): """ Returns a list of all Contactgroup that belong to this Host """ get_object = lambda x: Contactgroup.objects.get_by_shortname(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.host_contact_groups[self.host_name]) return list(map(get_object, list_of_shortnames)) def get_effective_hostgroups(self): """ Returns a list of all Hostgroup that belong to this Host """ get_object = lambda x: Hostgroup.objects.get_by_shortname(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.host_hostgroups[self.host_name]) return list(map(get_object, list_of_shortnames)) def get_effective_network_parents(self, recursive=False): """ Get all objects this one depends on via "parents" attribute Arguments: recursive - If true include grandparents in list to be returned Returns: a list of ObjectDefinition objects """ if self['parents'] is None: return [] results = [] parents = self['parents'].split(',') for parent_name in parents: results.append(self.objects.get_by_name(parent_name, cache_only=True)) if recursive is True: grandparents = [] for i in results: grandparents.append(i.get_effective_network_parents(recursive=True)) results += grandparents return results def get_effective_network_children(self, recursive=False): """ Get all objects that depend on this one via "parents" attribute Arguments: recursive - If true include grandchildren in list to be returned Returns: a list of ObjectDefinition objects """ if self.host_name is None: return [] children = self.objects.filter(parents__has_field=self.host_name) if recursive is True: for child in children: children += child.get_effective_network_children(recursive=True) return children def delete(self, recursive=False, cleanup_related_items=True): """ Delete this host and optionally its services Works like ObjectDefinition.delete() except for: Arguments: cleanup_related_items -- If True, remove references found in hostgroups and escalations recursive -- If True, also delete all services of this host """ if recursive is True and self.host_name: # Delete all services that use this host_name, and have no hostgroup_name set for service in Service.objects.filter(host_name=self.host_name, hostgroup_name__exists=False): service.delete(recursive=recursive, cleanup_related_items=cleanup_related_items) # In case of services that have multiple host_names, only clean up references for service in Service.objects.filter(host_name__has_field=self.host_name): service.attribute_removefield('host_name', self.host_name) service.save() if cleanup_related_items is True and self.host_name: hostgroups = Hostgroup.objects.filter(members__has_field=self.host_name) dependenciesAndEscalations = ObjectDefinition.objects.filter( host_name__has_field=self.host_name, object_type__isnot='host') services = Service.objects.filter(host_name__has_field=self.host_name) for i in hostgroups: # remove host from hostgroups i.attribute_removefield('members', self.host_name) i.save() for i in dependenciesAndEscalations: # remove from host/service escalations/dependencies i.attribute_removefield('host_name', self.host_name) if ((i.get_attribute('object_type').endswith("escalation") or i.get_attribute('object_type').endswith("dependency")) and recursive is True and i.attribute_is_empty("host_name") and i.attribute_is_empty("hostgroup_name")): i.delete(recursive=recursive, cleanup_related_items=cleanup_related_items) else: i.save() for service in services: service.attribute_removefield('host_name', self.host_name) service.save() # get these here as we might have deleted some in the block above dependencies = ObjectDefinition.objects.filter(dependent_host_name__has_field=self.host_name) for i in dependencies: # remove from host/service escalations/dependencies i.attribute_removefield('dependent_host_name', self.host_name) if (i.get_attribute('object_type').endswith("dependency") and recursive is True and i.attribute_is_empty("dependent_host_name") and i.attribute_is_empty("dependent_hostgroup_name")): i.delete(recursive=recursive, cleanup_related_items=cleanup_related_items) else: i.save() # Call parent to get delete myself return super(self.__class__, self).delete(recursive=recursive, cleanup_related_items=cleanup_related_items) def get_related_objects(self): result = super(self.__class__, self).get_related_objects() if self['host_name'] is not None: tmp = Service.objects.filter(host_name=self['host_name']) for i in tmp: result.append(i) return result def get_effective_check_command(self): """ Returns a Command object as defined by check_command attribute Raises KeyError if check_command is not found or not defined. """ c = self.check_command if not c or c == '': raise KeyError(None) check_command = c.split('!')[0] return Command.objects.get_by_shortname(check_command, cache_only=True) def get_current_status(self, status=None): """ Returns a dictionary with status data information for this object :param status: pynag.Parsers.status_dat.StatusDat instance """ if not status: status = pynag.Parsers.status_dat.StatusDat(cfg_file=cfg_file) host = status.get_hoststatus(self.host_name) return host def copy(self, recursive=False, filename=None, **args): """ Same as ObjectDefinition.copy() except can recursively copy services """ copies = [ObjectDefinition.copy(self, recursive=recursive, filename=filename, **args)] if recursive is True and 'host_name' in args: for i in self.get_effective_services(): copies.append(i.copy(filename=filename, host_name=args.get('host_name'))) return copies def _do_relations(self): super(self.__class__, self)._do_relations() # Do hostgroups hg = AttributeList(self.hostgroups) for i in hg.fields: ObjectRelations.host_hostgroups[self.host_name].add(i) ObjectRelations.hostgroup_hosts[i].add(self.host_name) # Contactgroups cg = AttributeList(self.contact_groups) for i in cg.fields: ObjectRelations.host_contact_groups[self.host_name].add(i) ObjectRelations.contactgroup_hosts[i].add(self.get_id()) contacts = AttributeList(self.contacts) for i in contacts.fields: ObjectRelations.host_contacts[self.host_name].add(i) ObjectRelations.contact_hosts[i].add(self.get_id()) if self.check_command: command_name = self.check_command.split('!')[0] ObjectRelations.command_service[self.host_name].add(command_name) def add_to_hostgroup(self, hostgroup_name): """ Add host to a hostgroup """ hostgroup = Hostgroup.objects.get_by_shortname(hostgroup_name) return _add_object_to_group(self, hostgroup) def remove_from_hostgroup(self, hostgroup_name): """ Removes host from specified hostgroup """ hostgroup = Hostgroup.objects.get_by_shortname(hostgroup_name) return _remove_object_from_group(self, hostgroup) def add_to_contactgroup(self, contactgroup): return _add_to_contactgroup(self, contactgroup) def remove_from_contactgroup(self, contactgroup): return _remove_from_contactgroup(self, contactgroup) def rename(self, shortname): """ Rename this host, and modify related objects """ old_name = self.get_shortname() super(Host, self).rename(shortname) for i in Service.objects.filter(host_name__has_field=old_name): i.attribute_replacefield('host_name', old_name, shortname) i.save() for i in Hostgroup.objects.filter(members__has_field=old_name): i.attribute_replacefield('members', old_name, shortname) i.save() class Service(ObjectDefinition): object_type = 'service' objects = ObjectFetcher('service') def get_shortname(self): host_name = self.host_name service_description = self.service_description if host_name and service_description: return "%s/%s" % (host_name, service_description) elif service_description: return "%s" % (service_description, ) else: return None def _get_host_macro(self, macroname, host_name=None): if not pynag.Utils.is_macro(macroname): return _UNRESOLVED_MACRO if not host_name: host_name = self['host_name'] if not host_name: return _UNRESOLVED_MACRO try: myhost = Host.objects.get_by_shortname(host_name) return myhost._get_host_macro(macroname) except Exception: return _UNRESOLVED_MACRO def _do_relations(self): super(self.__class__, self)._do_relations() # Do hostgroups hg = AttributeList(self.hostgroup_name) for i in hg.fields: ObjectRelations.service_hostgroups[self.get_id()].add(i) ObjectRelations.hostgroup_services[i].add(self.get_id()) # Contactgroups cg = AttributeList(self.contact_groups) for i in cg.fields: ObjectRelations.service_contact_groups[self.get_id()].add(i) ObjectRelations.contactgroup_services[i].add(self.get_id()) contacts = AttributeList(self.contacts) for i in contacts.fields: ObjectRelations.service_contacts[self.get_id()].add(i) ObjectRelations.contact_services[i].add(self.get_id()) sg = AttributeList(self.servicegroups) for i in sg.fields: ObjectRelations.service_servicegroups[self.get_id()].add(i) ObjectRelations.servicegroup_services[i].add(self.get_id()) if self.check_command: command_name = self.check_command.split('!')[0] ObjectRelations.command_service[self.get_id()].add(command_name) hosts = AttributeList(self.host_name) for i in hosts.fields: ObjectRelations.service_hosts[self.get_id()].add(i) ObjectRelations.host_services[i].add(self.get_id()) def acknowledge(self, sticky=1, notify=1, persistent=0, author='pynag', comment='acknowledged by pynag', timestamp=None): if timestamp is None: timestamp = int(time.time()) pynag.Control.Command.acknowledge_svc_problem(host_name=self.host_name, service_description=self.service_description, sticky=sticky, notify=notify, persistent=persistent, author=author, comment=comment, timestamp=timestamp, command_file=config.get_cfg_value('command_file') ) def downtime(self, start_time=None, end_time=None, trigger_id=0, duration=7200, author=None, comment='Downtime scheduled by pynag', recursive=False): """ Put this object in a schedule downtime. Arguments: start_time -- When downtime should start. If None, use time.time() (now) end_time -- When scheduled downtime should end. If None use start_time + duration duration -- Alternative to end_time, downtime lasts for duration seconds. Default 7200 seconds. trigger_id -- trigger_id>0 means that this downtime should trigger another downtime with trigger_id. author -- name of the contact scheduling downtime. If None, use current system user comment -- Comment that will be put in with the downtime recursive -- Here for compatibility. Has no effect on a service. Returns: None because commands sent to nagios have no return values Raises: ModelError if this does not look an active object. """ if recursive is True: pass # Only for compatibility, it has no effect. if self.register == '0': raise ModelError('Cannot schedule a downtime for unregistered object') if not self.host_name: raise ModelError('Cannot schedule a downtime for service with no host_name') if not self.service_description: raise ModelError('Cannot schedule a downtime for service with service_description') if start_time is None: start_time = time.time() if duration is None: duration = 7200 duration = int(duration) if end_time is None: end_time = start_time + duration if author is None: author = getpass.getuser() pynag.Control.Command.schedule_svc_downtime( host_name=self.host_name, service_description=self.service_description, start_time=start_time, end_time=end_time, fixed='1', trigger_id=trigger_id, duration=duration, author=author, comment=comment, ) def get_effective_hosts(self): """ Returns a list of all Host that belong to this Service """ get_object = lambda x: Host.objects.get_by_shortname(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.service_hosts[self.get_id()]) hosts = list(map(get_object, list_of_shortnames)) for hg in self.get_effective_hostgroups(): hosts += hg.get_effective_hosts() return hosts def get_effective_contacts(self): """ Returns a list of all Contact that belong to this Service """ get_object = lambda x: Contact.objects.get_by_shortname(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.service_contacts[self.get_id()]) return list(map(get_object, list_of_shortnames)) def get_effective_contact_groups(self): """ Returns a list of all Contactgroup that belong to this Service """ get_object = lambda x: Contactgroup.objects.get_by_shortname(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.service_contact_groups[self.get_id()]) return list(map(get_object, list_of_shortnames)) def get_effective_hostgroups(self): """ Returns a list of all Hostgroup that belong to this Service """ get_object = lambda x: Hostgroup.objects.get_by_shortname(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.service_hostgroups[self.get_id()]) return list(map(get_object, list_of_shortnames)) def get_effective_servicegroups(self): """ Returns a list of all Servicegroup that belong to this Service """ get_object = lambda x: Servicegroup.objects.get_by_shortname(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.service_servicegroups[self.get_id()]) return list(map(get_object, list_of_shortnames)) def get_effective_check_command(self): """ Returns a Command object as defined by check_command attribute Raises KeyError if check_command is not found or not defined. """ c = self.check_command if not c or c == '': raise KeyError(None) check_command = c.split('!')[0] return Command.objects.get_by_shortname(check_command, cache_only=True) def get_current_status(self, status=None): """ Returns a dictionary with status data information for this object :param status: pynag.Parsers.status_dat.StatusDat instance """ if not status: status = pynag.Parsers.status_dat.StatusDat(cfg_file=cfg_file) service = status.get_servicestatus(self.host_name, service_description=self.service_description) return service def add_to_servicegroup(self, servicegroup_name): """ Add this service to a specific servicegroup """ sg = Servicegroup.objects.get_by_shortname(servicegroup_name) return _add_object_to_group(self, sg) def remove_from_servicegroup(self, servicegroup_name): """ remove this service from a specific servicegroup """ sg = Servicegroup.objects.get_by_shortname(servicegroup_name) return _remove_object_from_group(self, sg) def add_to_contactgroup(self, contactgroup): return _add_to_contactgroup(self, contactgroup) def remove_from_contactgroup(self, contactgroup): return _remove_from_contactgroup(self, contactgroup) def merge_with_host(self): """ Moves a service from its original file to the same file as the first effective host """ if not self.host_name: return else: host = Host.objects.get_by_shortname(self.host_name) host_filename = host.get_filename() if host_filename != self.get_filename(): new_serv = self.move(host_filename) new_serv.save() def rename(self, shortname): """ Not implemented. Do not use. """ raise Exception("Not implemented for service.") class Command(ObjectDefinition): object_type = 'command' objects = ObjectFetcher('command') def rename(self, shortname): """ Rename this command, and reconfigure all related objects """ old_name = self.get_shortname() super(Command, self).rename(shortname) objects = ObjectDefinition.objects.filter(check_command=old_name) # TODO: Do something with objects that have check_command!ARGS!ARGS #objects += ObjectDefinition.objects.filter(check_command__startswith="%s!" % old_name) for i in objects: # Skip objects that are inheriting this from a template if not i.is_defined("check_command"): continue i.check_command = shortname i.save() class Contact(ObjectDefinition): object_type = 'contact' objects = ObjectFetcher('contact') def get_effective_contactgroups(self): """ Get a list of all Contactgroup that are hooked to this contact """ get_object = lambda x: Contactgroup.objects.get_by_shortname(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.contact_contactgroups[self.contact_name]) return list(map(get_object, list_of_shortnames)) def get_effective_hosts(self): """ Get a list of all Host that are hooked to this Contact """ result = set() # First add all hosts that name this contact specifically get_object = lambda x: Host.objects.get_by_id(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.contact_hosts[self.contact_name]) result.update(list(map(get_object, list_of_shortnames))) # Next do the same for all contactgroups this contact belongs in for i in self.get_effective_contactgroups(): result.update(i.get_effective_hosts()) return result def get_effective_services(self): """ Get a list of all Service that are hooked to this Contact """ result = set() # First add all services that name this contact specifically get_object = lambda x: Service.objects.get_by_id(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.contact_services[self.contact_name]) result.update(list(map(get_object, list_of_shortnames))) return result def _get_contact_macro(self, macroname, contact_name=None): if not pynag.Utils.is_macro(macroname): return _UNRESOLVED_MACRO if macroname in macros.STANDARD_CONTACT_MACROS: attribute_name = macros.STANDARD_CONTACT_MACROS[macroname] elif macroname.startswith('$_CONTACT'): return self._get_custom_variable_macro(macroname) elif macroname.startswith('$CONTACT'): # Lets guess an attribute for this macro # So convert $CONTACTEMAIL$ to email name = macroname[len('$CONTACT'):-1] attribute_name = name.lower() else: return _UNRESOLVED_MACRO return self.get(attribute_name, _UNRESOLVED_MACRO) def _do_relations(self): super(self.__class__, self)._do_relations() groups = AttributeList(self.contactgroups) for i in groups.fields: ObjectRelations.contact_contactgroups[self.contact_name].add(i) ObjectRelations.contactgroup_contacts[i].add(self.contact_name) def add_to_contactgroup(self, contactgroup): return _add_to_contactgroup(self, contactgroup) def remove_from_contactgroup(self, contactgroup): return _remove_from_contactgroup(self, contactgroup) def delete(self, recursive=False, cleanup_related_items=True): """ Delete this contact and optionally remove references in groups and escalations Works like ObjectDefinition.delete() except: Arguments: cleanup_related_items -- If True, remove all references to this contact in contactgroups and escalations recursive -- If True, remove escalations/dependencies that rely on this (and only this) contact """ if recursive is True: # No object is 100% dependent on a contact pass if cleanup_related_items is True and self.contact_name: contactgroups = Contactgroup.objects.filter(members__has_field=self.contact_name) hostSvcAndEscalations = ObjectDefinition.objects.filter(contacts__has_field=self.contact_name) # will find references in Hosts, Services as well as Host/Service-escalations for i in contactgroups: # remove contact from contactgroups i.attribute_removefield('members', self.contact_name) i.save() for i in hostSvcAndEscalations: # remove contact from objects i.attribute_removefield('contacts', self.contact_name) if (i.get_attribute('object_type').endswith("escalation") and recursive is True and i.attribute_is_empty("contacts") and i.attribute_is_empty("contact_groups")): # no contacts or contact_groups defined for this escalation i.delete(recursive=recursive, cleanup_related_items=cleanup_related_items) else: i.save() # Call parent to get delete myself return super(self.__class__, self).delete(recursive=recursive, cleanup_related_items=cleanup_related_items) def rename(self, shortname): """ Renames this object, and triggers a change in related items as well. Args: shortname: New name for this object Returns: None """ old_name = self.contact_name super(Contact, self).rename(shortname) for i in Host.objects.filter(contacts__has_field=old_name): i.attribute_replacefield('contacts', old_name, shortname) i.save() for i in Service.objects.filter(contacts__has_field=old_name): i.attribute_replacefield('contacts', old_name, shortname) i.save() for i in Contactgroup.objects.filter(members__has_field=old_name): i.attribute_replacefield('members', old_name, shortname) i.save() class ServiceDependency(ObjectDefinition): object_type = 'servicedependency' objects = ObjectFetcher('servicedependency') class HostDependency(ObjectDefinition): object_type = 'hostdependency' objects = ObjectFetcher('hostdependency') class HostEscalation(ObjectDefinition): object_type = 'hostescalation' objects = ObjectFetcher('hostescalation') class ServiceEscalation(ObjectDefinition): object_type = 'serviceescalation' objects = ObjectFetcher('serviceescalation') class Contactgroup(ObjectDefinition): object_type = 'contactgroup' objects = ObjectFetcher('contactgroup') def get_effective_contactgroups(self): """ Returns a list of every Contactgroup that is a member of this Contactgroup """ get_object = lambda x: Contactgroup.objects.get_by_shortname(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.contactgroup_subgroups[self.contactgroup_name]) return list(map(get_object, list_of_shortnames)) def get_effective_contacts(self): """ Returns a list of every Contact that is a member of this Contactgroup """ get_object = lambda x: Contact.objects.get_by_shortname(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.contactgroup_contacts[self.contactgroup_name]) return list(map(get_object, list_of_shortnames)) def get_effective_hosts(self): """ Return every Host that belongs to this contactgroup """ list_of_shortnames = sorted(ObjectRelations.contactgroup_hosts[self.contactgroup_name]) get_object = lambda x: Host.objects.get_by_id(x, cache_only=True) return list(map(get_object, list_of_shortnames)) def get_effective_services(self): """ Return every Host that belongs to this contactgroup """ # TODO: review this method services = {} for i in Service.objects.all: services[i.get_id()] = i list_of_shortnames = sorted(ObjectRelations.contactgroup_services[self.contactgroup_name]) get_object = lambda x: services[x] return list(map(get_object, list_of_shortnames)) def _do_relations(self): super(self.__class__, self)._do_relations() members = AttributeList(self.members) for i in members.fields: ObjectRelations.contactgroup_contacts[self.contactgroup_name].add(i) ObjectRelations.contact_contactgroups[i].add(self.contactgroup_name) groups = AttributeList(self.contactgroup_members) for i in groups.fields: ObjectRelations.contactgroup_contactgroups[self.contactgroup_name].add(i) def add_contact(self, contact_name): """ Adds one specific contact to this contactgroup. """ contact = Contact.objects.get_by_shortname(contact_name) return _add_to_contactgroup(contact, self) def remove_contact(self, contact_name): """ Remove one specific contact from this contactgroup """ contact = Contact.objects.get_by_shortname(contact_name) return _remove_from_contactgroup(contact, self) def delete(self, recursive=False, cleanup_related_items=True): """ Delete this contactgroup and optionally remove references in hosts/services Works like ObjectDefinition.delete() except: Arguments: cleanup_related_items -- If True, remove all references to this group in hosts,services,etc. recursive -- If True, remove dependant escalations. """ if recursive is True: # No object is 100% dependent on a contactgroup pass if cleanup_related_items is True and self.contactgroup_name: contactgroups = Contactgroup.objects.filter(contactgroup_members__has_field=self.contactgroup_name) contacts = Contact.objects.filter(contactgroups__has_field=self.contactgroup_name) # nagios is inconsistent with the attribute names - notice the missing _ in contactgroups attribute name hostSvcAndEscalations = ObjectDefinition.objects.filter(contact_groups__has_field=self.contactgroup_name) # will find references in Hosts, Services as well as Host/Service-escalations for i in contactgroups: # remove contactgroup from other contactgroups i.attribute_removefield('contactgroup_members', self.contactgroup_name) i.save() for i in contacts: i.attribute_removefield('contactgroups', self.contactgroup_name) i.save() for i in hostSvcAndEscalations: # remove contactgroup from objects i.attribute_removefield('contact_groups', self.contactgroup_name) if (i.get_attribute('object_type').endswith("escalation") and recursive is True and i.attribute_is_empty("contacts") and i.attribute_is_empty("contact_groups")): # no contacts or contact_groups defined for this escalation i.delete(recursive=recursive, cleanup_related_items=cleanup_related_items) else: i.save() # Call parent to get delete myself return super(self.__class__, self).delete(recursive=recursive, cleanup_related_items=cleanup_related_items) def rename(self, shortname): """ Renames this object, and triggers a change in related items as well. Args: shortname: New name for this object Returns: None """ old_name = self.get_shortname() super(Contactgroup, self).rename(shortname) for i in Host.objects.filter(contactgroups__has_field=old_name): i.attribute_replacefield('contactgroups', old_name, shortname) i.save() for i in Service.objects.filter(contactgroups__has_field=old_name): i.attribute_replacefield('contactgroups', old_name, shortname) i.save() for i in Contact.objects.filter(contactgroups__has_field=old_name): i.attribute_replacefield('contactgroups', old_name, shortname) i.save() class Hostgroup(ObjectDefinition): object_type = 'hostgroup' objects = ObjectFetcher('hostgroup') def get_effective_services(self): """ Returns a list of all Service that belong to this hostgroup """ list_of_shortnames = sorted(ObjectRelations.hostgroup_services[self.hostgroup_name]) get_object = lambda x: Service.objects.get_by_id(x, cache_only=True) return list(map(get_object, list_of_shortnames)) def get_effective_hosts(self): """ Returns a list of all Host that belong to this hostgroup """ list_of_shortnames = sorted(ObjectRelations.hostgroup_hosts[self.hostgroup_name]) get_object = lambda x: Host.objects.get_by_shortname(x, cache_only=True) return list(map(get_object, list_of_shortnames)) def get_effective_hostgroups(self): """ Returns a list of every Hostgroup that is a member of this Hostgroup """ get_object = lambda x: Hostgroup.objects.get_by_shortname(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.hostgroup_subgroups[self.hostgroup_name]) return list(map(get_object, list_of_shortnames)) def _do_relations(self): super(self.__class__, self)._do_relations() members = AttributeList(self.members) for i in members.fields: ObjectRelations.hostgroup_hosts[self.hostgroup_name].add(i) ObjectRelations.host_hostgroups[i].add(self.hostgroup_name) groups = AttributeList(self.hostgroup_members) for i in groups.fields: ObjectRelations.hostgroup_hostgroups[self.hostgroup_name].add(i) def add_host(self, host_name): """ Adds host to this group. Behaves like Hostgroup._add_member_to_group """ host = Host.objects.get_by_shortname(host_name) return _add_object_to_group(host, self) def remove_host(self, host_name): """ Remove host from this group. Behaves like Hostgroup._remove_member_from_group """ host = Host.objects.get_by_shortname(host_name) return _remove_object_from_group(host, self) def delete(self, recursive=False, cleanup_related_items=True): """ Delete this hostgroup and optionally remove references in hosts and services Works like ObjectDefinition.delete() except: Arguments: cleanup_related_items -- If True, remove all references to this group in hosts/services,escalations,etc recursive -- If True, remove services and escalations that bind to this (and only this) hostgroup """ if recursive is True and self.hostgroup_name: for i in Service.objects.filter(hostgroup_name=self.hostgroup_name, host_name__exists=False): # remove only if self.hostgroup_name is the only hostgroup and no host_name is specified i.delete(recursive=recursive) if cleanup_related_items is True and self.hostgroup_name: hostgroups = Hostgroup.objects.filter(hostgroup_members__has_field=self.hostgroup_name) hosts = Host.objects.filter(hostgroups__has_field=self.hostgroup_name) dependenciesAndEscalations = ObjectDefinition.objects.filter( hostgroup_name__has_field=self.hostgroup_name, object_type__isnot='hostgroup') for i in hostgroups: # remove hostgroup from other hostgroups i.attribute_removefield('hostgroup_members', self.hostgroup_name) i.save() for i in hosts: # remove hostgroup from hosts i.attribute_removefield('hostgroups', self.hostgroup_name) i.save() for i in dependenciesAndEscalations: # remove from host/service escalations/dependencies i.attribute_removefield('hostgroup_name', self.hostgroup_name) if ((i.get_attribute('object_type').endswith("escalation") or i.get_attribute('object_type').endswith("dependency")) and recursive is True and i.attribute_is_empty("host_name") and i.attribute_is_empty("hostgroup_name")): i.delete(recursive=recursive, cleanup_related_items=cleanup_related_items) else: i.save() # get these here as we might have deleted some in the block above dependencies = ObjectDefinition.objects.filter(dependent_hostgroup_name__has_field=self.hostgroup_name) for i in dependencies: # remove from host/service escalations/dependencies i.attribute_removefield('dependent_hostgroup_name', self.hostgroup_name) if (i.get_attribute('object_type').endswith("dependency") and recursive is True and i.attribute_is_empty("dependent_host_name") and i.attribute_is_empty("dependent_hostgroup_name")): i.delete(recursive=recursive, cleanup_related_items=cleanup_related_items) else: i.save() # Call parent to get delete myself return super(self.__class__, self).delete(recursive=recursive, cleanup_related_items=cleanup_related_items) def downtime(self, start_time=None, end_time=None, trigger_id=0, duration=7200, author=None, comment='Downtime scheduled by pynag', recursive=False): """ Put every host and service in this hostgroup in a schedule downtime. Arguments: start_time -- When downtime should start. If None, use time.time() (now) end_time -- When scheduled downtime should end. If None use start_time + duration duration -- Alternative to end_time, downtime lasts for duration seconds. Default 7200 seconds. trigger_id -- trigger_id>0 means that this downtime should trigger another downtime with trigger_id. author -- name of the contact scheduling downtime. If None, use current system user comment -- Comment that will be put in with the downtime recursive -- For compatibility with other downtime commands, recursive is always assumed to be true Returns: None because commands sent to nagios have no return values Raises: ModelError if this does not look an active object. """ if recursive is True: pass # Not used, but is here for backwards compatibility if self.register == '0': raise ModelError('Cannot schedule a downtime for unregistered object') if not self.hostgroup_name: raise ModelError('Cannot schedule a downtime for hostgroup with no hostgroup_name') if start_time is None: start_time = time.time() if duration is None: duration = 7200 duration = int(duration) if end_time is None: end_time = start_time + duration if author is None: author = getpass.getuser() arguments = { 'hostgroup_name': self.hostgroup_name, 'start_time': start_time, 'end_time': end_time, 'fixed': '1', 'trigger_id': trigger_id, 'duration': duration, 'author': author, 'comment': comment, } pynag.Control.Command.schedule_hostgroup_host_downtime(**arguments) pynag.Control.Command.schedule_hostgroup_svc_downtime(**arguments) def rename(self, shortname): """ Rename this hostgroup, and modify hosts if required """ old_name = self.get_shortname() super(Hostgroup, self).rename(shortname) for i in Host.objects.filter(hostgroups__has_field=old_name): if not i.is_defined('hostgroups'): continue i.attribute_replacefield('hostgroups', old_name, shortname) i.save() class Servicegroup(ObjectDefinition): object_type = 'servicegroup' objects = ObjectFetcher('servicegroup') def get_effective_services(self): """ Returns a list of all Service that belong to this Servicegroup """ list_of_shortnames = sorted(ObjectRelations.servicegroup_services[self.servicegroup_name]) get_object = lambda x: Service.objects.get_by_id(x, cache_only=True) return list(map(get_object, list_of_shortnames)) def get_effective_servicegroups(self): """ Returns a list of every Servicegroup that is a member of this Servicegroup """ get_object = lambda x: Servicegroup.objects.get_by_shortname(x, cache_only=True) list_of_shortnames = sorted(ObjectRelations.servicegroup_subgroups[self.servicegroup_name]) return list(map(get_object, list_of_shortnames)) def add_service(self, shortname): """ Adds service to this group. Behaves like _add_object_to_group(object, group)""" service = Service.objects.get_by_shortname(shortname) return _add_object_to_group(service, self) def remove_service(self, shortname): """ remove service from this group. Behaves like _remove_object_from_group(object, group)""" service = Service.objects.get_by_shortname(shortname) return _remove_object_from_group(service, self) def _do_relations(self): super(self.__class__, self)._do_relations() # Members directive for the servicegroup is members = host1,service1,host2,service2,...,hostn,servicen members = AttributeList(self.members).fields while len(members) > 1: host_name = members.pop(0) service_description = members.pop(0) shortname = '%s/%s' % (host_name, service_description) ObjectRelations.servicegroup_members[self.servicegroup_name].add(shortname) # Handle servicegroup_members groups = AttributeList(self.servicegroup_members) for i in groups.fields: ObjectRelations.servicegroup_servicegroups[self.servicegroup_name].add(i) def downtime(self, start_time=None, end_time=None, trigger_id=0, duration=7200, author=None, comment='Downtime scheduled by pynag', recursive=False): """ Put every host and service in this servicegroup in a schedule downtime. Arguments: start_time -- When downtime should start. If None, use time.time() (now) end_time -- When scheduled downtime should end. If None use start_time + duration duration -- Alternative to end_time, downtime lasts for duration seconds. Default 7200 seconds. trigger_id -- trigger_id>0 means that this downtime should trigger another downtime with trigger_id. author -- name of the contact scheduling downtime. If None, use current system user comment -- Comment that will be put in with the downtime recursive -- For compatibility with other downtime commands, recursive is always assumed to be true Returns: None because commands sent to nagios have no return values Raises: ModelError if this does not look an active object. """ if recursive is True: pass # Its here for compatibility but we dont do anything with it. if self.register == '0': raise ModelError('Cannot schedule a downtime for unregistered object') if not self.servicegroup_name: raise ModelError('Cannot schedule a downtime for servicegroup with no servicegroup_name') if start_time is None: start_time = time.time() if duration is None: duration = 7200 duration = int(duration) if end_time is None: end_time = start_time + duration if author is None: author = getpass.getuser() arguments = { 'servicegroup_name': self.servicegroup_name, 'start_time': start_time, 'end_time': end_time, 'fixed': '1', 'trigger_id': trigger_id, 'duration': duration, 'author': author, 'comment': comment, } pynag.Control.Command.schedule_servicegroup_host_downtime(**arguments) pynag.Control.Command.schedule_servicegroup_svc_downtime(**arguments) class Timeperiod(ObjectDefinition): object_type = 'timeperiod' objects = ObjectFetcher('timeperiod') def _add_object_to_group(my_object, my_group): """ Add one specific object to a specified objectgroup Examples: c = Contact() g = Contactgroup() _add_to_group(c, g ) """ # First of all, we behave a little differently depending on what type of an object we lets define some variables: group_type = my_group.object_type # contactgroup,hostgroup,servicegroup group_name = my_group.get_shortname() # admins object_name = my_object.get_shortname() # root group_field = 'members' # i.e. Contactgroup.members object_field = group_type + 's' # i.e. Host.hostgroups groups = my_object[object_field] or '' # f.e. value of Contact.contactgroups list_of_groups = pynag.Utils.AttributeList(groups) members = my_group[group_field] or '' # f.e. Value of Contactgroup.members list_of_members = pynag.Utils.AttributeList(members) if group_name in list_of_groups: return False # Group says it already has object as a member if object_name in list_of_members: return False # Member says it is already part of group my_object.attribute_appendfield(object_field, group_name) my_object.save() return True def _remove_object_from_group(my_object, my_group): """ Remove one specific object to a specified objectgroup Examples: c = Contact() g = Contactgroup() _remove_object_from_group(c, g ) """ # First of all, we behave a little differently depending on what type of an object we lets define some variables: group_type = my_group.object_type # contactgroup,hostgroup,servicegroup group_name = my_group.get_shortname() # admins object_name = my_object.get_shortname() # root group_field = 'members' # i.e. Contactgroup.members object_field = group_type + 's' # i.e. Host.hostgroups groups = my_object[object_field] or '' # e. value of Contact.contactgroups list_of_groups = pynag.Utils.AttributeList(groups) members = my_group[group_field] or '' # f.e. Value of Contactgroup.members list_of_members = pynag.Utils.AttributeList(members) if group_name in list_of_groups: # Remove group from the object my_object.attribute_removefield(object_field, group_name) my_object.save() if object_name in list_of_members: # Remove object from the group my_group.attribute_removefield(group_field, object_name) my_group.save() def _add_to_contactgroup(my_object, contactgroup): """ add Host or Service to a contactgroup """ if isinstance(contactgroup, six.string_types): contactgroup = Contactgroup.objects.get_by_shortname(contactgroup) contactgroup_name = contactgroup.contactgroup_name if my_object.object_type == "contact": return _add_object_to_group(my_object, contactgroup) current_contactgroups = AttributeList(my_object.contact_groups) if contactgroup_name not in current_contactgroups.fields: my_object.attribute_appendfield('contact_groups', contactgroup_name) my_object.save() return True else: return False def _remove_from_contactgroup(my_object, contactgroup): """ remove Host or Service from a contactgroup """ if isinstance(contactgroup, six.string_types): contactgroup = Contactgroup.objects.get_by_shortname(contactgroup) contactgroup_name = contactgroup.contactgroup_name if my_object.object_type == "contact": return _remove_object_from_group(my_object, contactgroup) current_contactgroups = AttributeList(my_object.contact_groups) if contactgroup_name in current_contactgroups.fields: my_object.attribute_removefield('contact_groups', contactgroup_name) my_object.save() return True else: return False string_to_class = {} string_to_class['contact'] = Contact string_to_class['service'] = Service string_to_class['host'] = Host string_to_class['hostgroup'] = Hostgroup string_to_class['contactgroup'] = Contactgroup string_to_class['servicegroup'] = Servicegroup string_to_class['timeperiod'] = Timeperiod string_to_class['hostdependency'] = HostDependency string_to_class['servicedependency'] = ServiceDependency string_to_class['hostescalation'] = HostEscalation string_to_class['serviceescalation'] = ServiceEscalation string_to_class['command'] = Command #string_to_class[None] = ObjectDefinition # Attributelist is put here for backwards compatibility AttributeList = pynag.Utils.AttributeList def _add_property(ClassType, name): """ Create a dynamic property specific ClassType object_definition = ClassType() object_definition.name -> object_definition['name' So in human speak, this reads info from all_attributes and makes sure that Host has Host.host_name Returns: None """ fget = lambda self: self[name] fset = lambda self, value: self.set_attribute(name, value) fdel = lambda self: self.set_attribute(name, None) fdoc = "This is the %s attribute for object definition" setattr(ClassType, name, property(fget, fset, fdel, fdoc)) # Add register, name and use to all objects _add_property(ObjectDefinition, 'register') _add_property(ObjectDefinition, 'name') _add_property(ObjectDefinition, 'use') # For others, create attributes dynamically based on all_attributes.keys() for object_type, attributes in all_attributes.object_definitions.items(): # Lets find common attributes that every object definition should have: if object_type == 'any': continue if object_type not in string_to_class: continue Object = string_to_class[object_type] for attribute in attributes: _add_property(Object, attribute) if __name__ == '__main__': pass