From 7419c9e87cfafc761d2f65335b0594dd7827dcdd Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 5 Oct 2015 11:57:31 +0200 Subject: [PATCH 1/9] MonitoredObject: Allow to fetch a host's customvariables for services refs #10304 --- .../Monitoring/Object/MonitoredObject.php | 98 ++++++++++++++++--- 1 file changed, 82 insertions(+), 16 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php index 866e57c60..4cf9004c7 100644 --- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -5,7 +5,6 @@ namespace Icinga\Module\Monitoring\Object; use InvalidArgumentException; use Icinga\Application\Config; -use Icinga\Application\Logger; use Icinga\Data\Filter\Filter; use Icinga\Data\Filterable; use Icinga\Exception\InvalidPropertyException; @@ -50,12 +49,26 @@ abstract class MonitoredObject implements Filterable protected $comments; /** - * Custom variables + * This object's obfuscated custom variables * * @var array */ protected $customvars; + /** + * The host custom variables + * + * @var array + */ + protected $hostVariables; + + /** + * The service custom variables + * + * @var array + */ + protected $serviceVariables; + /** * Contact groups * @@ -438,9 +451,9 @@ abstract class MonitoredObject implements Filterable } /** - * Fetch the object's custom variables + * Fetch this object's obfuscated custom variables * - * @return $this + * @return $this */ public function fetchCustomvars() { @@ -463,28 +476,81 @@ abstract class MonitoredObject implements Filterable $blacklistPattern = '/^(' . implode('|', $blacklist) . ')$/i'; } + if ($this->type === self::TYPE_SERVICE) { + $this->fetchServiceVariables(); + $customvars = $this->serviceVariables; + } else { + $this->fetchHostVariables(); + $customvars = $this->hostVariables; + } + + $this->customvars = array(); + foreach ($customvars as $name => $value) { + if ($blacklistPattern && preg_match($blacklistPattern, $name)) { + $this->customvars[$name] = '***'; + } else { + $this->customvars[$name] = $value; + } + } + + return $this; + } + + /** + * 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' )) - ->where('object_type', $this->type) + ->where('object_type', static::TYPE_SERVICE) ->where('host_name', $this->host_name); - if ($this->type === self::TYPE_SERVICE) { - $query->where('service_description', $this->service_description); + + $this->hostVariables = array(); + 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; + } } - $this->customvars = array(); + return $this; + } - $customvars = $query->getQuery()->fetchAll(); - foreach ($customvars as $cv) { - $name = strtolower($cv->varname); - if ($blacklistPattern && preg_match($blacklistPattern, $cv->varname)) { - $this->customvars[$name] = '***'; - } elseif ($cv->is_json) { - $this->customvars[$name] = json_decode($cv->varvalue); + /** + * 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' + )) + ->where('object_type', static::TYPE_SERVICE) + ->where('host_name', $this->host_name) + ->where('service_description', $this->service_description); + + $this->serviceVariables = array(); + foreach ($query as $row) { + if ($row->is_json) { + $this->serviceVariables[strtolower($row->varname)] = json_decode($row->varvalue); } else { - $this->customvars[$name] = $cv->varvalue; + $this->serviceVariables[strtolower($row->varname)] = $row->varvalue; } } From 76961722498f88b34478ff95aa0e8b29bc3b7b14 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 5 Oct 2015 12:54:04 +0200 Subject: [PATCH 2/9] Logger: Also use the utility method for non-solitary exception arguments --- library/Icinga/Application/Logger.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Application/Logger.php b/library/Icinga/Application/Logger.php index aa03b49fc..9203a2f75 100644 --- a/library/Icinga/Application/Logger.php +++ b/library/Icinga/Application/Logger.php @@ -239,7 +239,9 @@ class Logger array_shift($arguments), array_map( function ($a) { - return is_string($a) ? $a : ($a instanceof Exception ? $a->getMessage() : json_encode($a)); + return is_string($a) ? $a : ($a instanceof Exception + ? IcingaException::describe($a) + : json_encode($a)); }, $arguments ) From 996959b65e780f137539e0d52d5267256537f000 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 5 Oct 2015 12:54:30 +0200 Subject: [PATCH 3/9] MonitoredObject: Fix object_type condition in method fetchHostVariables refs #10304 --- .../monitoring/library/Monitoring/Object/MonitoredObject.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php index 4cf9004c7..506fef73c 100644 --- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -508,7 +508,7 @@ abstract class MonitoredObject implements Filterable 'varvalue', 'is_json' )) - ->where('object_type', static::TYPE_SERVICE) + ->where('object_type', static::TYPE_HOST) ->where('host_name', $this->host_name); $this->hostVariables = array(); From 0ed6e0817561f585b4c06885e258424a48d5a979 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 5 Oct 2015 13:16:41 +0200 Subject: [PATCH 4/9] MonitoredObject: Allow to access custom variables by property refs #10304 --- .../Monitoring/Object/MonitoredObject.php | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php index 506fef73c..81cdb150d 100644 --- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -820,19 +820,60 @@ abstract class MonitoredObject implements Filterable { if (property_exists($this->properties, $name)) { return $this->properties->$name; - } elseif (property_exists($this, $name) && $this->$name !== null) { - return $this->$name; } elseif (property_exists($this, $name)) { - $fetchMethod = 'fetch' . ucfirst($name); - $this->$fetchMethod(); + if ($this->$name === null) { + $fetchMethod = 'fetch' . ucfirst($name); + $this->$fetchMethod(); + } + return $this->$name; - } - if (substr($name, 0, strlen($this->prefix)) !== $this->prefix) { - $prefixedName = $this->prefix . strtolower($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 (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); } From 499a7d628f74c2aafb5adb8eee435ecd46386999 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 5 Oct 2015 13:18:10 +0200 Subject: [PATCH 5/9] Macro: Resolve prefixed custom vars and object attributes refs #10304 --- .../monitoring/library/Monitoring/Object/Macro.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Object/Macro.php b/modules/monitoring/library/Monitoring/Object/Macro.php index 73e982b15..b311f95b4 100644 --- a/modules/monitoring/library/Monitoring/Object/Macro.php +++ b/modules/monitoring/library/Monitoring/Object/Macro.php @@ -3,6 +3,9 @@ namespace Icinga\Module\Monitoring\Object; +use Exception; +use Icinga\Application\Logger; + /** * Expand macros in string in the context of MonitoredObjects */ @@ -60,11 +63,14 @@ class Macro if (isset(self::$icingaMacros[$macro]) && isset($object->{self::$icingaMacros[$macro]})) { return $object->{self::$icingaMacros[$macro]}; } - $customVar = strtolower($macro); - if (isset($object->customvars[$customVar])) { - return $object->customvars[$customVar]; + + try { + $value = $object->$macro; + } catch (Exception $e) { + $value = null; + Logger::debug('Unable to resolve macro "%s". An error occured: %s', $macro, $e); } - return $macro; + return $value !== null ? $value : $macro; } } From 07e5664fbeed503e0f0cf1a4be4d28a66261b42f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 5 Oct 2015 13:59:57 +0200 Subject: [PATCH 6/9] MonitoredObject: Allow to access a set-value by name $object->contact|contactgroup|hostgroup|servicegroup + _name is now allowed and returns an array of strings refs #10304 --- .../Monitoring/Object/MonitoredObject.php | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php index 81cdb150d..2634ff051 100644 --- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -848,6 +848,32 @@ abstract class MonitoredObject implements Filterable } 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; From 65e7f7a8caad9c6b56e1a1ec4fc793028654b652 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 5 Oct 2015 14:01:03 +0200 Subject: [PATCH 7/9] FilterExpression: Give the row a chance to dynamically return a value refs #10304 --- .../Icinga/Data/Filter/FilterExpression.php | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/library/Icinga/Data/Filter/FilterExpression.php b/library/Icinga/Data/Filter/FilterExpression.php index ca5866a6a..86b36e81c 100644 --- a/library/Icinga/Data/Filter/FilterExpression.php +++ b/library/Icinga/Data/Filter/FilterExpression.php @@ -3,6 +3,8 @@ namespace Icinga\Data\Filter; +use Exception; + class FilterExpression extends Filter { protected $column; @@ -97,22 +99,24 @@ class FilterExpression extends Filter public function matches($row) { - if (! isset($row->{$this->column})) { + try { + $rowValue = $row->{$this->column}; + } catch (Exception $e) { // TODO: REALLY? Exception? return false; } if (is_array($this->expression)) { - return in_array($row->{$this->column}, $this->expression); + return in_array($rowValue, $this->expression); } $expression = (string) $this->expression; if (strpos($expression, '*') === false) { - if (is_array($row->{$this->column})) { - return in_array($expression, $row->{$this->column}); + if (is_array($rowValue)) { + return in_array($expression, $rowValue); } - return (string) $row->{$this->column} === $expression; + return (string) $rowValue === $expression; } $parts = array(); @@ -121,8 +125,8 @@ class FilterExpression extends Filter } $pattern = '/^' . implode('.*', $parts) . '$/'; - if (is_array($row->{$this->column})) { - foreach ($row->{$this->column} as $candidate) { + if (is_array($rowValue)) { + foreach ($rowValue as $candidate) { if (preg_match($pattern, $candidate)) { return true; } @@ -131,7 +135,7 @@ class FilterExpression extends Filter return false; } - return (bool) preg_match($pattern, $row->{$this->column}); + return (bool) preg_match($pattern, $rowValue); } public function andFilter(Filter $filter) From 6a68d25bc318b2ba1b0f32e579bebdad3442101b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 5 Oct 2015 14:04:40 +0200 Subject: [PATCH 8/9] MonitoredObject: Deprecate method matches() It's now possible to achieve the same by passing the object directly to a filter. refs #10304 --- .../Monitoring/Object/MonitoredObject.php | 35 ++----------------- .../Monitoring/Web/Navigation/Action.php | 2 +- 2 files changed, 4 insertions(+), 33 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php index 2634ff051..80a545784 100644 --- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -231,6 +231,8 @@ abstract class MonitoredObject implements Filterable * @return bool * * @throws ProgrammingError In case the object cannot be found + * + * @deprecated Use $filter->matches($object) instead */ public function matches(Filter $filter) { @@ -242,38 +244,7 @@ abstract class MonitoredObject implements Filterable ); } - $queryString = $filter->toQueryString(); - $row = clone $this->properties; - - if (strpos($queryString, '_host_') !== false || strpos($queryString, '_service_') !== false) { - if ($this->customvars === null) { - $this->fetchCustomvars(); - } - - foreach ($this->customvars as $name => $value) { - if (! is_object($value)) { - $row->{'_' . $this->getType() . '_' . $name} = $value; - } - } - } - - if (strpos($queryString, 'hostgroup_name') !== false) { - if ($this->hostgroups === null) { - $this->fetchHostgroups(); - } - - $row->hostgroup_name = array_keys($this->hostgroups); - } - - if (strpos($queryString, 'servicegroup_name') !== false) { - if ($this->servicegroups === null) { - $this->fetchServicegroups(); - } - - $row->servicegroup_name = array_keys($this->servicegroups); - } - - return $filter->matches($row); + return $filter->matches($this); } /** diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/Action.php b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php index 505229abb..1b17106ee 100644 --- a/modules/monitoring/library/Monitoring/Web/Navigation/Action.php +++ b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php @@ -101,7 +101,7 @@ class Action extends NavigationItem { if ($this->render === null) { $filter = $this->getFilter(); - $this->render = $filter ? $this->getObject()->matches(Filter::fromQueryString($filter)) : true; + $this->render = $filter ? Filter::fromQueryString($filter)->matches($this->getObject()) : true; } return $this->render; From 125ecb2f0ae414e569bab635cf7bbd797970ecbf Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 5 Oct 2015 14:05:17 +0200 Subject: [PATCH 9/9] ActionForm: Allow contact_name and contactgroup_name as filter column Because it works now. --- .../monitoring/application/forms/Navigation/ActionForm.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/monitoring/application/forms/Navigation/ActionForm.php b/modules/monitoring/application/forms/Navigation/ActionForm.php index e712b254d..fbd0a51f2 100644 --- a/modules/monitoring/application/forms/Navigation/ActionForm.php +++ b/modules/monitoring/application/forms/Navigation/ActionForm.php @@ -47,6 +47,8 @@ class ActionForm extends NavigationItemForm 'instance_name', 'service_description', 'servicegroup_name', + 'contact_name', + 'contactgroup_name', function ($c) { return preg_match('/^_(?:host|service)_/', $c); } @@ -63,6 +65,8 @@ class ActionForm extends NavigationItemForm 'hostgroup_name', 'service_description', 'servicegroup_name', + 'contact_name', + 'contactgroup_name', '_(host|service)_' )) ));