diff --git a/library/Icingadb/Common/IcingaRedis.php b/library/Icingadb/Common/IcingaRedis.php index 4427f17bb..d9512f8b3 100644 --- a/library/Icingadb/Common/IcingaRedis.php +++ b/library/Icingadb/Common/IcingaRedis.php @@ -5,6 +5,7 @@ namespace Icinga\Module\Icingadb\Common; use Exception; +use Generator; use Icinga\Application\Config; use Icinga\Application\Logger; use Predis\Client as Redis; @@ -34,6 +35,22 @@ public static function instance(): self return self::$instance; } + /** + * Get whether Redis is unavailable + * + * @return bool + */ + public static function isUnavailable(): bool + { + $self = self::instance(); + + if ($self->redis === null) { + $self->getConnection(); + } + + return $self->redisUnavailable; + } + /** * Get the connection to the Icinga Redis * @@ -95,6 +112,72 @@ public function getConnection(): Redis return $this->redis; } + /** + * Fetch host states + * + * @param array $ids The host ids to fetch results for + * @param array $columns The columns to include in the results + * + * @return Generator + */ + public static function fetchHostState(array $ids, array $columns): Generator + { + return self::fetchState('icinga:host:state', $ids, $columns); + } + + /** + * Fetch service states + * + * @param array $ids The service ids to fetch results for + * @param array $columns The columns to include in the results + * + * @return Generator + */ + public static function fetchServiceState(array $ids, array $columns): Generator + { + return self::fetchState('icinga:service:state', $ids, $columns); + } + + /** + * Fetch object states + * + * @param string $key The object key to access + * @param array $ids The object ids to fetch results for + * @param array $columns The columns to include in the results + * + * @return Generator + */ + protected static function fetchState(string $key, array $ids, array $columns): Generator + { + try { + $results = self::instance()->getConnection()->hmget($key, $ids); + } catch (Exception $_) { + // The error has already been logged elsewhere + return; + } + + foreach ($results as $i => $json) { + if ($json !== null) { + $data = json_decode($json, true); + $keyMap = array_fill_keys($columns, null); + unset($keyMap['is_overdue']); // Is calculated by Icinga DB, not Icinga 2, hence it's never in redis + + // TODO: Remove once https://github.com/Icinga/icinga2/issues/9427 is fixed + $data['state_type'] = $data['state_type'] === 0 ? 'soft' : 'hard'; + + if (isset($data['in_downtime']) && is_bool($data['in_downtime'])) { + $data['in_downtime'] = $data['in_downtime'] ? 'y' : 'n'; + } + + if (isset($data['is_acknowledged']) && is_int($data['is_acknowledged'])) { + $data['is_acknowledged'] = $data['is_acknowledged'] ? 'y' : 'n'; + } + + yield $ids[$i] => array_intersect_key(array_merge($keyMap, $data), $keyMap); + } + } + } + /** * Get the last icinga heartbeat from redis * diff --git a/library/Icingadb/Redis/VolatileStateResults.php b/library/Icingadb/Redis/VolatileStateResults.php index af9211cbb..59c332d6a 100644 --- a/library/Icingadb/Redis/VolatileStateResults.php +++ b/library/Icingadb/Redis/VolatileStateResults.php @@ -4,8 +4,6 @@ namespace Icinga\Module\Icingadb\Redis; -use Exception; -use Generator; use Icinga\Application\Benchmark; use Icinga\Module\Icingadb\Common\Auth; use Icinga\Module\Icingadb\Common\IcingaRedis; @@ -14,7 +12,6 @@ use ipl\Orm\Query; use ipl\Orm\Resolver; use ipl\Orm\ResultSet; -use Predis\Client; use RuntimeException; class VolatileStateResults extends ResultSet @@ -24,8 +21,8 @@ class VolatileStateResults extends ResultSet /** @var Resolver */ private $resolver; - /** @var Client */ - private $redis; + /** @var bool Whether Redis is unavailable */ + private $redisUnavailable; /** @var bool Whether Redis updates were applied */ private $updatesApplied = false; @@ -34,12 +31,7 @@ public static function fromQuery(Query $query) { $self = parent::fromQuery($query); $self->resolver = $query->getResolver(); - - try { - $self->redis = IcingaRedis::instance()->getConnection(); - } catch (Exception $e) { - // The error has already been logged - } + $self->redisUnavailable = IcingaRedis::isUnavailable(); return $self; } @@ -51,12 +43,12 @@ public static function fromQuery(Query $query) */ public function isRedisUnavailable(): bool { - return $this->redis === null; + return $this->redisUnavailable; } public function current() { - if ($this->redis && ! $this->updatesApplied && ! $this->isCacheDisabled) { + if (! $this->redisUnavailable && ! $this->updatesApplied && ! $this->isCacheDisabled) { $this->rewind(); } @@ -65,7 +57,7 @@ public function current() public function key(): int { - if ($this->redis && ! $this->updatesApplied && ! $this->isCacheDisabled) { + if (! $this->redisUnavailable && ! $this->updatesApplied && ! $this->isCacheDisabled) { $this->rewind(); } @@ -74,7 +66,7 @@ public function key(): int public function rewind(): void { - if ($this->redis && ! $this->updatesApplied && ! $this->isCacheDisabled) { + if (! $this->redisUnavailable && ! $this->updatesApplied && ! $this->isCacheDisabled) { $this->updatesApplied = true; $this->advance(); @@ -134,7 +126,13 @@ protected function applyRedisUpdates() return; } - foreach ($this->fetchStates("icinga:{$type}:state", array_keys($states), $keys) as $id => $data) { + if ($type === 'service') { + $results = IcingaRedis::fetchServiceState(array_keys($states), $keys); + } else { + $results = IcingaRedis::fetchHostState(array_keys($states), $keys); + } + + foreach ($results as $id => $data) { foreach ($data as $key => $value) { $data[$key] = $behaviors->retrieveProperty($value, $key); } @@ -143,7 +141,7 @@ protected function applyRedisUpdates() } if ($type === 'service' && ! empty($hostStates)) { - foreach ($this->fetchStates('icinga:host:state', array_keys($hostStates), $hostStateKeys) as $id => $data) { + foreach (IcingaRedis::fetchHostState(array_keys($hostStates), $hostStateKeys) as $id => $data) { foreach ($data as $key => $value) { $data[$key] = $behaviors->retrieveProperty($value, $key); } @@ -152,21 +150,4 @@ protected function applyRedisUpdates() } } } - - protected function fetchStates(string $key, array $ids, array $keys): Generator - { - $results = $this->redis->hmget($key, $ids); - foreach ($results as $i => $json) { - if ($json !== null) { - $data = json_decode($json, true); - $keyMap = array_fill_keys($keys, null); - unset($keyMap['is_overdue']); // Is calculated by Icinga DB, not Icinga 2, hence it's never in redis - - // TODO: Remove once https://github.com/Icinga/icinga2/issues/9427 is fixed - $data['state_type'] = $data['state_type'] === 0 ? 'soft' : 'hard'; - - yield $ids[$i] => array_intersect_key(array_merge($keyMap, $data), $keyMap); - } - } - } }