From 8ca6cc32b2c789a3149861159ad258f2cb9491e3 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 14:39:39 +0200 Subject: Adding upstream version 2.11.4. Signed-off-by: Daniel Baumann --- .../library/Monitoring/Object/Acknowledgement.php | 215 +++++ .../monitoring/library/Monitoring/Object/Host.php | 204 +++++ .../library/Monitoring/Object/HostList.php | 133 +++ .../monitoring/library/Monitoring/Object/Macro.php | 82 ++ .../library/Monitoring/Object/MonitoredObject.php | 930 +++++++++++++++++++++ .../library/Monitoring/Object/ObjectList.php | 293 +++++++ .../library/Monitoring/Object/Service.php | 219 +++++ .../library/Monitoring/Object/ServiceList.php | 184 ++++ 8 files changed, 2260 insertions(+) create mode 100644 modules/monitoring/library/Monitoring/Object/Acknowledgement.php create mode 100644 modules/monitoring/library/Monitoring/Object/Host.php create mode 100644 modules/monitoring/library/Monitoring/Object/HostList.php create mode 100644 modules/monitoring/library/Monitoring/Object/Macro.php create mode 100644 modules/monitoring/library/Monitoring/Object/MonitoredObject.php create mode 100644 modules/monitoring/library/Monitoring/Object/ObjectList.php create mode 100644 modules/monitoring/library/Monitoring/Object/Service.php create mode 100644 modules/monitoring/library/Monitoring/Object/ServiceList.php (limited to 'modules/monitoring/library/Monitoring/Object') diff --git a/modules/monitoring/library/Monitoring/Object/Acknowledgement.php b/modules/monitoring/library/Monitoring/Object/Acknowledgement.php new file mode 100644 index 0000000..3cd0d20 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/Acknowledgement.php @@ -0,0 +1,215 @@ +setProperties($properties); + } + } + + /** + * Get the author of the acknowledgement + * + * @return string + */ + public function getAuthor() + { + return $this->author; + } + + /** + * Set the author of the acknowledgement + * + * @param string $author + * + * @return $this + */ + public function setAuthor($author) + { + $this->author = (string) $author; + return $this; + } + + /** + * Get the comment of the acknowledgement + * + * @return string + */ + public function getComment() + { + return $this->comment; + } + + /** + * Set the comment of the acknowledgement + * + * @param string $comment + * + * @return $this + */ + public function setComment($comment) + { + $this->comment = (string) $comment; + + return $this; + } + + /** + * Get the entry time of the acknowledgement + * + * @return int + */ + public function getEntryTime() + { + return $this->entryTime; + } + + /** + * Set the entry time of the acknowledgement + * + * @param int $entryTime + * + * @return $this + */ + public function setEntryTime($entryTime) + { + $this->entryTime = (int) $entryTime; + + return $this; + } + + /** + * Get the expiration time of the acknowledgement + * + * @return int|null + */ + public function getExpirationTime() + { + return $this->expirationTime; + } + + /** + * Set the expiration time of the acknowledgement + * + * @param int|null $expirationTime Unix timestamp + * + * @return $this + */ + public function setExpirationTime($expirationTime = null) + { + $this->expirationTime = $expirationTime !== null ? (int) $expirationTime : null; + + return $this; + } + + /** + * Get whether the acknowledgement is sticky + * + * @return bool + */ + public function getSticky() + { + return $this->sticky; + } + + /** + * Set whether the acknowledgement is sticky + * + * @param bool $sticky + * + * @return $this + */ + public function setSticky($sticky = true) + { + $this->sticky = (bool) $sticky; + return $this; + } + + /** + * Get whether the acknowledgement expires + * + * @return bool + */ + public function expires() + { + return $this->expirationTime !== null; + } + + /** + * Set the properties of the acknowledgement + * + * @param array|object|Traversable $properties + * + * @return $this + * @throws InvalidArgumentException If the type of the given properties is invalid + */ + public function setProperties($properties) + { + if (! is_array($properties) && ! is_object($properties) && ! $properties instanceof Traversable) { + throw new InvalidArgumentException('Properties must be either an array or an instance of Traversable'); + } + foreach ($properties as $name => $value) { + $setter = 'set' . ucfirst(StringHelper::cname($name)); + if (method_exists($this, $setter)) { + $this->$setter($value); + } + } + return $this; + } +} diff --git a/modules/monitoring/library/Monitoring/Object/Host.php b/modules/monitoring/library/Monitoring/Object/Host.php new file mode 100644 index 0000000..dfb25ed --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/Host.php @@ -0,0 +1,204 @@ +host = $host; + } + + /** + * Get the hostname + * + * @return string + */ + public function getName() + { + return $this->host; + } + + /** + * Get the data view to fetch the host information from + * + * @return \Icinga\Module\Monitoring\DataView\HostStatus + */ + protected function getDataView() + { + $columns = array( + 'host_acknowledged', + 'host_acknowledgement_type', + 'host_action_url', + 'host_active_checks_enabled', + 'host_active_checks_enabled_changed', + 'host_address', + 'host_address6', + 'host_alias', + 'host_attempt', + 'host_check_command', + 'host_check_execution_time', + 'host_check_interval', + 'host_check_latency', + 'host_check_source', + 'host_check_timeperiod', + 'host_current_check_attempt', + 'host_current_notification_number', + 'host_display_name', + 'host_event_handler_enabled', + 'host_event_handler_enabled_changed', + 'host_flap_detection_enabled', + 'host_flap_detection_enabled_changed', + 'host_handled', + 'host_icon_image', + 'host_icon_image_alt', + 'host_in_downtime', + 'host_is_flapping', + 'host_is_reachable', + 'host_last_check', + 'host_last_notification', + 'host_last_state_change', + 'host_long_output', + 'host_max_check_attempts', + 'host_name', + 'host_next_check', + 'host_next_update', + 'host_notes', + 'host_notes_url', + 'host_notifications_enabled', + 'host_notifications_enabled_changed', + 'host_obsessing', + 'host_obsessing_changed', + 'host_output', + 'host_passive_checks_enabled', + 'host_passive_checks_enabled_changed', + 'host_percent_state_change', + 'host_perfdata', + 'host_process_perfdata' => 'host_process_performance_data', + 'host_state', + 'host_state_type', + 'instance_name' + ); + return $this->backend->select()->from('hoststatus', $columns) + ->whereEx(new FilterEqual('host_name', '=', $this->host)); + } + + /** + * Fetch the services running on the host + * + * @return $this + */ + public function fetchServices() + { + $services = array(); + foreach ($this->backend->select()->from('servicestatus', array('service_description')) + ->where('host_name', $this->host) + ->applyFilter($this->getFilter()) + ->getQuery() as $service) { + $services[] = new Service($this->backend, $this->host, $service->service_description); + } + $this->services = $services; + return $this; + } + + /** + * Get the optional translated textual representation of a host state + * + * @param int $state + * @param bool $translate + * + * @return string + * @throws InvalidArgumentException If the host state is not valid + */ + public static function getStateText($state, $translate = false) + { + $translate = (bool) $translate; + switch ((int) $state) { + case self::STATE_UP: + $text = $translate ? mt('monitoring', 'UP') : 'up'; + break; + case self::STATE_DOWN: + $text = $translate ? mt('monitoring', 'DOWN') : 'down'; + break; + case self::STATE_UNREACHABLE: + $text = $translate ? mt('monitoring', 'UNREACHABLE') : 'unreachable'; + break; + case self::STATE_PENDING: + $text = $translate ? mt('monitoring', 'PENDING') : 'pending'; + break; + default: + throw new InvalidArgumentException('Invalid host state \'%s\'', $state); + } + return $text; + } + + public function getNotesUrls() + { + return $this->resolveAllStrings( + MonitoredObject::parseAttributeUrls($this->host_notes_url) + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Object/HostList.php b/modules/monitoring/library/Monitoring/Object/HostList.php new file mode 100644 index 0000000..8b1947d --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/HostList.php @@ -0,0 +1,133 @@ +backend->select()->from($this->dataViewName, $this->columns)->applyFilter($this->filter) + ->getQuery()->getSelectQuery()->query(); + foreach ($query as $row) { + /** @var object $row */ + $host = new Host($this->backend, $row->host_name); + $host->setProperties($row); + $hosts[] = $host; + } + return $hosts; + } + + /** + * Create a state summary of all hosts that can be consumed by hostssummary.phtml + * + * @return SimpleQuery + */ + public function getStateSummary() + { + $hostStates = array_fill_keys(self::getHostStatesSummaryEmpty(), 0); + foreach ($this as $host) { + $unhandled = (bool) $host->problem === true && (bool) $host->handled === false; + + $stateName = 'hosts_' . $host::getStateText($host->state); + ++$hostStates[$stateName]; + ++$hostStates[$stateName. ($unhandled ? '_unhandled' : '_handled')]; + } + + $hostStates['hosts_total'] = count($this); + + $ds = new ArrayDatasource(array((object) $hostStates)); + return $ds->select(); + } + + /** + * Return an empty array with all possible host state names + * + * @return array An array containing all possible host states as keys and 0 as values. + */ + public static function getHostStatesSummaryEmpty() + { + return StringHelper::cartesianProduct( + array( + array('hosts'), + array( + Host::getStateText(Host::STATE_UP), + Host::getStateText(Host::STATE_DOWN), + Host::getStateText(Host::STATE_UNREACHABLE), + Host::getStateText(Host::STATE_PENDING) + ), + array(null, 'handled', 'unhandled') + ), + '_' + ); + } + + /** + * Returns a Filter that matches all hosts in this list + * + * @return Filter + */ + public function objectsFilter($columns = array('host' => 'host')) + { + $filterExpression = array(); + foreach ($this as $host) { + $filterExpression[] = Filter::where($columns['host'], $host->getName()); + } + return FilterOr::matchAny($filterExpression); + } + + /** + * Get the comments + * + * @return \Icinga\Module\Monitoring\DataView\Hostcomment + */ + public function getComments() + { + return $this->backend + ->select() + ->from('hostcomment', array('host_name')) + ->applyFilter(clone $this->filter); + } + + /** + * Get the scheduled downtimes + * + * @return \Icinga\Module\Monitoring\DataView\Hostdowntime + */ + public function getScheduledDowntimes() + { + return $this->backend + ->select() + ->from('hostdowntime', array('host_name')) + ->applyFilter(clone $this->filter); + } + + /** + * @return ObjectList + */ + public function getUnacknowledgedObjects() + { + $unhandledObjects = array(); + foreach ($this as $object) { + if (! in_array((int) $object->state, array(0, 99)) && + (bool) $object->host_acknowledged === false) { + $unhandledObjects[] = $object; + } + } + return $this->newFromArray($unhandledObjects); + } +} diff --git a/modules/monitoring/library/Monitoring/Object/Macro.php b/modules/monitoring/library/Monitoring/Object/Macro.php new file mode 100644 index 0000000..3f67154 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/Macro.php @@ -0,0 +1,82 @@ + 'host_name', + 'HOSTADDRESS' => 'host_address', + 'HOSTADDRESS6' => 'host_address6', + 'SERVICEDESC' => 'service_description', + 'host.name' => 'host_name', + 'host.address' => 'host_address', + 'host.address6' => 'host_address6', + 'service.description' => 'service_description', + 'service.name' => 'service_description' + ); + + /** + * Return the given string with macros being resolved + * + * @param string $input The string in which to look for macros + * @param MonitoredObject|stdClass $object The host or service used to resolve macros + * + * @return string The substituted or unchanged string + */ + public static function resolveMacros($input, $object) + { + $matches = array(); + if (preg_match_all('@\$([^\$\s]+)\$@', $input, $matches)) { + foreach ($matches[1] as $key => $value) { + $newValue = self::resolveMacro($value, $object); + if ($newValue !== $value) { + $input = str_replace($matches[0][$key], $newValue, $input); + } + } + } + + return $input; + } + + /** + * Resolve a macro based on the given object + * + * @param string $macro The macro to resolve + * @param MonitoredObject|stdClass $object The object used to resolve the macro + * + * @return string The new value or the macro if it cannot be resolved + */ + public static function resolveMacro($macro, $object) + { + if (isset(self::$icingaMacros[$macro]) && isset($object->{self::$icingaMacros[$macro]})) { + return $object->{self::$icingaMacros[$macro]}; + } + + try { + $value = $object->$macro; + } catch (Exception $e) { + $objectName = $object->getName(); + if ($object instanceof Service) { + $objectName = $object->getHost()->getName() . '!' . $objectName; + } + + $value = null; + Logger::debug('Unable to resolve macro "%s" on object "%s". An error occured: %s', $macro, $objectName, $e); + } + + return $value !== null ? $value : $macro; + } +} diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php new file mode 100644 index 0000000..91fd9e7 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -0,0 +1,930 @@ +backend = $backend; + } + + /** + * Get the object's data view + * + * @return \Icinga\Module\Monitoring\DataView\DataView + */ + abstract protected function getDataView(); + + /** + * Get all note urls configured for this monitored object + * + * @return array All note urls as a string + */ + abstract public function getNotesUrls(); + + /** + * {@inheritdoc} + */ + public function addFilter(Filter $filter) + { + // Left out on purpose. Interface is deprecated. + } + + /** + * {@inheritdoc} + */ + public function applyFilter(Filter $filter) + { + $this->getFilter()->addFilter($filter); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFilter() + { + if ($this->filter === null) { + $this->filter = Filter::matchAll(); + } + + return $this->filter; + } + + /** + * {@inheritdoc} + */ + public function setFilter(Filter $filter) + { + // Left out on purpose. Interface is deprecated. + } + + /** + * {@inheritdoc} + */ + public function where($condition, $value = null) + { + // Left out on purpose. Interface is deprecated. + } + + /** + * Require the object's type to be one of the given types + * + * @param array $oneOf + * + * @return bool + * @throws InvalidArgumentException If the object's type is not one of the given types. + */ + public function assertOneOf(array $oneOf) + { + if (! in_array($this->type, $oneOf)) { + throw new InvalidArgumentException; + } + return true; + } + + /** + * Fetch the object's properties + * + * @return bool + */ + public function fetch() + { + $properties = $this->getDataView()->applyFilter($this->getFilter())->getQuery()->fetchRow(); + + if ($properties === false) { + return false; + } + + if (isset($properties->host_contacts)) { + $this->contacts = array(); + foreach (preg_split('~,~', $properties->host_contacts) as $contact) { + $this->contacts[] = (object) array( + 'contact_name' => $contact, + 'contact_alias' => $contact, + 'contact_email' => null, + 'contact_pager' => null, + ); + } + } + + $this->properties = $properties; + + return true; + } + + /** + * Fetch the object's acknowledgement + */ + public function fetchAcknowledgement() + { + if ($this->comments === null) { + $this->fetchComments(); + } + + return $this; + } + + /** + * Fetch the object's comments + * + * @return $this + */ + public function fetchComments() + { + $commentsView = $this->backend->select()->from('comment', array( + 'author' => 'comment_author_name', + 'comment' => 'comment_data', + 'expiration' => 'comment_expiration', + 'id' => 'comment_internal_id', + 'name' => 'comment_name', + 'persistent' => 'comment_is_persistent', + 'timestamp' => 'comment_timestamp', + 'type' => 'comment_type' + )); + if ($this->type === self::TYPE_SERVICE) { + $commentsView + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + } else { + $commentsView->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + } + $commentsView + ->whereEx(new FilterEqual('comment_type', '=', ['ack', 'comment'])) + ->whereEx(new FilterEqual('object_type', '=', $this->type)); + + $comments = $commentsView->fetchAll(); + + if ((bool) $this->properties->{$this->prefix . 'acknowledged'}) { + $ackCommentIdx = null; + + foreach ($comments as $i => $comment) { + if ($comment->type === 'ack') { + $this->acknowledgement = new Acknowledgement(array( + 'author' => $comment->author, + 'comment' => $comment->comment, + 'entry_time' => $comment->timestamp, + 'expiration_time' => $comment->expiration, + 'sticky' => (int) $this->properties->{$this->prefix . 'acknowledgement_type'} === 2 + )); + $ackCommentIdx = $i; + break; + } + } + + if ($ackCommentIdx !== null) { + unset($comments[$ackCommentIdx]); + } + } + + $this->comments = $comments; + + return $this; + } + + /** + * Fetch the object's contact groups + * + * @return $this + */ + public function fetchContactgroups() + { + $contactsGroups = $this->backend->select()->from('contactgroup', array( + 'contactgroup_name', + 'contactgroup_alias' + )); + if ($this->type === self::TYPE_SERVICE) { + $contactsGroups + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + } else { + $contactsGroups->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + } + $this->contactgroups = $contactsGroups; + return $this; + } + + /** + * Fetch the object's contacts + * + * @return $this + */ + public function fetchContacts() + { + $contacts = $this->backend->select()->from("{$this->type}contact", array( + 'contact_name', + 'contact_alias', + 'contact_email', + 'contact_pager', + )); + if ($this->type === self::TYPE_SERVICE) { + $contacts + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + } else { + $contacts->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + } + $this->contacts = $contacts; + return $this; + } + + /** + * Fetch this object's obfuscated custom variables + * + * @return $this + */ + public function fetchCustomvars() + { + + if ($this->type === self::TYPE_SERVICE) { + $this->fetchServiceVariables(); + $customvars = $this->serviceVariables; + } else { + $this->fetchHostVariables(); + $customvars = $this->hostVariables; + } + + $this->customvars = $customvars; + $this->hideBlacklistedProperties(); + $this->customvars = $this->obfuscateCustomVars($this->customvars, null); + $this->customvarsWithOriginalNames = $this->obfuscateCustomVars($this->customvarsWithOriginalNames, null); + + return $this; + } + + /** + * Obfuscate custom variables recursively + * + * @param stdClass|array $customvars The custom variables to obfuscate + * + * @return stdClass|array The obfuscated custom variables + */ + protected function obfuscateCustomVars($customvars, $_) + { + return self::protectCustomVars($customvars); + } + + public static function protectCustomVars($customvars) + { + $blacklist = []; + $blacklistPattern = ''; + + if (($blacklistConfig = Config::module('monitoring')->get('security', 'protected_customvars', '')) !== '') { + foreach (explode(',', $blacklistConfig) as $customvar) { + $nonWildcards = array(); + foreach (explode('*', $customvar) as $nonWildcard) { + $nonWildcards[] = preg_quote($nonWildcard, '/'); + } + $blacklist[] = implode('.*', $nonWildcards); + } + $blacklistPattern = '/^(' . implode('|', $blacklist) . ')$/i'; + } + + if (! $blacklistPattern) { + return $customvars; + } + + $obfuscator = function ($vars) use ($blacklistPattern, &$obfuscator) { + $result = []; + foreach ($vars as $name => $value) { + if ($blacklistPattern && preg_match($blacklistPattern, $name)) { + $result[$name] = '***'; + } elseif ($value instanceof stdClass || is_array($value)) { + $obfuscated = $obfuscator($value); + $result[$name] = $value instanceof stdClass ? (object) $obfuscated : $obfuscated; + } else { + $result[$name] = $value; + } + } + + return $result; + }; + $obfuscatedCustomVars = $obfuscator($customvars); + + return $customvars instanceof stdClass ? (object) $obfuscatedCustomVars : $obfuscatedCustomVars; + } + + /** + * Hide all blacklisted properties from the user as restricted by monitoring/blacklist/properties + * + * Currently this only affects the custom variables + */ + protected function hideBlacklistedProperties() + { + if ($this->blacklistedProperties === null) { + $this->blacklistedProperties = new GlobFilter( + Auth::getInstance()->getRestrictions('monitoring/blacklist/properties') + ); + } + + $allProperties = $this->blacklistedProperties->removeMatching( + [$this->type => ['vars' => $this->customvars]] + ); + $this->customvars = isset($allProperties[$this->type]['vars']) + ? $allProperties[$this->type]['vars'] + : []; + + $allProperties = $this->blacklistedProperties->removeMatching( + [$this->type => ['vars' => $this->customvarsWithOriginalNames]] + ); + $this->customvarsWithOriginalNames = isset($allProperties[$this->type]['vars']) + ? $allProperties[$this->type]['vars'] + : []; + } + + /** + * Fetch the host custom variables related to this object + * + * @return $this + */ + public function fetchHostVariables() + { + $query = $this->backend->select()->from('customvar', array( + 'varname', + 'varvalue', + 'is_json' + )) + ->whereEx(new FilterEqual('object_type', '=', static::TYPE_HOST)) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + + $this->hostVariables = []; + + if ($this->type === static::TYPE_HOST) { + $this->customvarsWithOriginalNames = []; + } + + foreach ($query as $row) { + if ($row->is_json) { + $this->hostVariables[strtolower($row->varname)] = json_decode($row->varvalue); + } else { + $this->hostVariables[strtolower($row->varname)] = $row->varvalue; + } + + if ($this->type === static::TYPE_HOST) { + $this->customvarsWithOriginalNames[$row->varname] = $this->hostVariables[strtolower($row->varname)]; + } + } + + return $this; + } + + /** + * Fetch the service custom variables related to this object + * + * @return $this + * + * @throws ProgrammingError In case this object is not a service + */ + public function fetchServiceVariables() + { + if ($this->type !== static::TYPE_SERVICE) { + throw new ProgrammingError('Cannot fetch service custom variables for non-service objects'); + } + + $query = $this->backend->select()->from('customvar', array( + 'varname', + 'varvalue', + 'is_json' + )) + ->whereEx(new FilterEqual('object_type', '=', static::TYPE_SERVICE)) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + + $this->serviceVariables = []; + $this->customvarsWithOriginalNames = []; + foreach ($query as $row) { + if ($row->is_json) { + $this->customvarsWithOriginalNames[$row->varname] = json_decode($row->varvalue); + $this->serviceVariables[strtolower($row->varname)] = $this->customvarsWithOriginalNames[$row->varname]; + } else { + $this->serviceVariables[strtolower($row->varname)] = $row->varvalue; + $this->customvarsWithOriginalNames[$row->varname] = $row->varvalue; + } + } + + return $this; + } + + /** + * Fetch the object's downtimes + * + * @return $this + */ + public function fetchDowntimes() + { + $downtimes = $this->backend->select()->from('downtime', array( + 'author_name' => 'downtime_author_name', + 'comment' => 'downtime_comment', + 'duration' => 'downtime_duration', + 'end' => 'downtime_end', + 'entry_time' => 'downtime_entry_time', + 'id' => 'downtime_internal_id', + 'is_fixed' => 'downtime_is_fixed', + 'is_flexible' => 'downtime_is_flexible', + 'is_in_effect' => 'downtime_is_in_effect', + 'name' => 'downtime_name', + 'objecttype' => 'object_type', + 'scheduled_end' => 'downtime_scheduled_end', + 'scheduled_start' => 'downtime_scheduled_start', + 'start' => 'downtime_start' + )) + ->whereEx(new FilterEqual('object_type', '=', $this->type)) + ->order('downtime_is_in_effect', 'DESC') + ->order('downtime_scheduled_start', 'ASC'); + if ($this->type === self::TYPE_SERVICE) { + $downtimes + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->whereEx(new FilterEqual('service_description', '=', $this->service_description)); + } else { + $downtimes + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + } + $this->downtimes = $downtimes->getQuery()->fetchAll(); + return $this; + } + + /** + * Fetch the object's event history + * + * @return $this + */ + public function fetchEventhistory() + { + $eventHistory = $this->backend + ->select() + ->from( + 'eventhistory', + array( + 'id', + 'object_type', + 'host_name', + 'host_display_name', + 'service_description', + 'service_display_name', + 'timestamp', + 'state', + 'output', + 'type' + ) + ) + ->whereEx(new FilterEqual('object_type', '=', $this->type)) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + + if ($this->type === self::TYPE_SERVICE) { + $eventHistory->whereEx( + new FilterEqual('service_description', '=', $this->service_description) + ); + } + + $this->eventhistory = $eventHistory; + return $this; + } + + /** + * Fetch the object's host groups + * + * @return $this + */ + public function fetchHostgroups() + { + $this->hostgroups = $this->backend->select() + ->from('hostgroup', array('hostgroup_name', 'hostgroup_alias')) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)) + ->applyFilter($this->getFilter()) + ->fetchPairs(); + return $this; + } + + /** + * Fetch the object's service groups + * + * @return $this + */ + public function fetchServicegroups() + { + $query = $this->backend->select() + ->from('servicegroup', array('servicegroup_name', 'servicegroup_alias')) + ->whereEx(new FilterEqual('host_name', '=', $this->host_name)); + + if ($this->type === self::TYPE_SERVICE) { + $query->whereEx( + new FilterEqual('service_description', '=', $this->service_description) + ); + } + + $this->servicegroups = $query->applyFilter($this->getFilter())->fetchPairs(); + return $this; + } + + /** + * Fetch stats + * + * @return $this + */ + public function fetchStats() + { + $this->stats = $this->backend->select()->from('servicestatussummary', array( + 'services_total', + 'services_ok', + 'services_critical', + 'services_critical_unhandled', + 'services_critical_handled', + 'services_warning', + 'services_warning_unhandled', + 'services_warning_handled', + 'services_unknown', + 'services_unknown_unhandled', + 'services_unknown_handled', + 'services_pending', + )) + ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name)) + ->applyFilter($this->getFilter()) + ->fetchRow(); + return $this; + } + + /** + * Get all action urls configured for this monitored object + * + * @return array All note urls as a string + */ + public function getActionUrls() + { + return $this->resolveAllStrings( + MonitoredObject::parseAttributeUrls($this->action_url) + ); + } + + /** + * Get the type of the object + * + * @param bool $translate + * + * @return string + */ + public function getType($translate = false) + { + if ($translate !== false) { + switch ($this->type) { + case self::TYPE_HOST: + $type = mt('montiroing', 'host'); + break; + case self::TYPE_SERVICE: + $type = mt('monitoring', 'service'); + break; + default: + throw new InvalidArgumentException('Invalid type ' . $this->type); + } + } else { + $type = $this->type; + } + return $type; + } + + /** + * Parse the content of the action_url or notes_url attributes + * + * Find all occurences of http links, separated by whitespaces and quoted + * by single or double-ticks. + * + * @link http://docs.icinga.com/latest/de/objectdefinitions.html + * + * @param string $urlString A string containing one or more urls + * @return array Array of urls as strings + */ + public static function parseAttributeUrls($urlString) + { + if (empty($urlString)) { + return array(); + } + $links = array(); + if (strpos($urlString, "' ") === false) { + $links[] = $urlString; + } else { + // parse notes-url format + foreach (explode("' ", $urlString) as $url) { + $url = strpos($url, "'") === 0 ? substr($url, 1) : $url; + $url = strrpos($url, "'") === strlen($url) - 1 ? substr($url, 0, strlen($url) - 1) : $url; + $links[] = $url; + } + } + return $links; + } + + /** + * Fetch all available data of the object + * + * @return $this + */ + public function populate() + { + $this + ->fetchComments() + ->fetchContactgroups() + ->fetchContacts() + ->fetchCustomvars() + ->fetchDowntimes(); + + // Call fetchHostgroups or fetchServicegroups depending on the object's type + $fetchGroups = 'fetch' . ucfirst($this->type) . 'groups'; + $this->$fetchGroups(); + + return $this; + } + + /** + * Resolve macros in all given strings in the current object context + * + * @param array $strs An array of urls as string + * + * @return array + */ + protected function resolveAllStrings(array $strs) + { + foreach ($strs as $i => $str) { + $strs[$i] = Macro::resolveMacros($str, $this); + } + return $strs; + } + + /** + * Set the object's properties + * + * @param object $properties + * + * @return $this + */ + public function setProperties($properties) + { + $this->properties = (object) $properties; + return $this; + } + + public function __isset($name) + { + if (property_exists($this->properties, $name)) { + return isset($this->properties->$name); + } elseif (property_exists($this, $name)) { + return isset($this->$name); + } + return false; + } + + public function __get($name) + { + if (property_exists($this->properties, $name)) { + return $this->properties->$name; + } elseif (property_exists($this, $name)) { + if ($this->$name === null) { + $fetchMethod = 'fetch' . ucfirst($name); + $this->$fetchMethod(); + } + + return $this->$name; + } elseif (preg_match('/^_(host|service)_(.+)/i', $name, $matches)) { + if (strtolower($matches[1]) === static::TYPE_HOST) { + if ($this->hostVariables === null) { + $this->fetchHostVariables(); + } + + $customvars = $this->hostVariables; + } else { + if ($this->serviceVariables === null) { + $this->fetchServiceVariables(); + } + + $customvars = $this->serviceVariables; + } + + $variableName = strtolower($matches[2]); + if (isset($customvars[$variableName])) { + return $customvars[$variableName]; + } + + return null; // Unknown custom variables MUST NOT throw an error + } elseif (in_array($name, array('contact_name', 'contactgroup_name', 'hostgroup_name', 'servicegroup_name'))) { + if ($name === 'contact_name') { + if ($this->contacts === null) { + $this->fetchContacts(); + } + + return array_map(function ($el) { + return $el->contact_name; + }, $this->contacts); + } elseif ($name === 'contactgroup_name') { + if ($this->contactgroups === null) { + $this->fetchContactgroups(); + } + + return array_map(function ($el) { + return $el->contactgroup_name; + }, $this->contactgroups); + } elseif ($name === 'hostgroup_name') { + if ($this->hostgroups === null) { + $this->fetchHostgroups(); + } + + return array_keys($this->hostgroups); + } else { // $name === 'servicegroup_name' + if ($this->servicegroups === null) { + $this->fetchServicegroups(); + } + + return array_keys($this->servicegroups); + } + } elseif (strpos($name, $this->prefix) !== 0) { + $propertyName = strtolower($name); + $prefixedName = $this->prefix . $propertyName; + if (property_exists($this->properties, $prefixedName)) { + return $this->properties->$prefixedName; + } + + if ($this->type === static::TYPE_HOST) { + if ($this->hostVariables === null) { + $this->fetchHostVariables(); + } + + $customvars = $this->hostVariables; + } else { // $this->type === static::TYPE_SERVICE + if ($this->serviceVariables === null) { + $this->fetchServiceVariables(); + } + + $customvars = $this->serviceVariables; + } + + if (isset($customvars[$propertyName])) { + return $customvars[$propertyName]; + } + } + + throw new InvalidPropertyException('Can\'t access property \'%s\'. Property does not exist.', $name); + } +} diff --git a/modules/monitoring/library/Monitoring/Object/ObjectList.php b/modules/monitoring/library/Monitoring/Object/ObjectList.php new file mode 100644 index 0000000..36b922a --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/ObjectList.php @@ -0,0 +1,293 @@ +backend = $backend; + } + + /** + * @param array $columns + * + * @return $this + */ + public function setColumns(array $columns) + { + $this->columns = $columns; + return $this; + } + + /** + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * @param Filter $filter + * + * @return $this + */ + public function setFilter(Filter $filter) + { + $this->filter = $filter; + return $this; + } + + /** + * @return Filter + */ + public function getFilter() + { + if ($this->filter === null) { + $this->filter = Filter::matchAll(); + } + + return $this->filter; + } + + public function applyFilter(Filter $filter) + { + $this->getFilter()->addFilter($filter); + return $this; + } + + public function addFilter(Filter $filter) + { + $this->getFilter()->addFilter($filter); + } + + public function where($condition, $value = null) + { + $this->getFilter()->addFilter(Filter::where($condition, $value)); + } + + abstract protected function fetchObjects(); + + /** + * @return array + */ + public function fetch() + { + if ($this->objects === null) { + $this->objects = $this->fetchObjects(); + } + return $this->objects; + } + + public function count(): int + { + if ($this->count === null) { + $this->count = (int) $this->backend + ->select() + ->from($this->dataViewName, $this->columns) + ->applyFilter($this->filter) + ->getQuery() + ->count(); + } + + return $this->count; + } + + public function getIterator(): Traversable + { + if ($this->objects === null) { + $this->fetch(); + } + return new ArrayIterator($this->objects); + } + + /** + * Get the comments + * + * @return \Icinga\Module\Monitoring\DataView\Comment + */ + public function getComments() + { + return $this->backend->select()->from('comment')->applyFilter($this->filter); + } + + /** + * Get the scheduled downtimes + * + * @return type + */ + public function getScheduledDowntimes() + { + return $this->backend->select()->from('downtime')->applyFilter($this->filter); + } + + /** + * @return ObjectList + */ + public function getAcknowledgedObjects() + { + $acknowledgedObjects = array(); + foreach ($this as $object) { + if ((bool) $object->acknowledged === true) { + $acknowledgedObjects[] = $object; + } + } + return $this->newFromArray($acknowledgedObjects); + } + + /** + * @return ObjectList + */ + public function getObjectsInDowntime() + { + $objectsInDowntime = array(); + foreach ($this as $object) { + if ((bool) $object->in_downtime === true) { + $objectsInDowntime[] = $object; + } + } + return $this->newFromArray($objectsInDowntime); + } + + /** + * @return ObjectList + */ + public function getUnhandledObjects() + { + $unhandledObjects = array(); + foreach ($this as $object) { + if ((bool) $object->problem === true && (bool) $object->handled === false) { + $unhandledObjects[] = $object; + } + } + return $this->newFromArray($unhandledObjects); + } + + /** + * @return ObjectList + */ + public function getProblemObjects() + { + $handledObjects = array(); + foreach ($this as $object) { + if ((bool) $object->problem === true) { + $handledObjects[] = $object; + } + } + return $this->newFromArray($handledObjects); + } + + /** + * @return ObjectList + */ + abstract public function getUnacknowledgedObjects(); + + /** + * Create a ObjectList from an array of hosts without querying a backend + * + * @return ObjectList + */ + protected function newFromArray(array $objects) + { + $class = get_called_class(); + $list = new $class($this->backend); + $list->objects = $objects; + $list->count = count($objects); + $list->filter = $list->objectsFilter(); + return $list; + } + + /** + * Create a filter that matches exactly the elements of this object list + * + * @param array $columns Override default column names. + * + * @return Filter + */ + abstract public function objectsFilter($columns = array()); + + /** + * Get the feature status + * + * @return array + */ + public function getFeatureStatus() + { + // null - init + // 0 - disabled + // 1 - enabled + // 2 - enabled & disabled + $featureStatus = array( + 'active_checks_enabled' => null, + 'passive_checks_enabled' => null, + 'obsessing' => null, + 'notifications_enabled' => null, + 'event_handler_enabled' => null, + 'flap_detection_enabled' => null + ); + + $features = array(); + + foreach ($featureStatus as $feature => &$status) { + $features[$feature] = &$status; + } + + foreach ($this as $object) { + foreach ($features as $feature => &$status) { + $enabled = (int) $object->{$feature}; + if (! isset($status)) { + $status = $enabled; + } elseif ($status !== $enabled) { + $status = 2; + unset($features[$status]); + if (empty($features)) { + break 2; + } + break; + } + } + } + + return $featureStatus; + } +} diff --git a/modules/monitoring/library/Monitoring/Object/Service.php b/modules/monitoring/library/Monitoring/Object/Service.php new file mode 100644 index 0000000..95c00fc --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/Service.php @@ -0,0 +1,219 @@ +host = new Host($backend, $host); + $this->service = $service; + } + + /** + * Get the host the service is running on + * + * @return Host + */ + public function getHost() + { + return $this->host; + } + + /** + * Get the service name + * + * @return string + */ + public function getName() + { + return $this->service; + } + + /** + * Get the data view + * + * @return \Icinga\Module\Monitoring\DataView\ServiceStatus + */ + protected function getDataView() + { + return $this->backend->select()->from('servicestatus', array( + 'instance_name', + 'host_attempt', + 'host_icon_image', + 'host_icon_image_alt', + 'host_acknowledged', + 'host_active_checks_enabled', + 'host_address', + 'host_address6', + 'host_alias', + 'host_display_name', + 'host_handled', + 'host_in_downtime', + 'host_is_flapping', + 'host_last_state_change', + 'host_name', + 'host_notifications_enabled', + 'host_passive_checks_enabled', + 'host_state', + 'host_state_type', + 'service_icon_image', + 'service_icon_image_alt', + 'service_acknowledged', + 'service_acknowledgement_type', + 'service_action_url', + 'service_active_checks_enabled', + 'service_active_checks_enabled_changed', + 'service_attempt', + 'service_check_command', + 'service_check_execution_time', + 'service_check_interval', + 'service_check_latency', + 'service_check_source', + 'service_check_timeperiod', + 'service_current_notification_number', + 'service_description', + 'service_display_name', + 'service_event_handler_enabled', + 'service_event_handler_enabled_changed', + 'service_flap_detection_enabled', + 'service_flap_detection_enabled_changed', + 'service_handled', + 'service_in_downtime', + 'service_is_flapping', + 'service_is_reachable', + 'service_last_check', + 'service_last_notification', + 'service_last_state_change', + 'service_long_output', + 'service_next_check', + 'service_next_update', + 'service_notes', + 'service_notes_url', + 'service_notifications_enabled', + 'service_notifications_enabled_changed', + 'service_obsessing', + 'service_obsessing_changed', + 'service_output', + 'service_passive_checks_enabled', + 'service_passive_checks_enabled_changed', + 'service_percent_state_change', + 'service_perfdata', + 'service_process_perfdata' => 'service_process_performance_data', + 'service_state', + 'service_state_type' + )) + ->whereEx(new FilterEqual('host_name', '=', $this->host->getName())) + ->whereEx(new FilterEqual('service_description', '=', $this->service)); + } + + /** + * Get the optional translated textual representation of a service state + * + * @param int $state + * @param bool $translate + * + * @return string + * @throws InvalidArgumentException If the service state is not valid + */ + public static function getStateText($state, $translate = false) + { + $translate = (bool) $translate; + switch ((int) $state) { + case self::STATE_OK: + $text = $translate ? mt('monitoring', 'OK') : 'ok'; + break; + case self::STATE_WARNING: + $text = $translate ? mt('monitoring', 'WARNING') : 'warning'; + break; + case self::STATE_CRITICAL: + $text = $translate ? mt('monitoring', 'CRITICAL') : 'critical'; + break; + case self::STATE_UNKNOWN: + $text = $translate ? mt('monitoring', 'UNKNOWN') : 'unknown'; + break; + case self::STATE_PENDING: + $text = $translate ? mt('monitoring', 'PENDING') : 'pending'; + break; + default: + throw new InvalidArgumentException('Invalid service state \'%s\'', $state); + } + return $text; + } + + public function getNotesUrls() + { + return $this->resolveAllStrings( + MonitoredObject::parseAttributeUrls($this->service_notes_url) + ); + } +} diff --git a/modules/monitoring/library/Monitoring/Object/ServiceList.php b/modules/monitoring/library/Monitoring/Object/ServiceList.php new file mode 100644 index 0000000..5bc0bdb --- /dev/null +++ b/modules/monitoring/library/Monitoring/Object/ServiceList.php @@ -0,0 +1,184 @@ +backend->select()->from($this->dataViewName, $this->columns)->applyFilter($this->filter) + ->getQuery()->getSelectQuery()->query(); + foreach ($query as $row) { + /** @var object $row */ + $service = new Service($this->backend, $row->host_name, $row->service_description); + $service->setProperties($row); + $services[] = $service; + } + return $services; + } + + /** + * Create a state summary of all services that can be consumed by servicesummary.phtml + * + * @return SimpleQuery + */ + public function getServiceStateSummary() + { + if (! $this->serviceStateSummary) { + $this->initStateSummaries(); + } + + $ds = new ArrayDatasource(array((object) $this->serviceStateSummary)); + return $ds->select(); + } + + /** + * Create a state summary of all hosts that can be consumed by hostsummary.phtml + * + * @return SimpleQuery + */ + public function getHostStateSummary() + { + if (! $this->hostStateSummary) { + $this->initStateSummaries(); + } + + $ds = new ArrayDatasource(array((object) $this->hostStateSummary)); + return $ds->select(); + } + + /** + * Calculate the current state summary and populate hostStateSummary and serviceStateSummary + * properties + */ + protected function initStateSummaries() + { + $serviceStates = array_fill_keys(self::getServiceStatesSummaryEmpty(), 0); + $hostStates = array_fill_keys(HostList::getHostStatesSummaryEmpty(), 0); + + foreach ($this as $service) { + $unhandled = false; + if ((bool) $service->problem === true && (bool) $service->handled === false) { + $unhandled = true; + } + + $stateName = 'services_' . $service::getStateText($service->state); + ++$serviceStates[$stateName]; + ++$serviceStates[$stateName . ($unhandled ? '_unhandled' : '_handled')]; + + if (! isset($knownHostStates[$service->getHost()->getName()])) { + $unhandledHost = (bool) $service->host_problem === true && (bool) $service->host_handled === false; + ++$hostStates['hosts_' . $service->getHost()->getStateText($service->host_state)]; + ++$hostStates['hosts_' . $service->getHost()->getStateText($service->host_state) + . ($unhandledHost ? '_unhandled' : '_handled')]; + $knownHostStates[$service->getHost()->getName()] = true; + } + } + + $serviceStates['services_total'] = count($this); + $this->hostStateSummary = $hostStates; + $this->serviceStateSummary = $serviceStates; + } + + /** + * Return an empty array with all possible host state names + * + * @return array An array containing all possible host states as keys and 0 as values. + */ + public static function getServiceStatesSummaryEmpty() + { + return StringHelper::cartesianProduct( + array( + array('services'), + array( + Service::getStateText(Service::STATE_OK), + Service::getStateText(Service::STATE_WARNING), + Service::getStateText(Service::STATE_CRITICAL), + Service::getStateText(Service::STATE_UNKNOWN), + Service::getStateText(Service::STATE_PENDING) + ), + array(null, 'handled', 'unhandled') + ), + '_' + ); + } + + /** + * Returns a Filter that matches all hosts in this HostList + * + * @param array $columns Override filter column names + * + * @return Filter + */ + public function objectsFilter($columns = array('host' => 'host', 'service' => 'service')) + { + $filterExpression = array(); + foreach ($this as $service) { + $filterExpression[] = Filter::matchAll( + Filter::where($columns['host'], $service->getHost()->getName()), + Filter::where($columns['service'], $service->getName()) + ); + } + return FilterOr::matchAny($filterExpression); + } + + /** + * Get the comments + * + * @return \Icinga\Module\Monitoring\DataView\Hostcomment + */ + public function getComments() + { + return $this->backend + ->select() + ->from('servicecomment', array('host_name', 'service_description')) + ->applyFilter(clone $this->filter); + } + + /** + * Get the scheduled downtimes + * + * @return \Icinga\Module\Monitoring\DataView\Servicedowntime + */ + public function getScheduledDowntimes() + { + return $this->backend + ->select() + ->from('servicedowntime', array('host_name', 'service_description')) + ->applyFilter(clone $this->filter); + } + + /** + * @return ObjectList + */ + public function getUnacknowledgedObjects() + { + $unhandledObjects = array(); + foreach ($this as $object) { + if (! in_array((int) $object->state, array(0, 99)) && + (bool) $object->service_acknowledged === false) { + $unhandledObjects[] = $object; + } + } + return $this->newFromArray($unhandledObjects); + } +} -- cgit v1.2.3